接入tradplus

This commit is contained in:
yuqian 2026-01-12 18:11:16 +08:00
commit fee3222f98
109 changed files with 31157 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
.idea/
.safedk/
app/debug/

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

BIN
app/RainbowBoard Normal file

Binary file not shown.

175
app/build.gradle.kts Normal file
View File

@ -0,0 +1,175 @@
import java.util.Date
import java.text.SimpleDateFormat
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id ("kotlin-android")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
}
val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date())
android {
namespace = "com.rainbow.app.keyboard"
compileSdk = 36
defaultConfig {
applicationId = "com.rainbow.app.keyboard"
minSdk = 24
targetSdk = 36
versionCode = 2
versionName = "1.1"
setProperty(
"archivesBaseName",
"RainbowBoard_V" + versionName + "(${versionCode})_$timestamp"
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
buildConfig = true
viewBinding = true
}
}
dependencies {
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.13.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.activity:activity:1.12.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
implementation("com.squareup.okhttp3:okhttp:5.3.2")
implementation("com.github.bumptech.glide:glide:5.0.5")
implementation ("jp.wasabeef:glide-transformations:4.3.0")
//Glide支持webp动图的库
implementation("com.github.zjupure:webpdecoder:2.7.4.16.0")
implementation("com.github.omicronapps:7-Zip-JBinding-4Android:Release-16.02-2.03")
val room_version = "2.8.4"
implementation ("androidx.room:room-runtime:$room_version")
kapt("androidx.room:room-compiler:$room_version")
implementation ("androidx.room:room-ktx:$room_version")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("com.google.android.material:material:1.8.0")
implementation(files("libs/TradPlusLibrary_01_04_12_20-release.aar"))
implementation(files("libs/UpLoadLibrary_12_03_15_13-release.aar"))
implementation ("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation ("com.google.android.gms:play-services-ads-identifier:18.0.1")
// TradPlus
implementation("com.tradplusad:tradplus:15.2.0.1")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.appcompat:appcompat:1.3.0-alpha02")
// IronSource
implementation("com.ironsource.sdk:mediationsdk:9.0.0")
implementation("com.tradplusad:tradplus-ironsource:10.15.2.0.1")
// Pangle
implementation("com.tradplusad:tradplus-pangle:19.15.2.0.1")
implementation("com.pangle.global:pag-sdk:7.8.0.7")
// UnityAds
implementation("com.tradplusad:tradplus-unity:5.15.2.0.1")
implementation("com.unity3d.ads:unity-ads:4.16.3")
// Chartboost
// implementation("com.tradplusad:tradplus-chartboostx:15.15.2.0.1")
// implementation("com.chartboost:chartboost-sdk:9.10.0")
// implementation("com.google.android.gms:play-services-ads-identifier:17.0.0")
// implementation("com.google.android.gms:play-services-base:17.4.0")
//上面新版本下载失败用旧版本
implementation("com.tradplusad:tradplus-chartboostx:15.14.5.0.1")
implementation("com.chartboost:chartboost-sdk:9.8.3")
implementation("com.google.android.gms:play-services-ads-identifier:17.0.0")
implementation("com.google.android.gms:play-services-base:17.4.0")
// InMobi
implementation("com.tradplusad:tradplus-inmobix:23.15.2.0.1")
implementation("com.inmobi.monetization:inmobi-ads-kotlin:11.0.0")
implementation("com.squareup.okhttp3:okhttp:3.14.9")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
implementation("androidx.core:core-ktx:1.5.0")
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.0")
implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
implementation("com.google.android.gms:play-services-location:21.0.1") // optional
implementation("androidx.browser:browser:1.8.0")
implementation("com.squareup.picasso:picasso:2.8")
implementation("androidx.viewpager:viewpager:1.0.0")
implementation("androidx.recyclerview:recyclerview:1.2.1")
// Fyber
implementation("com.fyber:marketplace-sdk:8.4.0")
implementation("com.tradplusad:tradplus-fyber:24.15.2.0.1")
implementation("com.google.android.gms:play-services-ads-identifier:17.0.0")
implementation("com.google.android.gms:play-services-base:17.4.0")
// Mintegral
implementation("com.tradplusad:tradplus-mintegralx_overseas:18.15.2.0.1")
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("com.mbridge.msdk.oversea:mbridge_android_sdk:16.10.11")
// Liftoff (Vungle)
implementation("com.tradplusad:tradplus-vunglex:7.15.2.0.1")
implementation("com.vungle:vungle-ads:7.6.0")
// Bigo
implementation("com.bigossp:bigo-ads:5.5.2")
implementation("com.tradplusad:tradplus-bigo:57.15.2.0.1")
// Cross Promotion
implementation("com.tradplusad:tradplus-crosspromotion:27.15.2.0.1")
// TP Exchange注意与主包版本同步
implementation("com.google.code.gson:gson:2.8.6")
implementation("com.tradplusad:tp_exchange:40.15.2.0.1")
// Google UMP
implementation ("com.google.android.ump:user-messaging-platform:3.2.0")
// TradPlus Tools
// implementation 'com.tradplusad:tradplus-tool:1.1.4'
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-config")
}

29
app/google-services.json Normal file
View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "772081232638",
"project_id": "rainbowboard-96ae6",
"storage_bucket": "rainbowboard-96ae6.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:772081232638:android:491f6c55ffdfbf98e913af",
"android_client_info": {
"package_name": "com.rainbow.app.keyboard"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyCB9XOD2_jWWAG5gLK_Ob9AgQHbFMU-V9M"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

Binary file not shown.

Binary file not shown.

24
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,24 @@
# 保持 Room 的核心类不被混淆
-keep class androidx.room.** { *; }
-keep class androidx.sqlite.db.** { *; }
# 保持 Room 数据库类的基本结构
-keep class * extends androidx.room.RoomDatabase { *; }
# 保持 Room DAO 接口
-keep @androidx.room.Dao interface * { *; }
-keep @androidx.room.Dao class * { *; }
# 保持 Room 实体类
-keep @androidx.room.Entity class * { *; }
# 保持 Room 的注解类
-keep @androidx.room.Database class * { *; }
-keep class com.omicronapplications.** { *; }
-keep class net.sf.sevenzipjbinding.** { *; }

View File

@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.rainbow.app.keyboard",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 2,
"versionName": "1.1",
"outputFile": "RainbowBoard_V1.1(2)_01_12_17_17-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/RainbowBoard_V1.1(2)_01_12_17_17-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/RainbowBoard_V1.1(2)_01_12_17_17-release.dm"
]
}
],
"minSdkVersionForDexing": 24
}

View File

@ -0,0 +1,25 @@
package com.keyboard.skinning.cool
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.ba.ali.apps.keyboard", appContext.packageName)
}
}

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.AD_ID" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/net"
tools:replace="android:networkSecurityConfig"
android:theme="@style/MyKeyBoard"
tools:targetApi="31">
<activity
android:name=".uiactivity.SettingsActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Dialog"
android:windowSoftInputMode="adjustResize"
android:exported="false" />
<activity
android:name=".uiactivity.LaunchActivity"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".uiactivity.DetailkbActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.EditVActivity"
android:exported="false"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysVisible|adjustResize|adjustPan" />
<activity
android:name=".uiactivity.MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.CollectionActivity"
android:exported="false"
android:screenOrientation="portrait" />
<service
android:name=".helper.KeybinputService"
android:exported="true"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/keyborad_xml" />
</service>
</application>
</manifest>

21607
app/src/main/assets/kbres.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,123 @@
package com.rainbow.app.keyboard
//import com.pretty.keyboard.theme.keyboard.helper.ObjectBox
import android.app.Application
import android.graphics.Typeface
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
import com.rainbow.app.keyboard.javabean.WrapperjavaBean
import com.up.uploadlibrary.UpLoadManager
import org.json.JSONArray
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.StringWriter
class App : Application() {
companion object {
lateinit var appInstance: App
lateinit var list: MutableList<WrapperjavaBean>
const val TAG = "-----------------"
var defaultFont: Typeface? = null
const val DB_VERSION = 3
const val DB_NAME = "db_name"
}
override fun onCreate() {
super.onCreate()
appInstance = this
UpLoadManager.init(this, TAG) { s: String?, s2: String? -> null }
dealFile()
}
private fun dealFile() {
val openFile = appInstance.assets.open("kbres.json")
val jsonString = getJsonString(openFile)
if (jsonString != null) {
resolveJsonString(jsonString)
}
}
private fun resolveJsonString(string: String) {
val jsonData = JSONArray(string)
var dataList: MutableList<WrapperjavaBean> = mutableListOf()
for (i in 0 until jsonData.length()) {
jsonData.getJSONObject(i).run {
val pName = getString("parent_name")
val listArray = getJSONArray("keyboard_list")
var detailsjavaBeanList: MutableList<DetailsjavaBean> = mutableListOf()
for (listIndex in 0 until listArray.length()) {
listArray.getJSONObject(listIndex).also {
val title = it.getString("title")
val thUrl = it.getString("thumbUrl")
val thGif = it.getString("thumbUrlGif")
var zipPath = ""
var imgPath = ""
var imgGif = ""
var imgPreviewGif = ""
if (it.has("detail")) {
val contentObject =
it.getJSONObject("detail").getJSONObject("themeContent")
zipPath = contentObject.getString("androidRawZipUrl")
imgPath = contentObject.getString("img")
imgGif = contentObject.getString("imgGif")
imgPreviewGif = contentObject.getString("imgPreviewGif")
} else {
}
detailsjavaBeanList.add(
DetailsjavaBean().apply {
setImgPath(imgPath)
setZipPath(zipPath)
setTitleName(title)
setImgGif(imgGif)
thumbUrl = thUrl
thumbGif = thGif
})
}
}
val shuffled = detailsjavaBeanList.shuffled()
val dataWrapperjavaBean = WrapperjavaBean()
.apply {
parentName = pName
keyboardList = shuffled
}
dataList.add(dataWrapperjavaBean)
}
}
updateDataList(dataList)
}
private fun updateDataList(mainList: MutableList<WrapperjavaBean>) {
list = mainList
}
private fun getJsonString(fileInputStream: InputStream): String? {
return try {
// FileInputStream fileInputStream = new FileInputStream(path);
val charArray = CharArray(fileInputStream.available())
var readCount = 0
val streamReader = InputStreamReader(fileInputStream)
val bufferedReader = BufferedReader(streamReader)
val stringWriter = StringWriter()
while (bufferedReader.read(charArray).also { readCount = it } != -1) {
stringWriter.write(charArray, 0, readCount)
}
stringWriter.toString()
} catch (e: IOException) {
""
}
}
}

View File

@ -0,0 +1,100 @@
package com.rainbow.app.keyboard.helper
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.mysourcecode.KeybCode
import com.rainbow.app.keyboard.utils.UtilsKeyname
import com.rainbow.app.keyboard.utils.UtilsSavecurrentkb
import java.io.File
object KeybFuncs {
fun keyToUpper(mKeyBoard: KeybCode) {
for (key in mKeyBoard.keys) {
if (key.label != null) {
if (key.label.length == 1) {
val charLabel = key.label.toString()[0]
val toUpperCase = charLabel.uppercaseChar()
key.codes[0] = toUpperCase.toInt()
key.label = toUpperCase.toString()
}
}
}
}
fun keyToLowerCase(mKeyBoard: KeybCode) {
for (key in mKeyBoard.keys) {
if (key.label != null) {
if (key.label.length == 1) {
val charLabel = key.label.toString()[0]
val toLowerCase = charLabel.lowercaseChar()
key.codes[0] = toLowerCase.toInt()
key.label = toLowerCase.toString()
}
}
}
}
fun primaryCodeToChar(primCode: Int): String {
val toString = primCode.toChar().toString()
return toString
}
@SuppressLint("SuspiciousIndentation")
fun readBgOrVideo(
context: Context,
playAction: (gif: String?, bgDraw: Drawable?) -> Unit
) {
UtilsSavecurrentkb.getSkinPath()?.let { resPath ->
val videoPath = "${resPath}res/raw/${UtilsKeyname.videoName}"
val videoPath2 = "${resPath}res/raw/${UtilsKeyname.video}"
val backgroundPath = "${resPath}res/drawable-xxhdpi-v4/${UtilsKeyname.bgName}"
val backgroundPath_png = "${resPath}res/drawable-xxhdpi-v4/${UtilsKeyname.bgName_png}"
val file = File(videoPath)
val file2 = File(videoPath2)
val file3 = File(backgroundPath)
val file4 = File(backgroundPath_png)
if (file.exists() || file2.exists()) {
Log.d(App.Companion.TAG, "--------11111111= resPath=${resPath}")
val path: String = if (file.exists()) {
videoPath
} else {
videoPath2
}
val bitmapDrawable =
BitmapDrawable(context.resources, BitmapFactory.decodeFile(path))
playAction.invoke(path, null)
// playAction.invoke(mediaPlayer,null);
} else {
Log.d(
App.Companion.TAG,
"--------11111111= file3.exists()" + file3.exists() + "---resPath=" + resPath
)
if (file3.exists()) {
val bitmapDrawable =
BitmapDrawable(context.resources, BitmapFactory.decodeFile(backgroundPath))
playAction.invoke(null, bitmapDrawable)
} else if (file4.exists()) {
val bitmapDrawable =
BitmapDrawable(context.resources, BitmapFactory.decodeFile(backgroundPath_png))
playAction.invoke(null, bitmapDrawable)
} else {
playAction.invoke(null, null)
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.rainbow.app.keyboard.helper;
// 按键对象模型
public class KeybModel {
private String name;
private String background;
private String label;
public KeybModel(String name) {
this.name = name;
}
// Getters and Setters
public String getName() {
return name;
}
public String getBackground() {
return background;
}
public void setBackground(String background) {
this.background = background;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}

View File

@ -0,0 +1,324 @@
package com.rainbow.app.keyboard.helper;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import androidx.core.graphics.drawable.DrawableCompat;
import com.rainbow.app.keyboard.App;
import com.rainbow.app.keyboard.R;
import com.rainbow.app.keyboard.mysourcecode.KeybCode;
import com.rainbow.app.keyboard.mysourcecode.KeybCodeView;
import com.rainbow.app.keyboard.utils.UtilsKeyname;
import com.rainbow.app.keyboard.utils.UtilsKbmanager;
import java.util.ArrayList;
import java.util.List;
public class KeybView extends KeybCodeView {
private Paint mPaint;
private Context mContext;
private float mRation = 0.5f;
//0 小写 1 大写 2 大写锁定
private int isLowerCase = 0;
//0 默认键盘 1 字母键盘 2 符号键盘
private int mMode = 0;
private UtilsKbmanager UtilsKbmanager;
private int curImeAction = EditorInfo.IME_ACTION_UNSPECIFIED;
public KeybView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setAttribute(attrs, context);
}
public KeybView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
setAttribute(attrs, context);
}
public KeybView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
setAttribute(attrs, context);
}
public void setMode(int mType) {
this.mMode = mType;
}
public int getMode() {
return mMode;
}
public int isLowerCase() {
return isLowerCase;
}
public void setLowerCase(int lowerCase) {
isLowerCase = lowerCase;
}
public void updateUi(int ime) {
Log.d(App.TAG, "----------ime=" + ime);
curImeAction = ime;
UtilsKbmanager.updateSkinConfig();
invalidate();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
}
private void setAttribute(AttributeSet attrs, Context con) {
UtilsKbmanager = new UtilsKbmanager(con);
initPaint();
TypedArray mTypedArray = con.obtainStyledAttributes(attrs, R.styleable.CustomInputView);
// int color = mTypedArray.getColor(R.styleable.CustomInputView_text_color_done, 1);
//
// Drawable drawable = mTypedArray.getDrawable(R.styleable.CustomInputView_drawable_cancel);
//
// int textSize = mTypedArray.getInt(R.styleable.CustomInputView_text_size_key, 12);
mTypedArray.recycle();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
ResConfig config = UtilsKbmanager.getConfig();
List<KeybModel> keybModels = new ArrayList<>();
int i = 0;
for (KeybCode.Key curKey : getKeyboard().getKeys()) {
int code = curKey.codes[0];
if (config != null&& !config.getLayouts().isEmpty()) {
if (code == 113 ||code == 81 || code == 49||code == 91) {
i = 0;
ResLayout resLayout = config.getLayouts().get(0);
keybModels = resLayout.getKeys();
} else if (code == 97||code == 65 || code == 33||code == 126) {
i = 0;
ResLayout resLayout = config.getLayouts().get(1);
keybModels = resLayout.getKeys();
}else if (code == -1 || code == -103||code==-101) {
i = 0;
ResLayout resLayout = config.getLayouts().get(2);
keybModels = resLayout.getKeys();
}else if (code == -2 || code == -102) {
i = 0;
ResLayout resLayout = config.getLayouts().get(3);
keybModels = resLayout.getKeys();
}
String background = keybModels.get(i).getBackground()+".9.png";
i++;
Drawable configBg = UtilsKbmanager.getConfigBg(background);
realNewDraw(configBg, curKey, canvas, code);
} else {
realDraw(curKey, canvas, code);
}
}
}
private void realNewDraw(Drawable configBg, KeybCode.Key curKey, Canvas canvas, int code) {
switch (code) {
case UtilsKeyname.KEY_CODE_SHIFT:
// drawAllShift(curKey, canvas);
onDrawCurKey(curKey, canvas, "Shift", configBg, null);
break;
case UtilsKeyname.KEY_CODE_NUMBER_SHIFT:
onDrawCurKey(curKey, canvas, "More", configBg, null);
break;
case UtilsKeyname.KEY_CODE_DELETE:
onDrawCurKey(curKey, canvas, "Delete", configBg, null);
break;
case UtilsKeyname.KEY_CODE_SYMBOL_SHIFT:
onDrawCurKey(curKey, canvas, "123", configBg, null);
break;
case UtilsKeyname.KEY_CODE_CHANGE_NUMBER:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
case UtilsKeyname.KEY_CODE_BACK:
onDrawCurKey(curKey, canvas, "Back", configBg, null);
break;
case UtilsKeyname.KEY_CODE_SPACE:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
case UtilsKeyname.KEY_CODE_COMPLETE, UtilsKeyname.KEY_CODE_CANCEL:
Log.d(App.TAG, "-11111111111---------curImeAction=" + curImeAction);
if (curImeAction == EditorInfo.IME_ACTION_SEARCH) {
onDrawCurKey(curKey, canvas, "Search", configBg, null);
} else {
onDrawCurKey(curKey, canvas, "Done", configBg, null);
}
break;
default:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
}
}
private void realDraw(KeybCode.Key curKey, Canvas canvas, int code) {
switch (code) {
case UtilsKeyname.KEY_CODE_SHIFT:
drawAllShift(curKey, canvas);
break;
case UtilsKeyname.KEY_CODE_NUMBER_SHIFT:
onDrawCurKey(curKey, canvas, "More", UtilsKbmanager.getFunctionDraw(), null);
break;
case UtilsKeyname.KEY_CODE_DELETE:
onDrawCurKey(curKey, canvas, "Delete", UtilsKbmanager.getFunctionDraw(), null);
break;
case UtilsKeyname.KEY_CODE_SYMBOL_SHIFT:
onDrawCurKey(curKey, canvas, "123", UtilsKbmanager.getFunctionDraw(), null);
break;
case UtilsKeyname.KEY_CODE_CHANGE_NUMBER:
onDrawCurKey(curKey, canvas, null, UtilsKbmanager.getToDraw(), null);
break;
case UtilsKeyname.KEY_CODE_BACK:
onDrawCurKey(curKey, canvas, "Back", UtilsKbmanager.getToDraw(), null);
break;
case UtilsKeyname.KEY_CODE_SPACE:
onDrawCurKey(curKey, canvas, null, UtilsKbmanager.getSpaceDraw(), null);
break;
case UtilsKeyname.KEY_CODE_COMPLETE, UtilsKeyname.KEY_CODE_CANCEL:
Log.d(App.TAG, "-11111111111---------curImeAction=" + curImeAction);
if (curImeAction == EditorInfo.IME_ACTION_SEARCH) {
onDrawCurKey(curKey, canvas, "Search", UtilsKbmanager.getFunctionDraw(), null);
} else {
onDrawCurKey(curKey, canvas, "Done", UtilsKbmanager.getFunctionDraw(), null);
}
break;
default:
onDrawCurKey(curKey, canvas, null, UtilsKbmanager.getGeneralDraw(), null);
break;
}
}
private void drawAllShift(KeybCode.Key curKey, Canvas canvas) {
if (isLowerCase == 0) {
onDrawCurKey(curKey, canvas, "Shift", UtilsKbmanager.getFunctionDraw(), null);
} else if (isLowerCase == 1) {
onDrawCurKey(curKey, canvas, "Shift", UtilsKbmanager.getFunctionDraw(), null);
} else if (isLowerCase == 2) {
onDrawCurKey(curKey, canvas, "Shift", UtilsKbmanager.getFunctionDraw(), null);
}
}
private void onDrawCurKey(KeybCode.Key curKey, Canvas curCanvas, String label, Drawable bgDrawable, Drawable iconDraw) {
if (bgDrawable != null) {
onDrawKeyBackground(curKey, curCanvas, bgDrawable);
}
if (iconDraw != null) {
onDrawKeyIcon(curKey, curCanvas, iconDraw);
}
onDrawKeyText(curKey, curCanvas, label);
}
private void onDrawKeyText(KeybCode.Key curKey, Canvas curCanvas, String label) {
mPaint.setColor(UtilsKbmanager.getKeyTextColor());
mPaint.setTextSize(mContext.getResources().getDimension(R.dimen.text_size));
float v = curKey.width / 2f;
float v1 = curKey.height / 2f;
float v2 = (mPaint.getTextSize() - mPaint.descent()) / 2f;
if (curKey.label != null) {
curCanvas.drawText((String) curKey.label, curKey.x + getPaddingLeft() + v, curKey.y + getPaddingRight() + v1 + v2, mPaint);
} else if (label != null) {
curCanvas.drawText(label, curKey.x + getPaddingLeft() + v, curKey.y + getPaddingRight() + v1 + v2, mPaint);
}
}
private void onDrawKeyBackground(KeybCode.Key curKey, Canvas curCanvas, Drawable curDrawable) {
if (curKey.codes[0] != 0) {
curDrawable.setState(curKey.getCurrentDrawableState());
}
Rect rect = new Rect((curKey.x + this.getPaddingLeft()), (curKey.y + this.getPaddingTop()), (curKey.x + this.getPaddingLeft() + curKey.width), (curKey.y + this.getPaddingTop() + curKey.height));
curDrawable.setBounds(rect);
curDrawable.draw(curCanvas);
}
private void onDrawKeyIcon(KeybCode.Key curKey, Canvas curCanvas, Drawable curDrawable) {
Drawable wrap = DrawableCompat.wrap(curDrawable);
curKey.icon = curDrawable;
float iconW = (float) curKey.icon.getIntrinsicWidth();
float iconH = (float) curKey.icon.getIntrinsicHeight();
float wDivRation = iconW / curKey.width;
float hDivRation = iconH / curKey.height;
curKey.icon.draw(curCanvas);
if (wDivRation > hDivRation) {
float minRatio = 0;
if (wDivRation <= mRation) {
minRatio = wDivRation;
} else {
minRatio = mRation;
}
iconH = (iconH / wDivRation) * minRatio;
iconW = (iconW / wDivRation) * minRatio;
} else {
float minRatio = 0;
if (hDivRation <= mRation) {
minRatio = hDivRation;
} else {
minRatio = mRation;
}
iconH = (iconH / hDivRation) * minRatio;
iconW = (iconW / hDivRation) * minRatio;
}
float subW = (curKey.width - iconW) / 2f;
float subH = (curKey.height - iconH) / 2f;
int xLeft = (int) (curKey.x + getPaddingLeft() + subW);
int yTop = (int) (curKey.y + getPaddingTop() + subH);
int xRight = (int) (xLeft + iconW);
int yBottom = (int) (yTop + iconH);
curKey.icon.setBounds(xLeft, yTop, xRight, yBottom);
curKey.icon.draw(curCanvas);
}
}

View File

@ -0,0 +1,366 @@
package com.rainbow.app.keyboard.helper;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.media.MediaPlayer;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.ImageView;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.integration.webp.decoder.WebpDrawable;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.rainbow.app.keyboard.App;
import com.rainbow.app.keyboard.R;
import com.rainbow.app.keyboard.utils.UtilsKeyname;
import com.rainbow.app.keyboard.utils.UtilsCommon;
import com.rainbow.app.keyboard.utils.UtilsSavecurrentkb;
import com.rainbow.app.keyboard.mysourcecode.KeybCode;
import com.rainbow.app.keyboard.mysourcecode.KeybCodeView;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
public class KeybinputService extends InputMethodService implements KeybCodeView.OnKeyboardActionListener {
private KeybView myKeyBoardView;
private KeybCode mKeyBoard;
private View parentView;
private ImageView imBG;
private VideoView videoView;
private int a = R.xml.xml_one;
private int b = R.xml.xml_two;
private int c = R.xml.xml_three;
private int curImeAction = EditorInfo.IME_ACTION_UNSPECIFIED;
@SuppressLint("InflateParams")
@Override
public View onCreateInputView() {
parentView = getLayoutInflater().inflate(R.layout.item_editv, null);
// API 30+ 的特殊处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 设置窗口标志确保键盘正确显示
try {
if (getWindow() != null && getWindow().getWindow() != null) {
getWindow().getWindow().addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
// 对于 API 31+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
getWindow().getWindow().setDecorFitsSystemWindows(false);
}
}
} catch (Exception e) {
Log.e(App.TAG, "Error setting window flags: " + e.getMessage());
}
}
findView();
return parentView;
}
private void findView() {
imBG = parentView.findViewById(R.id.gif_bg);
videoView = parentView.findViewById(R.id.video_view);
mKeyBoard = new KeybCode(this, a);
myKeyBoardView = parentView.findViewById(R.id.custom_input_view);
myKeyBoardView.setEnabled(true);
myKeyBoardView.setPreviewEnabled(false);
myKeyBoardView.setKeyboard(mKeyBoard);
myKeyBoardView.setOnKeyboardActionListener(this);
// API 31+ 的特殊处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
myKeyBoardView.setFocusable(true);
myKeyBoardView.setFocusableInTouchMode(true);
}
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
super.onStartInputView(info, restarting);
// API 31+ 可能需要额外的初始化
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// 确保键盘视图正确初始化
if (myKeyBoardView != null) {
myKeyBoardView.requestFocus();
}
}
// 更新当前输入法动作
if (info != null) {
curImeAction = UtilsCommon.INSTANCE.getTextForImeAction(info.imeOptions);
Log.d(App.TAG, "onStartInputView: curImeAction = " + curImeAction);
}
}
@Override
public void onWindowHidden() {
super.onWindowHidden();
if (videoView != null && videoView.isPlaying()) {
videoView.pause();
}
}
@Override
public void onWindowShown() {
super.onWindowShown();
// 获取当前编辑信息
EditorInfo currentInputEditorInfo = getCurrentInputEditorInfo();
if (currentInputEditorInfo != null) {
curImeAction = UtilsCommon.INSTANCE.getTextForImeAction(currentInputEditorInfo.imeOptions);
}
String skinPath = UtilsSavecurrentkb.INSTANCE.getSkinPath();
if (skinPath == null || skinPath.isEmpty()) {
Log.d(App.TAG, "---------skinPath= null");
if (myKeyBoardView != null) {
myKeyBoardView.updateUi(curImeAction);
}
} else {
Log.d(App.TAG, "---------skinPath= " + skinPath);
KeybFuncs.INSTANCE.readBgOrVideo(this, new Function2<String, Drawable, Unit>() {
@Override
public Unit invoke(String s, Drawable drawable) {
Log.d(App.TAG, "---------s= " + s + "---------drawable=" + drawable);
// 检查视图是否仍然有效
if (myKeyBoardView == null || imBG == null || videoView == null) {
return null;
}
if (s != null) {
myKeyBoardView.setBackground(null);
if (s.endsWith(".gif")) {
imBG.setVisibility(View.VISIBLE);
videoView.setVisibility(View.GONE);
try {
Glide.with(KeybinputService.this)
.load(s)
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
Log.e(App.TAG, "Failed to load GIF: " + s);
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
if (resource instanceof WebpDrawable) {
((WebpDrawable) resource).setLoopCount(WebpDrawable.LOOP_FOREVER);
}
return false;
}
}).into(imBG);
} catch (Exception e) {
Log.e(App.TAG, "Error loading GIF: " + e.getMessage());
}
} else if (s.endsWith(".mp4")) {
imBG.setVisibility(View.GONE);
videoView.setVisibility(View.VISIBLE);
try {
videoView.setVideoPath(s);
videoView.start();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
}
});
} catch (Exception e) {
Log.e(App.TAG, "Error playing video: " + e.getMessage());
}
} else {
Log.w(App.TAG, "Unknown media type: " + s);
}
} else if (drawable != null) {
myKeyBoardView.setBackground(drawable);
}
if (myKeyBoardView != null) {
myKeyBoardView.updateUi(curImeAction);
}
return null;
}
});
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (videoView != null) {
videoView.stopPlayback();
}
// 清理资源
if (myKeyBoardView != null) {
myKeyBoardView.setOnKeyboardActionListener(null);
}
}
@Override
public void onPress(int primaryCode) {
// 可选播放按键音效或提供触觉反馈
}
@Override
public void onRelease(int primaryCode) {
// 可选释放资源
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
InputConnection curInputConnect = getCurrentInputConnection();
if (curInputConnect == null) {
Log.w(App.TAG, "InputConnection is null");
return;
}
switch (primaryCode) {
case UtilsKeyname.KEY_CODE_DELETE:
curInputConnect.deleteSurroundingText(1, 0);
break;
case UtilsKeyname.KEY_CODE_SHIFT:
switchShift();
break;
case UtilsKeyname.KEY_CODE_NUMBER_SHIFT:
case UtilsKeyname.KEY_CODE_SYMBOL_SHIFT:
switchMoreOrNumber();
break;
case UtilsKeyname.KEY_CODE_CHANGE_NUMBER:
case UtilsKeyname.KEY_CODE_BACK:
switchNormalOrNumber();
break;
case UtilsKeyname.KEY_CODE_COMPLETE:
case UtilsKeyname.KEY_CODE_CANCEL:
if (curImeAction != EditorInfo.IME_ACTION_UNSPECIFIED) {
curInputConnect.performEditorAction(curImeAction);
} else {
// 如果没有指定动作使用完成动作
curInputConnect.performEditorAction(EditorInfo.IME_ACTION_DONE);
}
break;
default:
String codeToChar = KeybFuncs.INSTANCE.primaryCodeToChar(primaryCode);
if (codeToChar != null) {
curInputConnect.commitText(codeToChar, 1);
if (myKeyBoardView != null && myKeyBoardView.isLowerCase() == 1) {
// 自动转小写
myKeyBoardView.setLowerCase(0);
KeybFuncs.INSTANCE.keyToLowerCase(mKeyBoard);
myKeyBoardView.setKeyboard(mKeyBoard);
}
}
break;
}
}
private void switchMoreOrNumber() {
if (myKeyBoardView == null) return;
int mode = myKeyBoardView.getMode();
switch (mode) {
case 1:
mKeyBoard = new KeybCode(this, c);
myKeyBoardView.setMode(2);
myKeyBoardView.setKeyboard(mKeyBoard);
break;
case 2:
mKeyBoard = new KeybCode(this, b);
myKeyBoardView.setMode(1);
myKeyBoardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchNormalOrNumber() {
if (myKeyBoardView == null) return;
int mode = myKeyBoardView.getMode();
switch (mode) {
case 0:
mKeyBoard = new KeybCode(this, b);
myKeyBoardView.setMode(1);
myKeyBoardView.setKeyboard(mKeyBoard);
break;
case 1:
case 2:
mKeyBoard = new KeybCode(this, a);
myKeyBoardView.setMode(0);
myKeyBoardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchShift() {
if (myKeyBoardView == null) return;
int lowerCase = myKeyBoardView.isLowerCase();
switch (lowerCase) {
case 0:
// 当前小写转大写
myKeyBoardView.setLowerCase(1);
KeybFuncs.INSTANCE.keyToUpper(mKeyBoard);
myKeyBoardView.setKeyboard(mKeyBoard);
break;
case 1:
// 当前大写转锁定大写
myKeyBoardView.setLowerCase(2);
break;
case 2:
// 当前锁定大写转小写
myKeyBoardView.setLowerCase(0);
KeybFuncs.INSTANCE.keyToLowerCase(mKeyBoard);
myKeyBoardView.setKeyboard(mKeyBoard);
break;
}
}
@Override
public void onText(CharSequence text) {
// 处理文本输入
}
@Override
public void swipeLeft() {
// 处理向左滑动
}
@Override
public void swipeRight() {
// 处理向右滑动
}
@Override
public void swipeDown() {
// 处理向下滑动
}
@Override
public void swipeUp() {
// 处理向上滑动
}
}

View File

@ -0,0 +1,168 @@
package com.rainbow.app.keyboard.helper;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class ResConfig {
private String version;
private String supportLayouts;
private int hideHint;
private String layoutStyle;
private List<ResLayout> layouts = new ArrayList<>();
private List<KeybModel> keybModelList = new ArrayList<>();
private LinkedHashMap<String, String> maps = new LinkedHashMap<>();
private String KeyDefault;
private String KeyMarkDefault;
private String KeyFuncDefault;
private String KeyShift;
private String KeyDelete;
private String KeyAlphaSymbol;
private String KeyEmoji;
private String KeyMark;
private String KeySpace;
private String KeyEnter;
// Getters and Setters
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getSupportLayouts() {
return supportLayouts;
}
public void setSupportLayouts(String supportLayouts) {
this.supportLayouts = supportLayouts;
}
public int getHideHint() {
return hideHint;
}
public void setHideHint(int hideHint) {
this.hideHint = hideHint;
}
public String getLayoutStyle() {
return layoutStyle;
}
public void setLayoutStyle(String layoutStyle) {
this.layoutStyle = layoutStyle;
}
public List<ResLayout> getLayouts() {
return layouts;
}
public void addLayout(ResLayout layout) {
this.layouts.add(layout);
}
public List<KeybModel> getKeyList() {
return keybModelList;
}
public KeybModel getLastKeyList() {
return keybModelList.isEmpty() ? null : keybModelList.get(keybModelList.size() - 1);
}
public void addKey(KeybModel KeybModel) {
this.keybModelList.add(KeybModel);
}
public LinkedHashMap<String, String> getMaps() {
return maps;
}
public void setMaps(LinkedHashMap<String, String> maps) {
this.maps = maps;
}
public String getKeyDefault() {
return KeyDefault;
}
public void setKeyDefault(String keyDefault) {
KeyDefault = keyDefault;
}
public String getKeyMarkDefault() {
return KeyMarkDefault;
}
public void setKeyMarkDefault(String keyMarkDefault) {
KeyMarkDefault = keyMarkDefault;
}
public String getKeyFuncDefault() {
return KeyFuncDefault;
}
public void setKeyFuncDefault(String keyFuncDefault) {
KeyFuncDefault = keyFuncDefault;
}
public String getKeyShift() {
return KeyShift;
}
public void setKeyShift(String keyShift) {
KeyShift = keyShift;
}
public String getKeyDelete() {
return KeyDelete;
}
public void setKeyDelete(String keyDelete) {
KeyDelete = keyDelete;
}
public String getKeyAlphaSymbol() {
return KeyAlphaSymbol;
}
public void setKeyAlphaSymbol(String keyAlphaSymbol) {
KeyAlphaSymbol = keyAlphaSymbol;
}
public String getKeyEmoji() {
return KeyEmoji;
}
public void setKeyEmoji(String keyEmoji) {
KeyEmoji = keyEmoji;
}
public String getKeyMark() {
return KeyMark;
}
public void setKeyMark(String keyMark) {
KeyMark = keyMark;
}
public String getKeySpace() {
return KeySpace;
}
public void setKeySpace(String keySpace) {
KeySpace = keySpace;
}
public String getKeyEnter() {
return KeyEnter;
}
public void setKeyEnter(String keyEnter) {
KeyEnter = keyEnter;
}
}

View File

@ -0,0 +1,35 @@
package com.rainbow.app.keyboard.helper;
import java.util.ArrayList;
import java.util.List;
public class ResLayout {
private String name;
private List<KeybModel> keybModels = new ArrayList<>();
public ResLayout(String name) {
this.name = name;
}
// Getters and Setters
public String getName() {
return name;
}
public List<KeybModel> getKeys() {
return keybModels;
}
public void addKey(KeybModel KeybModel) {
this.keybModels.add(KeybModel);
}
public KeybModel getLastKey() {
return keybModels.isEmpty() ? null : keybModels.get(keybModels.size() - 1);
}
}

View File

@ -0,0 +1,87 @@
package com.rainbow.app.keyboard.javabean;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.io.Serializable;
@Entity
public class DetailsjavaBean implements Serializable {
@PrimaryKey(autoGenerate = true)
private long id;
private String titleName;
private String thumbUrl;
private String thumbGif;
private String zipPath;
private String imgPath;
private String imgGif;
public void setImgGif(String imgGif) {
this.imgGif = imgGif;
}
public String getTitleName() {
return titleName;
}
public String getThumbUrl() {
return thumbUrl;
}
public String getThumbGif() {
return thumbGif;
}
public String getZipPath() {
return zipPath;
}
public String getImgPath() {
return imgPath;
}
public void setTitleName(String name) {
this.titleName = name;
}
public void setThumbUrl(String thumbUrl) {
this.thumbUrl = thumbUrl;
}
public void setThumbGif(String thumbGif) {
this.thumbGif = thumbGif;
}
public void setZipPath(String path) {
this.zipPath = path;
}
public void setImgPath(String imgPath) {
this.imgPath = imgPath;
}
public String getImgGif() {
return imgGif;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}

View File

@ -0,0 +1,27 @@
package com.rainbow.app.keyboard.javabean;
import java.util.List;
public class WrapperjavaBean {
private String parentName;
private List<DetailsjavaBean> keyboardList;
public String getParentName() {
return parentName;
}
public List<DetailsjavaBean> getKeyboardList() {
return keyboardList;
}
public void setParentName(String name) {
this.parentName = name;
}
public void setKeyboardList(List<DetailsjavaBean> keyboardList) {
this.keyboardList = keyboardList;
}
}

View File

@ -0,0 +1,530 @@
package com.rainbow.app.keyboard.myadapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.rainbow.app.keyboard.R;
import com.rainbow.app.keyboard.javabean.DetailsjavaBean;
import com.rainbow.app.keyboard.mycallback.DeleteCallback;
import com.rainbow.app.keyboard.mycallback.KBClickCallback;
import com.rainbow.app.keyboard.databinding.AdapterKbitemBinding;
import com.rainbow.app.keyboard.uiactivity.DetailkbActivity;
import com.rainbow.app.keyboard.utils.UtilsCommon;
import java.util.ArrayList;
import java.util.List;
public class KBItemAdapter extends RecyclerView.Adapter<KBItemAdapter.KeyboardViewHolder> {
private Context mContext;
private List<DetailsjavaBean> mList = new ArrayList<>();
private DeleteCallback mMyremoveCallback;
private KBClickCallback mKBClickCallback; // 新增用于KBPreviewActivity的回调
// 模式设置
private boolean showFavoriteIcon = false; // 是否显示收藏图标
private int spanCount = 2; // 每行显示数量
private int itemSpacingDp = 10; // item之间的水平间距(dp)
private int verticalSpacingDp = 12; // item之间的垂直间距(dp)
private int sidePaddingDp = 10; // 左右边距(dp)
private float aspectRatio = 0.65f; // 宽高比 10:7
private boolean includeEdgePadding = true; // 是否包含边缘边距
private int containerWidth = 0;
private boolean isPreviewMode = false;
public KBItemAdapter(Context context) {
mContext = context;
}
public KBItemAdapter(Context context, List<DetailsjavaBean> list) {
mContext = context;
this.mList = list;
}
/**
* 设置KBPreviewActivity的回调
*/
public void setItemClickListener(KBClickCallback callback) {
this.mKBClickCallback = callback;
}
/**
* 设置容器宽度用于MainActivity中右侧网格
*/
public void setContainerWidth(int width) {
this.containerWidth = width;
notifyDataSetChanged();
}
/**
* 预设配置MainActivity首页使用侧边栏布局
* 因为右侧只有屏幕的3/4宽度间距需要调整
*/
public void applyMainActivityConfig() {
this.spanCount = 2;
this.itemSpacingDp = 4; // 适当增加间距
this.verticalSpacingDp = 8;
this.sidePaddingDp = 6; // 减小边距
this.aspectRatio = 0.65f;
this.includeEdgePadding = false; // MainActivity中设为false因为容器已经有padding
this.containerWidth = 0; // 需要外部设置
this.isPreviewMode = false;
this.showFavoriteIcon = false;
notifyDataSetChanged();
}
/**
* 预设配置CollectionActivity收藏页使用全屏布局
*/
public void applyCollectionConfig() {
this.spanCount = 2;
this.itemSpacingDp = 6;
this.verticalSpacingDp = 10;
this.sidePaddingDp = 10;
this.aspectRatio = 0.65f;
this.includeEdgePadding = true;
this.isPreviewMode = false;
this.showFavoriteIcon = true; // 收藏页面显示收藏图标
notifyDataSetChanged();
}
/**
* 预设配置KBPreviewActivity推荐列表使用小图布局
*/
public void applyRecoItemConfig() {
this.spanCount = 2;
this.itemSpacingDp = 5; // 对应原RecoItemAdapter的 marginEnd="5dp"
this.verticalSpacingDp = 8; // 对应原RecoItemAdapter的 marginBottom="8dp"
this.sidePaddingDp = 0; // 不需要额外的左右边距
this.aspectRatio = 0.0f; // 在预览模式下不使用宽高比使用固定高度
this.includeEdgePadding = false;
this.isPreviewMode = true; // 关键启用预览模式
this.showFavoriteIcon = false; // 不显示收藏图标
notifyDataSetChanged();
}
/**
* 自定义配置灵活调整
*/
public void applyCustomConfig(int spanCount, int itemSpacingDp, int verticalSpacingDp,
int sidePaddingDp, float aspectRatio, boolean includeEdgePadding) {
this.spanCount = spanCount;
this.itemSpacingDp = itemSpacingDp;
this.verticalSpacingDp = verticalSpacingDp;
this.sidePaddingDp = sidePaddingDp;
this.aspectRatio = aspectRatio;
this.includeEdgePadding = includeEdgePadding;
notifyDataSetChanged();
}
/**
* 设置数据
*/
public void setData(List<DetailsjavaBean> list) {
this.mList = list;
notifyDataSetChanged();
}
/**
* 设置是否显示收藏图标
*/
public void setShowFavoriteIcon(boolean show) {
this.showFavoriteIcon = show;
}
/**
* 设置每行显示数量
*/
public void setSpanCount(int spanCount) {
this.spanCount = spanCount;
notifyDataSetChanged();
}
/**
* 设置item水平间距(dp)
*/
public void setItemSpacing(int spacingDp) {
this.itemSpacingDp = spacingDp;
}
/**
* 设置item垂直间距(dp)
*/
public void setVerticalSpacing(int spacingDp) {
this.verticalSpacingDp = spacingDp;
}
/**
* 设置左右边距(dp)
*/
public void setSidePadding(int paddingDp) {
this.sidePaddingDp = paddingDp;
}
/**
* 设置宽高比
*/
public void setAspectRatio(float ratio) {
this.aspectRatio = ratio;
}
/**
* 设置是否包含边缘边距
*/
public void setIncludeEdgePadding(boolean include) {
this.includeEdgePadding = include;
}
/**
* 设置移除回调
*/
public void setRemoveCallback(DeleteCallback callback) {
this.mMyremoveCallback = callback;
}
@NonNull
@Override
public KeyboardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
AdapterKbitemBinding binding = AdapterKbitemBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
// 动态计算item尺寸
calculateAndSetItemSize(binding, parent);
return new KeyboardViewHolder(binding);
}
/**
* 动态计算并设置item尺寸
*/
private void calculateAndSetItemSize(AdapterKbitemBinding binding, ViewGroup parent) {
if (isPreviewMode) {
// KBPreviewActivity模式固定高度120dp
int columnHeight = dpToPx(120); // 固定120dp高度
// 设置FrameLayout尺寸
ViewGroup.LayoutParams layoutParams = binding.getRoot().getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; // 宽度由GridLayoutManager自动分配
layoutParams.height = columnHeight;
// 设置边距
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) layoutParams;
marginParams.bottomMargin = dpToPx(verticalSpacingDp); // 底部间距8dp
// 水平间距将在onBindViewHolder中根据位置设置
}
binding.getRoot().setLayoutParams(layoutParams);
// 设置CardView尺寸
ViewGroup.LayoutParams cardParams = binding.cardView.getLayoutParams();
cardParams.width = ViewGroup.LayoutParams.MATCH_PARENT; // 宽度充满
cardParams.height = columnHeight;
binding.cardView.setLayoutParams(cardParams);
} else {
// 普通网格布局原有的动态计算逻辑
int widthToUse;
// 如果 containerWidth 还没有设置尝试从RecyclerView获取
if (containerWidth <= 0) {
try {
// 尝试获取RecyclerView的实际宽度
RecyclerView recyclerView = findParentRecyclerView(binding.getRoot());
if (recyclerView != null && recyclerView.getWidth() > 0) {
widthToUse = recyclerView.getWidth();
// 缓存这个宽度供下次使用
containerWidth = widthToUse;
} else {
// 如果RecyclerView还没有测量使用屏幕宽度
widthToUse = parent.getContext().getResources().getDisplayMetrics().widthPixels;
}
} catch (Exception e) {
widthToUse = parent.getContext().getResources().getDisplayMetrics().widthPixels;
}
} else {
// 使用指定的容器宽度
widthToUse = containerWidth;
}
// 将dp转换为px
int sidePaddingPx = dpToPx(sidePaddingDp);
int itemSpacingPx = dpToPx(itemSpacingDp);
// 计算可用宽度
int totalPadding = includeEdgePadding ? sidePaddingPx * 2 : 0; // 左右边距
int totalSpacing = itemSpacingPx * (spanCount - 1); // item之间的间距
int availableWidth = widthToUse - totalPadding - totalSpacing;
int columnWidth = availableWidth / spanCount;
// 确保最小宽度
int minWidth = dpToPx(120); // 最小120dp
if (columnWidth < minWidth) {
columnWidth = minWidth;
}
int columnHeight = (int) (columnWidth * aspectRatio);
// 设置FrameLayout尺寸
ViewGroup.LayoutParams layoutParams = binding.getRoot().getLayoutParams();
layoutParams.width = columnWidth;
layoutParams.height = columnHeight;
// 设置边距
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) layoutParams;
// 设置垂直间距底部间距
marginParams.bottomMargin = dpToPx(verticalSpacingDp);
// 水平间距将在onBindViewHolder中根据位置设置
}
binding.getRoot().setLayoutParams(layoutParams);
// 设置CardView尺寸
ViewGroup.LayoutParams cardParams = binding.cardView.getLayoutParams();
cardParams.width = columnWidth;
cardParams.height = columnHeight;
binding.cardView.setLayoutParams(cardParams);
}
}
/**
* 查找父RecyclerView
*/
private RecyclerView findParentRecyclerView(View view) {
ViewParent parent = view.getParent();
while (parent != null) {
if (parent instanceof RecyclerView) {
return (RecyclerView) parent;
}
parent = parent.getParent();
}
return null;
}
@Override
public void onBindViewHolder(@NonNull KeyboardViewHolder holder, @SuppressLint("RecyclerView") int position) {
DetailsjavaBean detailsjavaBean = mList.get(position);
// 显示或隐藏收藏图标
holder.binding.favoriteIcon.setVisibility(showFavoriteIcon ? View.VISIBLE : View.GONE);
// 设置收藏图标状态
if (showFavoriteIcon) {
holder.binding.favoriteIcon.setSelected(true); // 假设都是已收藏状态
holder.binding.favoriteIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.binding.favoriteIcon.setSelected(false);
int adapterPosition = holder.getAdapterPosition();
if (adapterPosition != RecyclerView.NO_POSITION && mMyremoveCallback != null) {
mMyremoveCallback.OnRemoveLike(detailsjavaBean);
}
}
});
}
// 加载图片
String thumbGif = detailsjavaBean.getThumbGif();
String thumb = detailsjavaBean.getThumbUrl();
if (thumbGif != null && !thumbGif.isEmpty()) {
UtilsCommon.INSTANCE.loadWepJif(mContext, thumbGif, holder.binding.keyboardImage);
} else {
Glide.with(mContext)
.load(thumb)
.error(R.drawable.phold)
.placeholder(R.drawable.phold)
.centerCrop()
.into(holder.binding.keyboardImage);
}
// 设置卡片点击事件
holder.binding.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openKeyboardPreview(detailsjavaBean, thumbGif, thumb);
}
});
// 根据位置动态设置水平边距
setHorizontalMargins(holder, position);
}
/**
* 根据位置设置水平边距
*/
private void setHorizontalMargins(KeyboardViewHolder holder, int position) {
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) layoutParams;
int column = position % spanCount; // 获取当前列
// 重置水平边距
marginParams.leftMargin = 0;
marginParams.rightMargin = 0;
if (includeEdgePadding) {
// 包含边缘边距的情况
int sidePaddingPx = dpToPx(sidePaddingDp);
int itemSpacingPx = dpToPx(itemSpacingDp);
if (column == 0) {
// 第一列左边距 + 右边距为item间距的一半
marginParams.leftMargin = sidePaddingPx;
marginParams.rightMargin = itemSpacingPx / 2;
} else if (column == spanCount - 1) {
// 最后一列左边距为item间距的一半 + 右边距
marginParams.leftMargin = itemSpacingPx / 2;
marginParams.rightMargin = sidePaddingPx;
} else {
// 中间列左右边距各为item间距的一半
marginParams.leftMargin = itemSpacingPx / 2;
marginParams.rightMargin = itemSpacingPx / 2;
}
} else {
// 不包含边缘边距的情况
int itemSpacingPx = dpToPx(itemSpacingDp);
if (isPreviewMode) {
// 第一列右边距5dp第二列无左边距
if (column == 0) {
// 第一列右边距5dp
marginParams.rightMargin = itemSpacingPx;
}
// 第二列不设置左边距保持0
} else {
// 普通网格布局原有的间距逻辑
if (column == 0) {
// 第一列右边距为item间距
marginParams.rightMargin = itemSpacingPx;
} else if (column == spanCount - 1) {
// 最后一列左边距为item间距
marginParams.leftMargin = itemSpacingPx;
} else {
// 中间列左右边距各为item间距
marginParams.leftMargin = itemSpacingPx;
marginParams.rightMargin = itemSpacingPx;
}
}
}
holder.itemView.setLayoutParams(marginParams);
}
}
@Override
public int getItemCount() {
return mList.size();
}
/**
* 打开键盘预览
*/
private void openKeyboardPreview(DetailsjavaBean detailsjavaBean, String thumbGif, String thumb) {
Intent intentApply = new Intent(mContext, DetailkbActivity.class);
intentApply.putExtra(DetailkbActivity.SOURCE_KEY, detailsjavaBean);
intentApply.putExtra(DetailkbActivity.DISPLAY_URL_KEY, detailsjavaBean.getImgPath());
intentApply.putExtra(DetailkbActivity.ZIP_URL_KEY, detailsjavaBean.getZipPath());
intentApply.putExtra(DetailkbActivity.NAME_KEY, detailsjavaBean.getTitleName());
intentApply.putExtra(DetailkbActivity.GIF_KEY, detailsjavaBean.getImgGif());
String intent_thumb;
if (thumbGif != null && !thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(DetailkbActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
// 调用回调用于KBPreviewActivity
if (mKBClickCallback != null) {
mKBClickCallback.OnItemClickListener();
}
}
/**
* 移除指定位置的项目
*/
public void removeItem(int position) {
if (position >= 0 && position < mList.size()) {
mList.remove(position);
notifyItemRemoved(position);
// 更新剩余item的位置
if (position < mList.size()) {
notifyItemRangeChanged(position, mList.size() - position);
}
}
}
/**
* 添加项目
*/
public void addItem(DetailsjavaBean item) {
mList.add(item);
notifyItemInserted(mList.size() - 1);
}
/**
* 清空所有项目
*/
public void clearAll() {
mList.clear();
notifyDataSetChanged();
}
public static class KeyboardViewHolder extends RecyclerView.ViewHolder {
private AdapterKbitemBinding binding;
public KeyboardViewHolder(@NonNull AdapterKbitemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* 获取指定位置的项目
*/
public DetailsjavaBean getItem(int position) {
if (position >= 0 && position < mList.size()) {
return mList.get(position);
}
return null;
}
/**
* 查找指定项目的索引位置
*/
public int getPositionOfItem(DetailsjavaBean item) {
for (int i = 0; i < mList.size(); i++) {
if (mList.get(i).equals(item)) {
return i;
}
}
return -1;
}
/**
* dp转px
*/
private int dpToPx(int dp) {
if (mContext == null || mContext.getResources() == null) {
return dp;
}
float density = mContext.getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
}

View File

@ -0,0 +1,85 @@
package com.rainbow.app.keyboard.myadapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.rainbow.app.keyboard.R;
import com.rainbow.app.keyboard.javabean.DetailsjavaBean;
import com.rainbow.app.keyboard.javabean.WrapperjavaBean;
import java.util.List;
public class NavCategoryAdapter extends RecyclerView.Adapter<NavCategoryAdapter.NavViewHolder> {
private Context context;
private List<WrapperjavaBean> categoryList;
private CategoryClickListener categoryClickListener;
private int selectedPosition = 0; // 默认选中第一个分类
public interface CategoryClickListener {
void onCategoryClick(String categoryName, List<DetailsjavaBean> keyboards);
}
public NavCategoryAdapter(Context context, List<WrapperjavaBean> categoryList) {
this.context = context;
this.categoryList = categoryList;
}
public void setCategoryClickListener(CategoryClickListener listener) {
this.categoryClickListener = listener;
}
@NonNull
@Override
public NavViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_nav_category, parent, false);
return new NavViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull NavViewHolder holder, @SuppressLint("RecyclerView") int position) {
WrapperjavaBean category = categoryList.get(position);
String categoryName = category.getParentName();
List<DetailsjavaBean> keyboards = category.getKeyboardList();
holder.categoryName.setText(categoryName);
// 设置选中状态
if (position == selectedPosition) {
holder.itemView.setBackgroundResource(R.color.nav_selected_bg);
holder.categoryName.setTextColor(context.getResources().getColor(R.color.text_color_selected));
} else {
holder.itemView.setBackgroundResource(R.color.transparent);
holder.categoryName.setTextColor(context.getResources().getColor(R.color.text_color));
}
holder.itemView.setOnClickListener(v -> {
selectedPosition = position;
notifyDataSetChanged(); // 更新选中状态
if (categoryClickListener != null) {
categoryClickListener.onCategoryClick(categoryName, keyboards);
}
});
}
@Override
public int getItemCount() {
return categoryList.size();
}
static class NavViewHolder extends RecyclerView.ViewHolder {
TextView categoryName;
public NavViewHolder(@NonNull View itemView) {
super(itemView);
categoryName = itemView.findViewById(R.id.nav_category_name);
}
}
}

View File

@ -0,0 +1,8 @@
package com.rainbow.app.keyboard.mycallback
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
interface DeleteCallback {
fun OnRemoveLike(data: DetailsjavaBean)
}

View File

@ -0,0 +1,8 @@
package com.rainbow.app.keyboard.mycallback
import java.io.File
interface EnablekbCallback {
fun OnApplySkinListener(fileList: List<File?>?)
}

View File

@ -0,0 +1,6 @@
package com.rainbow.app.keyboard.mycallback
interface KBClickCallback {
fun OnItemClickListener( )
}

View File

@ -0,0 +1,6 @@
package com.rainbow.app.keyboard.mycallback
interface MylistCallback {
fun OnClickSeeAll(name: String)
}

View File

@ -0,0 +1,32 @@
package com.rainbow.app.keyboard.mydb
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
@Database(
entities = [DetailsjavaBean::class],
version = App.Companion.DB_VERSION,
exportSchema = false
)
abstract class DatabBase : RoomDatabase() {
abstract fun ThemesDao(): DatabDao
companion object {
val databBase: DatabBase by lazy {
Room.databaseBuilder(
App.Companion.appInstance, DatabBase::class.java,
App.Companion.DB_NAME
)
.fallbackToDestructiveMigration()
.build()
}
}
}

View File

@ -0,0 +1,31 @@
package com.rainbow.app.keyboard.mydb
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
@Dao
interface DatabDao {
@Insert(onConflict = OnConflictStrategy.Companion.IGNORE)
suspend fun insertData(data: DetailsjavaBean): Long
@Query("select * from DetailsjavaBean ")
fun queryAllLike(): LiveData<List<DetailsjavaBean?>?>
@Query("select * from DetailsjavaBean where titleName = :title ")
suspend fun queryIsLike(title: String ): DetailsjavaBean?
@Delete
suspend fun delete(data: DetailsjavaBean)
@Update
suspend fun updateLike(data: DetailsjavaBean)
}

View File

@ -0,0 +1,34 @@
package com.rainbow.app.keyboard.mydb
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object DatabManager {
suspend fun addLike(data: DetailsjavaBean) {
withContext(Dispatchers.IO) {
DatabBase.databBase.ThemesDao().insertData(data)
}
}
suspend fun removeLike(data: DetailsjavaBean) {
withContext(Dispatchers.IO) {
val queryIsLike = DatabBase.databBase.ThemesDao().queryIsLike(data.titleName)
if (queryIsLike != null) {
DatabBase.databBase.ThemesDao().delete(queryIsLike)
}
}
}
suspend fun getIsLike(name: String, action: (isLike: Boolean) -> Unit) {
withContext(Dispatchers.IO) {
val query = DatabBase.databBase.ThemesDao().queryIsLike(name)
withContext(Dispatchers.Main) {
action.invoke(query != null)
}
}
}
}

View File

@ -0,0 +1,858 @@
package com.rainbow.app.keyboard.mysourcecode;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.util.Xml;
import androidx.annotation.XmlRes;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import com.rainbow.app.keyboard.R;
public class KeybCode {
static final String TAG = "------------Keyboard-----------";
// Keyboard XML Tags
private static final String TAG_KEYBOARD = "Keyboard";
private static final String TAG_ROW = "Row";
private static final String TAG_KEY = "Key";
public static final int EDGE_LEFT = 0x01;
public static final int EDGE_RIGHT = 0x02;
public static final int EDGE_TOP = 0x04;
public static final int EDGE_BOTTOM = 0x08;
public static final int KEYCODE_SHIFT = -1;
public static final int KEYCODE_MODE_CHANGE = -2;
public static final int KEYCODE_CANCEL = -3;
public static final int KEYCODE_DONE = -4;
public static final int KEYCODE_DELETE = -5;
public static final int KEYCODE_ALT = -6;
/** Keyboard label **/
private CharSequence mLabel;
/** Horizontal gap default for all rows */
private int mDefaultHorizontalGap;
/** Default key width */
private int mDefaultWidth;
/** Default key height */
private int mDefaultHeight;
/** Default gap between rows */
private int mDefaultVerticalGap;
/** Is the keyboard in the shifted state */
private boolean mShifted;
/** Key instance for the shift key, if present */
private KeybCode.Key[] mShiftKeys = { null, null };
/** Key index for the shift key, if present */
private int[] mShiftKeyIndices = {-1, -1};
/** Current key width, while loading the keyboard */
private int mKeyWidth;
/** Current key height, while loading the keyboard */
private int mKeyHeight;
/** Total height of the keyboard, including the padding and keys */
private int mTotalHeight;
/**
* Total width of the keyboard, including left side gaps and keys, but not any gaps on the
* right side.
*/
private int mTotalWidth;
/** List of keys in this keyboard */
private List<KeybCode.Key> mKeys;
/** List of modifier keys such as Shift & Alt, if any */
private List<KeybCode.Key> mModifierKeys;
/** Width of the screen available to fit the keyboard */
private int mDisplayWidth;
/** Height of the screen */
private int mDisplayHeight;
/** Keyboard mode, or zero, if none. */
private int mKeyboardMode;
// Variables for pre-computing nearest keys.
private static final int GRID_WIDTH = 10;
private static final int GRID_HEIGHT = 5;
private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
private int mCellWidth;
private int mCellHeight;
private int[][] mGridNeighbors;
private int mProximityThreshold;
/** Number of key widths from current touch point to search for nearest keys. */
private static float SEARCH_DISTANCE = 1.8f;
private ArrayList<KeybCode.Row> rows = new ArrayList<>();
/**
* Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
* Some of the key size defaults can be overridden per row from what the {@link KeybCode}
* defines.
* @attr ref android.R.styleable#King_Keyboard_keyWidth
* @attr ref android.R.styleable#King_Keyboard_keyHeight
* @attr ref android.R.styleable#King_Keyboard_horizontalGap
* @attr ref android.R.styleable#King_Keyboard_verticalGap
* @attr ref android.R.styleable#King_Keyboard_Row_rowEdgeFlags
* @attr ref android.R.styleable#King_Keyboard_Row_keyboardMode
*/
public static class Row {
/** Default width of a key in this row. */
public int defaultWidth;
/** Default height of a key in this row. */
public int defaultHeight;
/** Default horizontal gap between keys in this row. */
public int defaultHorizontalGap;
/** Vertical gap following this row. */
public int verticalGap;
ArrayList<KeybCode.Key> mKeys = new ArrayList<>();
/**
* Edge flags for this row of keys. Possible values that can be assigned are
* {@link KeybCode#EDGE_TOP EDGE_TOP} and {@link KeybCode#EDGE_BOTTOM EDGE_BOTTOM}
*/
public int rowEdgeFlags;
/** The keyboard mode for this row */
public int mode;
private KeybCode parent;
public Row(KeybCode parent) {
this.parent = parent;
}
public Row(Resources res, KeybCode parent, XmlResourceParser parser) {
this.parent = parent;
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.My_Keyboard_view);
defaultWidth = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
parent.mDisplayWidth, parent.mDefaultWidth);
defaultHeight = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
parent.mDisplayHeight, parent.mDefaultHeight);
defaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
parent.mDisplayWidth, parent.mDefaultHorizontalGap);
verticalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_verticalGap,
parent.mDisplayHeight, parent.mDefaultVerticalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Kil_Keyboard_Row);
rowEdgeFlags = a.getInt(R.styleable.Kil_Keyboard_Row_android_rowEdgeFlags, 0);
mode = a.getResourceId(R.styleable.Kil_Keyboard_Row_android_keyboardMode,
0);
}
}
/**
* Class for describing the position and characteristics of a single key in the keyboard.
*
* @attr ref android.R.styleable#King_Keyboard_keyWidth
* @attr ref android.R.styleable#King_Keyboard_keyHeight
* @attr ref android.R.styleable#King_Keyboard_horizontalGap
* @attr ref android.R.styleable#King_Keyboard_Key_codes
* @attr ref android.R.styleable#King_Keyboard_Key_keyIcon
* @attr ref android.R.styleable#King_Keyboard_Key_keyLabel
* @attr ref android.R.styleable#King_Keyboard_Key_iconPreview
* @attr ref android.R.styleable#King_Keyboard_Key_isSticky
* @attr ref android.R.styleable#King_Keyboard_Key_isRepeatable
* @attr ref android.R.styleable#King_Keyboard_Key_isModifier
* @attr ref android.R.styleable#King_Keyboard_Key_popupKeyboard
* @attr ref android.R.styleable#King_Keyboard_Key_popupCharacters
* @attr ref android.R.styleable#King_Keyboard_Key_keyOutputText
* @attr ref android.R.styleable#King_Keyboard_Key_keyEdgeFlags
*/
public static class Key {
/**
* All the key codes (unicode or custom code) that this key could generate, zero'th
* being the most important.
*/
public int[] codes;
/** Label to display */
public CharSequence label;
/** Icon to display instead of a label. Icon takes precedence over a label */
public Drawable icon;
/** Preview version of the icon, for the preview popup */
public Drawable iconPreview;
/** Width of the key, not including the gap */
public int width;
/** Height of the key, not including the gap */
public int height;
/** The horizontal gap before this key */
public int gap;
/** Whether this key is sticky, i.e., a toggle key */
public boolean sticky;
/** X coordinate of the key in the keyboard layout */
public int x;
/** Y coordinate of the key in the keyboard layout */
public int y;
/** The current pressed state of this key */
public boolean pressed;
/** If this is a sticky key, is it on? */
public boolean on;
/** Text to output when pressed. This can be multiple characters, like ".com" */
public CharSequence text;
/** Popup characters */
public CharSequence popupCharacters;
/**
* Flags that specify the anchoring to edges of the keyboard for detecting touch events
* that are just out of the boundary of the key. This is a bit mask of
* {@link KeybCode#EDGE_LEFT}, {@link KeybCode#EDGE_RIGHT}, {@link KeybCode#EDGE_TOP} and
* {@link KeybCode#EDGE_BOTTOM}.
*/
public int edgeFlags;
/** Whether this is a modifier key, such as Shift or Alt */
public boolean modifier;
/** The keyboard that this key belongs to */
private KeybCode keyboard;
/**
* If this key pops up a mini keyboard, this is the resource id for the XML layout for that
* keyboard.
*/
public int popupResId;
/** Whether this key repeats itself when held down */
public boolean repeatable;
private final static int[] KEY_STATE_NORMAL_ON = {
android.R.attr.state_checkable,
android.R.attr.state_checked
};
private final static int[] KEY_STATE_PRESSED_ON = {
android.R.attr.state_pressed,
android.R.attr.state_checkable,
android.R.attr.state_checked
};
private final static int[] KEY_STATE_NORMAL_OFF = {
android.R.attr.state_checkable
};
private final static int[] KEY_STATE_PRESSED_OFF = {
android.R.attr.state_pressed,
android.R.attr.state_checkable
};
private final static int[] KEY_STATE_NORMAL = {
};
private final static int[] KEY_STATE_PRESSED = {
android.R.attr.state_pressed
};
/** Create an empty key with no attributes. */
public Key(KeybCode.Row parent) {
keyboard = parent.parent;
height = parent.defaultHeight;
width = parent.defaultWidth;
gap = parent.defaultHorizontalGap;
edgeFlags = parent.rowEdgeFlags;
}
/** Create a key with the given top-left coordinate and extract its attributes from
* the XML parser.
* @param res resources associated with the caller's context
* @param parent the row that this key belongs to. The row must already be attached to
* a {@link KeybCode}.
* @param x the x coordinate of the top-left
* @param y the y coordinate of the top-left
* @param parser the XML parser containing the attributes for this key
*/
public Key(Resources res, KeybCode.Row parent, int x, int y, XmlResourceParser parser) {
this(parent);
this.x = x;
this.y = y;
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.My_Keyboard_view);
width = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
keyboard.mDisplayWidth, parent.defaultWidth);
height = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
keyboard.mDisplayHeight, parent.defaultHeight);
gap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
keyboard.mDisplayWidth, parent.defaultHorizontalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.K_Keyboard_Key);
this.x += gap;
TypedValue codesValue = new TypedValue();
a.getValue(R.styleable.K_Keyboard_Key_android_codes,
codesValue);
if (codesValue.type == TypedValue.TYPE_INT_DEC
|| codesValue.type == TypedValue.TYPE_INT_HEX) {
codes = new int[] { codesValue.data };
} else if (codesValue.type == TypedValue.TYPE_STRING) {
codes = parseCSV(codesValue.string.toString());
}
iconPreview = a.getDrawable(R.styleable.K_Keyboard_Key_android_iconPreview);
if (iconPreview != null) {
iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
iconPreview.getIntrinsicHeight());
}
popupCharacters = a.getText(
R.styleable.K_Keyboard_Key_android_popupCharacters);
popupResId = a.getResourceId(
R.styleable.K_Keyboard_Key_android_popupKeyboard, 0);
repeatable = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isRepeatable, false);
modifier = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isModifier, false);
sticky = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isSticky, false);
edgeFlags = a.getInt(R.styleable.K_Keyboard_Key_android_keyEdgeFlags, 0);
edgeFlags |= parent.rowEdgeFlags;
icon = a.getDrawable(
R.styleable.K_Keyboard_Key_android_keyIcon);
if (icon != null) {
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
}
label = a.getText(R.styleable.K_Keyboard_Key_android_keyLabel);
text = a.getText(R.styleable.K_Keyboard_Key_android_keyOutputText);
if (codes == null && !TextUtils.isEmpty(label)) {
codes = new int[] { label.charAt(0) };
}
a.recycle();
}
/**
* Informs the key that it has been pressed, in case it needs to change its appearance or
* state.
* @see #onReleased(boolean)
*/
public void onPressed() {
pressed = !pressed;
}
/**
* Changes the pressed state of the key.
*
* <p>Toggled state of the key will be flipped when all the following conditions are
* fulfilled:</p>
*
* <ul>
* <li>This is a sticky key, that is, {@link #sticky} is {@code true}.
* <li>The parameter {@code inside} is {@code true}.
* <li>{@link android.os.Build.VERSION#SDK_INT} is greater than
* {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
* </ul>
*
* @param inside whether the finger was released inside the key. Works only on Android M and
* later. See the method document for details.
* @see #onPressed()
*/
public void onReleased(boolean inside) {
pressed = !pressed;
if (sticky && inside) {
on = !on;
}
}
int[] parseCSV(String value) {
int count = 0;
int lastIndex = 0;
if (value.length() > 0) {
count++;
while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
count++;
}
}
int[] values = new int[count];
count = 0;
StringTokenizer st = new StringTokenizer(value, ",");
while (st.hasMoreTokens()) {
try {
values[count++] = Integer.parseInt(st.nextToken());
} catch (NumberFormatException nfe) {
}
}
return values;
}
/**
* Detects if a point falls inside this key.
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return whether or not the point falls inside the key. If the key is attached to an edge,
* it will assume that all points between the key and the edge are considered to be inside
* the key.
*/
public boolean isInside(int x, int y) {
boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
if ((x >= this.x || (leftEdge && x <= this.x + this.width))
&& (x < this.x + this.width || (rightEdge && x >= this.x))
&& (y >= this.y || (topEdge && y <= this.y + this.height))
&& (y < this.y + this.height || (bottomEdge && y >= this.y))) {
return true;
} else {
return false;
}
}
/**
* Returns the square of the distance between the center of the key and the given point.
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return the square of the distance of the point from the center of the key
*/
public int squaredDistanceFrom(int x, int y) {
int xDist = this.x + width / 2 - x;
int yDist = this.y + height / 2 - y;
return xDist * xDist + yDist * yDist;
}
/**
* Returns the drawable state for the key, based on the current state and type of the key.
* @return the drawable state of the key.
* @see android.graphics.drawable.StateListDrawable#setState(int[])
*/
public int[] getCurrentDrawableState() {
int[] states = KEY_STATE_NORMAL;
if (on) {
if (pressed) {
states = KEY_STATE_PRESSED_ON;
} else {
states = KEY_STATE_NORMAL_ON;
}
} else {
if (sticky) {
if (pressed) {
states = KEY_STATE_PRESSED_OFF;
} else {
states = KEY_STATE_NORMAL_OFF;
}
} else {
if (pressed) {
states = KEY_STATE_PRESSED;
}
}
}
return states;
}
}
/**
* Creates a keyboard from the given xml key layout file.
* @param context the application or service context
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
*/
public KeybCode(Context context, int xmlLayoutResId) {
this(context, xmlLayoutResId, 0);
}
/**
* Creates a keyboard from the given xml key layout file. Weeds out rows
* that have a keyboard mode defined but don't match the specified mode.
* @param context the application or service context
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
* @param modeId keyboard mode identifier
* @param width sets width of keyboard
* @param height sets height of keyboard
*/
public KeybCode(Context context, @XmlRes int xmlLayoutResId, int modeId, int width,
int height) {
mDisplayWidth = width;
mDisplayHeight = height;
mDefaultHorizontalGap = 0;
mDefaultWidth = mDisplayWidth / 10;
mDefaultVerticalGap = 0;
mDefaultHeight = mDefaultWidth;
mKeys = new ArrayList<>();
mModifierKeys = new ArrayList<>();
mKeyboardMode = modeId;
loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
}
/**
* Creates a keyboard from the given xml key layout file. Weeds out rows
* that have a keyboard mode defined but don't match the specified mode.
* @param context the application or service context
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
* @param modeId keyboard mode identifier
*/
public KeybCode(Context context, @XmlRes int xmlLayoutResId, int modeId) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
mDisplayWidth = dm.widthPixels;
mDisplayHeight = dm.heightPixels;
//Log.v(TAG, "keyboard's display metrics:" + dm);
mDefaultHorizontalGap = 0;
mDefaultWidth = mDisplayWidth / 10;
mDefaultVerticalGap = 0;
mDefaultHeight = mDefaultWidth;
mKeys = new ArrayList<>();
mModifierKeys = new ArrayList<>();
mKeyboardMode = modeId;
loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
}
public KeybCode(Context context, int layoutTemplateResId,
CharSequence characters, int columns, int horizontalPadding) {
this(context, layoutTemplateResId);
int x = 0;
int y = 0;
int column = 0;
mTotalWidth = 0;
KeybCode.Row row = new KeybCode.Row(this);
row.defaultHeight = mDefaultHeight;
row.defaultWidth = mDefaultWidth;
row.defaultHorizontalGap = mDefaultHorizontalGap;
row.verticalGap = mDefaultVerticalGap;
row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
for (int i = 0; i < characters.length(); i++) {
char c = characters.charAt(i);
if (column >= maxColumns
|| x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
x = 0;
y += mDefaultVerticalGap + mDefaultHeight;
column = 0;
}
final KeybCode.Key key = new KeybCode.Key(row);
key.x = x;
key.y = y;
key.label = String.valueOf(c);
key.codes = new int[] { c };
column++;
x += key.width + key.gap;
mKeys.add(key);
row.mKeys.add(key);
if (x > mTotalWidth) {
mTotalWidth = x;
}
}
mTotalHeight = y + mDefaultHeight;
rows.add(row);
}
final void resize(int newWidth, int newHeight) {
int numRows = rows.size();
for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
KeybCode.Row row = rows.get(rowIndex);
int numKeys = row.mKeys.size();
int totalGap = 0;
int totalWidth = 0;
for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
KeybCode.Key key = row.mKeys.get(keyIndex);
if (keyIndex > 0) {
totalGap += key.gap;
}
totalWidth += key.width;
}
if (totalGap + totalWidth > newWidth) {
int x = 0;
float scaleFactor = (float)(newWidth - totalGap) / totalWidth;
for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
KeybCode.Key key = row.mKeys.get(keyIndex);
key.width *= scaleFactor;
key.x = x;
x += key.width + key.gap;
}
}
}
mTotalWidth = newWidth;
// TODO: This does not adjust the vertical placement according to the new size.
// The main problem in the previous code was horizontal placement/size, but we should
// also recalculate the vertical sizes/positions when we get this resize call.
}
public List<KeybCode.Key> getKeys() {
return mKeys;
}
public List<KeybCode.Key> getModifierKeys() {
return mModifierKeys;
}
protected int getHorizontalGap() {
return mDefaultHorizontalGap;
}
protected void setHorizontalGap(int gap) {
mDefaultHorizontalGap = gap;
}
protected int getVerticalGap() {
return mDefaultVerticalGap;
}
protected void setVerticalGap(int gap) {
mDefaultVerticalGap = gap;
}
protected int getKeyHeight() {
return mDefaultHeight;
}
protected void setKeyHeight(int height) {
mDefaultHeight = height;
}
protected int getKeyWidth() {
return mDefaultWidth;
}
protected void setKeyWidth(int width) {
mDefaultWidth = width;
}
/**
* Returns the total height of the keyboard
* @return the total height of the keyboard
*/
public int getHeight() {
return mTotalHeight;
}
public int getMinWidth() {
return mTotalWidth;
}
public boolean setShifted(boolean shiftState) {
for (KeybCode.Key shiftKey : mShiftKeys) {
if (shiftKey != null) {
shiftKey.on = shiftState;
}
}
if (mShifted != shiftState) {
mShifted = shiftState;
return true;
}
return false;
}
public boolean isShifted() {
return mShifted;
}
/**
* @hide
*/
public int[] getShiftKeyIndices() {
return mShiftKeyIndices;
}
public int getShiftKeyIndex() {
return mShiftKeyIndices[0];
}
private void computeNearestNeighbors() {
// Round-up so we don't have any pixels outside the grid
mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
mGridNeighbors = new int[GRID_SIZE][];
int[] indices = new int[mKeys.size()];
final int gridWidth = GRID_WIDTH * mCellWidth;
final int gridHeight = GRID_HEIGHT * mCellHeight;
for (int x = 0; x < gridWidth; x += mCellWidth) {
for (int y = 0; y < gridHeight; y += mCellHeight) {
int count = 0;
for (int i = 0; i < mKeys.size(); i++) {
final KeybCode.Key key = mKeys.get(i);
if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
< mProximityThreshold ||
key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
indices[count++] = i;
}
}
int [] cell = new int[count];
System.arraycopy(indices, 0, cell, 0, count);
mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
}
}
}
/**
* Returns the indices of the keys that are closest to the given point.
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return the array of integer indices for the nearest keys to the given point. If the given
* point is out of range, then an array of size zero is returned.
*/
public int[] getNearestKeys(int x, int y) {
if (mGridNeighbors == null) computeNearestNeighbors();
if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
if (index < GRID_SIZE) {
return mGridNeighbors[index];
}
}
return new int[0];
}
protected KeybCode.Row createRowFromXml(Resources res, XmlResourceParser parser) {
return new KeybCode.Row(res, this, parser);
}
protected KeybCode.Key createKeyFromXml(Resources res, KeybCode.Row parent, int x, int y,
XmlResourceParser parser) {
return new KeybCode.Key(res, parent, x, y, parser);
}
private void loadKeyboard(Context context, XmlResourceParser parser) {
boolean inKey = false;
boolean inRow = false;
boolean leftMostKey = false;
int row = 0;
int x = 0;
int y = 0;
KeybCode.Key key = null;
KeybCode.Row currentRow = null;
Resources res = context.getResources();
boolean skipRow = false;
try {
int event;
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
if (event == XmlResourceParser.START_TAG) {
String tag = parser.getName();
if (TAG_ROW.equals(tag)) {
inRow = true;
x = 0;
currentRow = createRowFromXml(res, parser);
rows.add(currentRow);
skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
if (skipRow) {
skipToEndOfRow(parser);
inRow = false;
}
} else if (TAG_KEY.equals(tag)) {
inKey = true;
key = createKeyFromXml(res, currentRow, x, y, parser);
mKeys.add(key);
if (key.codes[0] == KEYCODE_SHIFT) {
// Find available shift key slot and put this shift key in it
for (int i = 0; i < mShiftKeys.length; i++) {
if (mShiftKeys[i] == null) {
mShiftKeys[i] = key;
mShiftKeyIndices[i] = mKeys.size()-1;
break;
}
}
mModifierKeys.add(key);
} else if (key.codes[0] == KEYCODE_ALT) {
mModifierKeys.add(key);
}
currentRow.mKeys.add(key);
} else if (TAG_KEYBOARD.equals(tag)) {
parseKeyboardAttributes(res, parser);
}
} else if (event == XmlResourceParser.END_TAG) {
if (inKey) {
inKey = false;
x += key.gap + key.width;
if (x > mTotalWidth) {
mTotalWidth = x;
}
} else if (inRow) {
inRow = false;
y += currentRow.verticalGap;
y += currentRow.defaultHeight;
row++;
} else {
// TODO: error or extend?
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
mTotalHeight = y - mDefaultVerticalGap;
}
private void skipToEndOfRow(XmlResourceParser parser)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
if (event == XmlResourceParser.END_TAG
&& parser.getName().equals(TAG_ROW)) {
break;
}
}
}
private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.My_Keyboard_view);
mDefaultWidth = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
mDisplayWidth, mDisplayWidth / 10);
mDefaultHeight = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
mDisplayHeight, 50);
mDefaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
mDisplayWidth, 0);
mDefaultVerticalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_verticalGap,
mDisplayHeight, 0);
mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
a.recycle();
}
static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
TypedValue value = a.peekValue(index);
if (value == null) return defValue;
if (value.type == TypedValue.TYPE_DIMENSION) {
return a.getDimensionPixelOffset(index, defValue);
} else if (value.type == TypedValue.TYPE_FRACTION) {
// Round it to avoid values like 47.9999 from getting truncated
return Math.round(a.getFraction(index, base, base, defValue));
}
return defValue;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
package com.rainbow.app.keyboard.uiactivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.ad.tradpluslibrary.TPAdManager
import com.ad.tradpluslibrary.TPAdManager.showTPAD
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.R
import com.rainbow.app.keyboard.myadapter.KBItemAdapter
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
import com.rainbow.app.keyboard.mycallback.DeleteCallback
import com.rainbow.app.keyboard.mydb.DatabBase
import com.rainbow.app.keyboard.mydb.DatabManager
import com.rainbow.app.keyboard.databinding.ActivityCollectionBinding
import kotlinx.coroutines.launch
class CollectionActivity : AppCompatActivity() {
private lateinit var binding: ActivityCollectionBinding
private lateinit var adapter: KBItemAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCollectionBinding.inflate(layoutInflater)
this.enableEdgeToEdge()
TPAdManager.loadAllAd(this)
setContentView(binding.getRoot())
showTPAD(this) { }
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v: View, insets: WindowInsetsCompat ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val topPadding = systemBars.top
v.setPadding(systemBars.left, topPadding, systemBars.right, systemBars.bottom)
insets
}
initViews()
initData()
}
private fun initViews() {
binding.back.setOnClickListener {
showTPAD(this) {
finish()
}
}
}
private fun initData() {
// 初始化适配器
adapter = KBItemAdapter(this).apply {
setShowFavoriteIcon(true)
applyCollectionConfig()
setRemoveCallback(object : DeleteCallback {
override fun OnRemoveLike(data: DetailsjavaBean) {
lifecycleScope.launch {
DatabManager.removeLike(data)
// 从列表中移除该项
removeItemFromAdapter(data)
android.widget.Toast.makeText(
this@CollectionActivity,
"Removed from favorites",
android.widget.Toast.LENGTH_SHORT
).show()
}
}
})
}
// 设置RecyclerView
binding.likeRecycler.run {
adapter = this@CollectionActivity.adapter
layoutManager = GridLayoutManager(this@CollectionActivity, 2)
clipToPadding = false
setPadding(0, 0, 0, 0)
}
// 观察收藏数据变化
DatabBase.databBase.ThemesDao().queryAllLike().observe(this) { favoriteList ->
Log.d(App.TAG, "Collection size: ${favoriteList?.size}")
if (favoriteList.isNullOrEmpty()) {
showEmptyState()
} else {
showCollectionList(favoriteList as List<DetailsjavaBean>)
updateFavoriteCountDisplay(favoriteList.size)
}
}
}
private fun showEmptyState() {
binding.likeRecycler.visibility = View.GONE
binding.textFavoriteCountContainer.visibility = View.GONE
val emptyContainer = findViewById<View>(R.id.empty_container)
emptyContainer?.visibility = View.VISIBLE
}
private fun showCollectionList(favoriteList: List<DetailsjavaBean>) {
binding.likeRecycler.visibility = View.VISIBLE
val emptyContainer = findViewById<View>(R.id.empty_container)
emptyContainer?.visibility = View.GONE
// 更新适配器数据
adapter.setData(favoriteList)
}
private fun updateFavoriteCountDisplay(count: Int) {
if (count > 0) {
binding.textFavoriteCountContainer.visibility = View.VISIBLE
val countText = when (count) {
1 -> "1 keyboard collected"
else -> "$count keyboards collected"
}
binding.textFavoriteCount.text = countText
} else {
binding.textFavoriteCountContainer.visibility = View.GONE
}
}
// 从适配器中移除指定项
private fun removeItemFromAdapter(bean: DetailsjavaBean) {
val position = adapter.getPositionOfItem(bean)
if (position != -1) {
adapter.removeItem(position)
}
}
}

View File

@ -0,0 +1,347 @@
package com.rainbow.app.keyboard.uiactivity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ad.tradpluslibrary.TPAdManager
import com.ad.tradpluslibrary.TPAdManager.showTPAD
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.R
import com.rainbow.app.keyboard.javabean.DetailsjavaBean
import com.rainbow.app.keyboard.mycallback.KBClickCallback
import com.rainbow.app.keyboard.mycallback.EnablekbCallback
import com.rainbow.app.keyboard.mydb.DatabManager
import com.rainbow.app.keyboard.utils.UtilsCommon
import com.rainbow.app.keyboard.utils.UtilszipFile
import com.rainbow.app.keyboard.utils.UtilsSavecurrentkb
import com.bumptech.glide.Glide
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.rainbow.app.keyboard.myadapter.KBItemAdapter
import kotlinx.coroutines.launch
import java.io.File
class DetailkbActivity : AppCompatActivity() {
companion object {
@JvmField
var DISPLAY_URL_KEY: String = "display_url_key"
@JvmField
val ZIP_URL_KEY = "zip_url_key"
@JvmField
val NAME_KEY = "name_key"
@JvmField
val GIF_KEY = "gif_key"
@JvmField
val THUMB_KEY = "thumb_key"
@JvmField
val SOURCE_KEY = "data_key"
}
private var dialog: EnablekbDialog? = null
private lateinit var displayUrl: String
private lateinit var gifUrl: String
private lateinit var zipUrl: String
private lateinit var name: String
private lateinit var applyBtn: LinearLayout
private lateinit var imgData: ImageView
private lateinit var imgBack: ImageView
private lateinit var textName: TextView
private lateinit var btnScrollToTop: ImageView
private lateinit var recommendedRecycler: RecyclerView
private lateinit var nestedScrollView: androidx.core.widget.NestedScrollView
private lateinit var loadingLayout: FrameLayout
private lateinit var unzipPath: String
private lateinit var tvDownload: TextView
private lateinit var imDownload: ImageView
private lateinit var thumb: String
private lateinit var imgLike: ImageView
private lateinit var data: DetailsjavaBean
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detailkb)
this.enableEdgeToEdge()
TPAdManager.loadAllAd(this)
showTPAD(this) { }
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main),
OnApplyWindowInsetsListener { v: View, insets: WindowInsetsCompat ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
})
findViewId()
getExtraData()
displayData()
setApply()
onClick()
setupScrollListener() // 添加滚动监听
}
private fun findViewId() {
applyBtn = findViewById(R.id.layoutDownloadApply)
imgData = findViewById(R.id.image_data)
imgBack = findViewById(R.id.back)
textName = findViewById(R.id.textview_data_name)
recommendedRecycler = findViewById(R.id.recommended_recycler)
nestedScrollView = findViewById(R.id.nested_scroll_view) // NestedScrollView
btnScrollToTop = findViewById(R.id.btn_scroll_to_top)
imgLike = findViewById(R.id.im_like)
loadingLayout = findViewById(R.id.loading)
imDownload = findViewById(R.id.im_download)
tvDownload = findViewById(R.id.tv_download)
}
private fun getExtraData() {
data = intent.getSerializableExtra(SOURCE_KEY) as DetailsjavaBean
displayUrl = intent.getStringExtra(DISPLAY_URL_KEY).toString()
zipUrl = intent.getStringExtra(ZIP_URL_KEY).toString()
name = intent.getStringExtra(NAME_KEY).toString()
gifUrl = intent.getStringExtra(GIF_KEY).toString()
thumb = intent.getStringExtra(THUMB_KEY).toString()
val serviceZipName = UtilszipFile.getServiceZipName(zipUrl)
unzipPath = UtilszipFile.getUnzipPath(serviceZipName)
Log.d("KeyboardActivity", "unzipPath=$unzipPath")
lifecycleScope.launch {
DatabManager.getIsLike(name) {
imgLike.isSelected = it
}
}
if (File(unzipPath).exists()) {
imDownload.isVisible = false
tvDownload.text = getString(R.string.apply)
} else {
imDownload.isVisible = true
tvDownload.text = getString(R.string.download_apply)
}
}
private fun displayData() {
textName.text = name
textName.setTypeface(textName.typeface, Typeface.BOLD)
if (gifUrl.isNotEmpty()) {
loadImgGif()
} else {
Glide.with(this)
.load(displayUrl)
.thumbnail(Glide.with(this).load(thumb))
.into(imgData)
}
}
private fun onClick() {
imgBack.setOnClickListener {
finish()
}
imgLike.setOnClickListener {
showTPAD(this) {
imgLike.isSelected = !imgLike.isSelected
lifecycleScope.launch {
if (imgLike.isSelected) {
DatabManager.addLike(data)
} else {
DatabManager.removeLike(data)
}
}
}
}
// 回到顶部按钮点击事件
btnScrollToTop.setOnClickListener {
nestedScrollView.smoothScrollTo(0, 0)
btnScrollToTop.visibility = View.GONE
}
val forYouList = App.Companion.list.filter {
it.parentName == getString(R.string.recommend_name)
}
recommendedRecycler.run {
adapter = KBItemAdapter(this@DetailkbActivity).apply {
val shuffled = forYouList[0].keyboardList.shuffled()
setData(shuffled)
applyRecoItemConfig() // 应用RecoItem配置
setItemClickListener(object : KBClickCallback {
override fun OnItemClickListener() {
finish()
}
})
}
layoutManager = GridLayoutManager(this@DetailkbActivity, 2) // 保持2列网格
}
}
// ==================== 滚动监听器 ====================
private fun setupScrollListener() {
nestedScrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
// 当滚动超过200像素时显示回到顶部按钮
if (scrollY > 200) {
btnScrollToTop.visibility = View.VISIBLE
} else {
btnScrollToTop.visibility = View.GONE
}
}
// 也可以为RecyclerView添加监听双重保险
recommendedRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// 获取NestedScrollView的滚动位置
val scrollY = nestedScrollView.scrollY
if (scrollY > 200) {
btnScrollToTop.visibility = View.VISIBLE
} else {
btnScrollToTop.visibility = View.GONE
}
}
})
// 初始检查一次
nestedScrollView.post {
val scrollY = nestedScrollView.scrollY
btnScrollToTop.visibility = if (scrollY > 200) View.VISIBLE else View.GONE
}
}
@SuppressLint("CheckResult")
private fun loadImgGif() {
Glide.with(this)
.load(gifUrl)
.thumbnail(Glide.with(this).load(thumb))
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(imgData)
}
private fun setApply() {
applyBtn.setOnClickListener {
val checkEnable = UtilsCommon.checkEnable(this)
val checkSetDefault = UtilsCommon.checkSetDefault(this)
if (!checkEnable || !checkSetDefault) {
showDialog()
return@setOnClickListener
}
startDown()
}
}
private fun showDialog() {
dialog = dialog ?: EnablekbDialog.newInstance()
dialog?.setClickListener {
startDown()
}
dialog?.show(supportFragmentManager, "")
}
private fun startDown() {
showTPAD(this) {
loadingLayout.isVisible = true
applyBtn.isEnabled = false
val file = File(unzipPath)
if (file.exists()) {
val findFirstDirectory = UtilszipFile.findFirstDirectory(file)
apply("${findFirstDirectory}/")
applyBtn.isEnabled = true
loadingLayout.isVisible = false
} else {
UtilszipFile.startDownloadZip(zipUrl, object : EnablekbCallback {
override fun OnApplySkinListener(fileList: List<File?>?) {
runOnUiThread {
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
if (fileList.isNullOrEmpty()) {
runOnUiThread {
Toast.makeText(
this@DetailkbActivity,
getString(R.string.download_fail),
Toast.LENGTH_SHORT
).show()
}
} else {
if (file.exists()) {
val findFirstDirectory = UtilszipFile.findFirstDirectory(file)
Log.d(
App.Companion.TAG,
"----apply------------it=$findFirstDirectory"
)
runOnUiThread {
apply("${findFirstDirectory}/")
}
}
}
}
})
}
}
}
private fun apply(path: String) {
var skinParentPath = path
if (path.contains("res")) {
skinParentPath = path.substringBeforeLast("res")
}
UtilsSavecurrentkb.updateSkinPath(skinParentPath)
Toast.makeText(
this@DetailkbActivity,
getString(R.string.theme_application_successful),
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this, EditVActivity::class.java).apply {
putExtra(EditVActivity.key_name, name)
})
finish()
}
}

View File

@ -0,0 +1,201 @@
package com.rainbow.app.keyboard.uiactivity;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import androidx.activity.EdgeToEdge;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.ad.tradpluslibrary.TPAdManager;
import com.rainbow.app.keyboard.R;
import com.rainbow.app.keyboard.databinding.ActivityEditvBinding;
import com.rainbow.app.keyboard.utils.UtilsKeyname;
import com.rainbow.app.keyboard.utils.UtilsCommon;
import com.rainbow.app.keyboard.utils.UtilsFastblur;
import com.rainbow.app.keyboard.utils.UtilsSavecurrentkb;
public class EditVActivity extends AppCompatActivity {
private ActivityEditvBinding vb;
public static String key_name = "key_name";
private int mPreviousKeyboardHeight = -1;
private Drawable blurBackground;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityEditvBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
TPAdManager.INSTANCE.loadAllAd(this);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
onInit();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 清理资源
blurBackground = null;
}
public void onInit() {
String stringExtra = getIntent().getStringExtra(key_name);
vb.title.setText(stringExtra);
vb.title.setTypeface(vb.title.getTypeface(), Typeface.BOLD);
String curPath = UtilsSavecurrentkb.INSTANCE.getSkinPath();
vb.idBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
if (curPath == null) {
return;
}
String bgPath = curPath + "res/drawable-xxhdpi-v4/" + UtilsKeyname.previewBg;
Drawable bgDraw = UtilsCommon.INSTANCE.getBgDrawable(this, bgPath);
if (bgDraw != null) {
// 使用FastBlur进行模糊处理
applyBlurBackground(bgDraw);
}
keyboardheight();
setupEditText();
vb.et.requestFocus();
// 针对不同 API 设置不同的软键盘模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// API 30+
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
// 延迟显示键盘
showKeyboardWithDelay();
}
private void setupEditText() {
// 设置焦点监听
vb.et.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
showKeyboard();
}
}
});
// 设置点击监听
vb.et.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showKeyboard();
}
});
}
private void showKeyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(vb.et, InputMethodManager.SHOW_IMPLICIT);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (!isKeyboardVisible()) {
// 方法2尝试强制显示
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
}, 200);
}
}
private void showKeyboardWithDelay() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (!isKeyboardVisible()) {
showKeyboard();
}
}
}, 300);
}
private boolean isKeyboardVisible() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
return imm != null && imm.isActive(vb.et);
}
private void applyBlurBackground(Drawable originalDrawable) {
// 在子线程中进行模糊处理避免阻塞UI
new Thread(() -> {
try {
// 使用FastBlur进行模糊
Drawable blurredDrawable = UtilsFastblur.blurDrawable(originalDrawable, 15, this);
runOnUiThread(() -> {
if (blurredDrawable != null) {
blurBackground = blurredDrawable;
vb.main.setBackground(blurredDrawable);
} else {
// 如果模糊失败使用原始背景
vb.main.setBackground(originalDrawable);
}
});
} catch (Exception e) {
e.printStackTrace();
runOnUiThread(() -> {
// 异常情况下使用原始背景
vb.main.setBackground(originalDrawable);
});
}
}).start();
}
private void keyboardheight() {
final View rootView = getWindow().getDecorView();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int screenHeight = rootView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight != mPreviousKeyboardHeight) {
if (mPreviousKeyboardHeight < keypadHeight) {
mPreviousKeyboardHeight = keypadHeight;
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) vb.et.getLayoutParams();
params.bottomMargin = mPreviousKeyboardHeight;
vb.et.setLayoutParams(params);
}
}
}
});
}
}

View File

@ -0,0 +1,180 @@
package com.rainbow.app.keyboard.uiactivity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.provider.Settings
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.R
import com.rainbow.app.keyboard.databinding.DialogEnablekbBinding
import com.rainbow.app.keyboard.utils.UtilsCommon
class EnablekbDialog : DialogFragment() {
private lateinit var vb: DialogEnablekbBinding
private lateinit var layoutStepOne: LinearLayout
private lateinit var layoutStepTwo: LinearLayout
private lateinit var imgStepOkOne: ImageView
private lateinit var imgStepOkTwo: ImageView
private lateinit var intentFilter: IntentFilter
private var myreceiver: BroadcastReceiver? = null
private lateinit var stepOne: TextView
private lateinit var stepTwo: TextView
private lateinit var context: Context
private var clickAction: (() -> Unit )? = null
companion object {
fun newInstance(): EnablekbDialog {
val fragment = EnablekbDialog()
return fragment
}
}
fun setClickListener(action:() -> Unit){
clickAction = action
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
vb = DialogEnablekbBinding.inflate(layoutInflater)
context = App.Companion.appInstance
findViewId()
onViewStep()
getReceiver()
return vb.root
}
override fun onStart() {
super.onStart()
dialog?.run {
setCanceledOnTouchOutside(true)
window?.run {
setGravity(Gravity.BOTTOM)
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
attributes = attributes.apply {
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.WRAP_CONTENT
}
}
}
}
private fun findViewId() {
layoutStepOne = vb.linearStepOne
layoutStepTwo = vb.linearStepTwo
imgStepOkOne = vb.okOne
imgStepOkTwo = vb.okTwo
stepOne = vb.textStepOne
stepTwo = vb.textStepTwo
}
private fun onViewStep() {
layoutStepOne.setOnClickListener {
startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
}
layoutStepTwo.setOnClickListener {
val inputMethodManager =
context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showInputMethodPicker()
}
vb.imClose.setOnClickListener {
dismiss()
}
}
override fun onResume() {
super.onResume()
updateUI()
}
private fun getReceiver() {
myreceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateUI()
}
}
intentFilter = IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED)
context.registerReceiver(myreceiver, intentFilter)
}
private fun updateUI() {
val checkEnable = UtilsCommon.checkEnable(App.Companion.appInstance)
val checkSetDefault = UtilsCommon.checkSetDefault(App.Companion.appInstance)
if (checkEnable && checkSetDefault) {
clickAction?.invoke()
dismiss()
return
}
if (checkEnable) {
layoutStepOne.isEnabled = false
layoutStepOne.isSelected = true
imgStepOkOne.isVisible = true
stepOne.setTextColor(context.getColor(R.color.step_true))
} else {
layoutStepOne.isEnabled = true
layoutStepOne.isSelected = false
imgStepOkOne.isVisible = false
stepOne.setTextColor(context.getColor(R.color.white))
}
if (checkSetDefault) {
layoutStepTwo.isEnabled = false
layoutStepTwo.isSelected = true
imgStepOkTwo.isVisible = true
stepTwo.setTextColor(context.getColor(R.color.step_true))
} else {
layoutStepTwo.isEnabled = true
layoutStepTwo.isSelected = false
imgStepOkTwo.isVisible = false
stepTwo.setTextColor(context.getColor(R.color.white))
}
}
override fun onDestroy() {
super.onDestroy()
if (myreceiver != null) {
context.unregisterReceiver(myreceiver)
}
}
}

View File

@ -0,0 +1,129 @@
package com.rainbow.app.keyboard.uiactivity
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.os.Looper
import android.widget.ProgressBar
import android.widget.TextView
import com.ad.tradpluslibrary.TPAdManager
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.R
import com.rainbow.app.keyboard.utils.UtilsCommon
class LaunchActivity : Activity() {
private lateinit var progressBar: ProgressBar
private lateinit var progressPercentText: TextView
private lateinit var versionText: TextView
private var totalTime = 15000L // 总时间15秒
private lateinit var timer: CountDownTimer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_launch)
UtilsCommon.initFullScreen(this, true,true)
initViews()
setVersionInfo()
initProgressTimer()
}
private fun initViews() {
progressBar = findViewById(R.id.progressbar)
progressPercentText = findViewById(R.id.textview_progress_percent)
versionText = findViewById(R.id.textview_version)
// 确保进度条从0开始
progressBar.max = 100
progressBar.progress = 0
// 清除二级进度
progressBar.secondaryProgress = 0
}
private fun setVersionInfo() {
try {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
val versionName = packageInfo.versionName
versionText.text = "Version $versionName"
} catch (e: Exception) {
e.printStackTrace()
versionText.text = "Version 1.0.0"
}
}
private fun initProgressTimer() {
TPAdManager.init(
this,
App.TAG,
"D385CBF21CC0AAEC52780FA0231CAA11",
"81263152D7C8427BEAC81DCD943AA312",
"429D6B55BF3D4B279B08C3F631EBC012",
"647276D17478D3867FD2223AEAB4F112"
) {
}
timer = TPAdManager.showWelcomeAd(this, totalTime, { millisUntilFinished ->
//倒计时更新
val progressPercentage = (100 * millisUntilFinished) / totalTime
val percentage = 100 - progressPercentage
progressBar.progress = percentage.toInt()
progressPercentText.text = "$percentage%"
}) {
//跳转首页
// 完成时设置进度为100%
progressBar.progress = 100
progressPercentText.text = "100%"
// 添加一个短暂的延迟让用户看到100%的完成状态
Handler(Looper.getMainLooper()).postDelayed({
toHome()
}, 0)
}
timer.start()
//
// timer = object : CountDownTimer(totalTime, 100) {
// override fun onTick(millisUntilFinished: Long) {
// // 计算进度百分比(递增逻辑)
// val progressPercentage = (100 * millisUntilFinished / totalTime).toInt()
// val countdownPercentage = 100 - progressPercentage
//
// // 确保百分比在有效范围内
// val safePercentage = countdownPercentage.coerceIn(0, 100)
//
// // 更新进度条和百分比显示
// progressBar.progress = safePercentage
// progressPercentText.text = "$safePercentage%"
// }
//
// override fun onFinish() {
// // 完成时设置进度为100%
// progressBar.progress = 100
// progressPercentText.text = "100%"
//
// // 添加一个短暂的延迟让用户看到100%的完成状态
// Handler(Looper.getMainLooper()).postDelayed({
// toHome()
// }, 0)
// }
// }
//
// // 启动计时器
// timer.start()
}
private fun toHome() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
override fun onDestroy() {
super.onDestroy()
if (::timer.isInitialized) {
timer.cancel()
}
}
}

View File

@ -0,0 +1,232 @@
package com.rainbow.app.keyboard.uiactivity;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.rainbow.app.keyboard.App;
import com.rainbow.app.keyboard.R;
import com.rainbow.app.keyboard.javabean.DetailsjavaBean;
import com.rainbow.app.keyboard.javabean.WrapperjavaBean;
import com.rainbow.app.keyboard.myadapter.KBItemAdapter;
import com.rainbow.app.keyboard.myadapter.NavCategoryAdapter;
import com.rainbow.app.keyboard.databinding.ActivityMainBinding;
import java.util.List;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private List<WrapperjavaBean> categoryList;
private KBItemAdapter keyboardAdapter;
private NavCategoryAdapter navAdapter;
private List<DetailsjavaBean> firstCategoryKeyboards;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(binding.getRoot());
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
initViews();
initData();
setupNavigationSidebar();
setupClickListeners();
setupScrollListener();
}
private void initViews() {
// 设置应用名称字体
binding.appName.setTypeface(Typeface.create(binding.appName.getTypeface(), Typeface.BOLD));
}
private void initData() {
// 获取所有分类
categoryList = new ArrayList<>(App.list);
// 初始化成员变量而不是局部变量
firstCategoryKeyboards = new ArrayList<>();
if (!categoryList.isEmpty()) {
firstCategoryKeyboards = categoryList.get(0).getKeyboardList();
}
keyboardAdapter = new KBItemAdapter(this);
keyboardAdapter.setSpanCount(2); // 每行显示两个
// 先应用配置使用默认值
keyboardAdapter.applyMainActivityConfig();
// 设置RecyclerView键盘壁纸网格
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
binding.recyclerview.setLayoutManager(layoutManager);
binding.recyclerview.setAdapter(keyboardAdapter);
binding.recyclerview.setClipToPadding(false);
binding.recyclerview.setPadding(0, 0, 0, 0);
// 使用ViewTreeObserver确保在布局完成后设置宽度
binding.recyclerview.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 移除监听器避免重复执行
binding.recyclerview.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int recyclerViewWidth = binding.recyclerview.getWidth();
if (recyclerViewWidth > 0) {
// 设置容器宽度
keyboardAdapter.setContainerWidth(recyclerViewWidth);
// 现在设置数据
keyboardAdapter.setData(firstCategoryKeyboards);
}
}
}
);
}
private void setupNavigationSidebar() {
// 初始化侧边栏分类适配器
navAdapter = new NavCategoryAdapter(this, categoryList);
navAdapter.setCategoryClickListener(new NavCategoryAdapter.CategoryClickListener() {
@Override
public void onCategoryClick(String categoryName, List<DetailsjavaBean> keyboards) {
// 更新主内容区域显示选中的分类
updateMainContent(keyboards);
}
});
// 设置侧边栏RecyclerView
LinearLayoutManager navLayoutManager = new LinearLayoutManager(this);
binding.navRecyclerview.setLayoutManager(navLayoutManager);
binding.navRecyclerview.setAdapter(navAdapter);
}
private void updateMainContent(List<DetailsjavaBean> keyboards) {
firstCategoryKeyboards = keyboards;
// 更新键盘壁纸列表
keyboardAdapter.setData(keyboards);
keyboardAdapter.notifyDataSetChanged();
// 滚动到顶部
binding.recyclerview.smoothScrollToPosition(0);
}
private void setupClickListeners() {
// 应用图标点击事件 - 跳转到SettingDialog保持原样
binding.appIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openSettingDialog();
}
});
// 收藏图标点击事件 - 跳转到CollectionActivity
binding.collectionIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openCollectionActivity();
}
});
// 回到顶部按钮点击事件
binding.btnScrollToTop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scrollToTop();
}
});
}
private void openSettingDialog() {
try {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
private void openCollectionActivity() {
try {
Intent intent = new Intent(this, CollectionActivity.class);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
private void setupScrollListener() {
binding.recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
boolean canScrollUp = recyclerView.canScrollVertically(-1);
binding.btnScrollToTop.setVisibility(canScrollUp ? View.VISIBLE : View.GONE);
}
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
boolean canScrollUp = recyclerView.canScrollVertically(-1);
binding.btnScrollToTop.setVisibility(canScrollUp ? View.VISIBLE : View.GONE);
}
}
});
binding.recyclerview.post(new Runnable() {
@Override
public void run() {
boolean canScrollUp = binding.recyclerview.canScrollVertically(-1);
binding.btnScrollToTop.setVisibility(canScrollUp ? View.VISIBLE : View.GONE);
}
});
}
private void scrollToTop() {
if (binding.recyclerview.getLayoutManager() != null) {
binding.recyclerview.smoothScrollToPosition(0);
}
}
// dp转px的辅助方法
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
@Override
protected void onDestroy() {
super.onDestroy();
binding = null;
}
@Override
protected void onResume() {
super.onResume();
// 从CollectionActivity返回时可以在这里更新数据如果需要
// 例如刷新收藏状态等
}
}

View File

@ -0,0 +1,264 @@
package com.rainbow.app.keyboard.uiactivity;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.ad.tradpluslibrary.TPAdManager;
import com.rainbow.app.keyboard.R;
import com.google.android.material.button.MaterialButton;
public class SettingsActivity extends AppCompatActivity {
private int currentRating = 0;
private AlertDialog ratingDialog;
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_settings);
setupDialogWindow();
TPAdManager.INSTANCE.loadAllAd(this);
setupViews();
setVersionNumber();
}
private void setupDialogWindow() {
Window window = getWindow();
if (window != null) {
// 设置窗口宽度和高度
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.dimAmount = 0.5f; // 背景暗色程度
window.setAttributes(params);
// 创建圆角背景
GradientDrawable background = new GradientDrawable();
background.setColor(getResources().getColor(R.color.theme_background));
background.setCornerRadius(dpToPx(20));
// 设置背景
window.setBackgroundDrawable(background);
// 确保没有标题栏
if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
}
}
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
private void setupViews() {
// 关闭按钮
ImageView closeButton = findViewById(R.id.close_button);
closeButton.setOnClickListener(v -> finish());
// 评分区域
View rateUsLayout = findViewById(R.id.rateUsLayout);
rateUsLayout.setOnClickListener(v -> showRatingDialog());
}
private void setVersionNumber() {
try {
PackageInfo info = getPackageManager()
.getPackageInfo(getPackageName(), 0);
String version = info.versionName;
TextView versionValue = findViewById(R.id.versionValue);
versionValue.setText(version);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
TextView versionValue = findViewById(R.id.versionValue);
versionValue.setText("1.0");
}
}
private void showRatingDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
android.view.ContextThemeWrapper themedContext =
new android.view.ContextThemeWrapper(this, R.style.Theme_PaintingHelper);
View dialogView;
try {
LayoutInflater inflater = LayoutInflater.from(themedContext);
dialogView = inflater.inflate(R.layout.dialog_feedback, null);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Error loading rating dialog", Toast.LENGTH_SHORT).show();
return;
}
// 查找所有视图
ImageView star1 = dialogView.findViewById(R.id.star1);
ImageView star2 = dialogView.findViewById(R.id.star2);
ImageView star3 = dialogView.findViewById(R.id.star3);
ImageView star4 = dialogView.findViewById(R.id.star4);
ImageView star5 = dialogView.findViewById(R.id.star5);
// 使用MaterialButton类型
MaterialButton btnCancel = dialogView.findViewById(R.id.btn_cancel);
MaterialButton btnRate = dialogView.findViewById(R.id.btn_rate);
currentRating = 0;
if (btnRate != null) {
btnRate.setEnabled(false);
updateRateButtonState(btnRate, currentRating);
}
View.OnClickListener starClickListener = v -> {
int rating = 0;
int viewId = v.getId();
if (viewId == R.id.star1) rating = 1;
else if (viewId == R.id.star2) rating = 2;
else if (viewId == R.id.star3) rating = 3;
else if (viewId == R.id.star4) rating = 4;
else if (viewId == R.id.star5) rating = 5;
updateStarRating(star1, star2, star3, star4, star5, rating);
currentRating = rating;
if (btnRate != null) {
btnRate.setEnabled(rating > 0);
updateRateButtonState(btnRate, rating);
}
};
if (star1 != null) star1.setOnClickListener(starClickListener);
if (star2 != null) star2.setOnClickListener(starClickListener);
if (star3 != null) star3.setOnClickListener(starClickListener);
if (star4 != null) star4.setOnClickListener(starClickListener);
if (star5 != null) star5.setOnClickListener(starClickListener);
ratingDialog = builder.setView(dialogView)
.setCancelable(true)
.create();
// 设置Dialog窗口属性
Window ratingWindow = ratingDialog.getWindow();
if (ratingWindow != null) {
ratingWindow.setBackgroundDrawableResource(android.R.color.transparent);
ratingWindow.setDimAmount(0.6f);
}
if (btnCancel != null) {
btnCancel.setOnClickListener(v -> {
if (ratingDialog != null && ratingDialog.isShowing()) {
ratingDialog.dismiss();
}
});
}
if (btnRate != null) {
btnRate.setOnClickListener(v -> {
if (currentRating > 0) {
handleRatingSubmission(currentRating);
if (ratingDialog != null && ratingDialog.isShowing()) {
ratingDialog.dismiss();
}
}
});
}
ratingDialog.show();
}
private void updateStarRating(ImageView star1, ImageView star2, ImageView star3,
ImageView star4, ImageView star5, int rating) {
if (star1 == null || star2 == null || star3 == null || star4 == null || star5 == null) {
return;
}
int starFilledRes = R.mipmap.ic_star;
int starOutlineRes = R.mipmap.ic_star_outline;
star1.setImageResource(rating >= 1 ? starFilledRes : starOutlineRes);
star2.setImageResource(rating >= 2 ? starFilledRes : starOutlineRes);
star3.setImageResource(rating >= 3 ? starFilledRes : starOutlineRes);
star4.setImageResource(rating >= 4 ? starFilledRes : starOutlineRes);
star5.setImageResource(rating >= 5 ? starFilledRes : starOutlineRes);
}
private void updateRateButtonState(MaterialButton rateButton, int rating) {
if (rateButton == null) return;
if (rating > 0) {
rateButton.setEnabled(true);
try {
rateButton.setBackgroundResource(R.drawable.bg_rateit);
} catch (Exception e) {
// 使用默认背景
}
} else {
rateButton.setEnabled(false);
try {
rateButton.setBackgroundResource(R.drawable.bg_rateit_outline);
} catch (Exception e) {
// 使用默认背景
}
}
}
private void handleRatingSubmission(int rating) {
String message;
if (rating >= 4) {
message = "Thank you for your " + rating + " star rating!";
openPlayStoreForReview();
} else {
message = "Thanks for your " + rating + " star feedback!";
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
private void openPlayStoreForReview() {
try {
String packageName = getPackageName();
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageName));
startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
String packageName = getPackageName();
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("https://play.google.com/store/apps/details?id=" + packageName));
startActivity(intent);
}
}
@Override
public void finish() {
super.finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (ratingDialog != null && ratingDialog.isShowing()) {
ratingDialog.dismiss();
}
ratingDialog = null;
}
}

View File

@ -0,0 +1,153 @@
package com.rainbow.app.keyboard.utils
import android.app.Activity
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import com.rainbow.app.keyboard.App
import com.bumptech.glide.Glide
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import java.io.File
object UtilsCommon {
val transform = RequestOptions().transform(CenterCrop(), RoundedCorners(dpToPx(8f)))
fun initFullScreen(activity: Activity, dark: Boolean? = true, showStatusBar: Boolean = false) {
val window = activity.window
val decorView = window.decorView
if (showStatusBar) {
// 显示状态栏,但保持透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
// 设置内容延伸到状态栏下
var flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && dark == false) {
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
decorView.systemUiVisibility = flags
} else {
// 使用真正的全屏标志
decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
)
// 移除状态栏的透明效果
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
}
fun loadWepJif(mContext: Context, webpGifUrl: String, view: ImageView) {
Glide.with(mContext)
.load(webpGifUrl)
// .apply(transform)
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(view)
}
fun getBgDrawable(con: Context, filePath: String): Drawable? {
if (!File(filePath).exists()) {
return null
}
return BitmapDrawable(con.resources, BitmapFactory.decodeFile(filePath))
}
private val systemService =
App.Companion.appInstance.getSystemService(Context.INPUT_METHOD_SERVICE)
private val inputMethodManager = systemService as InputMethodManager
fun checkSetDefault(con: Context): Boolean {
// API 30+ 需要 QUERY_ALL_PACKAGES 权限
return try {
val defaultId =
Settings.Secure.getString(con.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)
defaultId != null && defaultId.startsWith(con.packageName)
} catch (e: SecurityException) {
// API 30+ 可能没有权限访问
Log.e("Comutils", "Permission denied to check default input method", e)
false
}
}
fun checkEnable(con: Context): Boolean {
return try {
// 总是使用传入的 context
val inputMethodManager = con.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
// 添加空值检查
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val enabledMethods = inputMethodManager.enabledInputMethodList
enabledMethods.any { methodInfo ->
methodInfo.id.startsWith(con.packageName)
}
} else {
// 旧版本的回退方案
true // 假设已启用,避免阻塞
}
} catch (e: SecurityException) {
Log.e("Comutils", "Permission denied checking enabled IMEs", e)
// 在 API 30+ 上,如果没有权限,假设已启用
true
} catch (e: Exception) {
Log.e("Comutils", "Error checking enabled IMEs", e)
true // 出错时假设已启用,让用户继续
}
}
fun getTextForImeAction(imeOptions: Int): Int {
return imeOptions and EditorInfo.IME_MASK_ACTION
}
fun dpToPx(dpValue: Float): Int {
val scale = App.Companion.appInstance.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
}

View File

@ -0,0 +1,291 @@
package com.rainbow.app.keyboard.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class UtilsFastblur {
public static Bitmap fastBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
// 图片缩放比例提高模糊效率
int scaleRatio = 8;
int blurRadius = radius;
if (sentBitmap == null || sentBitmap.isRecycled()) {
return null;
}
int width = Math.max(sentBitmap.getWidth() / scaleRatio, 1);
int height = Math.max(sentBitmap.getHeight() / scaleRatio, 1);
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig() != null ? sentBitmap.getConfig() : Bitmap.Config.ARGB_8888, true);
if (canReuseInBitmap) {
bitmap = sentBitmap;
}
// 创建缩放后的Bitmap
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
// 进行模糊处理
Bitmap blurredBitmap = doBlur(scaledBitmap, blurRadius, true);
// 缩放回原始尺寸
Bitmap resultBitmap = Bitmap.createScaledBitmap(blurredBitmap, sentBitmap.getWidth(), sentBitmap.getHeight(), false);
// 清理临时Bitmap
if (scaledBitmap != null && scaledBitmap != blurredBitmap) {
scaledBitmap.recycle();
}
if (blurredBitmap != null && blurredBitmap != resultBitmap) {
blurredBitmap.recycle();
}
return resultBitmap;
}
private static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
Bitmap bitmap;
if (canReuseInBitmap) {
bitmap = sentBitmap;
} else {
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
}
if (radius < 1) {
return null;
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int[] r = new int[wh];
int[] g = new int[wh];
int[] b = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int[] vmin = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int[] dv = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[Math.min(wm, Math.max(i, 0)) + y * w];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[vmin[x] + y * w];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return bitmap;
}
public static Drawable blurDrawable(Drawable drawable, int blurRadius, Context context) {
if (drawable == null) {
return null;
}
try {
// 将Drawable转换为Bitmap
Bitmap bitmap;
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
bitmap = bitmapDrawable.getBitmap();
if (bitmap == null || bitmap.isRecycled()) {
return drawable;
}
} else {
// 非BitmapDrawable创建新的Bitmap
bitmap = Bitmap.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
}
// 应用模糊
Bitmap blurredBitmap = fastBlur(bitmap, blurRadius, false);
if (blurredBitmap != null && !blurredBitmap.isRecycled()) {
return new BitmapDrawable(context.getResources(), blurredBitmap);
}
return drawable;
} catch (Exception e) {
e.printStackTrace();
return drawable;
}
}
}

View File

@ -0,0 +1,181 @@
package com.rainbow.app.keyboard.utils
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.util.Xml
import androidx.core.content.ContextCompat
import com.rainbow.app.keyboard.App
import com.rainbow.app.keyboard.R
import com.rainbow.app.keyboard.helper.ResConfig
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.StringReader
import kotlin.collections.iterator
class UtilsKbmanager(var context: Context) {
private var textSize = 13f
var functionDraw: Drawable =
getDefaultDrawList(R.drawable.phold_key_outline, R.drawable.phold_key)
var generalDraw: Drawable =
getDefaultDrawList(R.drawable.phold_key_outline, R.drawable.phold_key)
var toDraw: Drawable = getDefaultDrawList(R.drawable.phold_key_outline, R.drawable.phold_key)
var spaceDraw: Drawable = getDefaultDrawList(R.drawable.phold_key_outline, R.drawable.phold_key)
var switchDraw: Drawable? = null
var deleteDraw: Drawable? = null
var backDraw: Drawable? = null
var searchDraw: Drawable? = null
var shiftDraw: Drawable? = null
var shiftLockDraw: Drawable? = null
var keyTextColor: Int = ContextCompat.getColor(context, R.color.black)
var keyTextColorFunction: Int = ContextCompat.getColor(context, R.color.black)
fun getConfig(): ResConfig? {
val skinPath = UtilsSavecurrentkb.getSkinPath()
val configFilePath = skinPath + "assets/keyboard.conf"
val file = File(configFilePath)
return if (file.exists()) {
UtilsconfigFile.initConfig(configFilePath)
} else {
null
}
}
fun getConfigBg(name: String): Drawable? {
UtilsSavecurrentkb.getSkinPath()?.let { resPath ->
val pPath = "${resPath}res/drawable-xhdpi-v4/"
return getDrawList(
pPath + name,
pPath + name
)
}
return null
}
fun updateSkinConfig() {
UtilsSavecurrentkb.getSkinPath()?.let { resPath ->
val pPath = "${resPath}res/drawable-xhdpi-v4/"
pPath.let {
readColors(resPath) {
for ((name, value) in it) {
if (name == UtilsKeyname.keyTextColorName) {
keyTextColor = value
}
if (name == UtilsKeyname.keyTextColorFunctionName) {
keyTextColorFunction = value
}
}
}
functionDraw = getDrawList(
it + UtilsKeyname.functionNormalName,
it + UtilsKeyname.functionPressName
)
generalDraw = getDrawList(it + UtilsKeyname.normalName, it + UtilsKeyname.pressName)
toDraw = getDrawList(it + UtilsKeyname.toNormalName, it + UtilsKeyname.toPressName)
spaceDraw =
getDrawList(it + UtilsKeyname.spaceNormalName, it + UtilsKeyname.spacePressName)
switchDraw =
getDrawList(it + UtilsKeyname.imeSwitchName, it + UtilsKeyname.imeSwitchName)
deleteDraw = getDrawList(
it + UtilsKeyname.deleteNormalName,
it + UtilsKeyname.deletePressName
)
backDraw = getDrawList(it + UtilsKeyname.backName, it + UtilsKeyname.backName)
searchDraw = getDrawList(it + UtilsKeyname.searchName, it + UtilsKeyname.searchName)
shiftDraw = getDrawList(
it + UtilsKeyname.shiftNormalName,
it + UtilsKeyname.shiftNormalName
)
shiftLockDraw =
getDrawList(it + UtilsKeyname.shiftLockName, it + UtilsKeyname.shiftLockName)
}
}
}
private fun getDefaultDrawList(normalDrawId: Int, pressDrawId: Int): StateListDrawable {
val normalDraw = ContextCompat.getDrawable(App.Companion.appInstance, normalDrawId)
val pressDraw = ContextCompat.getDrawable(App.Companion.appInstance, pressDrawId)
val stateListDrawable = StateListDrawable().apply {
addState(
intArrayOf(android.R.attr.state_pressed),
pressDraw
)
addState(intArrayOf(), normalDraw)
}
return stateListDrawable
}
private fun getDrawList(normalPath: String, pressPath: String): StateListDrawable {
val pressDraw = BitmapFactory.decodeFile(pressPath)
val normalDraw = BitmapFactory.decodeFile(normalPath)
val stateListDrawable = StateListDrawable().apply {
addState(
intArrayOf(android.R.attr.state_pressed),
BitmapDrawable(context.resources, pressDraw)
)
addState(intArrayOf(), BitmapDrawable(context.resources, normalDraw))
}
return stateListDrawable
}
private fun readColors(resPath: String, callBack: (Map<String, Int>) -> Unit) {
val resMaps = mutableMapOf<String, Int>()
val pPath = "${resPath}res/colors.xml"
val file = File(pPath)
if (file.exists()) {
val xmlPullParser = Xml.newPullParser().apply {
setInput(StringReader(file.readText()))
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
}
var curType = xmlPullParser.eventType
while (curType != XmlPullParser.END_DOCUMENT) {
val b = curType == XmlPullParser.START_TAG
val b1 = xmlPullParser.name == "color"
val b2 = xmlPullParser.name == "item"
if (b && (b1 || b2)) {
val attributeName = xmlPullParser.getAttributeValue(null, "name")
val nextTextValue = xmlPullParser.nextText()
val b3 = attributeName == UtilsKeyname.keyTextColorName
val b4 = attributeName == UtilsKeyname.keyTextColorFunctionName
if (b3 || b4) {
resMaps[attributeName] = Color.parseColor(nextTextValue)
}
}
curType = xmlPullParser.next()
}
}
callBack.invoke(resMaps)
}
}

View File

@ -0,0 +1,60 @@
package com.rainbow.app.keyboard.utils
object UtilsKeyname {
const val KEY_CODE_DELETE = -5
//同一个按键
const val KEY_CODE_SHIFT = -1
const val KEY_CODE_NUMBER_SHIFT = -103
const val KEY_CODE_SYMBOL_SHIFT = -101
//同一个按键
const val KEY_CODE_CHANGE_NUMBER = -2
const val KEY_CODE_BACK = -102
const val KEY_CODE_COMPLETE = -4
const val KEY_CODE_CANCEL = -3
const val KEY_CODE_SPACE = 32
const val functionNormalName = "btn_keyboard_key_functional_normal.9.png"
const val functionPressName = "btn_keyboard_key_functional_pressed.9.png"
const val normalName = "btn_keyboard_key_normal_normal.9.png"
const val pressName = "btn_keyboard_key_normal_pressed.9.png"
const val toNormalName="btn_keyboard_key_toggle_normal_on.9.png"
const val toPressName="btn_keyboard_key_toggle_pressed_on.9.png"
const val spaceNormalName = "btn_keyboard_spacekey_normal_normal.9.png"
const val spacePressName = "btn_keyboard_spacekey_normal_pressed.9.png"
const val imeSwitchName ="ic_ime_switcher.png"
const val deleteNormalName = "sym_keyboard_delete_normal.png"
const val deletePressName = "sym_keyboard_delete_pressed.png"
const val backName ="sym_keyboard_return_normal.png"
const val searchName ="sym_keyboard_search.png"
const val shiftNormalName ="sym_keyboard_shift.png"
const val shiftLockName ="sym_keyboard_shift_locked.png"
const val keyTextColorName ="key_text_color_normal"
const val keyTextColorFunctionName ="key_text_color_functional"
const val videoName ="keyboard_background_video.mp4"
const val bgName ="keyboard_background.jpg"
const val bgName_png ="keyboard_background.png"
const val previewBg="keyboard_preview_screenshot.jpg"
const val video ="keyboard_background_video.gif"
}

View File

@ -0,0 +1,19 @@
package com.rainbow.app.keyboard.utils
import android.content.Context
import com.rainbow.app.keyboard.App
object UtilsSavecurrentkb {
val SP_NAME = "keyboard_skin"
val SKIN_PATH = "skin_path"
val spSkin = App.Companion.appInstance.getSharedPreferences(SP_NAME,Context.MODE_PRIVATE)
fun updateSkinPath(skinPath:String){
spSkin.edit().putString(SKIN_PATH,skinPath).apply()
}
fun getSkinPath( ):String?{
return spSkin.getString(SKIN_PATH,null)
}
}

View File

@ -0,0 +1,37 @@
package com.rainbow.app.keyboard.utils;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.rainbow.app.keyboard.App;
import com.rainbow.app.keyboard.R;
public class UtilsTextvcustom extends androidx.appcompat.widget.AppCompatTextView {
public UtilsTextvcustom(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context,attrs);
}
private void initAttrs(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTV);
boolean aBoolean = typedArray.getBoolean(R.styleable.MyTV_apply_font,false);
if(aBoolean){
initFont(this);
}
typedArray.recycle();
}
public static void initFont(TextView tv) {
tv.setTypeface(App.Companion.getDefaultFont());
}
}

View File

@ -0,0 +1,106 @@
package com.rainbow.app.keyboard.utils;
import com.rainbow.app.keyboard.helper.KeybModel;
import com.rainbow.app.keyboard.helper.ResConfig;
import com.rainbow.app.keyboard.helper.ResLayout;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class UtilsconfigFile {
public static ResConfig initConfig(String path) {
String filePath = "keyboard.conf"; // 文件路径
ResConfig config = parseConfig(path);
return config;
}
public static ResConfig parseConfig(String filePath) {
// InputStream open = App.appInstance.getAssets().open(filePath);
ResConfig config = new ResConfig();
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
ResLayout currentLayout = null;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue; // 跳过空行
}
if (line.startsWith("Version:")) {
config.setVersion(line.split(":")[1].trim());
} else if (line.startsWith("SupportLayouts:")) {
config.setSupportLayouts(line.split(":")[1].trim());
} else if (line.startsWith("HideHint:")) {
config.setHideHint(Integer.parseInt(line.split(":")[1].trim()));
} else if (line.startsWith("LayoutStyle:")) {
config.setLayoutStyle(line.split(":")[1].trim());
} else if (line.equals("KeyDefault") || line.equals("KeyMarkDefault") || line.equals("KeyFuncDefault")) {
LinkedHashMap<String, String> maps = config.getMaps();
maps.put(line, "");
} else if (line.contains(":") && currentLayout == null) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
String latestKey = null;
LinkedHashMap<String, String> maps = config.getMaps();
for (Map.Entry<String, String> entry : maps.entrySet()) {
latestKey = entry.getKey();
}
if (latestKey != null) {
maps.put(latestKey, keyValue);
}
} else if (line.startsWith("Row")) {
currentLayout = new ResLayout(line.split(":")[0].trim());
config.addLayout(currentLayout);
} else if (currentLayout != null) {
if (line.equals("Key")) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
KeybModel KeybModel = new KeybModel(keyName);
currentLayout.addKey(KeybModel);
} else if (line.contains(":") && currentLayout.getLastKey().getBackground() == null) {
// 解析按键的其他属性 Label
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
KeybModel KeybModel = currentLayout.getLastKey();
if (keyName.equals("Label")) {
KeybModel.setLabel(keyValue);
}
if (keyName.equals("Background")) {
KeybModel.setBackground(keyValue);
}
} else {
if (line.equals("KeyShift") || line.equals("KeyDelete") || line.equals("KeyAlphaSymbol") || line.equals("KeyEmoji")
|| line.equals("KeyMark")
|| line.equals("KeySpace")
|| line.equals("KeyEnter")) {
KeybModel funcationKeybModel = new KeybModel(line);
config.addKey(funcationKeybModel);
} else if (line.contains(":")) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
KeybModel lastKeybModel = config.getLastKeyList();
if (keyName.equals("Label")) {
lastKeybModel.setLabel(keyValue);
}
if (keyName.equals("Background")) {
lastKeybModel.setBackground(keyValue);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return config;
}
}

View File

@ -0,0 +1,238 @@
package com.rainbow.app.keyboard.utils;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.rainbow.app.keyboard.App;
import com.rainbow.app.keyboard.mycallback.EnablekbCallback;
import net.sf.sevenzipjbinding.ArchiveFormat;
import net.sf.sevenzipjbinding.IArchiveOpenCallback;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class UtilszipFile {
public static void startDownloadZip(String zipPath, EnablekbCallback callback) {
OkHttpClient clientZip = new OkHttpClient().newBuilder().
connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS).build();
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(zipPath).build();
clientZip.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.OnApplySkinListener(null);
}
@Override
public void onResponse(Call call, Response response) {
InputStream inputStream = response.body().byteStream();
long l = response.body().contentLength();
MediaType mediaType = response.body().contentType();
saveZipFile(inputStream, getServiceZipName(zipPath), callback);
}
});
}
public static String getServiceZipName(String zipPath) {
String pointStr = "/";
int lastIndexOf = zipPath.lastIndexOf(pointStr);
String zipName = zipPath.substring(lastIndexOf + pointStr.length());
return zipName;
}
private static String getunZipFolderName(String zipPath) {
String pointStr = ".";
int lastIndexOf = zipPath.lastIndexOf(pointStr);
String zipName = zipPath.substring(0, lastIndexOf);
return zipName;
}
private static void saveZipFile(InputStream inputStream, String zipFileName, EnablekbCallback callback) {
File zipfFile = new File(App.appInstance.getFilesDir(), zipFileName);
Log.d("-------------------","-------zipFileName="+zipFileName);
byte[] bytes = new byte[4096];
int readLength = 0;
InputStream is = inputStream;
FileOutputStream fileOs = null;
try {
fileOs = new FileOutputStream(zipfFile);
while ((readLength = is.read(bytes)) != -1) {
fileOs.write(bytes, 0, readLength);
}
fileOs.flush();
} catch (Exception exception) {
} finally {
try {
if (is != null) {
is.close();
}
if (fileOs != null) {
fileOs.close();
}
} catch (IOException ioException) {
}
un7ZZipFile(zipfFile, callback);
}
}
public static String getUnzipPath(String zipName){
String folderName = getunZipFolderName(zipName);
String replace = folderName.replace(".", "");
return App.appInstance.getFilesDir().getPath() + "/" + replace;
}
private static void un7ZZipFile(File saveZipFile, EnablekbCallback callback) {
List<File> fileList = new ArrayList<>();
String unzipFolderPath = getUnzipPath(saveZipFile.getName());
try {
RandomAccessFileInStream inStream = new RandomAccessFileInStream(new RandomAccessFile(saveZipFile, "r"));
IInArchive open = SevenZip.openInArchive(ArchiveFormat.SEVEN_ZIP, inStream, new IArchiveOpenCallback() {
@Override
public void setTotal(Long files, Long bytes) {
}
@Override
public void setCompleted(Long files, Long bytes) {
}
});
ISimpleInArchive simple = open.getSimpleInterface();
for (ISimpleInArchiveItem archiveItem : simple.getArchiveItems()) {
RandomAccessFileOutStream outStream = null;
try {
File itemFile;
if (archiveItem.isFolder()) {
File itemFolder = new File(unzipFolderPath, archiveItem.getPath());
boolean mkdirs = itemFolder.mkdirs();
continue;
} else {
itemFile = new File(unzipFolderPath, archiveItem.getPath());
if (!itemFile.getParentFile().exists()) {
boolean mkdirs = itemFile.getParentFile().mkdirs();
}
}
outStream = new RandomAccessFileOutStream(new RandomAccessFile(itemFile, "rw"));
archiveItem.extractSlow(outStream);
fileList.add(itemFile);
} finally {
if (outStream != null) {
outStream.close();
}
}
}
inStream.close();
open.close();
} catch (FileNotFoundException | SevenZipException exception) {
} catch (IOException ioException) {
} finally {
if (saveZipFile.exists()) {
saveZipFile.delete();
}
callback.OnApplySkinListener(fileList);
}
}
public static File findFirstDirectory(File dir) {
if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
return file; // 返回第一个文件目录
}
}
}
}
return null; // 如果没有找到文件目录则返回null
}
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
// 如果不是 BitmapDrawable则创建一个空的 Bitmap 对象
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
// Bitmap 保存到文件
private static void saveBitmapToFile(Bitmap bitmap, File file) throws IOException {
if(!file.exists()){
file.createNewFile();
}
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // 保存为 PNG 格式
out.flush();
out.close();
}
// 示例 Drawable 写入文件
public static void saveDrawableToFile(Drawable drawable, File file) throws IOException {
Bitmap bitmap = drawableToBitmap(drawable);
saveBitmapToFile(bitmap, file);
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" />
<solid android:color="@color/button_cancel_bg" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="18dp" />
<solid android:color="@color/card_bg" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp"/>
<solid android:color="@color/theme_background"/>
</shape>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 进度条背景轨道 -->
<item android:id="@android:id/background">
<shape>
<corners android:radius="7dp" />
<solid android:color="@color/launch_progress_bg" />
<!-- 添加边框增加层次感 -->
<stroke
android:width="1dp"
android:color="#D1D5DB" />
</shape>
</item>
<!-- 主进度条 - 使用纯色 -->
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="7dp" />
<solid android:color="@color/launch_progress" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" />
<solid android:color="@color/button_rate_bg" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" />
<solid android:color="@color/button_rate_bg_disabled" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/ic_bg" />
<corners android:radius="23dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@mipmap/ic_star_outline" android:state_selected="false"/>
<item android:drawable="@mipmap/ic_star" android:state_selected="true" />
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_rateit" android:state_selected="false" />
<item android:drawable="@drawable/bg_rateit_outline" />
</selector>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/white"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/key_bg_press"/>
</shape>
</item>
</selector>

Binary file not shown.

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/theme_background"
android:paddingTop="8dp">
<!-- 标题 -->
<ImageView
android:id="@+id/back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingStart="5dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:src="@mipmap/ic_back"
android:tint="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/collection_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:letterSpacing="0.03"
android:text="Collection"
android:textStyle="bold"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 收藏数量显示 -->
<LinearLayout
android:id="@+id/text_favorite_count_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="12dp"
android:orientation="horizontal"
android:padding="8dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/collection_title">
<View
android:layout_width="0dp"
android:layout_height="1.2dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:background="@color/underline_color" />
<TextView
android:id="@+id/textFavoriteCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/underline_color"
android:textSize="14sp" />
<View
android:layout_width="0dp"
android:layout_height="1.2dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="30dp"
android:layout_weight="1"
android:background="@color/underline_color" />
</LinearLayout>
<!-- 内容区域 -->
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_favorite_count_container">
<!-- 收藏列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/likeRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:visibility="gone" />
<!-- 空状态 -->
<include
android:id="@+id/empty_container"
layout="@layout/empty_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,255 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/sidebar_bg">
<!-- ==================== 顶部键盘详情部分 ==================== -->
<androidx.cardview.widget.CardView
android:id="@+id/top_section_card"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardElevation="12dp"
app:cardCornerRadius="0dp"
app:cardBackgroundColor="@android:color/transparent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:background="@color/sidebar_bg">
<!-- 返回按钮 -->
<ImageView
android:id="@+id/back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingStart="5dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:src="@mipmap/ic_back"
android:tint="@color/text_color"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 标题 -->
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:id="@+id/textview_data_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/text_color"
android:textSize="18sp"
android:textStyle="bold"
app:apply_font="true"
app:layout_constraintBottom_toBottomOf="@id/back"
app:layout_constraintEnd_toStartOf="@+id/im_like"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="@id/back" />
<!-- 收藏按钮 -->
<ImageView
android:id="@+id/im_like"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginEnd="12dp"
android:padding="9dp"
android:src="@drawable/selector_collect"
app:layout_constraintBottom_toBottomOf="@id/back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/back" />
<!-- 键盘预览卡片 -->
<androidx.cardview.widget.CardView
android:id="@+id/card_viewData"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/back">
<ImageView
android:id="@+id/image_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</androidx.cardview.widget.CardView>
<!-- 下载应用按钮 -->
<LinearLayout
android:id="@+id/layoutDownloadApply"
android:layout_width="260dp"
android:layout_height="48dp"
android:layout_marginTop="22dp"
android:background="@drawable/bg_rateit"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_viewData">
<ImageView
android:id="@+id/im_download"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/kbpre_download" />
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:id="@+id/tv_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="@string/download_apply"
android:textColor="@color/white"
android:textSize="16sp"
app:apply_font="true" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- ==================== 推荐部分 ==================== -->
<androidx.core.widget.NestedScrollView
android:id="@+id/nested_scroll_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
android:background="@color/theme_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_section_card">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 推荐标题区域 -->
<LinearLayout
android:id="@+id/title_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="20dp"
android:paddingBottom="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<!-- 左边下划线 -->
<View
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_marginEnd="12dp"
android:background="@color/underline_color" />
<!-- 推荐标题 -->
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recommend"
android:textColor="@color/text_color"
android:textSize="18sp"
android:textStyle="bold"
android:letterSpacing="0.05"
app:apply_font="true" />
<!-- 右边下划线 -->
<View
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_marginStart="12dp"
android:background="@color/underline_color" />
</LinearLayout>
<!-- 副标题 -->
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/more_suggestions"
android:textColor="@color/subtext_color"
android:textSize="12sp"
app:apply_font="true" />
</LinearLayout>
<!-- 推荐键盘列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recommended_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:clipToPadding="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<!-- ==================== 回到顶部按钮 ==================== -->
<ImageView
android:id="@+id/btn_scroll_to_top"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="30dp"
android:background="@drawable/bg_topicon"
android:clickable="true"
android:focusable="true"
android:padding="8dp"
android:src="@mipmap/ic_top"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- ==================== 加载动画 ==================== -->
<FrameLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_loading"
android:clickable="true"
android:focusable="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateTint="@color/white" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/main"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_loading" />
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="5dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="5dp"
android:background="@drawable/bg_editv"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/et_hint"
android:paddingStart="15dp"
android:textColor="@color/black"
android:textColorHint="@color/apply_step_false"
android:textCursorDrawable="@color/color_74CBFF" />
</RelativeLayout>
<ImageView
android:id="@+id/id_back"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="5dp"
android:paddingStart="5dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:src="@mipmap/ic_back" />
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/id_back"
android:layout_alignBottom="@id/id_back"
android:layout_centerHorizontal="true"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18sp"
app:apply_font="true" />
</RelativeLayout>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@color/launch_bg"
tools:context=".uiactivity.LaunchActivity">
<ImageView
android:id="@+id/image"
android:layout_width="170dp"
android:layout_height="170dp"
android:layout_marginTop="160dp"
android:src="@mipmap/logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textview_appname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="@string/app_name"
android:fontFamily="@font/alimama_shuheiti_"
android:textColor="@color/launch_text"
android:textSize="42sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image" />
<ProgressBar
android:id="@+id/progressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="280dp"
android:layout_height="14dp"
android:layout_marginTop="40dp"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/bg_launchprogress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textview_appname" />
<TextView
android:id="@+id/textview_progress_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="0%"
android:textColor="@color/launch_subtext"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressbar" />
<TextView
android:id="@+id/textview_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:textColor="@color/launch_subtext"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/theme_background"
android:clipToPadding="false"
tools:context=".uiactivity.MainActivity">
<!-- 主内容区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 顶部标题栏 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:paddingStart="16dp"
android:paddingEnd="14dp">
<ImageView
android:id="@+id/appIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:scaleType="fitCenter"
android:src="@mipmap/logo" />
<TextView
android:id="@+id/appName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:fontFamily="@font/alimama_shuheiti_"
android:letterSpacing="0.03"
android:text="@string/app_name"
android:textColor="@color/title_color"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/collectionIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="4dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:src="@mipmap/home_tocollection" />
</RelativeLayout>
<!-- 分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/card_divider" />
<!-- 固定双栏布局:左侧分类列表 + 右侧键盘网格 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<!-- 左侧分类列表(屏幕宽度四分之一) -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/sidebar_bg"
android:orientation="vertical"
android:paddingStart="6dp"
android:paddingEnd="6dp">
<!-- 分类列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/navRecyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</LinearLayout>
<!-- 右侧键盘网格(屏幕宽度四分之三) -->
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:padding="8dp">
<!-- 键盘壁纸网格 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
<!-- 回到顶部按钮 -->
<ImageView
android:id="@+id/btn_scroll_to_top"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="bottom|end"
android:layout_marginEnd="20dp"
android:layout_marginBottom="30dp"
android:background="@drawable/bg_topicon"
android:clickable="true"
android:focusable="true"
android:padding="8dp"
android:src="@mipmap/ic_top"
android:visibility="gone" />
</FrameLayout>

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/theme_background"
android:minHeight="500dp">
<ImageView
android:id="@+id/close_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:src="@mipmap/dia_close"
android:padding="4dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_20_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.12" />
<!-- 顶部图标和名称区域 -->
<LinearLayout
android:id="@+id/iconContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline_20_percent">
<ImageView
android:id="@+id/appIcon"
android:layout_width="@dimen/logo_size"
android:layout_height="@dimen/logo_size"
android:scaleType="fitCenter"
android:src="@mipmap/logo" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:fontFamily="@font/alimama_shuheiti_"
android:letterSpacing="0.05"
android:text="@string/app_name"
android:textColor="@color/title_color"
android:textSize="@dimen/app_name_text_size" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="32dp"
app:cardBackgroundColor="@color/card_bg"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iconContainer"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/card_padding">
<!-- Rate Us 选项 -->
<RelativeLayout
android:id="@+id/rateUsLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/option_item_height"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="@dimen/option_padding_horizontal"
android:paddingVertical="@dimen/option_padding_vertical">
<ImageView
android:id="@+id/rateUsIcon"
android:layout_width="@dimen/option_icon_size"
android:layout_height="@dimen/option_icon_size"
android:layout_centerVertical="true"
android:src="@mipmap/dia_feedback" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/option_text_margin_start"
android:layout_toEndOf="@id/rateUsIcon"
android:text="@string/rate_us"
android:textColor="@color/card_text"
android:textSize="@dimen/option_text_size"
android:textStyle="bold" />
<ImageView
android:layout_width="@dimen/option_arrow_size"
android:layout_height="@dimen/option_arrow_size"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:src="@mipmap/dia_toright"
android:tint="@color/card_icon_tint" />
</RelativeLayout>
<!-- 分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginTop="@dimen/divider_margin_vertical"
android:layout_marginBottom="@dimen/divider_margin_vertical"
android:background="@color/card_divider" />
<!-- Version 选项 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/option_item_height"
android:paddingHorizontal="@dimen/option_padding_horizontal"
android:paddingVertical="@dimen/option_padding_vertical">
<ImageView
android:id="@+id/versionIcon"
android:layout_width="@dimen/option_icon_size"
android:layout_height="@dimen/option_icon_size"
android:layout_centerVertical="true"
android:src="@mipmap/dia_version" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/option_text_margin_start"
android:layout_toEndOf="@id/versionIcon"
android:text="@string/version"
android:textColor="@color/card_text"
android:textSize="@dimen/option_text_size"
android:textStyle="bold" />
<TextView
android:id="@+id/versionValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:textColor="@color/card_text"
android:textSize="@dimen/version_value_text_size" />
</RelativeLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="2dp">
<ImageView
android:id="@+id/keyboardImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/favoriteIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:src="@drawable/selector_collect"
android:visibility="gone" />
</androidx.cardview.widget.CardView>
</FrameLayout>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_dia"
android:paddingBottom="30dp">
<ImageView
android:id="@+id/im_close"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="15dp"
android:padding="5dp"
android:src="@mipmap/dia_close"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:id="@+id/text_open"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:gravity="center"
android:text="@string/open_str"
android:textColor="@color/black"
android:textSize="16sp"
app:apply_font="true"
app:layout_constraintTop_toBottomOf="@id/im_close" />
<LinearLayout
android:id="@+id/linear_step_one"
android:layout_width="280dp"
android:layout_height="52dp"
android:layout_marginTop="22dp"
android:background="@drawable/selector_enablekb"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_open">
<ImageView
android:id="@+id/ok_one"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@mipmap/kbpre_already"
android:visibility="gone" />
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:id="@+id/text_step_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/step_1"
android:textColor="@color/white"
android:textSize="15sp"
app:apply_font="true" />
</LinearLayout>
<LinearLayout
android:id="@+id/linear_step_two"
android:layout_width="280dp"
android:layout_height="52dp"
android:layout_marginTop="20dp"
android:background="@drawable/selector_enablekb"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/linear_step_one">
<ImageView
android:id="@+id/ok_two"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@mipmap/kbpre_already"
android:visibility="gone" />
<com.rainbow.app.keyboard.utils.UtilsTextvcustom
android:id="@+id/text_step_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/step_2"
android:textColor="@color/white"
android:textSize="15sp"
app:apply_font="true" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_dia"
android:orientation="vertical"
android:padding="24dp"
android:clipChildren="false"
android:clipToPadding="false">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="Rate us"
android:textColor="@color/card_title_color"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:text="We hope this app is useful for you, if it does, would you please give us a 5 star and a nice review on Google Play, it really helps!"
android:textColor="@color/card_text"
android:textSize="12sp" />
<!-- 星星容器 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/star1"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="4dp"
android:src="@mipmap/ic_star_outline" />
<ImageView
android:id="@+id/star2"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="4dp"
android:src="@mipmap/ic_star_outline" />
<ImageView
android:id="@+id/star3"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="4dp"
android:src="@mipmap/ic_star_outline" />
<ImageView
android:id="@+id/star4"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="4dp"
android:src="@mipmap/ic_star_outline" />
<ImageView
android:id="@+id/star5"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="4dp"
android:src="@mipmap/ic_star_outline" />
</LinearLayout>
<!-- 按钮布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:clipChildren="false">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="@drawable/bg_cancel"
android:text="cancel"
android:textAllCaps="true"
android:textColor="@color/card_button_text"
android:gravity="center"
app:backgroundTint="@null" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_rate"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="@drawable/bg_rateit_outline"
android:enabled="false"
android:text="RATE IT"
android:textAllCaps="true"
android:textColor="@color/white"
android:gravity="center"
app:backgroundTint="@null" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.4">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="24dp"
android:src="@mipmap/layout_emptycollect"
android:tint="@color/card_icon_tint" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:letterSpacing="0.02"
android:text="@string/empty_collection_title"
android:textColor="@color/title_color"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center"
android:text="@string/empty_collection_content"
android:textColor="@color/card_text"
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/gif_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignTop="@id/custom_input_view"
android:layout_alignBottom="@id/custom_input_view" />
<VideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignTop="@id/custom_input_view"
android:layout_alignBottom="@id/custom_input_view" />
<com.rainbow.app.keyboard.helper.KeybView
android:id="@+id/custom_input_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:keyBackground="@drawable/selector_presskb"
android:keyTextColor="@color/white"
android:keyTextSize="0sp"
android:labelTextSize="12sp"
android:paddingStart="5dp"
android:paddingTop="5dp"
android:paddingEnd="5dp"
android:paddingBottom="5dp" />
</RelativeLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:layout_marginTop="2dp"
android:background="?android:attr/selectableItemBackground">
<TextView
android:id="@+id/nav_category_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_color"
android:textSize="14sp"
android:gravity="center" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="MyKeyBoard" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">@color/transparent</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTV">
<attr name="apply_font" format="boolean"/>
</declare-styleable>
</resources>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="apply_step_false">#1A000000</color>
<color name="key_bg_press">#ffbbbbbb</color>
<color name="step_true">#666666</color>
<color name="transparent">#00000000</color>
<color name="bg_loading">#80000000</color>
<color name="color_74CBFF">#74CBFF</color>
<color name="title_color">#2C3E50</color> <!-- 深蓝灰色标题 -->
<color name="text_color">#34495E</color> <!-- 蓝灰色文字 -->
<!-- 首页色系 -->
<color name="theme_background">#E3F2FD</color> <!-- 浅蓝背景(蓝色更明显) -->
<color name="sidebar_bg">#BBDEFB</color> <!-- 中蓝侧边栏(明显蓝色调) -->
<color name="text_color_selected">#E74C3C</color> <!-- 红色选中文字 -->
<color name="nav_selected_bg">#26E74C3C</color> <!-- 淡红选中背景 -->
<color name="ic_bg">#F1948A</color> <!-- 极淡蓝灰图标背景 -->
<color name="underline_color">#2196F3</color> <!-- 亮蓝色下划线(更鲜艳) -->
<color name="subtext_color">#E74C3C</color> <!-- 红色辅助文字(点缀) -->
<!-- 启动页 -->
<color name="launch_bg">#3498DB</color> <!-- 蓝色启动背景 -->
<color name="launch_text">#FFFFFF</color> <!-- 白色主文字 -->
<color name="launch_subtext">#E8EDF2</color> <!-- 浅蓝灰辅助文字 -->
<color name="launch_progress_bg">#D6EAF8</color> <!-- 淡蓝进度背景 -->
<color name="launch_progress">#E74C3C</color> <!-- 红色进度条(点缀) -->
<!-- 卡片 -->
<color name="card_bg">#FFFFFF</color> <!-- 白色卡片 -->
<color name="card_text">#34495E</color> <!-- 蓝灰色卡片文字 -->
<color name="card_icon_tint">#F1948A</color> <!-- 蓝色图标 -->
<color name="card_divider">#D6EAF8</color> <!-- 淡蓝分割线 -->
<color name="card_title_color">#2980B9</color> <!-- 深蓝色标题 -->
<!-- 按钮 -->
<color name="button_cancel_bg">#F5F5F5</color> <!-- 浅灰取消按钮 -->
<color name="button_rate_bg">#F1948A</color> <!-- 蓝色评价按钮 -->
<color name="button_rate_bg_disabled">#BDC3C7</color> <!-- 灰蓝禁用状态 -->
<color name="card_button_text">#2C3E50</color> <!-- 深蓝灰按钮文字 -->
</resources>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="text_size">15sp</dimen>
<dimen name="key_height">48dp</dimen>
<dimen name="keyboard_vertical_gap">5dp</dimen>
<!-- Logo 尺寸 -->
<dimen name="logo_size">100dp</dimen>
<!-- 文本尺寸 -->
<dimen name="app_name_text_size">20sp</dimen>
<dimen name="option_text_size">18sp</dimen>
<dimen name="version_value_text_size">16sp</dimen>
<!-- 卡片样式 -->
<dimen name="card_corner_radius">30dp</dimen>
<dimen name="card_elevation">4dp</dimen>
<dimen name="card_padding">16dp</dimen>
<!-- 选项项样式 -->
<dimen name="option_item_height">60dp</dimen>
<dimen name="option_icon_size">38dp</dimen>
<dimen name="option_arrow_size">28dp</dimen>
<dimen name="option_padding_horizontal">16dp</dimen>
<dimen name="option_padding_vertical">10dp</dimen>
<dimen name="option_text_margin_start">14dp</dimen>
<!-- 分割线 -->
<dimen name="divider_height">1dp</dimen>
<dimen name="divider_margin_vertical">8dp</dimen>
</resources>

View File

@ -0,0 +1,19 @@
<resources>
<string name="app_name">RainbowBoard</string>
<string name="download_apply">Download &amp; Apply</string>
<string name="apply">Apply</string>
<string name="recommend_name">recommend</string>
<string name="recommend">Recommendation</string>
<string name="open_str">Activate RainbowBoard to enable more functions!</string>
<string name="step_1">Step 1:Select</string>
<string name="step_2">Step 2:Enable</string>
<string name="theme_application_successful">Theme application successful</string>
<string name="download_fail">Download failed, please try again</string>
<string name="et_hint">Type a Message</string>
<string name="rate_us">Rate us</string>
<string name="version">Version</string>
<string name="empty_collection_title">No Favorites Yet</string>
<string name="empty_collection_content">Tap the heart icon on images to add them to your collection</string>
<string name="more_suggestions">Discover more</string>
</resources>

View File

@ -0,0 +1,44 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="MyKeyBoard" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">@color/transparent</item>
<!-- Customize your theme here. -->
</style>
<style name="Base.Theme.PaintingHelper" parent="Theme.Material3.Light.NoActionBar">
<item name="android:statusBarColor">@color/launch_bg</item>
<item name="materialButtonStyle">@style/Widget.AppCompat.Button</item>
<item name="materialButtonOutlinedStyle">@style/Widget.AppCompat.Button.Borderless</item>
</style>
<style name="Theme.PaintingHelper" parent="Base.Theme.PaintingHelper" />
<declare-styleable name="CustomInputView">
<attr format="integer" name="text_size_label"/>
<attr format="integer" name="text_size_key"/>
<attr format="color" name="text_color_key"/>
<attr format="color" name="background_color_key"/>
<attr format="color" name="icon_color_key"/>
<attr format="color" name="text_color_special"/>
<attr format="color" name="text_color_done"/>
<attr format="color" name="text_color_none"/>
<attr format="reference" name="drawable_delete"/>
<attr format="reference" name="drawable_capital"/>
<attr format="reference" name="drawable_capital_lock"/>
<attr format="reference" name="drawable_cancel"/>
<attr format="reference" name="drawable_space"/>
<attr format="reference" name="drawable_special"/>
<attr format="reference" name="drawable_done"/>
<attr format="reference" name="drawable_none"/>
</declare-styleable>
</resources>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="K_Keyboard_Key">
<attr name="android:codes"/>
<attr name="android:popupKeyboard"/>
<attr name="android:popupCharacters"/>
<attr name="android:keyEdgeFlags"/>
<attr name="android:isModifier"/>
<attr name="android:isSticky"/>
<attr name="android:isRepeatable"/>
<attr name="android:iconPreview"/>
<attr name="android:keyOutputText"/>
<attr name="android:keyLabel"/>
<attr name="android:keyIcon"/>
<attr name="android:keyboardMode"/>
</declare-styleable>
<declare-styleable name="Kil_Keyboard_Row">
<attr name="android:rowEdgeFlags"/>
<attr name="android:keyboardMode"/>
</declare-styleable>
<declare-styleable name="My_Keyboard_view">
<attr name="android:keyWidth"/>
<attr name="android:keyHeight"/>
<attr name="android:horizontalGap"/>
<attr name="android:verticalGap"/>
</declare-styleable>
<declare-styleable name="My_KeyboardView">
<attr name="android:keyBackground"/>
<attr name="android:keyTextSize"/>
<attr name="android:labelTextSize"/>
<attr name="android:keyTextColor"/>
<attr name="android:keyPreviewLayout"/>
<attr name="android:keyPreviewOffset"/>
<attr name="android:keyPreviewHeight"/>
<attr name="android:verticalCorrection"/>
<attr name="android:popupLayout"/>
<attr name="android:shadowColor"/>
<attr name="android:shadowRadius"/>
</declare-styleable>
<declare-styleable name="IM_KeyboardViewPreviewState">
<attr name="android:state_long_pressable"/>
</declare-styleable>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<subtype
android:label="@string/app_name"
android:imeSubtypeLocale="en_US"
android:imeSubtypeMode="keyboard" />
</input-method>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<domain-config cleartextTrafficPermitted="true">
<domain tools:ignore="NetworkSecurityConfig">mobile-server.lux-ad.com</domain>
</domain-config>
</network-security-config>

View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="1%p"
android:keyWidth="9%p"
android:keyHeight="@dimen/key_height"
android:verticalGap="@dimen/keyboard_vertical_gap">
<Row>
<Key
android:codes="113"
android:keyLabel="q"
android:keyWidth="8.9%p"
android:keyEdgeFlags="left" />
<Key
android:codes="119"
android:keyLabel="w"
android:keyWidth="8.9%p"/>
<Key
android:codes="101"
android:keyLabel="e"
android:keyWidth="8.9%p" />
<Key
android:codes="114"
android:keyWidth="8.9%p"
android:keyLabel="r" />
<Key
android:codes="116"
android:keyLabel="t"
android:keyWidth="8.9%p"/>
<Key
android:codes="121"
android:keyLabel="y"
android:keyWidth="8.9%p"/>
<Key
android:codes="117"
android:keyLabel="u"
android:keyWidth="8.9%p"/>
<Key
android:codes="105"
android:keyLabel="i"
android:keyWidth="8.9%p"/>
<Key
android:codes="111"
android:keyLabel="o"
android:keyWidth="8.9%p"/>
<Key
android:codes="112"
android:keyLabel="p"
android:keyWidth="8.9%p"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key
android:codes="97"
android:keyLabel="a"
android:horizontalGap="5.5%p"
android:keyEdgeFlags="left" />
<Key
android:codes="115"
android:keyLabel="s" />
<Key
android:codes="100"
android:keyLabel="d" />
<Key
android:codes="102"
android:keyLabel="f" />
<Key
android:codes="103"
android:keyLabel="g" />
<Key
android:codes="104"
android:keyLabel="h" />
<Key
android:codes="106"
android:keyLabel="j" />
<Key
android:codes="107"
android:keyLabel="k" />
<Key
android:codes="108"
android:keyLabel="l"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key
android:codes="-1"
android:keyWidth="13.5%p"
android:isModifier="true"
android:isSticky="true"
android:keyEdgeFlags="left" />
<Key
android:codes="122"
android:keyLabel="z" />
<Key
android:codes="120"
android:keyLabel="x" />
<Key
android:codes="99"
android:keyLabel="c" />
<Key
android:codes="118"
android:keyLabel="v" />
<Key
android:codes="98"
android:keyLabel="b" />
<Key
android:codes="110"
android:keyLabel="n" />
<Key
android:codes="109"
android:keyLabel="m" />
<Key
android:codes="-5"
android:keyWidth="13.5%p"
android:isRepeatable="true" />
</Row>
<Row android:rowEdgeFlags="bottom">
<Key
android:codes="-2"
android:keyLabel="\?123"
android:keyWidth="17%p" />
<Key
android:codes="46"
android:keyLabel="."
android:keyWidth="12%p" />
<Key
android:codes="32"
android:keyLabel="English"
android:keyWidth="36%p"
android:isRepeatable="false"/>
<Key
android:codes="63"
android:keyLabel="\?"
android:keyWidth="12%p" />
<Key
android:codes="-4"
android:keyWidth="17%p"
android:keyEdgeFlags="right" />
</Row>
</Keyboard>

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="1%p"
android:keyWidth="9%p"
android:keyHeight="@dimen/key_height"
android:verticalGap="@dimen/keyboard_vertical_gap">
<Row>
<Key
android:codes="91"
android:keyLabel="["
android:horizontalGap="1%p"
android:keyWidth="8.9%p"
android:keyEdgeFlags="left" />
<Key
android:codes="93"
android:keyLabel="]"
android:keyWidth="8.9%p" />
<Key
android:codes="123"
android:keyLabel="{"
android:keyWidth="8.9%p" />
<Key
android:codes="125"
android:keyLabel="}"
android:keyWidth="8.9%p" />
<Key
android:codes="35"
android:keyLabel="#"
android:keyWidth="8.9%p" />
<Key
android:codes="37"
android:keyLabel="%"
android:keyWidth="8.9%p"
/>
<Key
android:codes="94"
android:keyLabel="^"
android:keyWidth="8.9%p" />
<Key
android:codes="96"
android:keyLabel="`"
android:keyWidth="8.9%p" />
<Key
android:codes="43"
android:keyLabel="+"
android:keyWidth="8.9%p" />
<Key
android:codes="61"
android:keyLabel="="
android:keyWidth="8.9%p"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key
android:codes="126"
android:keyLabel="~"
android:horizontalGap="5.5%p"
android:keyEdgeFlags="left" />
<Key
android:codes="92"
android:keyLabel="\\" />
<Key
android:codes="124"
android:keyLabel="|" />
<Key
android:codes="60"
android:keyLabel="&lt;" />
<Key
android:codes="62"
android:keyLabel="&gt;" />
<Key
android:codes="165"
android:keyLabel="¥" />
<Key
android:codes="8364"
android:keyLabel="€" />
<Key
android:codes="163"
android:keyLabel="£" />
<Key
android:codes="8356"
android:keyLabel="₤"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key
android:codes="-101"
android:keyLabel="123"
android:keyWidth="13.5%p"
android:isModifier="true"
android:isSticky="true"
android:keyEdgeFlags="left" />
<Key
android:codes="39"
android:keyLabel="'" />
<Key
android:codes="47"
android:keyLabel="/" />
<Key
android:codes="45"
android:keyLabel="-" />
<Key
android:codes="95"
android:keyLabel="_" />
<Key
android:codes="58"
android:keyLabel=":" />
<Key
android:codes="59"
android:keyLabel=";" />
<Key
android:codes="44"
android:keyLabel="," />
<Key
android:codes="-5"
android:keyWidth="13.5%p"
android:isRepeatable="true" />
</Row>
<Row android:rowEdgeFlags="bottom">
<Key
android:codes="-102"
android:keyWidth="17%p"
android:keyLabel="Back" />
<Key
android:codes="46"
android:keyWidth="12%p"
android:keyLabel="." />
<Key
android:codes="32"
android:isRepeatable="false"
android:keyWidth="36%p"
android:keyLabel="English" />
<Key
android:codes="63"
android:keyWidth="12%p"
android:keyLabel="\?" />
<Key
android:codes="-3"
android:keyWidth="17%p"
android:keyEdgeFlags="right" />
</Row>
</Keyboard>

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="1%p"
android:keyWidth="9%p"
android:keyHeight="@dimen/key_height"
android:verticalGap="@dimen/keyboard_vertical_gap">
<Row>
<Key
android:codes="49"
android:keyLabel="1"
android:horizontalGap="1%p"
android:keyWidth="8.9%p"
android:keyEdgeFlags="left" />
<Key
android:codes="50"
android:keyLabel="2"
android:keyWidth="8.9%p" />
<Key
android:codes="51"
android:keyLabel="3"
android:keyWidth="8.9%p" />
<Key
android:codes="52"
android:keyLabel="4"
android:keyWidth="8.9%p" />
<Key
android:codes="53"
android:keyLabel="5"
android:keyWidth="8.9%p" />
<Key
android:codes="54"
android:keyLabel="6"
android:keyWidth="8.9%p"
/>
<Key
android:codes="55"
android:keyLabel="7"
android:keyWidth="8.9%p" />
<Key
android:codes="56"
android:keyLabel="8"
android:keyWidth="8.9%p" />
<Key
android:codes="57"
android:keyLabel="9"
android:keyWidth="8.9%p" />
<Key
android:codes="48"
android:keyLabel="0"
android:keyWidth="8.9%p"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key
android:codes="33"
android:keyLabel="!"
android:horizontalGap="5.5%p"
android:keyEdgeFlags="left" />
<Key
android:codes="64"
android:keyLabel="\@" />
<Key
android:codes="35"
android:keyLabel="#" />
<Key
android:codes="36"
android:keyLabel="\$" />
<Key
android:codes="37"
android:keyLabel="%" />
<Key
android:codes="38"
android:keyLabel="&amp;" />
<Key
android:codes="42"
android:keyLabel="*" />
<Key
android:codes="40"
android:keyLabel="(" />
<Key
android:codes="41"
android:keyLabel=")"
android:keyEdgeFlags="right" />
</Row>
<Row>
<Key
android:codes="-103"
android:keyLabel="More"
android:keyWidth="13.5%p"
android:isModifier="true"
android:isSticky="true"
android:keyEdgeFlags="left" />
<Key
android:codes="39"
android:keyLabel="'" />
<Key
android:codes="47"
android:keyLabel="/" />
<Key
android:codes="45"
android:keyLabel="-" />
<Key
android:codes="95"
android:keyLabel="_" />
<Key
android:codes="58"
android:keyLabel=":" />
<Key
android:codes="59"
android:keyLabel=";" />
<Key
android:codes="44"
android:keyLabel="," />
<Key
android:codes="-5"
android:keyWidth="13.5%p"
android:isRepeatable="true" />
</Row>
<Row android:rowEdgeFlags="bottom">
<Key
android:codes="-102"
android:keyWidth="17%p"
android:keyLabel="Back" />
<Key
android:codes="46"
android:keyWidth="12%p"
android:keyLabel="." />
<Key
android:codes="32"
android:isRepeatable="false"
android:keyWidth="36%p"
android:keyLabel="English" />
<Key
android:codes="63"
android:keyWidth="12%p"
android:keyLabel="\?" />
<Key
android:codes="-3"
android:keyWidth="17%p"
android:keyEdgeFlags="right" />
</Row>
</Keyboard>

Some files were not shown because too many files have changed in this diff Show More