commit fc69284365716539f862c442fa2b6b5ea77cd3fa Author: litingting Date: Tue Apr 16 18:09:49 2024 +0800 V1.0.1(2) Ad版本 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d303db9 --- /dev/null +++ b/.gitignore @@ -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/release/ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/SignInfo b/app/SignInfo new file mode 100644 index 0000000..1881d0a --- /dev/null +++ b/app/SignInfo @@ -0,0 +1,3 @@ +签名文件:keyboardSkinning.jks +别名:keyboardSkinningkey0 +密码:keyboardSkinning \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..a52dd27 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,79 @@ +import java.util.Date +import java.text.SimpleDateFormat + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("applovin-quality-service") + id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") +} +applovin{ + apiKey = "y87o4e7vb5bbqzuGVTFyOIfZiyBG0Nf0Ksq8S3m2MJOHf_A5BcWGJnKuQqoxwxVvtdQdiTC4O3MPzFwy8rJ9Cc" +} +val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date()) +android { + namespace = "com.keyboardskinning.theme" + compileSdk = 34 + + defaultConfig { + applicationId = "com.keyboardskinning.theme" + minSdk = 23 + targetSdk = 34 + versionCode = 2 + versionName = "1.0.1" + + setProperty("archivesBaseName", "KeyboardSkinning_V" + versionName + "(${versionCode})_$timestamp") + testInstrumentationRunner = "androidx.theme.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("androidx.activity:activity-compose:1.8.2") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation ("com.github.bumptech.glide:glide:4.16.0") + implementation("com.github.omicronapps:7-Zip-JBinding-4Android:Release-16.02-2.02") + + implementation("com.applovin:applovin-sdk:+") + implementation("com.applovin.mediation:vungle-adapter:+") + implementation("com.applovin.mediation:bytedance-adapter:+") + implementation(platform("com.google.firebase:firebase-bom:32.3.1")) + implementation("com.google.firebase:firebase-analytics-ktx") + implementation ("com.google.firebase:firebase-crashlytics-ktx") +} \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..9f205fa --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "385313805976", + "project_id": "keyboardskinning", + "storage_bucket": "keyboardskinning.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:385313805976:android:9e3927a4b6f971fd4ce2ed", + "android_client_info": { + "package_name": "com.keyboardskinning.theme" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyBNx_DWofqkP9FDOyZ2UjK7ryHfLPcD9OU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/keyboardSkinning.jks b/app/keyboardSkinning.jks new file mode 100644 index 0000000..2bd7b55 Binary files /dev/null and b/app/keyboardSkinning.jks differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..9d9c834 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-keep class com.omicronapplications.** { *; } +-keep class net.sf.sevenzipjbinding.** { *; } \ No newline at end of file diff --git a/app/src/androidTest/java/com/keyboardskinning/theme/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/keyboardskinning/theme/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..7255903 --- /dev/null +++ b/app/src/androidTest/java/com/keyboardskinning/theme/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.keyboardskinning.theme + +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 theme, 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 theme. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.keyboardskinning.theme", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1b22719 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/selecion.json b/app/src/main/assets/selecion.json new file mode 100644 index 0000000..8497c8b --- /dev/null +++ b/app/src/main/assets/selecion.json @@ -0,0 +1,1138 @@ +{ + "list": [ + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/19c72296075224cc8b9f702da834dc92.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/67bd14ce39e3d1127f1d9290c932aee5.webp", + "title": "Funny Xmas Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c28846d1d4909623466315e00e54fc5b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7726ce5eca5559cffae7d09374b693dc.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/849c68ea7079053c790a72c70a065ba1.webp", + "title": "Bpink Kuromi Melody", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cfbf822f0566145979830f300d3dc803.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e611444bb8f5c320201681b157999a9b.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fbdc422a14eb6061b6c68f420af4872c.webp", + "title": "Cute Xmas Bear", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1feffec760891fcd014ee8f932284eab.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3dfe97e1613f4c8691f46b73dcbc4cb3.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9a9aebb1dde282afee915143cf08c38e.jpg", + "title": "Evil Mask Man", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ab40377c02cfc54dde46edfbff7ef744.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/65c1b7ca8f5793b08b7ec8c9d50a0ddb.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f3224f0b24703bf5c5cf18dc38f17d9f.jpg", + "title": "Christmas Santa", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/33cdf721aeb9319bf16f004246aed924.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cee280737d1e5ebaa720c95872ee3151.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b0b92f7f829f32c3ac3ee3b7a57d0f8e.webp", + "title": "Cute Xmas Kuromi", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f84a09a2792b05aa2b84dac22284a575.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fc4565b86b4f1083594b0387734c1836.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fa1898b6346177cb1bb972e8429dea51.webp", + "title": "AIGC Pink Unicorn", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7b828e919375fbfe0a8117befb879268.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6982d859647ec22618f036b45b98fa67.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b8e6aa60756bbe23de1d1214ae50e499.webp", + "title": "Neon Cool Wing", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d20b56ed72c6123f8076de1296af60c2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f475b077711f41b6ece8dfdbbc1885a4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9ccb3cddce8c852380be6de6c5227504.jpg", + "title": "Christmas Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/597c54130a6d88dfa6a4a196c923b69a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4c1d9da5bf3b19a9684ba5a1312a31f6.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/202074ea9e72cb4efa152eb46200aff1.jpg", + "title": "Cool Kakashi Naruto", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/028c883129ad8ca296ea2a00100a5d86.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b229b7f18a721abbb022298f644c7e48.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3659ea237954edee888248bff739a280.webp", + "title": "Cute Kuromi Melody", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8c4d30e090e6f7f523a1e51d53763a5e.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e05996b00af94d6d9aa8510187afabd2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ce0a9e71535715abf6ee3a522ecf2ce4.webp", + "title": "Cool Red Neon Wolf", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fcd8940f87c864f0c23985da0cfc2d62.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/93abfe4fad90e5ba17f41124a099953a.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e9d601af238ee5792ef07adac7faeb3f.jpg", + "title": "Romantic Neon Kiss", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/365f8ff6635d475002ef3decd5a9698c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6ffc7079cfb591d1d9cda86f756064d5.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f3ad8a95489cc711adfabebcc7380530.webp", + "title": "Pretty Colorful Key", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cbd38b72a48e246a6d29878f3a6fa86a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7eabb5b734ccb15e37c80e2acc79d3f2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9891901587a3a9f1add0135aee722565.jpg", + "title": "Christmas Nezuko", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/53e7253d92287e3e950fec09e21a39fd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/250a0646a4d7ff27730c51dcea975116.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2a784e04c665b8f0e423ddefd470da6b.webp", + "title": "Cool Heart Bat", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d965528505cf318c8e9ebb4745603ca6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e747a0e22258790a593acb4496ca876e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/081e65ef9eef2fd97fb8e0b1af447030.jpg", + "title": "Sad Simpson Nike", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e0c271782ebf5495f97cb3cef52e3524.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6712cbd2ebf9b2f76da5e78fac5415d8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bca73e99c4be52841bf1bbb2dc5abc8a.jpg", + "title": "Red Christmas", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/077ae89e2171637381e8f1b32a7578bb.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4fa76ec0e1343db328860cc771d288fa.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a7009422f4207c0b7ac3d418cbc61d3d.webp", + "title": "Cute Gingerbread Man", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5dec257d0e290c4856e79de9b0f3398f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/de3b7758d5aa862b573a4f192da07f95.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3c4aa84e6d22d059e150f2eb275cb794.webp", + "title": "AIGC Devil Angel", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b7f64f1802ca3dde387a9123d7e74aaa.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c8749ae47b6731b61018e406b1340d8d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/68884b53696f46116207b3ef9f964d90.webp", + "title": "Cutie Cinnamoroll", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b5ae2ebcbe3259bc6ee43ed5840e7694.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/31e81bc6237d682b647cdc686b71b37e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/67ab6aefcf8fffa6ce4a9a44cff032bc.webp", + "title": "Neon Drift Emoji", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d824ad24f725cfdacf208fbdf109a1f7.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e409a3da5f4dba5d54a60ff2d392f9f9.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dab694f4bc985c7b7d757905e7b8034f.webp", + "title": "Xmas Heart Circle", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3f6bcbb8ec8361e677a079780e719e28.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d3e8ab16af342abc1e2569c549510e63.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/25c160c57909c28d211822e017949c9e.jpg", + "title": "Flaming Skull 3D", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7ab00ff74e28544903845323ac39b922.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0df6a14dae6b078ba3f0e9f8281d1340.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4440ffa39901ba4228598d2b0f3341c1.webp", + "title": "Cool Nike Rick", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6c309ece2a9bb4b2f043ee5188964988.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1cc5bceeacd8f9b131f8db43f4f0b865.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/295db2678930c18ebd690639b85e4e83.jpg", + "title": "Stitch Christmas", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/59d8b8467f9ca9b49815e9bd3a3d8f6b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/149d4845cde5c0c13e2919a00b80eeab.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a0c719bcdefbdbf0a5d53e2b966c5004.webp", + "title": "Special Waterdrop Key", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8e586ae05a8b3e141e890d5b070ed4fb.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f9522d243bbb0c63cbd47ce3bd4ec0fc.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d0cd4507a09154d133943a7a3e7caa52.webp", + "title": "Cool Love Heart", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/317d6f9365450fdf635d9da547660ec6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/218b6bbf5bea541fdec77553ac82a737.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5b98879e233ff316f9b170bbfd911582.webp", + "title": "Cool Mixed Ghoul", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/14b8b447c40a2212964bbab6f1298834.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/997c00af1b3327857e0acd30a8517ab9.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e4e3306d615dcc407b0a7de70e17c097.webp", + "title": "Cool Mixed Emoji", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e2d0f4e61fdd4c6bfb1c8ac71e068098.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/24af12017b9a1c17a84b5317aeb8d133.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a7cfed5a97309aee2df2b28cdc67ccb9.jpg", + "title": "Christmas Gift Kuromi", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/480971ba45558dbbc8eec075da9c64dd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c2675ad23b1c85d6b1ade36eaaa1f8c6.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e059bbc65815156bc8eee9f9e860132c.webp", + "title": "Cool Nike Money", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/446f54b91b8dee5504733ae8a57d23ba.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b5f83c8899cdc2951ddc26ab9ffc2d02.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/28b66f028765852a04258b4673779c2c.webp", + "title": "Cool Venom Spider", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8f2fe009a00f60ea043b68252012cdae.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ecc9ef999fdbe5231125d50a3fab682f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6679ad952bb78f77ee274bec8f2f58f2.webp", + "title": "Neon Melting Heart", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6cf7a1c227207e1250b6159fa176a31a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/60378711e59cf8a75ad2591f06de4489.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d3b19bcc3a7e424853c72816abe46b3f.webp", + "title": "Cute Squid Man", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3f24e48d939c29e36426252e348098d0.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/159607275857cd4bd2b02d591c205a9f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bd10941715cae62f5cb1eda06bc3a204.webp", + "title": "AIGC Cute Panda", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9f2d356bbf9d7be536b93bcd5cf9d543.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8c8cdc83387722c6ea2516a2c62a5d93.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4de1cd0960e3ec2480d7cfca94b7e83c.webp", + "title": "Cute Xmas Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/206e1280394b53807c4391bca4c11d40.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2d05a2a234df44cc9def55ec33db91d6.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e517be463771ac08c9acd225a0682df6.webp", + "title": "Cool CR7", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/abf336051fb870e6cb92fcaeffc8022e.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/15e66e5f615fd3917a3d1c2047aeb559.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5007e0519d761be92cc2ecb1e08650c3.webp", + "title": "Cool Neon Flaming Boy", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/40a4f9bd14dba1714dbe72b5dea2eeca.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/415b84a5d098f495a15aeb378098e80e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8e97e2629a1da74ed5a5b9fad56b03cf.webp", + "title": "Cute Powerpuff Girls", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f756638f24ec008b6acbcdc5ab8c05a5.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6fdf8be7acd591573adf7bd908acee10.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5b439b7da0277c465b9da2b9966c30e5.webp", + "title": "Cute Football Player", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e1d5083956769fd06fc7639761becfef.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d3ce935f39cfa4bf877370e7166523e0.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f9b79cbc3b678e9a7e63d334ea66fc7c.jpg", + "title": "Neon Christmas Tree", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1d3a383e77eb647e2556090591f7dbb2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/612b0f2fe36fab27f7fcd57dd9b26d75.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a1acfad859114a6ae81730757ac67140.webp", + "title": "Cute Xmas Cinnamoroll", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8e210f36002c76db194aa180d3466595.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e9ee44d3abdc0296cc1d59b8b6183446.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6f46b9dd4d1c786b361088edf144b9a3.webp", + "title": "Cute Xmas Deer", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9e5730176b23e6a914a6a0bdf88916d2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0589390407b4edfc6b24c0015b7b2ac2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/46c43fd49c76bbc2b8dd02df338bf695.webp", + "title": "Neon Love Flamingo", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/12fe0fa555120bfc0be3051b32f86375.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/69433cef1acb4ffc6619084480dcf259.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/04905d4acd96521c99349ac80f5ccd67.jpg", + "title": "Santa Christmas", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2bc39e79ba2f51ef49f48979312227f4.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1b379c19538834341019c3295fa1f529.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f0983af41a1e5ce36dbc033fe4c400b6.webp", + "title": "Cute Stitch Cinnamoroll", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/67c52d5ca6ab3ae8696d9e8a95b03254.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3f49b5b00e613edaee088ca4f37f706e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/946b79b12bd50d674e63b5cf0593218d.jpg", + "title": "Silver Pink Minny", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c6de4110f95ae3b6e1d1bf4f5b61c498.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/39f1c021de417cac2d2f5adb91d42dad.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0e286fafd9b213777b0caa1ceba963f5.webp", + "title": "Cute Offline Dinosaur", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ec4891698215ac4fd0b6e63baa727833.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/29f27eedcac66402366aa9516e8daa3e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bfe6a2fc6d950cba569707c5c543078c.webp", + "title": "Neon Burj Khalifa", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/01338324032359830a77f6ab0014befa.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cdb7eede596df9d91457de27ad5d6815.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c0bdaf8bed469ce1053ab4297c616b0b.webp", + "title": "Cute Pikachu Couple", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d7ab8c56560ac97f5619de24eac41a92.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d8093364a2a6a6f9c31d2c5a63f5c4e6.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/50207c096ebc8f5995f99e3e4ae869d0.jpg", + "title": "Christmas Neon Light", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/11481d267beccb6640bbe91edea69c7f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fb609e8e53b4fd23c7229457392848e3.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/efcb9a2268268d64edd0457f37f6075a.webp", + "title": "Cool Moon Wolf", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/68a2409d9717e1d3fd3dbb6a159dc81c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2b3602b99dc2d22b7a6ad4967a4321b7.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3926b5f13a5aa50e0ea7cfaa17acbd6d.webp", + "title": "Cute Strawberry Cat", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a97b79f4113b60af52fde7d9ca0b2fec.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bee34a4872a99e750c9aa0cc0f8b3179.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/302b7b6dc4590c74f6454bed49b9d665.webp", + "title": "Cute Cinnamoroll Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/263f4e7f64420c2175a6b8c20d190e3a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6fc7dfdea315b6773db81606eb476ad5.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9d4393fbc371a7bab1fd9982cbe8d8c9.jpg", + "title": "Aureate Christmas", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/157046fdd05e8d4a780caf2a8f581b84.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/11536531038f4a945d88a284cf83a354.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/41dd2c02c73935c5f626355494405142.webp", + "title": "AIGC Laser Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1e4bc9b454202f96974304ca3bed1918.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b1660e51a8365bcd7ddd7fd5edf5b660.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7234993b12877171b146598274df09ec.jpg", + "title": "Xmas Candy Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d7b2a115a7765519af080acb5cac5b78.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/56aeb0b3f484debe1adc5dfe72000a3a.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c8334dac14fc0499dd6f2a1f7645582c.webp", + "title": "AIGC Horror Face", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d256de70b9b9f10c57b1710ff39034d2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e284869bfd6756834c580c808a662231.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/00001d79a10c164daba1aef9fa77c53a.webp", + "title": "AIGC Cute Cat Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/861fc86385757321d6527238c00410fa.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3373cab3c2bcdbf8e23d4345f0c169c8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/19f135b9cb5f52363f89a00af9e879f1.webp", + "title": "Pretty Laser Rose", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9e2292a30ff94aebceda47aab7c0c83b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/edddc804ae1dd2352b90ce9d3c506df9.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cb6553826244578449509407e94af124.webp", + "title": "Neon Fire Heart", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b4056791fbc8052a1242420e2f85fe97.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5654e6cf01b84afa943edd9d51ccfa0b.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/43d59f969c6eb35ecc90cccd949ba984.webp", + "title": "Cute Christmas Rabbit", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ce5bdab2a653a8f5d19fd41b7f03d909.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/250fdf5ef5ea7e48b083aeebadcf7d0e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1d96ef4f1c6b3a963019078b5d5e9bbd.jpg", + "title": "One Peace Luffy", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1c0861ea6a0840cba75cd68f75e361cd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b80b431839c71a5e67fed6943f4d4c3e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1402647cc32a756004c8b68d5de40226.jpg", + "title": "Galaxy Liquid Droplet", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/56a1d39ac052dce7ad7c5b8c3bfa21c2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ce2d17b1151c0a97b53622eee755b5fe.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/94293a2fe747d10b660082157243e9a0.webp", + "title": "Colorful Football Player", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ac5aae4846258797560ce2d19a423036.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0624a878ed2ac0ed926d23f197dbe2a7.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e8416a79e4f148fca9ea02c75120bfd3.webp", + "title": "AIGC Pretty Gothic Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c86645805ae4283839eaf084e029abcd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2f31412046aaef6884be1805e737df83.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bb30a02b550ebb38c0854c917bd3f075.webp", + "title": "Cool My Hero Boys", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1b9e8a6327af0294e5b2589954b93929.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c3ec0a90880940b4a5b20bdc3be7e277.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/09895d061bf0496651be7e3205524af6.webp", + "title": "Cute Heart Cat", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d86b256f7d712150c78b727390d7dacc.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/42397e7f5caa7cfcfde15c069cfdf6b8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d0e92526487439b64944a456839e54c6.jpg", + "title": "Neon Red Heartbeat", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f246376590b509c63173963c71335535.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6b4c7a57ace55a55e8c0851e942ab59b.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/26461eadf045f3da914f9f0fd2fe5314.webp", + "title": "Cutie Brown Bear", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4153f92552e83d90010b7e8dd69ac343.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b2734060cafe6151f864c405cddd87bf.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8b745374502e89ef538df12cce096c32.png", + "title": "Cute Love Stitch Couple", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/01fa065f178899fe2c18f29abb47ca5e.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/773c5093402f82cea9fd55ad0619fc58.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4a0ddd37999eea0d155c9383ca5cd95f.webp", + "title": "Pretty Snowflake", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9a08e8cd1a76b6a722dd0b859f3224f0.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e7967fde707961b6462ac00536bd0a43.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/715dbdfe900bdc2fb802cec981eee8a5.jpg", + "title": "Golden Dollar Drops", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/883a7599e21c89582de8904fea9887c7.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/415c44c1aa7888be017f3c47a91d8bd5.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c565deb861739cf5c83f6114347938ef.webp", + "title": "Happy Snowman", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a36c4ed9af3a5457ce4c300b5dad450b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c47d542938bde2fcfbd66dbf8cf6f3aa.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/29a342dc78dc7e2552e100c9d8d14867.webp", + "title": "Purple Neon Unicorn", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6ff9b3415b979b8d92b9c8cc7d085ead.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5c7b1822eb1a81baf4dd568c3babd199.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c9792762d40f0b26092fbd6b6face09c.webp", + "title": "Cute Naruto Kurama", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8a0931c0dc054b11549de33d96d8d6b6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/76e022b002a44788a7a0fe87dfd77a42.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dd00cb8a904731454f87e7211f810194.jpg", + "title": "Thunder Neon Wolf", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2ae0bd4ef1a787cddfd990d7eb68aa0a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8e9e438516ba33233295b4a86c3fa614.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6dde8575c9344cf57a3a6498ff6cf875.jpg", + "title": "Fire Phoenix", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e464c81b3681a1c0dffad3fb973388c6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9a3b917b143137d41c6e6c8d1e36dee4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ff2262efdc37025ebb4d485071f9a595.webp", + "title": "Cool Black White Nike", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/953e6c52b283051a40149233237761f4.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4ff92018445d75d3f8f8b5acc3133a0d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/402223a10d734581b955ed749ad84012.webp", + "title": "Romantic Purple Universe", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/20480729a207c5f930bb917ad94d29a3.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8379d2f98ac8efd25ff3e876e2d6356f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c22f1ded0493b39af61fce5ab3631503.jpg", + "title": "Broken Hearts Gravity", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d5fca72596b5a5c2f192100160018007.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d964df07da8ce5f0d056df5656fcbf1c.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c1dcc97407c149c92e02bf778429ae23.jpg", + "title": "BFF Besties", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ae8f770c80ed51603f631aab0b4ec5a1.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/581476ba9d98d54465d585683d38752d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4b2d441c2440a5f02aa4dc15da32519d.jpg", + "title": "Sad Emojis Gravity", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1de9a8cbe6df14c3c07049d27141232e.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6ba97361c79a9639d3c6099e219c2b43.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/97a1d4230c053cec2a187d9c39e5b449.jpg", + "title": "Love Heart Neon", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c96cedb306c2b6295e902a42a3539c27.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e1cab9141900726c0c14fe4adace8725.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/62a667571d748b81d8bb0767cc6c1f20.jpg", + "title": "Cute White Kitten", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c13b6813ebff77644478783841569658.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a0472c6ee4169ac7263e0ddc819599bb.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3819bc8f3eac85c49da446314e5f333e.jpg", + "title": "Neon Smokey Wolf", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b51bb61639f824bfc53bcc0bfa132499.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e8b924b23dbcffc8e23d558c80693bbd.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0f5f3d698a66475aaeb200c97a1999f0.jpg", + "title": "Luxury Gold", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/99580efba06827f7c24fb93d7596b227.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bd46ac13dc611f984fc18c17c9a647e6.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b5d27f5d7fdf570885e5c42154717843.webp", + "title": "Cool Neon Gojo", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d70311731a124c3205d4f332665540a9.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a39574ca0fe4af8898cf649eda785198.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d8cecddd87e1468d3768e2b364e61c63.webp", + "title": "Cool Supreme Goku", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/68d540805df27db11b5ad4a6aab103bc.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/98f0673511e03a3ec593db83e64269ea.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/34a22286b89a5313e6bdcf3859278f43.jpg", + "title": "Green Neon Check", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7d865fa5d872b508a40b405d12e04968.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/67f31c05d5979936fc599df6664c6282.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a940f85a9a6e03916a71f4dbcbb813a3.webp", + "title": "Glowing Moon Bear", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0e912e316d295a1c33e4b6d3528fdca2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2717adff9440f50fc3539292b4280f41.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c2a2a3a424336892a12d6527810b9fb4.jpg", + "title": "Neon Heart Love", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7b3ff95709c61045110379e41bd3651b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e1a2a2820955823179458b660f962ddf.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b50c5ec68d7aedd2830142793b6f8ba5.jpg", + "title": "Cool Supreme Kakashi", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3dab8d819cf8589f360d7268464dc091.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bc61592ff75190347e8840cc6637f43b.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/61555a1baaad3ea85194ed431af92172.jpg", + "title": "Neon Kitten Love", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/553a163368a028f07ea83b381f436434.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2446a3b6ea52ac07eb102b28845d085b.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/991c6e9ff84f157b3d9f905667b00733.webp", + "title": "Cool Miami Messi", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9099167d4da67117fa3a6278ec1e254f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/295402bbf71332321fe0f69841a15bf2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/aa90572d37d672c92826393040c0ca39.webp", + "title": "Neon Heart Love Hand", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f2ed79331bf9fc0dcae7787c4b5ae71d.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fc641c9e4ee1b68e8bb0a5bffa438db4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8c1f5d6642fc644650d04692117d6718.webp", + "title": "Heart Broken Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/84f26f69d3c37e0fbe748404fe84adf1.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6f11ee658a213b186fbbae4267dd0e4e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f62d987824bd0115fcafc65510558af8.webp", + "title": "Pink Power Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b7ae9cb348df6691ad69acf7df3b65cd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/519a79881a7503f235602a699e4446d2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8f7d2fac36b327b80905986f8a19fe9b.webp", + "title": "Cool Colorful Wolf", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/54b1c7e6195fd7169107a795b4ce0442.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e5dd9b83ee7c50013fa0c763d4b97cf8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/58beb08eded75a75d4b0b4457fe01a7b.webp", + "title": "Fantasy Glitter Sea", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f4fe349fbf0d07afb63d9c6a3c37904e.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/47e0244b8d31591737023dd30ae8e335.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/44c7cd6ddfd72c90755e0062778a91e4.webp", + "title": "Spooky Zombie Hand", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/093e2c6cb33ae5b0f07256d9e0f29dd0.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/02b9783ab528e4ea823682d00dadc1a4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ab381b25a35a3e689e113d70954d0c1c.webp", + "title": "Cute Mixed Stitch Couple", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/aba32940405dedc8febae62ed4b4ba51.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1d725cc37d4e868089e3b14fafc3dc96.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7f6971c44a4bce70afc514e7f39b546b.jpg", + "title": "Fluffy Cute Dog", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e731b589a8ed55bdc9298a308c8a3a19.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/50027210f8f4cf13c61998d906eddc2e.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/21c9965c2a63bfa42640f08e7974be9e.webp", + "title": "Cool Jack Nike", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d9832eab526fa16181e462313455d206.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0a4c27f9a8e5ab409124edfdd8159302.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9503af5c5db1f47b56527de23d609e7c.webp", + "title": "Cool Broken Batman", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/922542b804e2664c6c22fbf81c53fa6f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/36c3e498ce4bbbaddf164b258f636697.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/93e15cc90e3c17d0c3e094be97daf25a.webp", + "title": "Glitter Barbie Pink", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5e9d2f4b645292d21e327ed07ee91100.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a2893c26d90c762e409a3e2c60b6c604.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/364451115389dfe20779b27e68cfe586.webp", + "title": "AIGC Fire Butterfly", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4e9ea00b68a5ced5e6987e23e912887c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e5cfde953e0d1625a68fbb44756098fd.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0cd1ea4fc67b82bb9a472233a92aae92.webp", + "title": "Pray Money Skull", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a97f29676e85ccc128b220645961bcf1.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5336bae6e874a930a04b660102bf0ff3.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0481eb42f646a64ef5b6395884dda792.webp", + "title": "Cute Neon Pumpkin Cat", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7eca268d1f19054abce066ca51a70609.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d811c793fce17907374ee16179b1d288.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/75226c2c32ea1a28cec7cfc2d68a1861.webp", + "title": "Cool Natsu", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/591bea16d98f316039b722be8044936c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f9f105e0c2730f65cda1b7372f160ee0.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8f37c6ecc2cd9e012fbe658b50990378.webp", + "title": "Cool Skull Rose", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3e55cbb42d49d072eb06bafe6e862e52.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3e55fd7636c7953e0a94cee249496181.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7db247b6ed5f13f9d4fe367a99d5884c.webp", + "title": "Cute Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c2f952922d7d7f4c2a1006e3fa2baf44.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dc5472f21b4f962ad58ee4d602e1dbcc.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7452bfb4742b25e4797a4fbac5b0c36f.webp", + "title": "Cute Coffee Yoda", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ab53669b1d109f488d6476d52bdf5ebd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c6cd526630ef7ad3fa28ddfdb3083ebf.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fe2d82675e06de64122851e54d68d064.webp", + "title": "Cool Horror Scream", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/026e9d37fe1a3271b1ff514a12c4aa0c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b0f00de00c3005041f20cbef9be3b81d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3eba90a97d3f435f3c682c186f86f540.webp", + "title": "Love Bear Couple", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/587c041a47b1eb3c8131fb3763238769.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/660af5731173413885435ae3bfe2c414.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ce48fa4694894306fa77c560b192505f.webp", + "title": "Cute Spooky LV", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/36a87ff425fc48c37ee14917223a1c32.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d609594ab07dc8d3adc632a8d6104dbb.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/56af0400252e653b439ffe765d003b18.webp", + "title": "Cool Supreme Bart", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fea0b7362c5149ad10899e1275887cbe.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/eb6f2875f27a6ffe16eea477432fa3b2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b7975b0c56eee572c4c0ecf7408a9c5c.webp", + "title": "AIGC Demon Angel", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/559267b8b40feba8742b583e03d468b6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c2fc4bfa987ad4e4d1e4cbb4cd8f8e79.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f31cfd5e31998781f5a28dc8bac719d1.webp", + "title": "Cute Purple Witch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6996cf1224726828771ec248017d0d60.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7d9af181148acfc9012e70d2472eebbc.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cc9e583660b15b4e931191778495acb2.webp", + "title": "Pink Smokey Skull", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/357f4ea01be6641528aee0ce8c9adf02.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/74d6b9ad2ef0aac3cad0bb1c31fc7983.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4a16c5a53d7ebc552161df14ce64aa4b.webp", + "title": "Cute Love Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6b015946f9e230eb747e7735a3f3b69d.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/304bd9e2ef110d90ee6fd9d0629e3d53.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8752a09615d81de04707d474e2212cd0.webp", + "title": "Halloween Cat Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3adf86b9b690e7f13c5522f4a80a9073.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8290532591f846a466f307426dd5106f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/47907642a2930ca5952f3b88591dcc2b.webp", + "title": "Cute OP Molang", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fadb7afd6abc13064c365b1963adaa07.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0cd3014dfe1299673a425ebc66ec2ba3.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/21858a3a2b1370dd2dcdacf4289046da.jpg", + "title": "Love Parrots", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/05cdbfe6c9781ad45af02e2a1fe1656c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dda951cc92bf26a1afb7533097617542.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a988dad681f1136820c0031a445f7b4f.webp", + "title": "Neon Heart Kitty", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8559e55a5b5d9cac7c6fa6020812e07f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/41447fb48ac10590f5e32157c54e9ee2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4483a0071d703caf667fce975eaa94d7.jpg", + "title": "Fly Halloween Kitty", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/047a974042106e70ceed6fdff7a0ace8.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0c71843e172194a28c366a920e50c120.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d92e512ba6699e4f7f84f0f88fa419b9.webp", + "title": "Cool Neon Alien", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/65648185d44577b9375a4b5bbca96e85.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/452f91d05726acb051a3c720c911c218.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a3699f5a6bcc5843db71dcfd65b510c5.webp", + "title": "Pink Miami Messi", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d35c2fbacb24497a87144a7ae34572de.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4d073e98015c7ce15ff496d5aad637ab.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a04ae6cdc67da2cb0d91593849751675.webp", + "title": "Lovely Moon Cat", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9ea4400be830254af528c52d8874d95b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/33be81c491f825d671d1d91d976ba52d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a190ec1de0603851507df0ec9b0a7729.webp", + "title": "AIGC Neon Cat Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ab7f8138c6bce76772f7971bfb2dedff.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/93e37e82df00401b2e8cb78d447032f9.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0efd9421eeb339899f5f188272e1ccab.webp", + "title": "Rose Heart Skull", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/90ff11938ab7d6d57487e4df5ed2a74d.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8ba47d924877b04b91fa4f1409b4a588.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/65b214274a02cadd6d6f738ae186cf04.webp", + "title": "Love Heart Bear", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/78fdda723d4dded718d0565a67f1509a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9da70d7b31903110d7e9772125040c74.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e06c2887246f213556f552bcfc7d046b.webp", + "title": "AIGC Barbie Pink Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d52f727727406bca52f183eb8cd660a3.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e6fcaab2212c76cdc53a5a72a65559b8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e517b9ff1e9e61ba33734542f0f7691e.webp", + "title": "Romantic Heart Beach", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b7d0d5b9f0cdd3f36293d2c40b8b27cb.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e3fb823b2f9f96d0fc4acd9e8970555d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/65781c5682de5bb899e4be0d0cfabc90.webp", + "title": "AIGC Colorful Neon Wolf", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/68fba9d44db763ad15145a9987eebb1f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a580332b2208ad99556fa4a433f9b8d2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/985c0e1e2d3169f728f0d9eda38a0db2.webp", + "title": "Cool Neon Rock Hand", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/820b32c879fd3206c94d3272985a2201.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/758646c5f5b0a34fe2e859a929caa297.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ed7f0138afdf0621e72fd17eb1672b49.webp", + "title": "Neon Grim Reaper", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9cf5c5b728fa6a41d7ea1d823ca9c35d.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6db9c40254b2d67f0c2eb4ccae3d14ac.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d8fde605919741fb0fc5e60db8f2ff55.webp", + "title": "Sailor Moon Girls", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6adb1bb0aaf10106173a5fc11e2c33ba.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dd0b496336ce694f541cbb898daa16e0.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/956cd25f327bd851afcd588c2f4aaf81.webp", + "title": "Colorful Soccer", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/50bffb1a1a780398dda9efdfa408d773.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bc64c525341f3b72eb370d80074728c4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e522c6172e0b320d0d18b8614c298e54.webp", + "title": "Cool Glitch Skull", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5831eac819a0c940b88ab366efb3a5d6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8a319132b6d9c1bcbdf56700d54eb946.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/40cbb01094af0248aee99f1d6a1158d3.webp", + "title": "Neon Rosy Love", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4d9c4e069b065fea34735fc21e5316e4.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3cdc96a78cb98f3fa2f636a6e06a8755.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8dfeb33f9f06925da460ee6b783ae118.webp", + "title": "AIGC Cool Street Boy", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/60ae18f18e1881346ca7a1573689b0d1.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/9cd6ab0683434a6764bc70f489bcfd37.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a84085c8ed0da1b4f2730dbfab48754f.webp", + "title": "Lakers Legend", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2d4e3807de270814d3d7f3d463262e73.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3d01259924e24a366034b42162d229b6.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8b77c859bd6efa52252b690f25e99625.webp", + "title": "Neon Pink Scream", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3aea7cdd831166730d64b401d985a054.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b294b0b76cf3a91adcc483c3dc93c7b4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c728cfd4dca1adc8969b89537f9df437.webp", + "title": "Luffy Gear 5", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/64d1ae4762fcc321796a0954655d5ce4.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e91e00db15fb31e4d232b1aebddee4d2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a65d5594ead607f38db9e235ea8c9872.webp", + "title": "Cool Neon Switch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f0c4914f0a15d02f7cfbe9385bd85c2b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/084af4f1e6e44c8ebf442de5c76fa187.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ef43f2f31ef3a5f25592cf4f1c4979c4.webp", + "title": "Cute Gothic Bunny", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f36b858c08b6115fbcd6741d3a34c66c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d2b705b89f7a6088f3888f637a22c859.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/39e932255eaeda17b566c5c145200cde.webp", + "title": "Fantasy Castle", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/81b2c0eab158da26ea76f9fd3b75df41.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/32a1e6099b77a9d3a517645681dff882.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3efe850e72cf9a549375e600b88a2254.webp", + "title": "Cute Smile Shiba Inu", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c7832751c343cdec1e3a7e7489ddcd10.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/90214763a231016f82127b9ba598b1d4.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d3a95e657091ccaf86372b79ffbad797.webp", + "title": "Romantic Sunset Couple", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0506816c201dd84e7e4293a02c327907.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3062c6c494ed0730ffb36abc03e9267c.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b630934122ad702396b8dd32c2f51cf5.jpg", + "title": "Purple Sparkle Live", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/db0dc00c3bf7ced21be4d65bc1cbf674.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/83d03503dc36c7588e57a8c7808c6efc.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f87e9c7aec2675b89c9692101fc5d3db.webp", + "title": "Pretty Cinnamoroll", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5286420a88b8eaacd2b8a9b5efbe41c0.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/56d09a862dd0bcebe193c19b34d9986a.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/38139313b9a5d9edd14b8303f38facff.webp", + "title": "Cute Ok Smiley", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d25de46da29dba77d39aef177198fb8f.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c5cf46899ae31946f15cb3cf355c7e05.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d7c5695339646f4b020707191ae5b264.webp", + "title": "Happy Heart Cheems", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/08bff89e5fcde339450ae224b162ca33.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8f51c53a5d692a25597a076887c0431f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6745e267850dae31387cfe23c96fd77d.webp", + "title": "AIGC Pink Barbie Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bf2339ca57610c3171a25278fa782131.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/226b504c98db299ecde1bc16888db487.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6beb9acbf52810ea71237ab7f4bd676d.webp", + "title": "Pretty Glitter Liquid", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/323b652bdfeae4d83f74b0c0198f8189.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2a589fa4ddd024a9beb49402e6dfd838.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bbd258d7ef1e431bb9cadfbcb3bfe8f2.webp", + "title": "Sad Baby Doge", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7110532db818650e370cb6efd2f03dc6.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b6e7f05553708df159ddb56d73be16cc.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/882e9dd76c15440a28654c3e56ed2790.webp", + "title": "Purple Neon Wave", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/63a10157419a3ec55314fcf795cecc8c.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bb4b51a862499de834ef9a2d56e8e33a.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2b7e4f13c3f6c412ab0cd44af2330a17.webp", + "title": "Cool Fire Itadori", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/68b3ad48afec1b770f80432461befaf7.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6ad0288014f7d06d44d37e02366325d3.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e46cbc3565569cb91a9172ca96edbdf4.webp", + "title": "Cool Melting Smiley", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8f7fa9b6b0b5915bf62ba5cac11d9c35.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d4937284d26a654e44d1b52ccb380652.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1fb88616f46c4fc68514cf01b2b2aed0.webp", + "title": "Cute Unicorn Icecream", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/06ab5b6481f5da6f99f0eaab1bc7f4e1.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a3682119903a8cb52f8e7536d94c57e5.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/b80061f43dfbe58e59b37479eb0142f9.webp", + "title": "Pretty Pink Barbie", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c7e8948477d7a3bd1b43f4b8d4d44267.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e8612286b4e79d5a68b6b27dcefd4df8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/bf706ea3553d88133cd6e282e3ed1d65.webp", + "title": "Glittering Pink Chanel", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1da60626d96ea32c0d4e81c21b665859.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/59ccc2d6702d71bb7ace5bee4cf91a3d.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/72e04323014c0307b6d0b8d8580b3a84.webp", + "title": "Neon Green Rick", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/616d76273d8aab2e4ac3507458f6f21a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2f518170e7dc5902860ac46ade2d681a.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/74a119c7653baa57b295830d5b28f95f.webp", + "title": "OS 17", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0ee3be0d32170237116253edeb38492b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e5dabdb9eb11d341b67ed0bad73fe1f9.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f079da5b0ae3cae81f27cca47b69be84.webp", + "title": "Purple Heart Circle", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a2e21b08532d7dd7a4822563b1c3b14d.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5fe3680a471591f5a0aeae4a2c19cc06.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1018326ebd0bb2878a21ec3deff652b6.webp", + "title": "Neon Horror Eyes", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e8df24a07d40703d592e4dacd3e2e7e2.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ad275e8af5534c575535bda834bb53fe.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7e4599519b9f1b59022527df70d6fddc.webp", + "title": "Cute Happy Pikachu", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/5f6cf51b2336fc5045fa5ab6f34aafcd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/85075ecbf146bf10c0570384b5695253.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f74ebb7aa8c641be0bf540ae4dea6047.webp", + "title": "Cool Evil Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/eb1f88c7e51d70b2043e28d7ef032b84.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/53dbe057106d0f0e6e84ac88c3fc4a32.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2b379e03b3be4d58b4c72de385fb3db4.jpg", + "title": "Pink Barbie Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8e06208d5478620b910836933cb2209a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d1a3e6274534a36705a4aa4c198dc2b8.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/68107313faa74b5d74701888daf86d0e.webp", + "title": "AIGC Cool Gothic Girl", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/26cf9347b097adc0b98939e513461319.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f323c0dc8d2fcc9e0020e8d6aa30d6a2.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a452d27daf9b1c3d45f04ed844b7eb53.webp", + "title": "Cool Fire Luffy", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/efedca977be976f65278ee94aa7c43fc.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/0ab9bac9ec5ff70ef311c012829318c1.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/a37ad3df93d752dedcdf011a5ef75efc.webp", + "title": "AIGC Cute Dragon", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dbb4f0feecf882786b6490ef87268a47.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6a95a0d7218d2213f5c93f7d55833d80.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/03dc9f0f18c2b4a5302a21ace871c534.webp", + "title": "Barbie Pink Beach", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e8266ba4e1dc52251aa019f719690166.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/156a9f013bbaa89533611c50b52829f9.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/f541b4dd45856073327c6c02f192d616.webp", + "title": "Sad Glitch Boy", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d26e71af9a5d144049320f1e1324941a.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c93ecd549795f3a847507bb45c9ef7ad.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/808783d28144ed1b62f63a242c887f79.jpg", + "title": "Spooky Skeleton Love", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e399503da07a40b5735ac1c1bc3aabc8.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/3f68e592d6b0032315f4c9f5ad079b85.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/172cf893d15b8f72bd6b1521ed5799d9.webp", + "title": "Cute MM Beans", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/1d49905d292e542ff4755e03d98e1e44.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/36fbe019433abc03d2c0467781c70a2f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8a54724da6f1fc43e05ebb5265754f4d.webp", + "title": "Neon Moon Slayer", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/09ffce8573bfec37960cc4e72436c90b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/ba1a00cb2ec17cc11ee61a7bc392c982.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d5b6db7c058ad1d3e8c22d9c99be18b9.webp", + "title": "Neon Magical Mushroom", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d73170ed4d0dcb02db55d78706f419a0.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/459050e1e98bdac885f6510b92ca881f.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c91a27b0cda49ca4af3d11418a974e39.webp", + "title": "Cool Skeleton Hand", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/6bfb3ba21d3e605a7941cf240f130491.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4ec50b7aa436c625b9f5e394a314b790.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/dab485f3c95de35c7068ed442e6aa8e1.webp", + "title": "Cool Neon Selfie Skeleton", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e688c5ed4fcd4b5dbca70baa1eb42beb.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/119d480415d6ef3de72a3797a8d16dc3.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e6affafa45f99203d68587753d462307.webp", + "title": "AIGC Virgin Mary", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d556d0ebdbe49769a940e03cb1277a57.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/8ed4a416ce5be16527824c9159c171bd.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/737965868384f128f2d26b9d3f69b309.webp", + "title": "Cute Smile Sunflower", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/38e388c44f57d74938344a52e799fd9b.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/2cf7cf4ee52a7ba069011ec8d88a3184.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/93aae47ff15df72ff3a7bfd02093e48e.webp", + "title": "Love Toothless Couple", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/7e308c7b1ebb1057b13c92eac7f4d3b3.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/d90c21c20f582b8b76896a65a3284b66.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/939a6d31470ce3526e66aa07935dfed1.webp", + "title": "CG Sexy Lady", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/18133267460b2d18bd403a1708fc8ccd.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/381c393d91c5150e8f1cf6837cc36126.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/4fb3c984cb78cd6b067124e9f8e878fb.webp", + "title": "Barbie Pink Glass", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/e2518f061f87c850ba07f36ce250d5fa.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/804168ff29c22eb66dcfce2deb09499a.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/cdbe3c70a93a04e28bf2c728d012cef6.webp", + "title": "Cool Sasuke Supreme", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/09ec02ba5eba82acf90dcbac0bfb789e.7z" + }, + { + "preview": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/fc0312c2889b17c3f77f30e5c752e763.webp", + "thumb": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/987dd4b6180460cc557f5b67c7f96df3.jpg", + "title": "Pink Blue Stitch", + "zipUrl": "https://resource-sg-public.obs.ap-southeast-3.myhuaweicloud.com/keyboard/c4d2933d1a354b09b496073739b2c326.7z" + } + ] +} diff --git a/app/src/main/java/com/keyboardskinning/theme/KeyboardSkin.java b/app/src/main/java/com/keyboardskinning/theme/KeyboardSkin.java new file mode 100644 index 0000000..7139f62 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/KeyboardSkin.java @@ -0,0 +1,37 @@ +package com.keyboardskinning.theme; + +import android.app.Application; + +import com.applovin.mediation.ads.MaxInterstitialAd; +import com.applovin.sdk.AppLovinSdk; +import com.applovin.sdk.AppLovinSdkConfiguration; +import com.keyboardskinning.theme.tools.MyTools; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class KeyboardSkin extends Application { + public static KeyboardSkin app; + public static List myDataArrayList = new ArrayList<>(); + private boolean isInit = false; + @Override + public void onCreate() { + super.onCreate(); + app = this; + List myData = MyTools.parseJson(this); + if(myData != null){ + myDataArrayList = myData; + + } + + } + + public static List getMyDataArrayList() { + Collections.shuffle(myDataArrayList); + return myDataArrayList; + } + + + +} diff --git a/app/src/main/java/com/keyboardskinning/theme/MyData.java b/app/src/main/java/com/keyboardskinning/theme/MyData.java new file mode 100644 index 0000000..cfcce59 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/MyData.java @@ -0,0 +1,42 @@ +package com.keyboardskinning.theme; + +public class MyData { + + private String resourceName; + + private String thumbnail; + private String preview; + private String url; + + public void setPreview(String preview) { + this.preview = preview; + } + + public void setResourceName(String resourceName) { + this.resourceName = resourceName; + } + + public void setThumbnail(String thumbnail) { + this.thumbnail = thumbnail; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPreview() { + return preview; + } + + public String getResourceName() { + return resourceName; + } + + public String getThumbnail() { + return thumbnail; + } + + public String getUrl() { + return url; + } +} diff --git a/app/src/main/java/com/keyboardskinning/theme/mylistadapter/ThumbAdapter.java b/app/src/main/java/com/keyboardskinning/theme/mylistadapter/ThumbAdapter.java new file mode 100644 index 0000000..be7fce3 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/mylistadapter/ThumbAdapter.java @@ -0,0 +1,82 @@ +package com.keyboardskinning.theme.mylistadapter; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.keyboardskinning.theme.MyData; +import com.keyboardskinning.theme.R; +import com.keyboardskinning.theme.ui.PreviewActivity; + +import java.util.List; + +public class ThumbAdapter extends RecyclerView.Adapter { + private Context adapter_Context; + private List myDataList; + public ThumbAdapter(Context context,List list){ + adapter_Context = context; + myDataList = list; + + } + @Override + public void onBindViewHolder(@NonNull ThumbViewHolder holder, int position) { + MyData myData = myDataList.get(position); + String thumbnail = myData.getThumbnail(); + Glide.with(adapter_Context).load(thumbnail).into(holder.getImageViewThumb()); + holder.getConstraintLayout().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(adapter_Context, PreviewActivity.class); + intent.putExtra(PreviewActivity.KEY_NAME,myData.getResourceName()); + intent.putExtra(PreviewActivity.KEY_URL,myData.getUrl()); + intent.putExtra(PreviewActivity.KEY_PRE,myData.getPreview()); + adapter_Context.startActivity(intent); + } + }); + + + } + public static final class ThumbViewHolder extends RecyclerView.ViewHolder{ + + private ImageView imageViewThumb; + private ConstraintLayout constraintLayout; + public ThumbViewHolder(@NonNull View itemView) { + super(itemView); + imageViewThumb = itemView.findViewById(R.id.image_view_thumb_id); + constraintLayout = itemView.findViewById(R.id.constraint_layout_id); + } + + public ConstraintLayout getConstraintLayout() { + return constraintLayout; + } + + public ImageView getImageViewThumb() { + return imageViewThumb; + } + } + @Override + public int getItemCount() { + return myDataList.size(); + } + @NonNull + @Override + public ThumbViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(adapter_Context).inflate(R.layout.list_thumb, parent, false); + ThumbViewHolder thumbViewHolder = new ThumbViewHolder(itemView); + return thumbViewHolder; + } + + +} + + + + diff --git a/app/src/main/java/com/keyboardskinning/theme/mylistener/AdCallback.java b/app/src/main/java/com/keyboardskinning/theme/mylistener/AdCallback.java new file mode 100644 index 0000000..e69309e --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/mylistener/AdCallback.java @@ -0,0 +1,13 @@ +package com.keyboardskinning.theme.mylistener; + +import com.applovin.mediation.MaxAd; +import com.applovin.mediation.MaxError; + +public interface AdCallback { + void onShowFail(MaxAd ad); + void onShowSuccess(MaxAd ad); + void onHidden( ); + + void onLoadFail(String string, MaxError maxError); + void onLoadSuccess(MaxAd ad); +} diff --git a/app/src/main/java/com/keyboardskinning/theme/mylistener/DownloadCallback.java b/app/src/main/java/com/keyboardskinning/theme/mylistener/DownloadCallback.java new file mode 100644 index 0000000..09b8111 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/mylistener/DownloadCallback.java @@ -0,0 +1,8 @@ +package com.keyboardskinning.theme.mylistener; + +import java.io.File; + +public interface DownloadCallback { + + void onDownloadCall(boolean successful, File resource); +} diff --git a/app/src/main/java/com/keyboardskinning/theme/mylistener/UnzipCallback.java b/app/src/main/java/com/keyboardskinning/theme/mylistener/UnzipCallback.java new file mode 100644 index 0000000..26a7fe9 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/mylistener/UnzipCallback.java @@ -0,0 +1,6 @@ +package com.keyboardskinning.theme.mylistener; + +public interface UnzipCallback { + + void onUnzipCall(boolean successful, String resDirPath); +} diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/Admanager.java b/app/src/main/java/com/keyboardskinning/theme/tools/Admanager.java new file mode 100644 index 0000000..1182e72 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/Admanager.java @@ -0,0 +1,128 @@ +package com.keyboardskinning.theme.tools; + +import android.app.Activity; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.applovin.mediation.MaxAd; +import com.applovin.mediation.MaxAdListener; +import com.applovin.mediation.MaxError; +import com.applovin.mediation.ads.MaxInterstitialAd; +import com.keyboardskinning.theme.mylistener.AdCallback; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Admanager { + + public static final String unit1 = "aa5f1b6007e164bb"; + public static final String unit2 = "2508a453b5762c26"; + public static final String unit3 = "15208b32ede43744"; + + public static MaxInterstitialAd show(List ads) { + Collections.shuffle(ads); + for (int g = 0; g < ads.size(); g++) { + MaxInterstitialAd maxInterstitialAd = ads.get(g); + if (maxInterstitialAd.isReady()) { + return maxInterstitialAd; + } + } + return null; + } + + public static void setAdShowCall(MaxInterstitialAd ad, AdCallback adListener) { + ad.setListener(new MaxAdListener() { + @Override + public void onAdLoaded(@NonNull MaxAd maxAd) { + if (adListener != null) { + adListener.onLoadSuccess(maxAd); + } + } + + @Override + public void onAdDisplayed(@NonNull MaxAd maxAd) { + + if (adListener != null) { + adListener.onShowSuccess(maxAd); + } + + } + + @Override + public void onAdHidden(@NonNull MaxAd maxAd) { + if (adListener != null) { + adListener.onHidden(); + } + } + + @Override + public void onAdClicked(@NonNull MaxAd maxAd) { + + } + + @Override + public void onAdLoadFailed(@NonNull String s, @NonNull MaxError maxError) { + if (adListener != null) { + adListener.onLoadFail(s, maxError); + } + } + + @Override + public void onAdDisplayFailed(@NonNull MaxAd maxAd, @NonNull MaxError maxError) { + if (adListener != null) { + adListener.onShowFail(maxAd); + } + } + }); + + } + + public static List initMyAd(Activity mActivity) { + MaxInterstitialAd ad1 = new MaxInterstitialAd(unit1, mActivity); + ad1.setListener(new MaxAdListener() { + @Override + public void onAdLoaded(@NonNull MaxAd maxAd) { + Log.d("-------","--------onAdLoaded ad1"+ad1.getAdUnitId()); + } + + @Override + public void onAdDisplayed(@NonNull MaxAd maxAd) { + + } + + @Override + public void onAdHidden(@NonNull MaxAd maxAd) { + + } + + @Override + public void onAdClicked(@NonNull MaxAd maxAd) { + + } + + @Override + public void onAdLoadFailed(@NonNull String s, @NonNull MaxError maxError) { + Log.d("-------","--------onAdLoadFailed ad1"+ad1.getAdUnitId()); + } + + @Override + public void onAdDisplayFailed(@NonNull MaxAd maxAd, @NonNull MaxError maxError) { + + } + }); + ad1.loadAd(); + MaxInterstitialAd ad2 = new MaxInterstitialAd(unit2, mActivity); + ad2.loadAd(); + MaxInterstitialAd ad3 = new MaxInterstitialAd(unit3, mActivity); + ad3.loadAd(); + ArrayList ads = new ArrayList<>(); + ads.add(ad1); + ads.add(ad2); + ads.add(ad3); + return ads; + } + + +} diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/CustomViewConfig.java b/app/src/main/java/com/keyboardskinning/theme/tools/CustomViewConfig.java new file mode 100644 index 0000000..094dea8 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/CustomViewConfig.java @@ -0,0 +1,200 @@ +package com.keyboardskinning.theme.tools; + +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.keyboardskinning.theme.KeyboardSkin; +import com.keyboardskinning.theme.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.Objects; + +public class CustomViewConfig { + private String Bg_action_normal = "btn_keyboard_key_functional_normal.9.png"; + private String jpg_BG = "keyboard_background.jpg"; + private String color_normal_key = "key_text_color_normal"; + private String color_action_key = "key_text_color_functional"; + private String Bg_pressed = "btn_keyboard_key_normal_pressed.9.png"; + private String Bg_normal = "btn_keyboard_key_normal_normal.9.png"; + private String path_drawxh = "/drawable-xhdpi-v4/"; + private String path_drawxxh = "/drawable-xxhdpi-v4/"; + private String path_color = "/colors.xml"; + private String icon_del = "sym_keyboard_delete_normal.png"; + private String icon_shift = "sym_keyboard_shift.png"; + private String Bg_action_pressed = "btn_keyboard_key_functional_pressed.9.png"; + private String icon_shift_lock = "sym_keyboard_shift_locked.png"; + + private String Bg_space_normal = "btn_keyboard_spacekey_normal_normal.9.png"; + private String Bg_space_pressed = "btn_keyboard_spacekey_normal_pressed.9.png"; + + + private Drawable BgActionDraw; + private Drawable BgSpaceDraw; + private Drawable BgNormalDraw; + + private Drawable iconShift = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.ico_shift_lit); + private Drawable iconDel = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.del_icon); + private Drawable BG = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.de_keyboard_bg); + + private int keyNoramlcolor = KeyboardSkin.app.getResources().getColor(R.color.white, null); + + private int keyActioncolor = KeyboardSkin.app.getResources().getColor(R.color.white, null); + + private Drawable iconShiftLock = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.ico_shift_lit); + + + public Drawable getBG() { + return BG; + } + + public Drawable getBgActionDraw() { + return BgActionDraw; + } + + public Drawable getBgNormalDraw() { + return BgNormalDraw; + } + + public Drawable getBgSpaceDraw() { + return BgSpaceDraw; + } + + public Drawable getIconDel() { + return iconDel; + } + + public Drawable getIconShift() { + return iconShift; + } + + public Drawable getIconShiftLock() { + return iconShiftLock; + } + + public int getKeyNoramlcolor() { + return keyNoramlcolor; + } + + + public int getKeyActioncolor() { + return keyActioncolor; + } + + public void init() { + + iconShift = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.ico_shift_lit); + iconDel = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.del_icon); + BG = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.de_keyboard_bg); + + keyNoramlcolor = KeyboardSkin.app.getResources().getColor(R.color.white, null); + + iconShiftLock = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.ico_shift_lit); + + + Drawable drawable1 = ContextCompat.getDrawable( + KeyboardSkin.app, + R.drawable.de_keybg_press + ); + Drawable drawable = ContextCompat.getDrawable(KeyboardSkin.app, R.drawable.de_keybg); + StateListDrawable status = MyTools.getStatus(drawable, drawable1); + BgActionDraw = status; + BgNormalDraw = status; + BgSpaceDraw = status; + } + + private Drawable getKeyBackGround(Context context, String resDirPath,String drawName) + { + ///data/user/0/com.keyboardskinning.theme/cache/Funny Xmas Stitch/new-android-funny_xmas_stitch-theme-aws_master_zip_theme-version-2_noad_336433_972836_223993/res/drawable-xxhdpi-v4/btn_keyboard_key_normal_normal.9.png + String filePath = resDirPath+path_drawxh+drawName; + File file = new File(filePath); + if (!file.exists()) { + return null; + } + BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), BitmapFactory.decodeFile(filePath)); + return bitmapDrawable; + } + private Drawable getBackGround(Context context,String resDirPath) + { + String filePath = resDirPath+path_drawxxh+jpg_BG; + if (!new File(filePath).exists()) { + return null; + } + BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), BitmapFactory.decodeFile(filePath)); + return bitmapDrawable; + } + + private void updateKeyColor(String resDirPath) { + + String colorXmlPath = resDirPath+path_color; + File file = new File(colorXmlPath); + if (!file.exists()) { + return; + } + try { + XmlPullParser xmlPullParser = Xml.newPullParser(); + + String s = MyTools.parseString(file); + xmlPullParser.setInput(new StringReader(s)); + xmlPullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + int eventT = xmlPullParser.getEventType(); + while (eventT != XmlPullParser.END_DOCUMENT) { + if (eventT == XmlPullParser.START_TAG && (Objects.equals(xmlPullParser.getName(), "color") || Objects.equals(xmlPullParser.getName(), "item"))) { + String value = xmlPullParser.getAttributeValue(null, "name"); + if (value != null && value.equals(color_normal_key)) { + keyNoramlcolor = Color.parseColor(xmlPullParser.nextText()); + + } + if (value != null && value.equals(color_action_key)) { + keyActioncolor = Color.parseColor(xmlPullParser.nextText()); + + } + } + eventT = xmlPullParser.next(); + } + }catch (XmlPullParserException exception){ + + } catch (IOException e) { + throw new RuntimeException(e); + } + + + } + + public void updateConfig( Context con) { + String resDirPath = MyTools.getSkin(); + + updateKeyColor(resDirPath); + BG = getBackGround(con, resDirPath); + + Drawable keyBackGround = getKeyBackGround(con, resDirPath, Bg_normal); + Drawable keyBackGround1 = getKeyBackGround(con, resDirPath, Bg_pressed); + BgNormalDraw = MyTools.getStatus(keyBackGround, keyBackGround1); + + Drawable keyBackGround2 = getKeyBackGround(con, resDirPath, Bg_action_normal); + Drawable keyBackGround3 = getKeyBackGround(con, resDirPath, Bg_action_pressed); + BgActionDraw = MyTools.getStatus(keyBackGround2, keyBackGround3); + + Drawable keyBackGround4 = getKeyBackGround(con, resDirPath, Bg_space_normal); + Drawable keyBackGround5 = getKeyBackGround(con, resDirPath, Bg_space_pressed); + BgSpaceDraw = MyTools.getStatus(keyBackGround4, keyBackGround5); + + iconDel = getKeyBackGround(con, resDirPath, icon_del); + iconShift = getKeyBackGround(con, resDirPath, icon_shift); + iconShiftLock = getKeyBackGround(con, resDirPath, icon_shift_lock); + + } + + +} diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/DrawIcon.kt b/app/src/main/java/com/keyboardskinning/theme/tools/DrawIcon.kt new file mode 100644 index 0000000..828cc72 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/DrawIcon.kt @@ -0,0 +1,53 @@ +package com.keyboardskinning.theme.tools + +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable + + +object DrawIcon { + + public fun onDrawKeyIcon(currentKey: KeyBoard.Key, + drawKeyIcon: Drawable, + myCanvas: Canvas, + myKeyBoardView:MyKeyBoardView + ){ + drawKeyIcon.apply { + currentKey.icon = this + + var icon_w = currentKey.icon.intrinsicWidth.toFloat() + var icon_wr = icon_w / currentKey.width.toFloat() + var icon_h = currentKey.icon.intrinsicHeight.toFloat() + var icon_hr = icon_h / currentKey.height.toFloat() + + + var tep1 = 0f + var tep2 = 0f + if (icon_wr > icon_hr) { + tep2 = icon_wr + tep1 = icon_wr.coerceAtLeast(0.5f) + + } else { + tep2 = icon_hr + tep1 = icon_hr.coerceAtLeast(0.5f) + + } + icon_h = (icon_h / tep2) * tep1 + icon_w = (icon_w / tep2) * tep1 + currentKey.icon.let { + it.bounds = Rect().apply { + + top = + (currentKey.y + myKeyBoardView.paddingTop + (currentKey.height - icon_h) / 2f).toInt() + left = + (currentKey.x + myKeyBoardView.paddingLeft + (currentKey.width - icon_w) / 2f).toInt() + bottom = (top + icon_h).toInt() + right = (left + icon_w).toInt() + + } + it.draw(myCanvas) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/KeyBoard.java b/app/src/main/java/com/keyboardskinning/theme/tools/KeyBoard.java new file mode 100644 index 0000000..31363f4 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/KeyBoard.java @@ -0,0 +1,849 @@ +package com.keyboardskinning.theme.tools; + +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.Log; +import android.util.TypedValue; +import android.util.Xml; + +import androidx.annotation.XmlRes; + + +import com.keyboardskinning.theme.R; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +public class KeyBoard { + + 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; + public static final int KEYCODE_BLANK = 32; + public static final int KEYCODE_SHIFT_123 = -360; + public static final int KEYCODE_SHIFT_SYMBOL = -361; + + /** 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 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 mKeys; + + /** List of modifier keys such as Shift & Alt, if any */ + private List 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 rows = new ArrayList<>(); + + + 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 mKeys = new ArrayList<>(); + + + public int rowEdgeFlags; + + /** The keyboard mode for this row */ + public int mode; + + private KeyBoard parent; + + public Row(KeyBoard parent) { + this.parent = parent; + } + + public Row(Resources res, KeyBoard parent, XmlResourceParser parser) { + this.parent = parent; + TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.style_view); + defaultWidth = getDimensionOrFraction(a, + R.styleable.style_view_android_keyWidth, + parent.mDisplayWidth, parent.mDefaultWidth); + defaultHeight = getDimensionOrFraction(a, + R.styleable.style_view_android_keyHeight, + parent.mDisplayHeight, parent.mDefaultHeight); + defaultHorizontalGap = getDimensionOrFraction(a, + R.styleable.style_view_android_horizontalGap, + parent.mDisplayWidth, parent.mDefaultHorizontalGap); + verticalGap = getDimensionOrFraction(a, + R.styleable.style_view_android_verticalGap, + parent.mDisplayHeight, parent.mDefaultVerticalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.style_row); + rowEdgeFlags = a.getInt(R.styleable.style_row_android_rowEdgeFlags, 0); + mode = a.getResourceId(R.styleable.style_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 KeyBoard#EDGE_LEFT}, {@link KeyBoard#EDGE_RIGHT}, {@link KeyBoard#EDGE_TOP} and + * {@link KeyBoard#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 KeyBoard 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(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 KeyBoard}. + * @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, 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.style_view); + + width = getDimensionOrFraction(a, + R.styleable.style_view_android_keyWidth, + keyboard.mDisplayWidth, parent.defaultWidth); + height = getDimensionOrFraction(a, + R.styleable.style_view_android_keyHeight, + keyboard.mDisplayHeight, parent.defaultHeight); + gap = getDimensionOrFraction(a, + R.styleable.style_view_android_horizontalGap, + keyboard.mDisplayWidth, parent.defaultHorizontalGap); + a.recycle(); + a = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.style_key); + this.x += gap; + TypedValue codesValue = new TypedValue(); + a.getValue(R.styleable.style_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.style_key_android_iconPreview); + if (iconPreview != null) { + iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(), + iconPreview.getIntrinsicHeight()); + } + popupCharacters = a.getText( + R.styleable.style_key_android_popupCharacters); + popupResId = a.getResourceId( + R.styleable.style_key_android_popupKeyboard, 0); + repeatable = a.getBoolean( + R.styleable.style_key_android_isRepeatable, false); + modifier = a.getBoolean( + R.styleable.style_key_android_isModifier, false); + sticky = a.getBoolean( + R.styleable.style_key_android_isSticky, false); + edgeFlags = a.getInt(R.styleable.style_key_android_keyEdgeFlags, 0); + edgeFlags |= parent.rowEdgeFlags; + + icon = a.getDrawable( + R.styleable.style_key_android_keyIcon); + if (icon != null) { + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + } + label = a.getText(R.styleable.style_key_android_keyLabel); + text = a.getText(R.styleable.style_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. + * + *

Toggled state of the key will be flipped when all the following conditions are + * fulfilled:

+ * + *
    + *
  • This is a sticky key, that is, {@link #sticky} is {@code true}. + *
  • The parameter {@code inside} is {@code true}. + *
  • {@link android.os.Build.VERSION#SDK_INT} is greater than + * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}. + *
+ * + * @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) { + Log.e(TAG, "Error parsing keycodes " + value); + } + } + 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 KeyBoard(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 KeyBoard(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 KeyBoard(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 KeyBoard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + this(context, layoutTemplateResId); + int x = 0; + int y = 0; + int column = 0; + mTotalWidth = 0; + + Row row = new 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 Key key = new 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) { + Row row = rows.get(rowIndex); + int numKeys = row.mKeys.size(); + int totalGap = 0; + int totalWidth = 0; + for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) { + 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) { + 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 getKeys() { + return mKeys; + } + + public List 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 (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 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 Row createRowFromXml(Resources res, XmlResourceParser parser) { + return new Row(res, this, parser); + } + + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser) { + return new 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; + Key key = null; + 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) { + Log.e(TAG, "Parse error:" + 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.style_view); + + mDefaultWidth = getDimensionOrFraction(a, + R.styleable.style_view_android_keyWidth, + mDisplayWidth, mDisplayWidth / 10); + mDefaultHeight = getDimensionOrFraction(a, + R.styleable.style_view_android_keyHeight, + mDisplayHeight, 50); + mDefaultHorizontalGap = getDimensionOrFraction(a, + R.styleable.style_view_android_horizontalGap, + mDisplayWidth, 0); + mDefaultVerticalGap = getDimensionOrFraction(a, + R.styleable.style_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; + } +} + diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/KeyBoardView.java b/app/src/main/java/com/keyboardskinning/theme/tools/KeyBoardView.java new file mode 100644 index 0000000..2d637b1 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/KeyBoardView.java @@ -0,0 +1,1368 @@ +package com.keyboardskinning.theme.tools; + + + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.PopupWindow; +import android.widget.TextView; + + +import com.keyboardskinning.theme.R; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class KeyBoardView extends View implements View.OnClickListener { + + + public interface OnKeyboardActionListener { + + + void onPress(int primaryCode); + + + void onRelease(int primaryCode); + + void onKey(int primaryCode, int[] keyCodes); + + void onText(CharSequence text); + + + void swipeLeft(); + + + void swipeRight(); + + + void swipeDown(); + + + void swipeUp(); + } + + private static final boolean DEBUG = false; + private static final int NOT_A_KEY = -1; + private static final int[] KEY_DELETE = { KeyBoard.KEYCODE_DELETE }; + private static final int[] LONG_PRESSABLE_STATE_SET = { R.styleable.Style_Pre_state_android_state_long_pressable }; + + private Context mContext; + private KeyBoard mKeyboard; + private int mCurrentKeyIndex = NOT_A_KEY; + + private int mLabelTextSize; + private int mKeyTextSize; + private int mKeyTextColor; + private float mShadowRadius; + private int mShadowColor; + private float mBackgroundDimAmount; + + private TextView mPreviewText; + private PopupWindow mPreviewPopup; + private int mPreviewTextSizeLarge; + private int mPreviewOffset; + private int mPreviewHeight; + // Working variable + private final int[] mCoordinates = new int[2]; + + private PopupWindow mPopupKeyboard; + private View mMiniKeyboardContainer; + private KeyBoardView mMiniKeyboard; + private boolean mMiniKeyboardOnScreen; + private View mPopupParent; + private int mMiniKeyboardOffsetX; + private int mMiniKeyboardOffsetY; + private Map mMiniKeyboardCache; + private KeyBoard.Key[] mKeys; + + + private OnKeyboardActionListener mKeyboardActionListener; + + private static final int MSG_SHOW_PREVIEW = 1; + private static final int MSG_REMOVE_PREVIEW = 2; + private static final int MSG_REPEAT = 3; + private static final int MSG_LONGPRESS = 4; + + private static final int DELAY_BEFORE_PREVIEW = 0; + private static final int DELAY_AFTER_PREVIEW = 70; + private static final int DEBOUNCE_TIME = 70; + + private int mVerticalCorrection; + private int mProximityThreshold; + + private boolean mPreviewCentered = false; + private boolean mShowPreview = true; + private boolean mShowTouchPoints = true; + private int mPopupPreviewX; + private int mPopupPreviewY; + + private int mLastX; + private int mLastY; + private int mStartX; + private int mStartY; + + private boolean mProximityCorrectOn; + + private Paint mPaint; + private Rect mPadding; + + private long mDownTime; + private long mLastMoveTime; + private int mLastKey; + private int mLastCodeX; + private int mLastCodeY; + private int mCurrentKey = NOT_A_KEY; + private int mDownKey = NOT_A_KEY; + private long mLastKeyTime; + private long mCurrentKeyTime; + private int[] mKeyIndices = new int[12]; + private GestureDetector mGestureDetector; + private int mPopupX; + private int mPopupY; + private int mRepeatKeyIndex = NOT_A_KEY; + private int mPopupLayout; + private boolean mAbortKey; + private KeyBoard.Key mInvalidatedKey; + private Rect mClipRegion = new Rect(0, 0, 0, 0); + private boolean mPossiblePoly; + private SwipeTracker mSwipeTracker = new SwipeTracker(); + private int mSwipeThreshold; + private boolean mDisambiguateSwipe; + + // Variables for dealing with multiple pointers + private int mOldPointerCount = 1; + private float mOldPointerX; + private float mOldPointerY; + + private Drawable mKeyBackground; + + private static final int REPEAT_INTERVAL = 50; // ~20 keys per second + private static final int REPEAT_START_DELAY = 300; + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); + + private static int MAX_NEARBY_KEYS = 12; + private int[] mDistances = new int[MAX_NEARBY_KEYS]; + + // For multi-tap + private int mLastSentIndex; + private int mTapCount; + private long mLastTapTime; + private boolean mInMultiTap; + private static final int MULTITAP_INTERVAL = 600; // milliseconds + private StringBuilder mPreviewLabel = new StringBuilder(1); + + /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ + private boolean mDrawPending; + /** The dirty region in the keyboard bitmap */ + private Rect mDirtyRect = new Rect(); + /** The keyboard bitmap for faster updates */ + private Bitmap mBuffer; + /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ + private boolean mKeyboardChanged; + /** The canvas for the above mutable keyboard bitmap */ + private Canvas mCanvas; + /** The accessibility manager for accessibility support */ +// private AccessibilityManager mAccessibilityManager; + /** The audio manager for accessibility support */ + private AudioManager mAudioManager; + /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */ + private boolean mHeadsetRequiredToHearPasswordsAnnounced; + + Handler mHandler; + + public KeyBoardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyBoardView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public KeyBoardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mContext = context; + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.style_view_keyboard, defStyleAttr, defStyleRes); + + LayoutInflater inflate = + (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + int previewLayout = 0; + int keyTextSize = 0; + + int n = a.getIndexCount(); + + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + if (attr == R.styleable.style_view_keyboard_android_keyBackground) { + mKeyBackground = a.getDrawable(attr); + } else if (attr == R.styleable.style_view_keyboard_android_verticalCorrection) { + mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); + } else if (attr == R.styleable.style_view_keyboard_android_keyPreviewLayout) { + previewLayout = a.getResourceId(attr, 0); + } else if (attr == R.styleable.style_view_keyboard_android_keyPreviewOffset) { + mPreviewOffset = a.getDimensionPixelOffset(attr, 0); + } else if (attr == R.styleable.style_view_keyboard_android_keyPreviewHeight) { + mPreviewHeight = a.getDimensionPixelSize(attr, 80); + } else if (attr == R.styleable.style_view_keyboard_android_keyTextSize) { + mKeyTextSize = a.getDimensionPixelSize(attr, 18); + } else if (attr == R.styleable.style_view_keyboard_android_keyTextColor) { + mKeyTextColor = a.getColor(attr, 0xFF333333); + } else if (attr == R.styleable.style_view_keyboard_android_labelTextSize) { + mLabelTextSize = a.getDimensionPixelSize(attr, 14); + } else if (attr == R.styleable.style_view_keyboard_android_popupLayout) { + mPopupLayout = a.getResourceId(attr, 0); + } else if (attr == R.styleable.style_view_keyboard_android_shadowColor) { + mShadowColor = a.getColor(attr, 0); + } else if (attr == R.styleable.style_view_keyboard_android_shadowRadius) { + mShadowRadius = a.getFloat(attr, 0f); + } + } + + mPreviewPopup = new PopupWindow(context); + if (previewLayout != 0) { + mPreviewText = (TextView) inflate.inflate(previewLayout, null); + mPreviewTextSizeLarge = (int) mPreviewText.getTextSize(); + mPreviewPopup.setContentView(mPreviewText); + mPreviewPopup.setBackgroundDrawable(null); + } else { + mShowPreview = false; + } + + mPreviewPopup.setTouchable(false); + + mPopupKeyboard = new PopupWindow(context); + mPopupKeyboard.setBackgroundDrawable(null); + //mPopupKeyboard.setClippingEnabled(false); + + mPopupParent = this; + //mPredicting = true; + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTextSize(keyTextSize); + mPaint.setTextAlign(Paint.Align.CENTER); + mPaint.setAlpha(255); + + mPadding = new Rect(0, 0, 0, 0); + mMiniKeyboardCache = new HashMap(); + mKeyBackground.getPadding(mPadding); + + mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density); +// mDisambiguateSwipe = getResources().getBoolean( +// R.bool.config_swipeDisambiguation); + + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + resetMultiTap(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + initGestureDetector(); + if (mHandler == null) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_PREVIEW: + showKey(msg.arg1); + break; + case MSG_REMOVE_PREVIEW: + mPreviewText.setVisibility(INVISIBLE); + break; + case MSG_REPEAT: + if (repeatKey()) { + Message repeat = Message.obtain(this, MSG_REPEAT); + sendMessageDelayed(repeat, REPEAT_INTERVAL); + } + break; + case MSG_LONGPRESS: + openPopupIfRequired((MotionEvent) msg.obj); + break; + } + } + }; + } + } + + private void initGestureDetector() { + if (mGestureDetector == null) { + mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent me1, MotionEvent me2, + float velocityX, float velocityY) { + if (mPossiblePoly) return false; + final float absX = Math.abs(velocityX); + final float absY = Math.abs(velocityY); + float deltaX = me2.getX() - me1.getX(); + float deltaY = me2.getY() - me1.getY(); + int travelX = getWidth() / 2; // Half the keyboard width + int travelY = getHeight() / 2; // Half the keyboard height + mSwipeTracker.computeCurrentVelocity(1000); + final float endingVelocityX = mSwipeTracker.getXVelocity(); + final float endingVelocityY = mSwipeTracker.getYVelocity(); + boolean sendDownKey = false; + if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { + if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) { + sendDownKey = true; + } else { + swipeRight(); + return true; + } + } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { + if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) { + sendDownKey = true; + } else { + swipeLeft(); + return true; + } + } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { + if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) { + sendDownKey = true; + } else { + swipeUp(); + return true; + } + } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { + if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) { + sendDownKey = true; + } else { + swipeDown(); + return true; + } + } + + if (sendDownKey) { + detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime()); + } + return false; + } + }); + + mGestureDetector.setIsLongpressEnabled(false); + } + } + + public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { + mKeyboardActionListener = listener; + } + + protected OnKeyboardActionListener getOnKeyboardActionListener() { + return mKeyboardActionListener; + } + + + public void setKeyboard(KeyBoard keyboard) { + if (mKeyboard != null) { + showPreview(NOT_A_KEY); + } + // Remove any pending messages + removeMessages(); + mKeyboard = keyboard; + List keys = mKeyboard.getKeys(); + mKeys = keys.toArray(new KeyBoard.Key[keys.size()]); + requestLayout(); + // Hint to reallocate the buffer if the size changed + mKeyboardChanged = true; + invalidateAllKeys(); + computeProximityThreshold(keyboard); + mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views + // Switching to a different keyboard should abort any pending keys so that the key up + // doesn't get delivered to the old or new keyboard + mAbortKey = true; // Until the next ACTION_DOWN + } + + + public KeyBoard getKeyboard() { + return mKeyboard; + } + + + public boolean setShifted(boolean shifted) { + if (mKeyboard != null) { + if (mKeyboard.setShifted(shifted)) { + // The whole keyboard probably needs to be redrawn + invalidateAllKeys(); + return true; + } + } + return false; + } + + + public boolean isShifted() { + if (mKeyboard != null) { + return mKeyboard.isShifted(); + } + return false; + } + + + public void setPreviewEnabled(boolean previewEnabled) { + mShowPreview = previewEnabled; + } + + /** + * Returns the enabled state of the key feedback popup. + * @return whether or not the key feedback popup is enabled + * @see #setPreviewEnabled(boolean) + */ + public boolean isPreviewEnabled() { + return mShowPreview; + } + + public void setVerticalCorrection(int verticalOffset) { + + } + public void setPopupParent(View v) { + mPopupParent = v; + } + + public void setPopupOffset(int x, int y) { + mMiniKeyboardOffsetX = x; + mMiniKeyboardOffsetY = y; + if (mPreviewPopup.isShowing()) { + mPreviewPopup.dismiss(); + } + } + + public void setProximityCorrectionEnabled(boolean enabled) { + mProximityCorrectOn = enabled; + } + + /** + * Returns true if proximity correction is enabled. + */ + public boolean isProximityCorrectionEnabled() { + return mProximityCorrectOn; + } + + /** + * Popup keyboard close button clicked. + * @hide + */ + public void onClick(View v) { + dismissPopupKeyboard(); + } + + private CharSequence adjustCase(CharSequence label) { + if (mKeyboard.isShifted() && label != null && label.length() < 3 + && Character.isLowerCase(label.charAt(0))) { + label = label.toString().toUpperCase(); + } + return label; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Round up a little + if (mKeyboard == null) { + setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); + } else { + int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); + if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { + width = MeasureSpec.getSize(widthMeasureSpec); + } + setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); + } + } + + /** + * Compute the average distance between adjacent keys (horizontally and vertically) + * and square it to get the proximity threshold. We use a square here and in computing + * the touch distance from a key's center to avoid taking a square root. + * @param keyboard + */ + private void computeProximityThreshold(KeyBoard keyboard) { + if (keyboard == null) return; + final KeyBoard.Key[] keys = mKeys; + if (keys == null) return; + int length = keys.length; + int dimensionSum = 0; + for (int i = 0; i < length; i++) { + KeyBoard.Key key = keys[i]; + dimensionSum += Math.min(key.width, key.height) + key.gap; + } + if (dimensionSum < 0 || length == 0) return; + mProximityThreshold = (int) (dimensionSum * 1.4f / length); + mProximityThreshold *= mProximityThreshold; // Square it + } + + @Override + public void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (mKeyboard != null) { + mKeyboard.resize(w, h); + } + // Release the buffer, if any and it will be reallocated on the next draw + mBuffer = null; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); +// if (mDrawPending || mBuffer == null || mKeyboardChanged) { +// onBufferDraw(); +// } +// canvas.drawBitmap(mBuffer, 0, 0, null); + } + + private void onBufferDraw() { + if (mBuffer == null || mKeyboardChanged) { + if (mBuffer == null || mKeyboardChanged && + (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { + // Make sure our bitmap is at least 1x1 + final int width = Math.max(1, getWidth()); + final int height = Math.max(1, getHeight()); + mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBuffer); + } + invalidateAllKeys(); + mKeyboardChanged = false; + } + + if (mKeyboard == null) return; + + mCanvas.save(); + final Canvas canvas = mCanvas; + canvas.clipRect(mDirtyRect); + + final Paint paint = mPaint; + final Drawable keyBackground = mKeyBackground; + final Rect clipRegion = mClipRegion; + final Rect padding = mPadding; + final int kbdPaddingLeft = getPaddingLeft(); + final int kbdPaddingTop = getPaddingTop(); + final KeyBoard.Key[] keys = mKeys; + final KeyBoard.Key invalidKey = mInvalidatedKey; + + paint.setColor(mKeyTextColor); + boolean drawSingleKey = false; + if (invalidKey != null && canvas.getClipBounds(clipRegion)) { + // Is clipRegion completely contained within the invalidated key? + if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && + invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && + invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && + invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { + drawSingleKey = true; + } + } + canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); + final int keyCount = keys.length; + for (int i = 0; i < keyCount; i++) { + final KeyBoard.Key key = keys[i]; + if (drawSingleKey && invalidKey != key) { + continue; + } + int[] drawableState = key.getCurrentDrawableState(); + keyBackground.setState(drawableState); + + // Switch the character to uppercase if shift is pressed + String label = key.label == null? null : adjustCase(key.label).toString(); + + final Rect bounds = keyBackground.getBounds(); + if (key.width != bounds.right || + key.height != bounds.bottom) { + keyBackground.setBounds(0, 0, key.width, key.height); + } + canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop); + keyBackground.draw(canvas); + + if (label != null) { + // For characters, use large font. For labels like "Done", use small font. + if (label.length() > 1 && key.codes.length < 2) { + paint.setTextSize(mLabelTextSize); + paint.setTypeface(Typeface.DEFAULT_BOLD); + } else { + paint.setTextSize(mKeyTextSize); + paint.setTypeface(Typeface.DEFAULT); + } + // Draw a drop shadow for the text + paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + // Draw the text + canvas.drawText(label, + (key.width - padding.left - padding.right) / 2 + + padding.left, + (key.height - padding.top - padding.bottom) / 2 + + (paint.getTextSize() - paint.descent()) / 2 + padding.top, + paint); + // Turn off drop shadow + paint.setShadowLayer(0, 0, 0, 0); + } else if (key.icon != null) { + final int drawableX = (key.width - padding.left - padding.right + - key.icon.getIntrinsicWidth()) / 2 + padding.left; + final int drawableY = (key.height - padding.top - padding.bottom + - key.icon.getIntrinsicHeight()) / 2 + padding.top; + canvas.translate(drawableX, drawableY); + key.icon.setBounds(0, 0, + key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight()); + key.icon.draw(canvas); + canvas.translate(-drawableX, -drawableY); + } + canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); + } + mInvalidatedKey = null; + // Overlay a dark rectangle to dim the keyboard + if (mMiniKeyboardOnScreen) { +// paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); + canvas.drawRect(0, 0, getWidth(), getHeight(), paint); + } + + if (DEBUG && mShowTouchPoints) { + paint.setAlpha(128); + paint.setColor(0xFFFF0000); + canvas.drawCircle(mStartX, mStartY, 3, paint); + canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint); + paint.setColor(0xFF0000FF); + canvas.drawCircle(mLastX, mLastY, 3, paint); + paint.setColor(0xFF00FF00); + canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint); + } + mCanvas.restore(); + mDrawPending = false; + mDirtyRect.setEmpty(); + } + + private int getKeyIndices(int x, int y, int[] allKeys) { + final KeyBoard.Key[] keys = mKeys; + int primaryIndex = NOT_A_KEY; + int closestKey = NOT_A_KEY; + int closestKeyDist = mProximityThreshold + 1; + Arrays.fill(mDistances, Integer.MAX_VALUE); + int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y); + final int keyCount = nearestKeyIndices.length; + for (int i = 0; i < keyCount; i++) { + final KeyBoard.Key key = keys[nearestKeyIndices[i]]; + int dist = 0; + boolean isInside = key.isInside(x,y); + if (isInside) { + primaryIndex = nearestKeyIndices[i]; + } + + if (((mProximityCorrectOn + && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) + || isInside) + && key.codes[0] > 32) { + // Find insertion point + final int nCodes = key.codes.length; + if (dist < closestKeyDist) { + closestKeyDist = dist; + closestKey = nearestKeyIndices[i]; + } + + if (allKeys == null) continue; + + for (int j = 0; j < mDistances.length; j++) { + if (mDistances[j] > dist) { + // Make space for nCodes codes + System.arraycopy(mDistances, j, mDistances, j + nCodes, + mDistances.length - j - nCodes); + System.arraycopy(allKeys, j, allKeys, j + nCodes, + allKeys.length - j - nCodes); + for (int c = 0; c < nCodes; c++) { + allKeys[j + c] = key.codes[c]; + mDistances[j + c] = dist; + } + break; + } + } + } + } + if (primaryIndex == NOT_A_KEY) { + primaryIndex = closestKey; + } + return primaryIndex; + } + + private void detectAndSendKey(int index, int x, int y, long eventTime) { + if (index != NOT_A_KEY && index < mKeys.length) { + final KeyBoard.Key key = mKeys[index]; + if (key.text != null) { + mKeyboardActionListener.onText(key.text); + mKeyboardActionListener.onRelease(NOT_A_KEY); + } else { + int code = key.codes[0]; + //TextEntryState.keyPressedAt(key, x, y); + int[] codes = new int[MAX_NEARBY_KEYS]; + Arrays.fill(codes, NOT_A_KEY); + getKeyIndices(x, y, codes); + // Multi-tap + if (mInMultiTap) { + if (mTapCount != -1) { + mKeyboardActionListener.onKey(KeyBoard.KEYCODE_DELETE, KEY_DELETE); + } else { + mTapCount = 0; + } + code = key.codes[mTapCount]; + } + mKeyboardActionListener.onKey(code, codes); + mKeyboardActionListener.onRelease(code); + } + mLastSentIndex = index; + mLastTapTime = eventTime; + } + } + + + private CharSequence getPreviewText(KeyBoard.Key key) { + if (mInMultiTap) { + // Multi-tap + mPreviewLabel.setLength(0); + mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); + return adjustCase(mPreviewLabel); + } else { + return adjustCase(key.label); + } + } + + private void showPreview(int keyIndex) { + int oldKeyIndex = mCurrentKeyIndex; + final PopupWindow previewPopup = mPreviewPopup; + + mCurrentKeyIndex = keyIndex; + // Release the old key and press the new key + final KeyBoard.Key[] keys = mKeys; + if (oldKeyIndex != mCurrentKeyIndex) { + if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) { + KeyBoard.Key oldKey = keys[oldKeyIndex]; + oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY); + invalidateKey(oldKeyIndex); + final int keyCode = oldKey.codes[0]; + } + if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) { + KeyBoard.Key newKey = keys[mCurrentKeyIndex]; + newKey.onPressed(); + invalidateKey(mCurrentKeyIndex); + final int keyCode = newKey.codes[0]; + } + } + // If key changed and preview is on ... + if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) { + mHandler.removeMessages(MSG_SHOW_PREVIEW); + if (previewPopup.isShowing()) { + if (keyIndex == NOT_A_KEY) { + mHandler.sendMessageDelayed(mHandler + .obtainMessage(MSG_REMOVE_PREVIEW), + DELAY_AFTER_PREVIEW); + } + } + if (keyIndex != NOT_A_KEY) { + if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) { + // Show right away, if it's already visible and finger is moving around + showKey(keyIndex); + } else { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), + DELAY_BEFORE_PREVIEW); + } + } + } + } + + private void showKey(final int keyIndex) { + final PopupWindow previewPopup = mPreviewPopup; + final KeyBoard.Key[] keys = mKeys; + if (keyIndex < 0 || keyIndex >= mKeys.length) return; + KeyBoard.Key key = keys[keyIndex]; + if (key.icon != null) { + mPreviewText.setCompoundDrawables(null, null, null, + key.iconPreview != null ? key.iconPreview : key.icon); + mPreviewText.setText(null); + } else { + mPreviewText.setCompoundDrawables(null, null, null, null); + mPreviewText.setText(getPreviewText(key)); + if (key.label!=null && key.label.length() > 1 && key.codes.length < 2) { + mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize); + mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); + } else { + mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); + mPreviewText.setTypeface(Typeface.DEFAULT); + } + } + mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width + + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); + final int popupHeight = mPreviewHeight; + ViewGroup.LayoutParams lp = mPreviewText.getLayoutParams(); + if (lp != null) { + lp.width = popupWidth; + lp.height = popupHeight; + } + if (!mPreviewCentered) { + mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft(); + mPopupPreviewY = key.y - popupHeight + mPreviewOffset; + } else { + + mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2; + mPopupPreviewY = - mPreviewText.getMeasuredHeight(); + } + mHandler.removeMessages(MSG_REMOVE_PREVIEW); + getLocationInWindow(mCoordinates); + mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero + mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero + + + mPreviewText.getBackground().setState( + key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); + mPopupPreviewX += mCoordinates[0]; + mPopupPreviewY += mCoordinates[1]; + + + getLocationOnScreen(mCoordinates); + if (mPopupPreviewY + mCoordinates[1] < 0) { + + if (key.x + key.width <= getWidth() / 2) { + mPopupPreviewX += (int) (key.width * 2.5); + } else { + mPopupPreviewX -= (int) (key.width * 2.5); + } + mPopupPreviewY += popupHeight; + } + + if (previewPopup.isShowing()) { + previewPopup.update(mPopupPreviewX, mPopupPreviewY, + popupWidth, popupHeight); + } else { + previewPopup.setWidth(popupWidth); + previewPopup.setHeight(popupHeight); + previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, + mPopupPreviewX, mPopupPreviewY); + } + mPreviewText.setVisibility(VISIBLE); + } + + + public void invalidateAllKeys() { + mDirtyRect.union(0, 0, getWidth(), getHeight()); + mDrawPending = true; + invalidate(); + } + + + public void invalidateKey(int keyIndex) { + if (mKeys == null) return; + if (keyIndex < 0 || keyIndex >= mKeys.length) { + return; + } + final KeyBoard.Key key = mKeys[keyIndex]; + mInvalidatedKey = key; + mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(), + key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); + onBufferDraw(); + invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(), + key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); + } + + private boolean openPopupIfRequired(MotionEvent me) { + // Check if we have a popup layout specified first. + if (mPopupLayout == 0) { + return false; + } + if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) { + return false; + } + + KeyBoard.Key popupKey = mKeys[mCurrentKey]; + boolean result = onLongPress(popupKey); + if (result) { + mAbortKey = true; + showPreview(NOT_A_KEY); + } + return result; + } + + protected boolean onLongPress(KeyBoard.Key popupKey) { + int popupKeyboardId = popupKey.popupResId; + + if (popupKeyboardId != 0) { + mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey); + if (mMiniKeyboardContainer == null) { + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null); + mMiniKeyboard = mMiniKeyboardContainer.findViewById( + R.id.my_keyboard_view); +// View closeButton = mMiniKeyboardContainer.findViewById( +// R.id.closeButton); +// if (closeButton != null) closeButton.setOnClickListener(this); + mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() { + public void onKey(int primaryCode, int[] keyCodes) { + mKeyboardActionListener.onKey(primaryCode, keyCodes); + dismissPopupKeyboard(); + } + + public void onText(CharSequence text) { + mKeyboardActionListener.onText(text); + dismissPopupKeyboard(); + } + + public void swipeLeft() { } + public void swipeRight() { } + public void swipeUp() { } + public void swipeDown() { } + public void onPress(int primaryCode) { + mKeyboardActionListener.onPress(primaryCode); + } + public void onRelease(int primaryCode) { + mKeyboardActionListener.onRelease(primaryCode); + } + }); + //mInputView.setSuggest(mSuggest); + KeyBoard keyboard; + if (popupKey.popupCharacters != null) { + keyboard = new KeyBoard(getContext(), popupKeyboardId, + popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight()); + } else { + keyboard = new KeyBoard(getContext(), popupKeyboardId); + } + mMiniKeyboard.setKeyboard(keyboard); + mMiniKeyboard.setPopupParent(this); + mMiniKeyboardContainer.measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); + + mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer); + } else { + mMiniKeyboard = mMiniKeyboardContainer.findViewById( + R.id.my_keyboard_view); + } + getLocationInWindow(mCoordinates); + mPopupX = popupKey.x + getPaddingLeft(); + mPopupY = popupKey.y + getPaddingTop(); + mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth(); + mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight(); + final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0]; + final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1]; + mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y); + mMiniKeyboard.setShifted(isShifted()); + mPopupKeyboard.setContentView(mMiniKeyboardContainer); + mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth()); + mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight()); + mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y); + mMiniKeyboardOnScreen = true; + //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me)); + invalidateAllKeys(); + return true; + } + return false; + } + + + + @Override + public boolean onTouchEvent(MotionEvent me) { + // Convert multi-pointer up/down events to single up/down events to + // deal with the typical multi-pointer behavior of two-thumb typing + final int pointerCount = me.getPointerCount(); + final int action = me.getAction(); + boolean result = false; + final long now = me.getEventTime(); + + if (pointerCount != mOldPointerCount) { + if (pointerCount == 1) { + // Send a down event for the latest pointer + MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, + me.getX(), me.getY(), me.getMetaState()); + result = onModifiedTouchEvent(down, false); + down.recycle(); + // If it's an up action, then deliver the up as well. + if (action == MotionEvent.ACTION_UP) { + result = onModifiedTouchEvent(me, true); + } + } else { + // Send an up event for the last pointer + MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, + mOldPointerX, mOldPointerY, me.getMetaState()); + result = onModifiedTouchEvent(up, true); + up.recycle(); + } + } else { + if (pointerCount == 1) { + result = onModifiedTouchEvent(me, false); + mOldPointerX = me.getX(); + mOldPointerY = me.getY(); + } else { + // Don't do anything when 2 pointers are down and moving. + result = true; + } + } + mOldPointerCount = pointerCount; + + + return result; + } + + private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) { + int touchX = (int) me.getX() - getPaddingLeft(); + int touchY = (int) me.getY() - getPaddingTop(); + if (touchY >= -mVerticalCorrection) + touchY += mVerticalCorrection; + final int action = me.getAction(); + final long eventTime = me.getEventTime(); + int keyIndex = getKeyIndices(touchX, touchY, null); + mPossiblePoly = possiblePoly; + + // Track the last few movements to look for spurious swipes. + if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear(); + mSwipeTracker.addMovement(me); + + // Ignore all motion events until a DOWN. + if (mAbortKey + && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) { + mRepeatKeyIndex = NOT_A_KEY; + return true; + } + + if (mGestureDetector.onTouchEvent(me)) { + showPreview(NOT_A_KEY); + mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); + return true; + } + + // Needs to be called after the gesture detector gets a turn, as it may have + // displayed the mini keyboard + if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) { + mRepeatKeyIndex = NOT_A_KEY; + return true; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + mAbortKey = false; + mStartX = touchX; + mStartY = touchY; + mLastCodeX = touchX; + mLastCodeY = touchY; + mLastKeyTime = 0; + mCurrentKeyTime = 0; + mLastKey = NOT_A_KEY; + mCurrentKey = keyIndex; + mDownKey = keyIndex; + mDownTime = me.getEventTime(); + mLastMoveTime = mDownTime; + checkMultiTap(eventTime, keyIndex); + mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? + mKeys[keyIndex].codes[0] : 0); + if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) { + mRepeatKeyIndex = mCurrentKey; + Message msg = mHandler.obtainMessage(MSG_REPEAT); + mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY); + repeatKey(); + // Delivering the key could have caused an abort + if (mAbortKey) { + mRepeatKeyIndex = NOT_A_KEY; + break; + } + } + if (mCurrentKey != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } + showPreview(keyIndex); + break; + + case MotionEvent.ACTION_MOVE: + boolean continueLongPress = false; + if (keyIndex != NOT_A_KEY) { + if (mCurrentKey == NOT_A_KEY) { + mCurrentKey = keyIndex; + mCurrentKeyTime = eventTime - mDownTime; + } else { + if (keyIndex == mCurrentKey) { + mCurrentKeyTime += eventTime - mLastMoveTime; + continueLongPress = true; + } else if (mRepeatKeyIndex == NOT_A_KEY) { + resetMultiTap(); + mLastKey = mCurrentKey; + mLastCodeX = mLastX; + mLastCodeY = mLastY; + mLastKeyTime = + mCurrentKeyTime + eventTime - mLastMoveTime; + mCurrentKey = keyIndex; + mCurrentKeyTime = 0; + } + } + } + if (!continueLongPress) { + // Cancel old longpress + mHandler.removeMessages(MSG_LONGPRESS); + // Start new longpress if key has changed + if (keyIndex != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } + } + showPreview(mCurrentKey); + mLastMoveTime = eventTime; + break; + + case MotionEvent.ACTION_UP: + removeMessages(); + if (keyIndex == mCurrentKey) { + mCurrentKeyTime += eventTime - mLastMoveTime; + } else { + resetMultiTap(); + mLastKey = mCurrentKey; + mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime; + mCurrentKey = keyIndex; + mCurrentKeyTime = 0; + } + if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME + && mLastKey != NOT_A_KEY) { + mCurrentKey = mLastKey; + touchX = mLastCodeX; + touchY = mLastCodeY; + } + showPreview(NOT_A_KEY); + Arrays.fill(mKeyIndices, NOT_A_KEY); + // If we're not on a repeating key (which sends on a DOWN event) + if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) { + detectAndSendKey(mCurrentKey, touchX, touchY, eventTime); + } + invalidateKey(keyIndex); + mRepeatKeyIndex = NOT_A_KEY; + break; + case MotionEvent.ACTION_CANCEL: + removeMessages(); + dismissPopupKeyboard(); + mAbortKey = true; + showPreview(NOT_A_KEY); + invalidateKey(mCurrentKey); + break; + } + mLastX = touchX; + mLastY = touchY; + return true; + } + + private boolean repeatKey() { + if(mRepeatKeyIndex != NOT_A_KEY){ + KeyBoard.Key key = mKeys[mRepeatKeyIndex]; + detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime); + return true; + } + return false; + } + + protected void swipeRight() { + mKeyboardActionListener.swipeRight(); + } + + protected void swipeLeft() { + mKeyboardActionListener.swipeLeft(); + } + + protected void swipeUp() { + mKeyboardActionListener.swipeUp(); + } + + protected void swipeDown() { + mKeyboardActionListener.swipeDown(); + } + + public void closing() { + if (mPreviewPopup.isShowing()) { + mPreviewPopup.dismiss(); + } + removeMessages(); + + dismissPopupKeyboard(); + mBuffer = null; + mCanvas = null; + mMiniKeyboardCache.clear(); + } + + private void removeMessages() { + if (mHandler != null) { + mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); + mHandler.removeMessages(MSG_SHOW_PREVIEW); + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + closing(); + } + + private void dismissPopupKeyboard() { + if (mPopupKeyboard.isShowing()) { + mPopupKeyboard.dismiss(); + mMiniKeyboardOnScreen = false; + invalidateAllKeys(); + } + } + + public boolean handleBack() { + if (mPopupKeyboard.isShowing()) { + dismissPopupKeyboard(); + return true; + } + return false; + } + + private void resetMultiTap() { + mLastSentIndex = NOT_A_KEY; + mTapCount = 0; + mLastTapTime = -1; + mInMultiTap = false; + } + + private void checkMultiTap(long eventTime, int keyIndex) { + if (keyIndex == NOT_A_KEY) return; + KeyBoard.Key key = mKeys[keyIndex]; + if (key.codes.length > 1) { + mInMultiTap = true; + if (eventTime < mLastTapTime + MULTITAP_INTERVAL + && keyIndex == mLastSentIndex) { + mTapCount = (mTapCount + 1) % key.codes.length; + return; + } else { + mTapCount = -1; + return; + } + } + if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) { + resetMultiTap(); + } + } + + private static class SwipeTracker { + + static final int NUM_PAST = 4; + static final int LONGEST_PAST_TIME = 200; + + final float mPastX[] = new float[NUM_PAST]; + final float mPastY[] = new float[NUM_PAST]; + final long mPastTime[] = new long[NUM_PAST]; + + float mYVelocity; + float mXVelocity; + + public void clear() { + mPastTime[0] = 0; + } + + public void addMovement(MotionEvent ev) { + long time = ev.getEventTime(); + final int N = ev.getHistorySize(); + for (int i=0; i= 0) { + final int start = drop+1; + final int count = NUM_PAST-drop-1; + System.arraycopy(pastX, start, pastX, 0, count); + System.arraycopy(pastY, start, pastY, 0, count); + System.arraycopy(pastTime, start, pastTime, 0, count); + i -= (drop+1); + } + pastX[i] = x; + pastY[i] = y; + pastTime[i] = time; + i++; + if (i < NUM_PAST) { + pastTime[i] = 0; + } + } + + public void computeCurrentVelocity(int units) { + computeCurrentVelocity(units, Float.MAX_VALUE); + } + + public void computeCurrentVelocity(int units, float maxVelocity) { + final float[] pastX = mPastX; + final float[] pastY = mPastY; + final long[] pastTime = mPastTime; + + final float oldestX = pastX[0]; + final float oldestY = pastY[0]; + final long oldestTime = pastTime[0]; + float accumX = 0; + float accumY = 0; + int N=0; + while (N < NUM_PAST) { + if (pastTime[N] == 0) { + break; + } + N++; + } + + for (int i=1; i < N; i++) { + final int dur = (int)(pastTime[i] - oldestTime); + if (dur == 0) continue; + float dist = pastX[i] - oldestX; + float vel = (dist/dur) * units; // pixels/frame. + if (accumX == 0) accumX = vel; + else accumX = (accumX + vel) * .5f; + + dist = pastY[i] - oldestY; + vel = (dist/dur) * units; // pixels/frame. + if (accumY == 0) accumY = vel; + else accumY = (accumY + vel) * .5f; + } + mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) + : Math.min(accumX, maxVelocity); + mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) + : Math.min(accumY, maxVelocity); + } + + public float getXVelocity() { + return mXVelocity; + } + + public float getYVelocity() { + return mYVelocity; + } + } +} + diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/MyKeyBoardView.java b/app/src/main/java/com/keyboardskinning/theme/tools/MyKeyBoardView.java new file mode 100644 index 0000000..e980950 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/MyKeyBoardView.java @@ -0,0 +1,157 @@ +package com.keyboardskinning.theme.tools; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; + +import java.util.List; + +public class MyKeyBoardView extends KeyBoardView { + + private CustomViewConfig config = new CustomViewConfig(); + private int shift_status = 0; + private int viewType = 0; + + private Context context; + + public void setShift_status(int shift_status) { + this.shift_status = shift_status; + } + + public int getShift_status() { + return shift_status; + } + + public void setViewType(int viewType) { + this.viewType = viewType; + } + + public int getViewType() { + return viewType; + } + + public MyKeyBoardView(Context context, AttributeSet attrs) { + + super(context, attrs); + initView(); + } + + public MyKeyBoardView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + public MyKeyBoardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initView(); + } + + + private Paint mPaint; + + + private void initView() { + config.init(); + context = getContext(); + mPaint = new Paint(); + mPaint.setTextAlign(Paint.Align.CENTER); + float textSize = MyTools.spToPpx(16f, context); + mPaint.setTextSize(textSize); + mPaint.setColor(config.getKeyNoramlcolor()); + } + + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + KeyBoard keyboard = getKeyboard(); + List keys = keyboard.getKeys(); + for (int r = 0; r < keys.size(); r++) { + KeyBoard.Key key = keys.get(r); + int code = key.codes[0]; + + mPaint.setColor(config.getKeyActioncolor()); + if (code == KeyBoard.KEYCODE_MODE_CHANGE) { + + onDrawKeyBackground(key, config.getBgActionDraw(), canvas); + onDrawLabel(key, canvas); + + } else if (code == KeyBoard.KEYCODE_SHIFT) { + onDrawKeyBackground(key, config.getBgActionDraw(), canvas); + DrawIcon.INSTANCE.onDrawKeyIcon(key, getShiftDraw(), canvas, this); + + } else if (code == KeyBoard.KEYCODE_SHIFT_123) { + + onDrawKeyBackground(key, config.getBgActionDraw(), canvas); +// DrawIcon.INSTANCE.onDrawKeyIcon(key, getShiftDraw(), canvas, this); + onDrawLabel(key, canvas); + + } else if (code == KeyBoard.KEYCODE_SHIFT_SYMBOL) { + + onDrawKeyBackground(key, config.getBgActionDraw(), canvas); +// DrawIcon.INSTANCE.onDrawKeyIcon(key, getShiftDraw(), canvas, this); + onDrawLabel(key, canvas); + + } else if (code == KeyBoard.KEYCODE_DONE) { + onDrawKeyBackground(key, config.getBgActionDraw(), canvas); + onDrawLabel(key, canvas); + } else if (code == KeyBoard.KEYCODE_DELETE) { + + onDrawKeyBackground(key, config.getBgActionDraw(), canvas); + DrawIcon.INSTANCE.onDrawKeyIcon(key, config.getIconDel(), canvas, this); + onDrawLabel(key, canvas); + } else { + mPaint.setColor(config.getKeyNoramlcolor()); + onDrawKeyBackground(key, config.getBgNormalDraw(), canvas); + onDrawLabel(key, canvas); + } + } + + } + + public void updateConfigView(Context con) { + config.updateConfig(con); + setBackground(config.getBG()); + invalidateAllKeys(); + + } + + private Drawable getShiftDraw() { + if (shift_status == 0) { + return config.getIconShift(); + } else if (shift_status == 1) { + return config.getIconShiftLock(); + } else { + return config.getIconShiftLock(); + } + } + + + private void onDrawKeyBackground(KeyBoard.Key myKey, + Drawable keyBG, + Canvas canvas) { + if (keyBG != null) { + Rect rect = new Rect(myKey.x + getPaddingLeft(), myKey.y + getPaddingTop(), myKey.width + myKey.x + getPaddingLeft(), myKey.height + myKey.y + getPaddingTop()); + keyBG.setBounds(rect); + keyBG.setState(myKey.getCurrentDrawableState()); + keyBG.draw(canvas); + } + } + + private void onDrawLabel( + KeyBoard.Key myKey, + Canvas canvas) { + boolean b = myKey.label == null || myKey.label == ""; + if (!b) { + float y1 = myKey.y + getPaddingRight() + (myKey.height/ 2f) + ((mPaint.getTextSize() - mPaint.descent()) / 2f); + float x1 = myKey.x + getPaddingLeft() + ((myKey.width / 2f)); + canvas.drawText(myKey.label.toString(), x1, y1, mPaint); + + } + } + +} diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/MyService.java b/app/src/main/java/com/keyboardskinning/theme/tools/MyService.java new file mode 100644 index 0000000..465c045 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/MyService.java @@ -0,0 +1,183 @@ +package com.keyboardskinning.theme.tools; + +import android.annotation.SuppressLint; +import android.inputmethodservice.InputMethodService; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.EditorInfo; + +import com.keyboardskinning.theme.R; + +import java.util.List; + +public class MyService extends InputMethodService implements KeyBoardView.OnKeyboardActionListener { + private long last_click = -1L; + + + private int[] ViewXmls = new int[3]; + + private boolean is_double = false; + + private MyKeyBoardView myKeyBoardView; + + @SuppressLint("MissingInflatedId") + @Override + public View onCreateInputView() { + + ViewXmls[0] = R.xml.view_1; + ViewXmls[1] = R.xml.view_2; + ViewXmls[2] = R.xml.view_3; + + View inputView = LayoutInflater.from(this).inflate(R.layout.customer_input_view, null, false); + + myKeyBoardView = (MyKeyBoardView) inputView.findViewById(R.id.my_keyboard_view); + myKeyBoardView.setPreviewEnabled(false); + myKeyBoardView.setKeyboard(new KeyBoard(this, ViewXmls[0])); + myKeyBoardView.setOnKeyboardActionListener(this); + return inputView; + } + + @Override + public void onWindowShown() { + super.onWindowShown(); + myKeyBoardView.updateConfigView(this); + } + + @Override + public void onPress(int primaryCode) { + + if (primaryCode == KeyBoard.KEYCODE_SHIFT) { + if (300 > SystemClock.elapsedRealtime() - last_click) { + is_double = true; + } + last_click = SystemClock.elapsedRealtime(); + } + } + + @Override + public void onRelease(int primaryCode) { + + } + private void switchCapital(Boolean toCapital, KeyBoard keyboard) { + List keys = keyboard.getKeys(); + for(int h = 0;h parseJson(Context context) { + StringBuilder sb = new StringBuilder(); + try { + InputStream open = context.getAssets().open("selecion.json"); + + BufferedReader br = new BufferedReader(new InputStreamReader(open)); + String next = ""; + while (null != (next = br.readLine())) { + sb.append(next); + } + String trim = sb.toString().trim(); + return parseJsonString(trim); + + } catch (IOException ioException) { + return null; + } + + } + + + public static String parseString(File file) { + StringBuilder sb = new StringBuilder(); + try { + + + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + String next = ""; + while (null != (next = br.readLine())) { + sb.append(next); + } + String trim = sb.toString().trim(); + return trim; + + } catch (IOException ioException) { + + return null; + } + + } + + public static void saveSkin(String resDirPath) { + editorShare.putString(sharedpreferenceKey_skin, resDirPath); + editorShare.apply(); + } + + public static String getSkin() { + return share.getString(sharedpreferenceKey_skin, ""); + } + + public static String getSkinPathByName(String name) { + return share.getString(name, ""); + } + + public static void saveSkinByName(String name, String resDirPath) { + editorShare.putString(name, resDirPath); + editorShare.apply(); + } + + + public static int dpToPx(int dp, Context context) { + float density = context.getResources().getDisplayMetrics().density; + return (int) (dp * density); + } + + private static List parseJsonString(String jsonString) { + List myDataArrayList = new ArrayList<>(); + try { + JSONObject string1 = new JSONObject(jsonString); + JSONArray list = string1.getJSONArray("list"); + for (int g = 0; g < list.length(); g++) { + MyData myData = new MyData(); + JSONObject item = list.getJSONObject(g); + String preview = item.getString("preview"); + String thumb = item.getString("thumb"); + String title = item.getString("title"); + String zipUrl = item.getString("zipUrl"); + myData.setPreview(String.valueOf(preview)); + myData.setThumbnail(thumb); + myData.setUrl(zipUrl); + myData.setResourceName(title); + myDataArrayList.add(myData); + } + + return myDataArrayList; + } catch (JSONException jsonException) { + + return null; + } + } + + public static void goSetStep2() { + methodManager.showInputMethodPicker(); + } + + public static boolean isStep2() { + String string = Settings.Secure.getString(KeyboardSkin.app.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + if (string.startsWith(KeyboardSkin.app.getPackageName())) { + return true; + } else { + return false; + } + } + + public static void goSetStep1(Context context) { + Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); + context.startActivity(intent); + } + + public static boolean isStep1() { + for (InputMethodInfo inputMethodInfo : methodManager.getEnabledInputMethodList()) { + if (inputMethodInfo.getId().startsWith(KeyboardSkin.app.getPackageName())) { + return true; + } + } + return false; + } + + public static void downloadZip(String url, Context context, DownloadCallback callback) { + + Glide.with(context) + .asFile() + .load(url) + .addListener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + callback.onDownloadCall(false, null); + return false; + } + + @Override + public boolean onResourceReady(@NonNull File resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + callback.onDownloadCall(true, resource); + return false; + } + } + ).preload(); + + + } + + public static void unzipFile( + String unzipPath, + File resource, + UnzipCallback unzipCallback + ) { + if (!resource.exists()) { + unzipCallback.onUnzipCall(false, ""); + return; + } + String itemFilePath = ""; + + RandomAccessFileOutStream outStream = null; + IInArchive openInArchive; + RandomAccessFileInStream randomAccessFileInStream; + try { + randomAccessFileInStream = new RandomAccessFileInStream(new RandomAccessFile(resource, "r")); + openInArchive = SevenZip.openInArchive( + ArchiveFormat.SEVEN_ZIP, + randomAccessFileInStream + ); + + ISimpleInArchiveItem[] archiveItems = openInArchive.getSimpleInterface().getArchiveItems(); + for (int d = 0; d < archiveItems.length; d++) { + ISimpleInArchiveItem simple = archiveItems[d]; + File file = new File(unzipPath, simple.getPath()); + if (!simple.isFolder()) { + outStream = new RandomAccessFileOutStream(new RandomAccessFile(file, "rw")); + simple.extractSlow(outStream); + itemFilePath = file.getPath(); + + } else { + boolean mkdirs = file.mkdirs(); + } + } + randomAccessFileInStream.close(); + openInArchive.close(); + if (outStream != null) { + outStream.close(); + } + int res = itemFilePath.indexOf("res"); + String substring = itemFilePath.substring(0, res + 3); + unzipCallback.onUnzipCall(true, substring); + + } catch (FileNotFoundException | SevenZipException e) { + unzipCallback.onUnzipCall(false, ""); + + } catch (IOException e) { + unzipCallback.onUnzipCall(false, ""); + throw new RuntimeException(e); + } + + + } + + +} diff --git a/app/src/main/java/com/keyboardskinning/theme/tools/SpaceItem.java b/app/src/main/java/com/keyboardskinning/theme/tools/SpaceItem.java new file mode 100644 index 0000000..63845b9 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/tools/SpaceItem.java @@ -0,0 +1,56 @@ +package com.keyboardskinning.theme.tools; + +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class SpaceItem extends RecyclerView.ItemDecoration { + private int ex_space = 0; + private int v_space = 0; + private int h_space = 0; + + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + + int position = parent.getChildAdapterPosition(view); + int spanSize = 1; + int spanIndex = 0; + int spanCount = 1; + + RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); + + if (layoutManager instanceof GridLayoutManager) { + GridLayoutManager layoutManager1 = (GridLayoutManager) layoutManager; + GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams(); + spanCount = layoutManager1.getSpanCount(); + spanSize = layoutManager1.getSpanSizeLookup().getSpanSize(position); + spanIndex = layoutParams.getSpanIndex(); + + } + + + if (spanSize == spanCount) { + outRect.left = v_space + ex_space; + outRect.right = v_space + ex_space; + outRect.bottom = h_space; + } else { + int itemAllSpacing = (v_space * (spanCount + 1) + ex_space * 2) / spanCount; + int left = v_space * (spanIndex + 1) - itemAllSpacing * spanIndex + ex_space; + int right = itemAllSpacing - left; + outRect.left = left; + outRect.right = right; + outRect.bottom = h_space; + } + + } + + public SpaceItem(int v_space, int h_space, int ex_space) { + this.ex_space = ex_space; + this.h_space = h_space; + this.v_space = v_space; + + } +} diff --git a/app/src/main/java/com/keyboardskinning/theme/ui/ActivityHome.java b/app/src/main/java/com/keyboardskinning/theme/ui/ActivityHome.java new file mode 100644 index 0000000..8844412 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/ui/ActivityHome.java @@ -0,0 +1,117 @@ +package com.keyboardskinning.theme.ui; + +import androidx.appcompat.app.AppCompatActivity; + +import android.content.Intent; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.util.Log; + +import com.applovin.mediation.MaxAd; +import com.applovin.mediation.MaxError; +import com.applovin.mediation.ads.MaxInterstitialAd; +import com.applovin.sdk.AppLovinSdk; +import com.applovin.sdk.AppLovinSdkConfiguration; +import com.keyboardskinning.theme.R; +import com.keyboardskinning.theme.mylistener.AdCallback; +import com.keyboardskinning.theme.tools.Admanager; + +import java.util.List; + +public class ActivityHome extends AppCompatActivity implements AdCallback { + + private boolean needShow = true; + + private List maxInterstitialAds; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initSDK(); + setContentView(R.layout.activity_home); + } + + private void initSDK() { + AppLovinSdk.getInstance(this).setMediationProvider("max"); + AppLovinSdk.getInstance(this).initializeSdk(new AppLovinSdk.SdkInitializationListener() { + @Override + public void onSdkInitialized(AppLovinSdkConfiguration appLovinSdkConfiguration) { + maxInterstitialAds = Admanager.initMyAd(ActivityHome.this); + Log.d("-------","--------init"); + go(); + } + }); + } + + private void go() { + CountDownTimer countDownTimer = new CountDownTimer(13000, 500) { + @Override + public void onTick(long millisUntilFinished) { + if (needShow) { + MaxInterstitialAd cacheAd = Admanager.show(maxInterstitialAds); + if (cacheAd != null) { + Log.d("-------","--------showAd0"); + showAd(cacheAd); + } + } + + } + + @Override + public void onFinish() { + if (needShow) { + MaxInterstitialAd cacheAd = Admanager.show(maxInterstitialAds); + if (cacheAd != null) { + Log.d("-------","--------showAd1"); + showAd(cacheAd); + } else { + Log.d("-------","--------enterMain0"); + enterMain(); + } + } + + } + }; + countDownTimer.start(); + } + + + private void showAd(MaxInterstitialAd cacheAd) { + needShow = false; + Admanager.setAdShowCall(cacheAd, ActivityHome.this); + cacheAd.showAd(); + } + + @Override + public void onShowFail(MaxAd ad) { + Log.d("-------","--------onShowFail"); + enterMain(); + } + + @Override + public void onShowSuccess(MaxAd ad) { + + } + + @Override + public void onHidden() { + Log.d("-------","--------onHidden"); + enterMain(); + } + + @Override + public void onLoadFail(String string, MaxError maxError) { + + } + + @Override + public void onLoadSuccess(MaxAd ad) { + + } + + private void enterMain() { + Intent intent = new Intent(ActivityHome.this, MainActivity.class); + startActivity(intent); + finish(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyboardskinning/theme/ui/MainActivity.java b/app/src/main/java/com/keyboardskinning/theme/ui/MainActivity.java new file mode 100644 index 0000000..0ab8c4c --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/ui/MainActivity.java @@ -0,0 +1,33 @@ +package com.keyboardskinning.theme.ui; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.os.Bundle; + +import com.keyboardskinning.theme.KeyboardSkin; +import com.keyboardskinning.theme.R; +import com.keyboardskinning.theme.mylistadapter.ThumbAdapter; +import com.keyboardskinning.theme.tools.MyTools; +import com.keyboardskinning.theme.tools.SpaceItem; + +public class MainActivity extends AppCompatActivity { + private RecyclerView recyclerView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + initView(); + + recyclerView.setAdapter(new ThumbAdapter(this, KeyboardSkin.getMyDataArrayList())); + recyclerView.setLayoutManager(new GridLayoutManager(this,2)); + recyclerView.addItemDecoration(new SpaceItem(MyTools.dpToPx(10,this),MyTools.dpToPx(10,this),MyTools.dpToPx(10,this))); + } + + private void initView(){ + recyclerView = findViewById(R.id.recycler_view_id); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/keyboardskinning/theme/ui/PreviewActivity.java b/app/src/main/java/com/keyboardskinning/theme/ui/PreviewActivity.java new file mode 100644 index 0000000..54cd5ad --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/ui/PreviewActivity.java @@ -0,0 +1,236 @@ +package com.keyboardskinning.theme.ui; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.applovin.mediation.MaxAd; +import com.applovin.mediation.MaxError; +import com.applovin.mediation.ads.MaxInterstitialAd; +import com.bumptech.glide.Glide; +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.keyboardskinning.theme.R; +import com.keyboardskinning.theme.mylistener.AdCallback; +import com.keyboardskinning.theme.mylistener.DownloadCallback; +import com.keyboardskinning.theme.mylistener.UnzipCallback; +import com.keyboardskinning.theme.tools.Admanager; +import com.keyboardskinning.theme.tools.MyTools; + +import java.io.File; +import java.util.List; + +public class PreviewActivity extends AppCompatActivity { + + + private String url; + private String name; + private String preview; + + private ImageView imageViewBack; + + private ImageView imageViewPreviwew; + private TextView textViewName; + private LinearLayout linearLayoutDownload; + public static String KEY_NAME = "key_name"; + + public static String KEY_URL = "key_url"; + + public static String KEY_PRE = "key_pre"; + + private String unzipPath; + private ProgressBar progressBarPreview; + + private ProgressBar progressBarDownload; + private List maxInterstitialAds; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + maxInterstitialAds = Admanager.initMyAd(PreviewActivity.this); + setContentView(R.layout.activity_preview); + + initView(); + progressBarPreview.setVisibility(View.VISIBLE); + onGetData(); + } + + + private void initView() { + imageViewBack = findViewById(R.id.image_view_back_id); + imageViewPreviwew = findViewById(R.id.image_view_preview_id); + textViewName = findViewById(R.id.text_view_name_id); + linearLayoutDownload = findViewById(R.id.linear_layout_download_id); + progressBarPreview = findViewById(R.id.progress_bar_preview_id); + progressBarDownload = findViewById(R.id.progress_bar_download_id); + } + + public void onGetData() { + + Intent intent = getIntent(); + name = intent.getStringExtra(KEY_NAME); + preview = intent.getStringExtra(KEY_PRE); + url = intent.getStringExtra(KEY_URL); + + textViewName.setText(name); + Glide.with(this).load(preview).addListener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + progressBarPreview.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + progressBarPreview.setVisibility(View.GONE); + return false; + } + }).into(imageViewPreviwew); + + File cacheDir = this.getCacheDir(); + unzipPath = cacheDir + "/" + name; + setListener(); + } + + + private void setListener() { + + linearLayoutDownload.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setAction(); + } + }); + imageViewBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d("-------","--------finish "); + showBackAd(); + finish(); + } + }); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + Log.d("-------","--------onBackPressed "); + showBackAd(); + } + + private void showBackAd(){ + MaxInterstitialAd cacheAd = Admanager.show(maxInterstitialAds); + if(cacheAd != null){ + cacheAd.showAd(); + } + } + + private void setAction() { + if (!MyTools.isStep1() || !MyTools.isStep2()) { + Toast.makeText(this, getString(R.string.text_hint), Toast.LENGTH_SHORT).show(); + Intent intent = new Intent(this, SettingActivity.class); + startActivity(intent); + return; + } + + MaxInterstitialAd cacheAd = Admanager.show(maxInterstitialAds); + if (cacheAd != null) { + Admanager.setAdShowCall(cacheAd, new AdCallback() { + @Override + public void onShowFail(MaxAd ad) { + goApply(); + } + + @Override + public void onShowSuccess(MaxAd ad) { + + } + + @Override + public void onHidden() { + reLoadAd(); + goApply(); + } + + @Override + public void onLoadFail(String string, MaxError maxError) { + + } + + @Override + public void onLoadSuccess(MaxAd ad) { + + } + }); + cacheAd.showAd(); + } else { + goApply(); + } + } + + + private void reLoadAd() { + for (int i = 0; i < maxInterstitialAds.size(); i++) { + MaxInterstitialAd maxInterstitialAd = maxInterstitialAds.get(i); + if (!maxInterstitialAd.isReady()) { + Log.d("-------","--------reLoadAd "+maxInterstitialAd.getAdUnitId()); + maxInterstitialAd.loadAd(); + } + } + } + + private void goApply() { + progressBarDownload.setVisibility(View.VISIBLE); + String skinPathByName = MyTools.getSkinPathByName(name); + if (!skinPathByName.isEmpty()) { + setCurrentKeyboardSkin(skinPathByName); + progressBarDownload.setVisibility(View.GONE); + Toast.makeText(PreviewActivity.this, getString(R.string.set_successful), Toast.LENGTH_SHORT).show(); + finish(); + return; + } + MyTools.downloadZip(url, this, new DownloadCallback() { + @Override + public void onDownloadCall(boolean successful, File resource) { + if (successful) { + MyTools.unzipFile(unzipPath, resource, new UnzipCallback() { + + @Override + public void onUnzipCall(boolean successful, String resDirPath) { + progressBarDownload.setVisibility(View.GONE); + if (successful) { + MyTools.saveSkinByName(name, resDirPath); + setCurrentKeyboardSkin(resDirPath); + Toast.makeText(PreviewActivity.this, getString(R.string.set_successful), Toast.LENGTH_SHORT).show(); + finish(); + } + + } + }); + } else { + progressBarDownload.setVisibility(View.GONE); + } + + } + }); + } + + private void setCurrentKeyboardSkin(String resDirPath) { + MyTools.saveSkin(resDirPath); + } + + +} diff --git a/app/src/main/java/com/keyboardskinning/theme/ui/SettingActivity.java b/app/src/main/java/com/keyboardskinning/theme/ui/SettingActivity.java new file mode 100644 index 0000000..910fa57 --- /dev/null +++ b/app/src/main/java/com/keyboardskinning/theme/ui/SettingActivity.java @@ -0,0 +1,98 @@ +package com.keyboardskinning.theme.ui; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.keyboardskinning.theme.R; +import com.keyboardskinning.theme.tools.MyTools; + + +public class SettingActivity extends AppCompatActivity { + + private BroadcastReceiver broadcastReceiver; + + private ImageView imageViewBack; + + private TextView textViewStep1; + private TextView textViewStep2; + + + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_setting); + findView(); + broadcastReceiver = new inputReceive(); + registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED)); + refreshStatus(); + setListener(); + } + + private void findView(){ + imageViewBack = findViewById(R.id.image_view_back_id); + textViewStep1 = findViewById(R.id.textview_step1); + textViewStep2 = findViewById(R.id.textview_step2); + } + + private void setListener(){ + imageViewBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + textViewStep1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MyTools.goSetStep1(SettingActivity.this); + + } + }); + textViewStep2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MyTools.goSetStep2(); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(broadcastReceiver); + } + + private void refreshStatus() { + textViewStep2.setSelected(MyTools.isStep2()); + textViewStep1.setSelected(MyTools.isStep1()); + + } + + @Override + protected void onResume() { + super.onResume(); + refreshStatus(); + } + + class inputReceive extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + refreshStatus(); + } + } + + + +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_download_background.xml b/app/src/main/res/drawable/btn_download_background.xml new file mode 100644 index 0000000..70988e1 --- /dev/null +++ b/app/src/main/res/drawable/btn_download_background.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/de_keybg.9.png b/app/src/main/res/drawable/de_keybg.9.png new file mode 100644 index 0000000..fcf5166 Binary files /dev/null and b/app/src/main/res/drawable/de_keybg.9.png differ diff --git a/app/src/main/res/drawable/de_keybg_press.9.png b/app/src/main/res/drawable/de_keybg_press.9.png new file mode 100644 index 0000000..b3f2727 Binary files /dev/null and b/app/src/main/res/drawable/de_keybg_press.9.png differ diff --git a/app/src/main/res/drawable/de_keyboard_bg.xml b/app/src/main/res/drawable/de_keyboard_bg.xml new file mode 100644 index 0000000..7e0c160 --- /dev/null +++ b/app/src/main/res/drawable/de_keyboard_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/del_icon.xml b/app/src/main/res/drawable/del_icon.xml new file mode 100644 index 0000000..1c653c8 --- /dev/null +++ b/app/src/main/res/drawable/del_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ico_shift_lit.xml b/app/src/main/res/drawable/ico_shift_lit.xml new file mode 100644 index 0000000..858c5ef --- /dev/null +++ b/app/src/main/res/drawable/ico_shift_lit.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_back.xml b/app/src/main/res/drawable/icon_back.xml new file mode 100644 index 0000000..6f0920b --- /dev/null +++ b/app/src/main/res/drawable/icon_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_download.xml b/app/src/main/res/drawable/icon_download.xml new file mode 100644 index 0000000..ec67a29 --- /dev/null +++ b/app/src/main/res/drawable/icon_download.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/step_background.xml b/app/src/main/res/drawable/step_background.xml new file mode 100644 index 0000000..70988e1 --- /dev/null +++ b/app/src/main/res/drawable/step_background.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/step_background_selected.xml b/app/src/main/res/drawable/step_background_selected.xml new file mode 100644 index 0000000..40a6b83 --- /dev/null +++ b/app/src/main/res/drawable/step_background_selected.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/step_selector.xml b/app/src/main/res/drawable/step_selector.xml new file mode 100644 index 0000000..49be956 --- /dev/null +++ b/app/src/main/res/drawable/step_selector.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000..8aad718 --- /dev/null +++ b/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5f8b6a4 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_preview.xml b/app/src/main/res/layout/activity_preview.xml new file mode 100644 index 0000000..0e46d6f --- /dev/null +++ b/app/src/main/res/layout/activity_preview.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml new file mode 100644 index 0000000..116ff31 --- /dev/null +++ b/app/src/main/res/layout/activity_setting.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/customer_input_view.xml b/app/src/main/res/layout/customer_input_view.xml new file mode 100644 index 0000000..760d763 --- /dev/null +++ b/app/src/main/res/layout/customer_input_view.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_thumb.xml b/app/src/main/res/layout/list_thumb.xml new file mode 100644 index 0000000..be9016e --- /dev/null +++ b/app/src/main/res/layout/list_thumb.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon2.png b/app/src/main/res/mipmap-xxxhdpi/icon2.png new file mode 100644 index 0000000..52f6677 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon2.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..eefcbef --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,14 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #d9c7ff + #faa052 + #0e0701 + #DCDCDC + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..084a05f --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + KeyboardSkinning + Step One + Step Two + For normal use, please enter the setting to complete the setting steps. + Apply the keyboard skin successfully + Settings + \ No newline at end of file diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml new file mode 100644 index 0000000..dbbad75 --- /dev/null +++ b/app/src/main/res/values/style.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..7375b41 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/im.xml b/app/src/main/res/xml/im.xml new file mode 100644 index 0000000..aeaa192 --- /dev/null +++ b/app/src/main/res/xml/im.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/view_1.xml b/app/src/main/res/xml/view_1.xml new file mode 100644 index 0000000..42868c9 --- /dev/null +++ b/app/src/main/res/xml/view_1.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/view_2.xml b/app/src/main/res/xml/view_2.xml new file mode 100644 index 0000000..e2484e3 --- /dev/null +++ b/app/src/main/res/xml/view_2.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/view_3.xml b/app/src/main/res/xml/view_3.xml new file mode 100644 index 0000000..86d6441 --- /dev/null +++ b/app/src/main/res/xml/view_3.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/keyboardskinning/theme/ExampleUnitTest.kt b/app/src/test/java/com/keyboardskinning/theme/ExampleUnitTest.kt new file mode 100644 index 0000000..f6250d4 --- /dev/null +++ b/app/src/test/java/com/keyboardskinning/theme/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.keyboardskinning.theme + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit theme, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/app/testKeyboardSkinning.jks b/app/testKeyboardSkinning.jks new file mode 100644 index 0000000..b3347d0 Binary files /dev/null and b/app/testKeyboardSkinning.jks differ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..1b56b3d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,12 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version "8.1.3" apply false + id("org.jetbrains.kotlin.android") version "1.8.10" apply false + id("com.google.gms.google-services") version "4.3.15" apply false + id ("com.google.firebase.crashlytics") version "2.9.2" apply false +} +buildscript{ + dependencies{ + classpath("com.applovin.quality:AppLovinQualityServiceGradlePlugin:+") + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..3c5031e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..98d01bd --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 11 11:05:07 CST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..a972e42 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,22 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven{url = uri("https://artifacts.applovin.com/android")} + + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven("https://jitpack.io") + maven ("https://artifact.bytedance.com/repository/pangle") + } +} + +rootProject.name = "KeyboardSkinning" +include(":app") + \ No newline at end of file