接入tradplus广告

This commit is contained in:
yuqian 2026-01-06 11:35:18 +08:00
commit dbaff1b05e
138 changed files with 31871 additions and 0 deletions

18
.gitignore vendored Normal file
View File

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

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

BIN
app/BreezeBoard Normal file

Binary file not shown.

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

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

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

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "813677613184",
"project_id": "breezeboard-d7ee6",
"storage_bucket": "breezeboard-d7ee6.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:813677613184:android:e5d0616a75c7df5df59665",
"android_client_info": {
"package_name": "com.app.input.breeze.board"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBO3XszZGdKKi0ICF_Kwt6lc2qdF3s0ayY"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

Binary file not shown.

Binary file not shown.

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

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

View File

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

View File

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

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.AD_ID" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/net"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:theme="@style/MyKeyBoard"
tools:replace="android:networkSecurityConfig"
tools:targetApi="31">
<activity
android:name=".uiactivity.ThemeListActivity"
android:exported="false" />
<activity
android:name=".uiactivity.SplashActivity"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".uiactivity.MainActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.ThemeDetailActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.PreviewActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.CategoryScreen"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.FavoriteThemeActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".uiactivity.SettingsScreen"
android:exported="false"
android:screenOrientation="portrait" />
<service
android:name=".keyboardhelper.BreezeInputMethodService"
android:exported="true"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/keyborad_xml" />
</service>
</application>
</manifest>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,27 @@
package com.app.input.breeze.board.bean;
import java.util.List;
public class CategoryWrapper {
private String parentName;
private List<KeyboardTheme> keyboardList;
public String getParentName() {
return parentName;
}
public List<KeyboardTheme> getKeyboardList() {
return keyboardList;
}
public void setParentName(String name) {
this.parentName = name;
}
public void setKeyboardList(List<KeyboardTheme> keyboardList) {
this.keyboardList = keyboardList;
}
}

View File

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

View File

@ -0,0 +1,6 @@
package com.app.input.breeze.board.callback
interface ItemClickListener {
fun OnItemClickListener( )
}

View File

@ -0,0 +1,8 @@
package com.app.input.breeze.board.callback
import java.io.File
interface KeyboardApplyCallback {
fun OnApplySkinListener(fileList: List<File?>?)
}

View File

@ -0,0 +1,8 @@
package com.app.input.breeze.board.callback
import com.app.input.breeze.board.bean.KeyboardTheme
interface RemoveFavoriteCallback {
fun OnRemoveLike(data: KeyboardTheme)
}

View File

@ -0,0 +1,6 @@
package com.app.input.breeze.board.callback
interface ViewAllClickListener {
fun OnClickSeeAll(name: String)
}

View File

@ -0,0 +1,30 @@
package com.app.input.breeze.board.database
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.app.input.breeze.board.App
import com.app.input.breeze.board.bean.KeyboardTheme
@Database(
entities = [KeyboardTheme::class],
version = App.Companion.DB_VERSION,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun favoriteThemeDao(): FavoriteThemeDao
companion object {
val instance: AppDatabase by lazy {
Room.databaseBuilder(
App.Companion.appInstance, AppDatabase::class.java,
App.Companion.DB_NAME
).build()
}
}
}

View File

@ -0,0 +1,34 @@
package com.app.input.breeze.board.database
import com.app.input.breeze.board.bean.KeyboardTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object FavoriteRepository {
suspend fun addLike(data: KeyboardTheme) {
withContext(Dispatchers.IO) {
AppDatabase.instance.favoriteThemeDao().insertData(data)
}
}
suspend fun removeLike(data: KeyboardTheme) {
withContext(Dispatchers.IO) {
val queryIsLike = AppDatabase.instance.favoriteThemeDao().queryIsLike(data.titleName)
if (queryIsLike != null) {
AppDatabase.instance.favoriteThemeDao().delete(queryIsLike)
}
}
}
suspend fun getIsLike(name: String, action: (isLike: Boolean) -> Unit) {
withContext(Dispatchers.IO) {
val query = AppDatabase.instance.favoriteThemeDao().queryIsLike(name)
withContext(Dispatchers.Main) {
action.invoke(query != null)
}
}
}
}

View File

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

View File

@ -0,0 +1,285 @@
package com.app.input.breeze.board.keyboardhelper;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.media.MediaPlayer;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.ImageView;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.integration.webp.decoder.WebpDrawable;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.app.input.breeze.board.App;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.utils.KeyboardConstants;
import com.app.input.breeze.board.utils.AppUtils;
import com.app.input.breeze.board.utils.ThemePreferences;
import com.app.input.breeze.board.sourcecode.Keyboard;
import com.app.input.breeze.board.sourcecode.KeyboardView;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
public class BreezeInputMethodService extends InputMethodService implements KeyboardView.OnKeyboardActionListener {
private BreezeKeyboardView keyboardView;
private Keyboard mKeyBoard;
private View parentView;
private ImageView imBG;
private VideoView videoView;
private int a = R.xml.xml_one;
private int b= R.xml.xml_two;
private int c = R.xml.xml_three;
private int curImeAction = EditorInfo.IME_ACTION_UNSPECIFIED;
@SuppressLint("InflateParams")
@Override
public View onCreateInputView() {
parentView = getLayoutInflater().inflate(R.layout.keyboard_default_view, null);
findView();
return parentView;
}
private void findView() {
imBG = parentView.findViewById(R.id.gif_bg);
videoView = parentView.findViewById(R.id.video_view);
mKeyBoard = new Keyboard(this, a);
keyboardView = parentView.findViewById(R.id.custom_input_view);
keyboardView.setEnabled(true);
keyboardView.setPreviewEnabled(false);
keyboardView.setKeyboard(mKeyBoard);
keyboardView.setOnKeyboardActionListener(this);
}
@Override
public void onWindowHidden() {
super.onWindowHidden();
if(videoView.isPlaying()){
videoView.pause();
}
}
@Override
public void onWindowShown() {
super.onWindowShown();
EditorInfo currentInputEditorInfo = getCurrentInputEditorInfo();
curImeAction = AppUtils.INSTANCE.getTextForImeAction(currentInputEditorInfo.imeOptions);
String skinPath = ThemePreferences.INSTANCE.getSkinPath();
if(skinPath == null || skinPath.isEmpty()){
Log.d(App.TAG, "---------skinPath= bull");
keyboardView.updateUi(curImeAction);
}else {
Log.d(App.TAG, "---------skinPath= 1111");
KeyboardHelper.INSTANCE.readBgOrVideo(this, new Function2<String, Drawable, Unit>() {
@Override
public Unit invoke(String s, Drawable drawable) {
Log.d(App.TAG, "---------s= "+s+"---------drawable="+drawable);
if (s != null) {
keyboardView.setBackground(null);
if(s.endsWith(".gif")){
imBG.setVisibility(View.VISIBLE);
videoView.setVisibility(View.GONE);
Glide.with(BreezeInputMethodService.this)
.load(s)
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
if (resource instanceof WebpDrawable) {
((WebpDrawable) resource).setLoopCount(WebpDrawable.LOOP_FOREVER);
}
return false;
}
}).into(imBG) ;
}else if(s.endsWith(".mp4")){
imBG.setVisibility(View.GONE);
videoView.setVisibility(View.VISIBLE);
videoView.setVideoPath(s);
videoView.start();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
}
});
}
} else {
keyboardView.setBackground(drawable);
}
keyboardView.updateUi(curImeAction);
return null;
}
});
}
}
@Override
public void onDestroy() {
super.onDestroy();
videoView.stopPlayback();
}
@Override
public void onPress(int primaryCode) {
}
@Override
public void onRelease(int primaryCode) {
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
InputConnection curInputConnect = getCurrentInputConnection();
switch (primaryCode) {
case KeyboardConstants.KEY_CODE_DELETE:
curInputConnect.deleteSurroundingText(1, 0);
break;
case KeyboardConstants.KEY_CODE_SHIFT:
switchShift();
break;
case KeyboardConstants.KEY_CODE_NUMBER_SHIFT:
case KeyboardConstants.KEY_CODE_SYMBOL_SHIFT:
switchMoreOrNumber();
break;
case KeyboardConstants.KEY_CODE_CHANGE_NUMBER:
case KeyboardConstants.KEY_CODE_BACK:
switchNormalOrNumber();
break;
case KeyboardConstants.KEY_CODE_COMPLETE:
case KeyboardConstants.KEY_CODE_CANCEL:
curInputConnect.performEditorAction(curImeAction);
// curInputConnect.performEditorAction(EditorInfo.IME_ACTION_DONE);
break;
default:
String codeToChar = KeyboardHelper.INSTANCE.primaryCodeToChar(primaryCode);
curInputConnect.commitText(codeToChar, 1);
if (keyboardView.isLowerCase() == 1) {
//自动转小写
keyboardView.setLowerCase(0);
KeyboardHelper.INSTANCE.keyToLowerCase(mKeyBoard);
keyboardView.setKeyboard(mKeyBoard);
}
break;
}
}
private void switchMoreOrNumber() {
int mode = keyboardView.getMode();
switch (mode) {
case 1:
mKeyBoard = new Keyboard(this, c);
keyboardView.setMode(2);
keyboardView.setKeyboard(mKeyBoard);
break;
case 2:
mKeyBoard = new Keyboard(this, b);
keyboardView.setMode(1);
keyboardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchNormalOrNumber() {
int mode = keyboardView.getMode();
switch (mode) {
case 0:
mKeyBoard = new Keyboard(this, b);
keyboardView.setMode(1);
keyboardView.setKeyboard(mKeyBoard);
break;
case 1:
case 2:
mKeyBoard = new Keyboard(this, a);
keyboardView.setMode(0);
keyboardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchShift() {
int lowerCase = keyboardView.isLowerCase();
switch (lowerCase) {
case 0:
//当前小写转大写
keyboardView.setLowerCase(1);
KeyboardHelper.INSTANCE.keyToUpper(mKeyBoard);
keyboardView.setKeyboard(mKeyBoard);
break;
case 1:
//当前大写转锁定大写
keyboardView.setLowerCase(2);
break;
case 2:
//当前锁定大写转小写
keyboardView.setLowerCase(0);
KeyboardHelper.INSTANCE.keyToLowerCase(mKeyBoard);
keyboardView.setKeyboard(mKeyBoard);
break;
}
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeDown() {
}
@Override
public void swipeUp() {
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,35 @@
package com.app.input.breeze.board.keyboardhelper;
import java.util.ArrayList;
import java.util.List;
public class KeyboardLayout {
private String name;
private List<KeyboardKey> KeyboardKeys = new ArrayList<>();
public KeyboardLayout(String name) {
this.name = name;
}
// Getters and Setters
public String getName() {
return name;
}
public List<KeyboardKey> getKeys() {
return KeyboardKeys;
}
public void addKey(KeyboardKey KeyboardKey) {
this.KeyboardKeys.add(KeyboardKey);
}
public KeyboardKey getLastKey() {
return KeyboardKeys.isEmpty() ? null : KeyboardKeys.get(KeyboardKeys.size() - 1);
}
}

View File

@ -0,0 +1,90 @@
package com.app.input.breeze.board.listutils;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.breeze.board.bean.CategoryWrapper;
import com.app.input.breeze.board.databinding.CategoryCardItemBinding;
import com.bumptech.glide.Glide;
import java.util.List;
public class CategoryCardAdapter extends RecyclerView.Adapter<CategoryCardAdapter.CategoryCardViewHolder> {
private Context mContext;
private List<CategoryWrapper> mList;
private OnCategoryCardClick mOnClick;
public interface OnCategoryCardClick {
void onCategoryClick(String categoryName);
}
public CategoryCardAdapter(Context context, List<CategoryWrapper> list, OnCategoryCardClick onClick) {
mContext = context;
mList = list;
mOnClick = onClick;
}
@NonNull
@Override
public CategoryCardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
CategoryCardItemBinding binding = CategoryCardItemBinding.inflate(
LayoutInflater.from(parent.getContext()),
parent,
false
);
return new CategoryCardViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull CategoryCardViewHolder holder, int position) {
CategoryWrapper wrapper = mList.get(position);
holder.bind(wrapper);
}
@Override
public int getItemCount() {
return mList.size();
}
class CategoryCardViewHolder extends RecyclerView.ViewHolder {
private CategoryCardItemBinding binding;
public CategoryCardViewHolder(@NonNull CategoryCardItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(CategoryWrapper wrapper) {
String categoryName = wrapper.getParentName();
binding.categoryTitle.setText(categoryName);
// 如果有主题显示第一个主题的缩略图作为背景
if (wrapper.getKeyboardList() != null && !wrapper.getKeyboardList().isEmpty()) {
String thumbUrl = wrapper.getKeyboardList().get(0).getThumbUrl();
if (thumbUrl != null && !thumbUrl.isEmpty()) {
Glide.with(mContext)
.load(thumbUrl)
.centerCrop()
.into(binding.categoryImage);
}
}
binding.getRoot().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnClick != null) {
mOnClick.onCategoryClick(categoryName);
}
}
});
}
}
}

View File

@ -0,0 +1,74 @@
package com.app.input.breeze.board.listutils;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.breeze.board.bean.CategoryWrapper;
import com.app.input.breeze.board.callback.ViewAllClickListener;
import com.app.input.breeze.board.databinding.HomeAdapterItemBinding;
import com.app.input.breeze.board.utils.GridItemDecoration;
import java.util.ArrayList;
import java.util.List;
public class CategoryListAdapter extends RecyclerView.Adapter<CategoryListAdapter.MainViewHolder> {
private Context mContext;
private List<CategoryWrapper> mList = new ArrayList<>();
private ViewAllClickListener mCallBack;
public CategoryListAdapter(Context context, List<CategoryWrapper> list) {
mContext = context;
this.mList = list;
}
public void setClickAction(ViewAllClickListener callback) {
mCallBack = callback;
}
@NonNull
@Override
public MainViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
HomeAdapterItemBinding inflate = HomeAdapterItemBinding.inflate(LayoutInflater.from(parent.getContext()));
return new MainViewHolder(inflate);
}
@Override
public void onBindViewHolder(@NonNull MainViewHolder holder, int position) {
CategoryWrapper CategoryWrapper = mList.get(position);
String parentName = CategoryWrapper.getParentName();
holder.binding.className.setText(parentName);
GridItemDecoration GridItemDecoration = new GridItemDecoration(3, 3, 0);
ThemeGridAdapter ThemeGridAdapter = new ThemeGridAdapter(mContext, CategoryWrapper.getKeyboardList().subList(0, 6));
holder.binding.childRecycler.setLayoutManager(new GridLayoutManager(mContext, 3));
holder.binding.childRecycler.setAdapter(ThemeGridAdapter);
if (holder.binding.childRecycler.getItemDecorationCount() <= 0) {
holder.binding.childRecycler.addItemDecoration(GridItemDecoration);
}
holder.binding.tvAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCallBack.OnClickSeeAll(parentName);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class MainViewHolder extends RecyclerView.ViewHolder {
private HomeAdapterItemBinding binding;
public MainViewHolder(@NonNull HomeAdapterItemBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
}

View File

@ -0,0 +1,126 @@
package com.app.input.breeze.board.listutils;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.bean.KeyboardTheme;
import com.app.input.breeze.board.utils.AppUtils;
import com.app.input.breeze.board.callback.RemoveFavoriteCallback;
import com.app.input.breeze.board.uiactivity.ThemeDetailActivity;
import java.util.ArrayList;
import java.util.List;
public class FavoriteThemeAdapter extends RecyclerView.Adapter<FavoriteThemeAdapter.ForYouViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
private RemoveFavoriteCallback mCallBack;
public FavoriteThemeAdapter(Context context) {
mContext = context;
}
public void setForYouList(List<KeyboardTheme> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setRemoveLike(RemoveFavoriteCallback callback) {
mCallBack = callback;
}
@NonNull
@Override
public ForYouViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.favorite_theme_adapter_item, parent, false);
return new ForYouViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ForYouViewHolder holder, int position) {
KeyboardTheme KeyboardTheme = mList.get(position);
String thumbGif = KeyboardTheme.getThumbGif();
String thumb = KeyboardTheme.getThumbUrl();
if (!thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.itemImg);
} else {
Glide.with(mContext)
.load(thumb).error(R.drawable.place_holder)
.placeholder(R.drawable.place_holder).into(holder.itemImg);
}
holder.itemFavorite.setSelected(true);
holder.layoutFavorite.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.itemFavorite.setSelected(false);
int adapterPosition = holder.getAdapterPosition();
notifyItemRemoved(adapterPosition);
if (mCallBack != null) {
mCallBack.OnRemoveLike(KeyboardTheme);
}
}
});
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, KeyboardTheme);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, KeyboardTheme.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, KeyboardTheme.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, KeyboardTheme.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, KeyboardTheme.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ForYouViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
private FrameLayout layoutFavorite;
private ImageView itemImg, itemFavorite;
public ForYouViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
layoutFavorite = itemView.findViewById(R.id.layout_favorite);
itemImg = itemView.findViewById(R.id.im);
itemFavorite = itemView.findViewById(R.id.im_favorite);
}
}
}

View File

@ -0,0 +1,95 @@
package com.app.input.breeze.board.listutils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.app.input.breeze.board.R
import com.app.input.breeze.board.bean.KeyboardTheme
import com.app.input.breeze.board.databinding.FeaturedThemeItemBinding
import com.app.input.breeze.board.utils.AppUtils
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
class FeaturedThemeAdapter(
private val context: android.content.Context,
private val heroList: List<KeyboardTheme>,
private val onItemClick: (KeyboardTheme) -> Unit
) : RecyclerView.Adapter<FeaturedThemeAdapter.HeroViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeroViewHolder {
val binding = FeaturedThemeItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return HeroViewHolder(binding)
}
override fun onBindViewHolder(holder: HeroViewHolder, position: Int) {
val KeyboardTheme = heroList[position]
holder.bind(KeyboardTheme)
}
override fun getItemCount(): Int = heroList.size
inner class HeroViewHolder(
private val binding: FeaturedThemeItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(KeyboardTheme: KeyboardTheme) {
// 先设置占位图,确保立即显示
binding.heroImage.setImageResource(R.drawable.place_holder)
// 设置标题(已简化,移除副标题)
binding.heroTitle.text = KeyboardTheme.titleName
// 加载图片 - 优先使用预览图,如果没有则使用缩略图
val imgPath = KeyboardTheme.imgPath
val imgGif = KeyboardTheme.imgGif
val thumbGif = KeyboardTheme.thumbGif
val thumb = KeyboardTheme.thumbUrl
// 优先使用预览图imgPath/imgGif如果没有则使用缩略图thumbUrl/thumbGif
when {
imgGif != null && imgGif.isNotEmpty() -> {
AppUtils.loadWepJif(context, imgGif, binding.heroImage)
}
imgPath != null && imgPath.isNotEmpty() -> {
val options = RequestOptions()
.transform(RoundedCorners(AppUtils.dpToPx(20f)))
.centerCrop()
.placeholder(R.drawable.place_holder)
.error(R.drawable.place_holder)
Glide.with(context)
.load(imgPath)
.apply(options)
.into(binding.heroImage)
}
thumbGif != null && thumbGif.isNotEmpty() -> {
AppUtils.loadWepJif(context, thumbGif, binding.heroImage)
}
thumb != null && thumb.isNotEmpty() -> {
val options = RequestOptions()
.transform(RoundedCorners(AppUtils.dpToPx(20f)))
.centerCrop()
.placeholder(R.drawable.place_holder)
.error(R.drawable.place_holder)
Glide.with(context)
.load(thumb)
.apply(options)
.into(binding.heroImage)
}
// else 情况已经在开头设置了占位图
}
// 点击事件
binding.root.setOnClickListener {
onItemClick(KeyboardTheme)
}
}
}
}

View File

@ -0,0 +1,96 @@
package com.app.input.breeze.board.listutils
import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.app.input.breeze.board.R
import com.app.input.breeze.board.databinding.QuickCategoryItemBinding
class QuickCategoryAdapter(
private val context: android.content.Context,
private val categories: List<Pair<String, String>>, // (名称, emoji)
private val onCategoryClick: (String) -> Unit
) : RecyclerView.Adapter<QuickCategoryAdapter.CategoryViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val binding = QuickCategoryItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
val (name, emoji) = categories[position]
holder.bind(name, emoji)
}
override fun getItemCount(): Int = categories.size
inner class CategoryViewHolder(
private val binding: QuickCategoryItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(name: String, emoji: String) {
binding.categoryEmoji.text = emoji
binding.categoryName.text = name
// 根据分类名称设置不同的渐变背景(更精致的多色渐变)
val gradient = GradientDrawable().apply {
orientation = GradientDrawable.Orientation.TL_BR
cornerRadius = 22f * context.resources.displayMetrics.density
colors = getCategoryColors(name)
}
binding.categoryBg.background = gradient
binding.root.setOnClickListener {
onCategoryClick(name)
}
}
private fun getCategoryColors(categoryName: String): IntArray {
// 降低饱和度10-20%,使用同色系不同明度,更成熟优雅
return when (categoryName.lowercase()) {
"cute" -> intArrayOf(
0xFFFFB3D9.toInt(), // 降低饱和度:浅粉
0xFFFFC5E5.toInt(), // 中粉
0xFFFFD9F0.toInt() // 更浅粉
)
"neon" -> intArrayOf(
0xFFB794F6.toInt(), // 降低饱和度:柔紫
0xFFA78BEA.toInt(), // 中柔紫
0xFF9678E0.toInt() // 深柔紫
)
"cool" -> intArrayOf(
0xFF6BB6FF.toInt(), // 降低饱和度:柔蓝
0xFF5AA3F0.toInt(), // 中柔蓝
0xFF4A90E0.toInt() // 深柔蓝
)
"aesthetic" -> intArrayOf(
0xFFFFD699.toInt(), // 降低饱和度:柔橙
0xFFFFE0B3.toInt(), // 中柔橙
0xFFFFEACC.toInt() // 浅柔橙
)
"love" -> intArrayOf(
0xFFF5A3C7.toInt(), // 降低饱和度:柔玫红
0xFFFFB3D9.toInt(), // 中柔粉
0xFFFFC5E5.toInt() // 浅柔粉
)
"gravity" -> intArrayOf(
0xFF9D8FEB.toInt(), // 降低饱和度:柔紫蓝
0xFF8B7DD8.toInt(), // 中柔紫蓝
0xFF7A6BC5.toInt() // 深柔紫蓝
)
else -> intArrayOf(
0xFFB0BEC5.toInt(), // 降低饱和度:柔灰蓝
0xFFCFD8DC.toInt(), // 中柔灰蓝
0xFFE1E8ED.toInt() // 浅柔灰蓝
)
}
}
}
}

View File

@ -0,0 +1,127 @@
package com.app.input.breeze.board.listutils;
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.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.bean.KeyboardTheme;
import com.app.input.breeze.board.callback.ItemClickListener;
import com.app.input.breeze.board.uiactivity.ThemeDetailActivity;
import com.bumptech.glide.Glide;
import com.app.input.breeze.board.utils.AppUtils;
import java.util.ArrayList;
import java.util.List;
public class RecommendedThemeAdapter extends RecyclerView.Adapter<RecommendedThemeAdapter.ForYouViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
private ItemClickListener mCallBack;
public RecommendedThemeAdapter(Context context) {
mContext = context;
}
public void setForYouList(List<KeyboardTheme> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setClickAction(ItemClickListener callback) {
mCallBack = callback;
}
@NonNull
@Override
public ForYouViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recommended_theme_adapter_item, parent, false);
return new ForYouViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ForYouViewHolder holder, int position) {
KeyboardTheme KeyboardTheme = mList.get(position);
String thumbGif = KeyboardTheme.getThumbGif();
String thumb = KeyboardTheme.getThumbUrl();
if (!thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.itemImg);
} else {
Glide.with(mContext).load(thumb).into(holder.itemImg);
}
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, KeyboardTheme);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, KeyboardTheme.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, KeyboardTheme.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, KeyboardTheme.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, KeyboardTheme.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
if (mCallBack != null) {
mCallBack.OnItemClickListener();
}
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ForYouViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
private ImageView itemImg;
public ForYouViewHolder(@NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.card_view);
itemImg = itemView.findViewById(R.id.imPreview);
}
}
// private void loadWepJif(String webpGifUrl,ImageView view){
// Glide.with(mContext).load(webpGifUrl).addListener(new RequestListener<Drawable>() {
// @Override
// public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
// return false;
// }
//
// @Override
// public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
// if(resource instanceof WebpDrawable){
// WebpDrawable webpDrawable = (WebpDrawable) resource;
// webpDrawable.setLoopCount(LOOP_FOREVER);
// }
//
// return false;
// }
// }).into(view);
// }
}

View File

@ -0,0 +1,125 @@
package com.app.input.breeze.board.listutils;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.bean.KeyboardTheme;
import com.app.input.breeze.board.databinding.ThemeGridItemBinding;
import com.app.input.breeze.board.uiactivity.ThemeDetailActivity;
import com.app.input.breeze.board.utils.AppUtils;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class ThemeGridAdapter extends RecyclerView.Adapter<ThemeGridAdapter.ChildViewHolder> {
private Context mContext;
private List<KeyboardTheme> mList = new ArrayList<>();
public ThemeGridAdapter(Context context, List<KeyboardTheme> list) {
mContext = context;
this.mList = list;
}
@NonNull
@Override
public ChildViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ThemeGridItemBinding inflate = ThemeGridItemBinding.inflate(LayoutInflater.from(parent.getContext()));
return new ChildViewHolder(inflate);
}
@Override
public void onBindViewHolder(@NonNull ChildViewHolder holder, int position) {
KeyboardTheme KeyboardTheme = mList.get(position);
String thumbGif = KeyboardTheme.getThumbGif();
String thumb = KeyboardTheme.getThumbUrl();
if (!thumbGif.isEmpty()) {
AppUtils.INSTANCE.loadWepJif(mContext, thumbGif, holder.binding.imageView);
} else {
Glide.with(mContext).load(thumb)
.error(R.drawable.place_holder)
.placeholder(R.drawable.place_holder)
.into(holder.binding.imageView);
}
// 设置标题
holder.binding.titleText.setText(KeyboardTheme.getTitleName());
holder.binding.fragme.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, ThemeDetailActivity.class);
intentApply.putExtra(ThemeDetailActivity.SOURCE_KEY, KeyboardTheme);
intentApply.putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, KeyboardTheme.getImgPath());
intentApply.putExtra(ThemeDetailActivity.ZIP_URL_KEY, KeyboardTheme.getZipPath());
intentApply.putExtra(ThemeDetailActivity.NAME_KEY, KeyboardTheme.getTitleName());
intentApply.putExtra(ThemeDetailActivity.GIF_KEY, KeyboardTheme.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(ThemeDetailActivity.THUMB_KEY, intent_thumb);
mContext.startActivity(intentApply);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ChildViewHolder extends RecyclerView.ViewHolder {
private ThemeGridItemBinding binding;
public ChildViewHolder(@NonNull ThemeGridItemBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
// private void loadWepJif(String webpGifUrl,ImageView view){
// Glide.with(mContext).load(webpGifUrl).addListener(new RequestListener<Drawable>() {
// @Override
// public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
// return false;
// }
//
// @Override
// public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
// if(resource instanceof WebpDrawable){
// WebpDrawable webpDrawable = (WebpDrawable) resource;
// webpDrawable.setLoopCount(LOOP_FOREVER);
// }
//
// return false;
// }
// }).into(view);
// }
}

View File

@ -0,0 +1,61 @@
package com.app.input.breeze.board.listutils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.app.input.breeze.board.bean.KeyboardTheme
import com.app.input.breeze.board.databinding.TrendingThemeItemBinding
import com.app.input.breeze.board.utils.AppUtils
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
class TrendingThemeAdapter(
private val context: android.content.Context,
private val trendingList: List<KeyboardTheme>,
private val onItemClick: (KeyboardTheme) -> Unit
) : RecyclerView.Adapter<TrendingThemeAdapter.TrendingViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrendingViewHolder {
val binding = TrendingThemeItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return TrendingViewHolder(binding)
}
override fun onBindViewHolder(holder: TrendingViewHolder, position: Int) {
val KeyboardTheme = trendingList[position]
holder.bind(KeyboardTheme)
}
override fun getItemCount(): Int = trendingList.size
inner class TrendingViewHolder(
private val binding: TrendingThemeItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(KeyboardTheme: KeyboardTheme) {
val thumbGif = KeyboardTheme.thumbGif
val thumb = KeyboardTheme.thumbUrl
val options = RequestOptions()
.transform(RoundedCorners(AppUtils.dpToPx(16f)))
if (thumbGif != null && thumbGif.isNotEmpty()) {
AppUtils.loadWepJif(context, thumbGif, binding.trendingImage)
} else {
Glide.with(context)
.load(thumb)
.apply(options)
.into(binding.trendingImage)
}
binding.root.setOnClickListener {
onItemClick(KeyboardTheme)
}
}
}
}

View File

@ -0,0 +1,767 @@
package com.app.input.breeze.board.sourcecode;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.util.Xml;
import androidx.annotation.XmlRes;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import com.app.input.breeze.board.R;
public class Keyboard {
static final String TAG = "------------Keyboard-----------";
// 键盘 XML 标签
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;
/** 键盘标签 **/
private CharSequence mLabel;
/** 所有行的默认水平间距 */
private int mDefaultHorizontalGap;
/** 默认按键宽度 */
private int mDefaultWidth;
/** 默认按键高度 */
private int mDefaultHeight;
/** 行之间的默认间距 */
private int mDefaultVerticalGap;
/** 键盘是否处于大写状态 */
private boolean mShifted;
/** Shift 键的实例(如果存在) */
private Keyboard.Key[] mShiftKeys = { null, null };
/** Shift 键的索引(如果存在) */
private int[] mShiftKeyIndices = {-1, -1};
/** 加载键盘时的当前按键宽度 */
private int mKeyWidth;
/** 加载键盘时的当前按键高度 */
private int mKeyHeight;
/** 键盘的总高度,包括内边距和按键 */
private int mTotalHeight;
/**
* 键盘的总宽度包括左侧间距和按键但不包括右侧的任何间距
*/
private int mTotalWidth;
/** 此键盘中的按键列表 */
private List<Keyboard.Key> mKeys;
/** 修饰键列表,如 Shift 和 Alt如果有 */
private List<Keyboard.Key> mModifierKeys;
/** 可用于放置键盘的屏幕宽度 */
private int mDisplayWidth;
/** 屏幕高度 */
private int mDisplayHeight;
/** 键盘模式,如果没有则为零 */
private int mKeyboardMode;
// 用于预计算最近按键的变量
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;
/** 从当前触摸点到搜索最近按键的按键宽度数量 */
private static float SEARCH_DISTANCE = 1.8f;
private ArrayList<Keyboard.Row> rows = new ArrayList<>();
/**
* 键盘中按键的容器一行中的所有按键具有相同的 Y 坐标
* 某些按键大小的默认值可以在每行中覆盖 {@link Keyboard} 定义的值
*/
public static class Row {
/** 此行中按键的默认宽度 */
public int defaultWidth;
/** 此行中按键的默认高度 */
public int defaultHeight;
/** 此行中按键之间的默认水平间距 */
public int defaultHorizontalGap;
/** 此行之后的垂直间距 */
public int verticalGap;
ArrayList<Keyboard.Key> mKeys = new ArrayList<>();
/**
* 此行按键的边缘标志可以赋值的可能值有
* {@link Keyboard#EDGE_TOP EDGE_TOP} {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM}
*/
public int rowEdgeFlags;
/** 此行的键盘模式 */
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.My_Keyboard_view);
defaultWidth = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
parent.mDisplayWidth, parent.mDefaultWidth);
defaultHeight = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
parent.mDisplayHeight, parent.mDefaultHeight);
defaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
parent.mDisplayWidth, parent.mDefaultHorizontalGap);
verticalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_verticalGap,
parent.mDisplayHeight, parent.mDefaultVerticalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Kil_Keyboard_Row);
rowEdgeFlags = a.getInt(R.styleable.Kil_Keyboard_Row_android_rowEdgeFlags, 0);
mode = a.getResourceId(R.styleable.Kil_Keyboard_Row_android_keyboardMode, 0);
}
}
/**
* 用于描述键盘中单个按键的位置和特征的类
*/
public static class Key {
/**
* 此按键可以生成的所有键码Unicode 或自定义代码
* 第零个是最重要的
*/
public int[] codes;
/** 要显示的标签 */
public CharSequence label;
/** 要显示的图标(替代标签)。图标优先于标签 */
public Drawable icon;
/** 图标的预览版本,用于预览弹出窗口 */
public Drawable iconPreview;
/** 按键的宽度,不包括间距 */
public int width;
/** 按键的高度,不包括间距 */
public int height;
/** 此按键之前的水平间距 */
public int gap;
/** 此按键是否为粘性键,即切换键 */
public boolean sticky;
/** 按键在键盘布局中的 X 坐标 */
public int x;
/** 按键在键盘布局中的 Y 坐标 */
public int y;
/** 此按键的当前按下状态 */
public boolean pressed;
/** 如果这是粘性键,它是否处于开启状态? */
public boolean on;
/** 按下时输出的文本。可以是多个字符,如 ".com" */
public CharSequence text;
/** 弹出字符 */
public CharSequence popupCharacters;
/**
* 指定键盘边缘锚定的标志用于检测刚好超出按键边界的触摸事件
* 这是 {@link Keyboard#EDGE_LEFT}{@link Keyboard#EDGE_RIGHT}{@link Keyboard#EDGE_TOP}
* {@link Keyboard#EDGE_BOTTOM} 的位掩码
*/
public int edgeFlags;
/** 这是否为修饰键,如 Shift 或 Alt */
public boolean modifier;
/** 此按键所属的键盘 */
private Keyboard keyboard;
/**
* 如果此按键弹出迷你键盘这是该键盘的 XML 布局的资源 ID
*/
public int popupResId;
/** 此按键在按住时是否重复 */
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
};
/** 创建一个没有属性的空按键 */
public Key(Keyboard.Row parent) {
keyboard = parent.parent;
height = parent.defaultHeight;
width = parent.defaultWidth;
gap = parent.defaultHorizontalGap;
edgeFlags = parent.rowEdgeFlags;
}
/** 使用给定的左上角坐标创建按键,并从 XML 解析器中提取其属性 */
public Key(Resources res, Keyboard.Row parent, int x, int y, XmlResourceParser parser) {
this(parent);
this.x = x;
this.y = y;
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.My_Keyboard_view);
width = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
keyboard.mDisplayWidth, parent.defaultWidth);
height = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
keyboard.mDisplayHeight, parent.defaultHeight);
gap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
keyboard.mDisplayWidth, parent.defaultHorizontalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.K_Keyboard_Key);
this.x += gap;
TypedValue codesValue = new TypedValue();
a.getValue(R.styleable.K_Keyboard_Key_android_codes, codesValue);
if (codesValue.type == TypedValue.TYPE_INT_DEC
|| codesValue.type == TypedValue.TYPE_INT_HEX) {
codes = new int[] { codesValue.data };
} else if (codesValue.type == TypedValue.TYPE_STRING) {
codes = parseCSV(codesValue.string.toString());
}
iconPreview = a.getDrawable(R.styleable.K_Keyboard_Key_android_iconPreview);
if (iconPreview != null) {
iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
iconPreview.getIntrinsicHeight());
}
popupCharacters = a.getText(
R.styleable.K_Keyboard_Key_android_popupCharacters);
popupResId = a.getResourceId(
R.styleable.K_Keyboard_Key_android_popupKeyboard, 0);
repeatable = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isRepeatable, false);
modifier = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isModifier, false);
sticky = a.getBoolean(
R.styleable.K_Keyboard_Key_android_isSticky, false);
edgeFlags = a.getInt(R.styleable.K_Keyboard_Key_android_keyEdgeFlags, 0);
edgeFlags |= parent.rowEdgeFlags;
icon = a.getDrawable(
R.styleable.K_Keyboard_Key_android_keyIcon);
if (icon != null) {
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
}
label = a.getText(R.styleable.K_Keyboard_Key_android_keyLabel);
text = a.getText(R.styleable.K_Keyboard_Key_android_keyOutputText);
if (codes == null && !TextUtils.isEmpty(label)) {
codes = new int[] { label.charAt(0) };
}
a.recycle();
}
/**
* 通知按键已被按下以防它需要更改外观或状态
*/
public void onPressed() {
pressed = !pressed;
}
/**
* 更改按键的按下状态
*/
public void onReleased(boolean inside) {
pressed = !pressed;
if (sticky && inside) {
on = !on;
}
}
int[] parseCSV(String value) {
int count = 0;
int lastIndex = 0;
if (value.length() > 0) {
count++;
while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
count++;
}
}
int[] values = new int[count];
count = 0;
StringTokenizer st = new StringTokenizer(value, ",");
while (st.hasMoreTokens()) {
try {
values[count++] = Integer.parseInt(st.nextToken());
} catch (NumberFormatException nfe) {
}
}
return values;
}
/**
* 检测点是否落在此按键内
*/
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;
}
}
/**
* 返回按键中心与给定点之间距离的平方
*/
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;
}
/**
* 根据按键的当前状态和类型返回按键的可绘制状态
*/
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;
}
}
/**
* 从给定的 XML 按键布局文件创建键盘
*/
public Keyboard(Context context, int xmlLayoutResId) {
this(context, xmlLayoutResId, 0);
}
/**
* 从给定的 XML 按键布局文件创建键盘过滤掉定义了键盘模式但与指定模式不匹配的行
*/
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));
}
/**
* 从给定的 XML 按键布局文件创建键盘过滤掉定义了键盘模式但与指定模式不匹配的行
*/
public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
mDisplayWidth = dm.widthPixels;
mDisplayHeight = dm.heightPixels;
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;
Keyboard.Row row = new Keyboard.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 Keyboard.Key key = new Keyboard.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) {
Keyboard.Row row = rows.get(rowIndex);
int numKeys = row.mKeys.size();
int totalGap = 0;
int totalWidth = 0;
for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
Keyboard.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) {
Keyboard.Key key = row.mKeys.get(keyIndex);
key.width *= scaleFactor;
key.x = x;
x += key.width + key.gap;
}
}
}
mTotalWidth = newWidth;
}
public List<Keyboard.Key> getKeys() {
return mKeys;
}
public List<Keyboard.Key> getModifierKeys() {
return mModifierKeys;
}
protected int getHorizontalGap() {
return mDefaultHorizontalGap;
}
protected void setHorizontalGap(int gap) {
mDefaultHorizontalGap = gap;
}
protected int getVerticalGap() {
return mDefaultVerticalGap;
}
protected void setVerticalGap(int gap) {
mDefaultVerticalGap = gap;
}
protected int getKeyHeight() {
return mDefaultHeight;
}
protected void setKeyHeight(int height) {
mDefaultHeight = height;
}
protected int getKeyWidth() {
return mDefaultWidth;
}
protected void setKeyWidth(int width) {
mDefaultWidth = width;
}
/**
* 返回键盘的总高度
*/
public int getHeight() {
return mTotalHeight;
}
public int getMinWidth() {
return mTotalWidth;
}
public boolean setShifted(boolean shiftState) {
for (Keyboard.Key shiftKey : mShiftKeys) {
if (shiftKey != null) {
shiftKey.on = shiftState;
}
}
if (mShifted != shiftState) {
mShifted = shiftState;
return true;
}
return false;
}
public boolean isShifted() {
return mShifted;
}
public int[] getShiftKeyIndices() {
return mShiftKeyIndices;
}
public int getShiftKeyIndex() {
return mShiftKeyIndices[0];
}
private void computeNearestNeighbors() {
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 Keyboard.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;
}
}
}
/**
* 返回最接近给定点的按键索引
*/
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 Keyboard.Row createRowFromXml(Resources res, XmlResourceParser parser) {
return new Keyboard.Row(res, this, parser);
}
protected Keyboard.Key createKeyFromXml(Resources res, Keyboard.Row parent, int x, int y,
XmlResourceParser parser) {
return new Keyboard.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;
Keyboard.Key key = null;
Keyboard.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) {
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++;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
mTotalHeight = y - mDefaultVerticalGap;
}
private void skipToEndOfRow(XmlResourceParser parser)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
if (event == XmlResourceParser.END_TAG
&& parser.getName().equals(TAG_ROW)) {
break;
}
}
}
private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.My_Keyboard_view);
mDefaultWidth = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyWidth,
mDisplayWidth, mDisplayWidth / 10);
mDefaultHeight = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_keyHeight,
mDisplayHeight, 50);
mDefaultHorizontalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_horizontalGap,
mDisplayWidth, 0);
mDefaultVerticalGap = getDimensionOrFraction(a,
R.styleable.My_Keyboard_view_android_verticalGap,
mDisplayHeight, 0);
mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
mProximityThreshold = mProximityThreshold * mProximityThreshold;
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) {
return Math.round(a.getFraction(index, base, base, defValue));
}
return defValue;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
package com.app.input.breeze.board.uiactivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import com.ad.tradpluslibrary.TPAdManager;
import com.app.input.breeze.board.App;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.bean.CategoryWrapper;
import com.app.input.breeze.board.databinding.CategoryScreenBinding;
import com.app.input.breeze.board.listutils.CategoryCardAdapter;
public class CategoryScreen extends AppCompatActivity {
private CategoryScreenBinding vb;
private CategoryCardAdapter categoryCardAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = CategoryScreenBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
init();
}
private void init() {
setupRecyclerView();
setupClickListeners();
}
private void setupRecyclerView() {
// 使用 GridLayoutManager一行 2
categoryCardAdapter = new CategoryCardAdapter(this, App.list, new CategoryCardAdapter.OnCategoryCardClick() {
@Override
public void onCategoryClick(String categoryName) {
// 点击分类卡片跳转到分类详情页
Intent intent = new Intent(CategoryScreen.this, ThemeListActivity.class);
intent.putExtra(ThemeListActivity.KEY_NAME, categoryName);
startActivity(intent);
}
});
vb.categoryRecycler.setLayoutManager(new GridLayoutManager(this, 2));
vb.categoryRecycler.setAdapter(categoryCardAdapter);
}
private void setupClickListeners() {
setupBottomNavigation();
}
private void setupBottomNavigation() {
// 设置默认选中分类
updateBottomNavState(R.id.nav_category);
// 主页点击
View navHome = findViewById(R.id.nav_home);
navHome.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(CategoryScreen.this, MainActivity.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
}
});
// 分类点击
View navCategory = findViewById(R.id.nav_category);
navCategory.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 已经在分类页不需要操作
updateBottomNavState(R.id.nav_category);
}
});
// 设置点击
View navSettings = findViewById(R.id.nav_settings);
navSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(CategoryScreen.this, SettingsScreen.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
});
}
private void updateBottomNavState(int selectedId) {
int selectedColor = getColor(R.color.blue_primary);
int unselectedColor = 0x80000000;
android.widget.ImageView homeIcon = findViewById(R.id.nav_home_icon);
android.widget.TextView homeText = findViewById(R.id.nav_home_text);
View homeIndicator = findViewById(R.id.nav_home_indicator);
android.widget.ImageView categoryIcon = findViewById(R.id.nav_category_icon);
android.widget.TextView categoryText = findViewById(R.id.nav_category_text);
View categoryIndicator = findViewById(R.id.nav_category_indicator);
android.widget.ImageView settingsIcon = findViewById(R.id.nav_settings_icon);
android.widget.TextView settingsText = findViewById(R.id.nav_settings_text);
View settingsIndicator = findViewById(R.id.nav_settings_indicator);
if (selectedId == R.id.nav_home) {
homeIcon.setColorFilter(selectedColor);
homeText.setTextColor(selectedColor);
homeIndicator.setVisibility(View.VISIBLE);
categoryIcon.setColorFilter(unselectedColor);
categoryText.setTextColor(unselectedColor);
categoryIndicator.setVisibility(View.GONE);
settingsIcon.setColorFilter(unselectedColor);
settingsText.setTextColor(unselectedColor);
settingsIndicator.setVisibility(View.GONE);
} else if (selectedId == R.id.nav_category) {
homeIcon.setColorFilter(unselectedColor);
homeText.setTextColor(unselectedColor);
homeIndicator.setVisibility(View.GONE);
categoryIcon.setColorFilter(selectedColor);
categoryText.setTextColor(selectedColor);
categoryIndicator.setVisibility(View.VISIBLE);
settingsIcon.setColorFilter(unselectedColor);
settingsText.setTextColor(unselectedColor);
settingsIndicator.setVisibility(View.GONE);
} else if (selectedId == R.id.nav_settings) {
homeIcon.setColorFilter(unselectedColor);
homeText.setTextColor(unselectedColor);
homeIndicator.setVisibility(View.GONE);
categoryIcon.setColorFilter(unselectedColor);
categoryText.setTextColor(unselectedColor);
categoryIndicator.setVisibility(View.GONE);
settingsIcon.setColorFilter(selectedColor);
settingsText.setTextColor(selectedColor);
settingsIndicator.setVisibility(View.VISIBLE);
}
}
}

View File

@ -0,0 +1,75 @@
package com.app.input.breeze.board.uiactivity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.ad.tradpluslibrary.TPAdManager.showTPAD
import com.app.input.breeze.board.bean.KeyboardTheme
import com.app.input.breeze.board.database.AppDatabase
import com.app.input.breeze.board.database.FavoriteRepository
import com.app.input.breeze.board.databinding.FavoriteActivityBinding
import com.app.input.breeze.board.listutils.FavoriteThemeAdapter
import kotlinx.coroutines.launch
class FavoriteThemeActivity : AppCompatActivity() {
private lateinit var vb: FavoriteActivityBinding
private lateinit var FavoriteThemeAdapter: FavoriteThemeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vb = FavoriteActivityBinding.inflate(layoutInflater)
enableEdgeToEdge()
setContentView(vb.root)
showTPAD(this) { }
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
setupRecyclerView()
observeFavorites()
setupClickListeners()
}
private fun setupRecyclerView() {
FavoriteThemeAdapter = FavoriteThemeAdapter(this).apply {
setRemoveLike(object : com.app.input.breeze.board.callback.RemoveFavoriteCallback {
override fun OnRemoveLike(data: KeyboardTheme) {
lifecycleScope.launch {
FavoriteRepository.removeLike(data)
}
}
})
}
vb.likeRecycler.apply {
adapter = FavoriteThemeAdapter
layoutManager = GridLayoutManager(this@FavoriteThemeActivity, 2)
}
}
private fun observeFavorites() {
AppDatabase.instance.favoriteThemeDao().queryAllLike().observe(this) { favorites ->
if (favorites.isNullOrEmpty()) {
vb.likeRecycler.visibility = android.view.View.GONE
vb.emptyTitle.visibility = android.view.View.VISIBLE
} else {
vb.likeRecycler.visibility = android.view.View.VISIBLE
vb.emptyTitle.visibility = android.view.View.GONE
FavoriteThemeAdapter.setForYouList(favorites)
}
}
}
private fun setupClickListeners() {
vb.back.setOnClickListener {
finish()
}
}
}

View File

@ -0,0 +1,403 @@
package com.app.input.breeze.board.uiactivity
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import com.app.input.breeze.board.App
import com.app.input.breeze.board.R
import com.app.input.breeze.board.bean.KeyboardTheme
import com.app.input.breeze.board.bean.CategoryWrapper
import com.app.input.breeze.board.databinding.MainActivityBinding
import com.app.input.breeze.board.listutils.QuickCategoryAdapter
import com.app.input.breeze.board.listutils.FeaturedThemeAdapter
import com.app.input.breeze.board.listutils.TrendingThemeAdapter
class MainActivity : AppCompatActivity() {
private lateinit var vb: MainActivityBinding
private lateinit var FeaturedThemeAdapter: FeaturedThemeAdapter
private lateinit var QuickCategoryAdapter: QuickCategoryAdapter
private lateinit var TrendingThemeAdapter: TrendingThemeAdapter
private lateinit var newAdapter: TrendingThemeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vb = MainActivityBinding.inflate(layoutInflater)
enableEdgeToEdge()
setContentView(vb.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
initViews()
setupHero()
setupQuickCategories()
setupTrending()
setupNew()
setupClickListeners()
setupBottomNavigation()
}
private fun initViews() {
// Hero 区域 - 横向滑动,每次滑动显示一张完整卡片
val heroLayoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
vb.heroRecycler.layoutManager = heroLayoutManager
vb.heroRecycler.isNestedScrollingEnabled = false
// 使用 PagerSnapHelper 实现每次滑动对齐到一张完整卡片
val snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(vb.heroRecycler)
// 快速分类 - 横向滑动
vb.quickCategoryRecycler.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
// Trending - 横向滑动
vb.trendingRecycler.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
// New - 横向滑动
vb.newRecycler.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
}
/**
* 设置 Hero 区域 - 今日精选键盘
* 固定显示三个指定的精选主题支持横向滑动
*/
private fun setupHero() {
val heroList = mutableListOf<KeyboardTheme>()
// 固定的三个精选主题名称
val featuredThemeNames = listOf(
"Gravity Water Ice",
"Snowflake Gravity",
"Pastel Clouds Sky"
)
// 从所有分类中查找这三个主题
val allThemes = mutableListOf<KeyboardTheme>()
App.list.forEach { wrapper ->
allThemes.addAll(wrapper.keyboardList)
}
// 按照指定顺序查找并添加主题
featuredThemeNames.forEach { themeName ->
val theme = allThemes.find { it.titleName == themeName }
if (theme != null) {
heroList.add(theme)
}
}
FeaturedThemeAdapter = FeaturedThemeAdapter(this, heroList) { KeyboardTheme ->
navigateToDetail(KeyboardTheme)
}
// 设置 adapter 并立即刷新
vb.heroRecycler.adapter = FeaturedThemeAdapter
// 确保 RecyclerView 可见
if (vb.heroRecycler.visibility != View.VISIBLE) {
vb.heroRecycler.visibility = View.VISIBLE
}
// 立即通知数据变化
FeaturedThemeAdapter.notifyDataSetChanged()
// 强制请求布局,确保立即显示
vb.heroRecycler.requestLayout()
// 设置指示器
setupHeroIndicator(heroList.size)
// 初始化指示器状态
vb.heroRecycler.post {
updateHeroIndicator()
}
// 监听滚动状态变化,在滑动结束时更新指示器
vb.heroRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
// 当滑动停止时更新指示器
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
updateHeroIndicator()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// 滑动过程中也更新,但主要依赖 SCROLL_STATE_IDLE
}
})
}
/**
* 设置 Hero 指示器
*/
private fun setupHeroIndicator(count: Int) {
vb.heroIndicatorContainer.removeAllViews()
for (i in 0 until count) {
val indicator = View(this).apply {
layoutParams = android.widget.LinearLayout.LayoutParams(
resources.getDimensionPixelSize(android.R.dimen.app_icon_size) / 4,
resources.getDimensionPixelSize(android.R.dimen.app_icon_size) / 4
).apply {
if (i > 0) {
marginStart = resources.getDimensionPixelSize(android.R.dimen.app_icon_size) / 8
}
}
background = if (i == 0) {
resources.getDrawable(R.drawable.dot_indicator_selected, theme)
} else {
resources.getDrawable(R.drawable.dot_indicator, theme)
}
}
vb.heroIndicatorContainer.addView(indicator)
}
}
/**
* 更新 Hero 指示器状态
*/
private fun updateHeroIndicator() {
val layoutManager = vb.heroRecycler.layoutManager as? LinearLayoutManager ?: return
// 使用 PagerSnapHelper 后findFirstCompletelyVisibleItemPosition 更准确
val currentPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
// 如果找不到完全可见的,使用第一个可见的
val position = if (currentPosition != RecyclerView.NO_POSITION) {
currentPosition
} else {
layoutManager.findFirstVisibleItemPosition().coerceAtLeast(0)
}.coerceIn(0, vb.heroIndicatorContainer.childCount - 1)
// 更新指示器状态
for (i in 0 until vb.heroIndicatorContainer.childCount) {
val indicator = vb.heroIndicatorContainer.getChildAt(i)
indicator.background = if (i == position) {
resources.getDrawable(R.drawable.dot_indicator_selected, theme)
} else {
resources.getDrawable(R.drawable.dot_indicator, theme)
}
}
}
/**
* 设置快速分类入口
* 显示圆形/胶囊按钮只是入口不展示内容
* 使用实际数据中存在的分类名称
*/
private fun setupQuickCategories() {
// 快速分类列表:显示名称 -> (实际分类名称, emoji)
// 根据实际数据中的分类recommend, aesthetic, cool, cute, festival, live, love, neon, gravity, super theme
// 为每个分类选择更匹配的emoji图标
val categoryData = listOf(
Triple("Cute", "cute", "💖"), // 粉色爱心 - 可爱
Triple("Neon", "neon", "🌌"), // 银河 - 霓虹
Triple("Cool", "cool", "😎"), // 墨镜笑脸 - 酷
Triple("Aesthetic", "aesthetic", ""), // 星星 - 美学
Triple("Love", "love", "💕"), // 粉色爱心 - 爱情
Triple("Gravity", "gravity", "🌍") // 地球 - 重力
)
// 创建显示列表(名称, emoji
val categories = categoryData.map { it.first to it.third }
// 创建映射关系(显示名称 -> 实际分类名称)
val categoryMapping = categoryData.associate { it.first to it.second }
QuickCategoryAdapter = QuickCategoryAdapter(this, categories) { displayName ->
// 根据显示名称找到对应的实际分类名称
val actualCategoryName = categoryMapping[displayName] ?: displayName.lowercase()
// 跳转到分类详情页
navigateToCategory(actualCategoryName)
}
vb.quickCategoryRecycler.adapter = QuickCategoryAdapter
}
/**
* 设置 Trending 推荐
* recommend 分类获取数据横向滑动
*/
private fun setupTrending() {
val trendingList = mutableListOf<KeyboardTheme>()
val recommendWrapper = App.list.find { it.parentName == "recommend" }
recommendWrapper?.keyboardList?.take(10)?.let {
trendingList.addAll(it)
}
TrendingThemeAdapter = TrendingThemeAdapter(this, trendingList) { KeyboardTheme ->
navigateToDetail(KeyboardTheme)
}
vb.trendingRecycler.adapter = TrendingThemeAdapter
}
/**
* 设置 New 推荐
* 从除了 recommend 之外的其他分类中混合获取主题
*/
private fun setupNew() {
val newList = mutableListOf<KeyboardTheme>()
// 从所有分类中收集主题(排除 recommend
val otherCategories = App.list.filter {
it.parentName != "recommend"
}
// 从多个分类中混合获取主题,每个分类取前几个
otherCategories.forEachIndexed { index, wrapper ->
if (index < 3 && newList.size < 10) { // 最多从3个分类获取总共不超过10个
val takeCount = (10 - newList.size).coerceAtMost(4) // 每个分类最多取4个
wrapper.keyboardList.take(takeCount).let {
newList.addAll(it)
}
}
}
// 如果还是没有数据,从第一个非 recommend 分类获取
if (newList.isEmpty() && App.list.size > 1) {
App.list.find { it.parentName != "recommend" }?.keyboardList?.take(10)?.let {
newList.addAll(it)
}
}
// 打乱顺序,让不同分类的主题混合显示
val shuffled = newList.shuffled()
newAdapter = TrendingThemeAdapter(this, shuffled) { KeyboardTheme ->
navigateToDetail(KeyboardTheme)
}
vb.newRecycler.adapter = newAdapter
}
private fun setupClickListeners() {
// 跳转到收藏页面
vb.btnFavorite.setOnClickListener {
startActivity(Intent(this, FavoriteThemeActivity::class.java))
}
}
private fun setupBottomNavigation() {
// 设置默认选中主页
updateBottomNavState(R.id.nav_home)
// 主页点击
findViewById<View>(R.id.nav_home).setOnClickListener {
// 已经在主页,不需要操作
updateBottomNavState(R.id.nav_home)
}
// 分类点击
findViewById<View>(R.id.nav_category).setOnClickListener {
startActivity(Intent(this, CategoryScreen::class.java))
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
finish()
}
// 设置点击
findViewById<View>(R.id.nav_settings).setOnClickListener {
startActivity(Intent(this, SettingsScreen::class.java))
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
}
private fun updateBottomNavState(selectedId: Int) {
val selectedColor = getColor(R.color.blue_primary)
val unselectedColor = 0x80000000.toInt()
// 重置所有状态
val homeIcon = findViewById<android.widget.ImageView>(R.id.nav_home_icon)
val homeText = findViewById<android.widget.TextView>(R.id.nav_home_text)
val homeIndicator = findViewById<View>(R.id.nav_home_indicator)
val categoryIcon = findViewById<android.widget.ImageView>(R.id.nav_category_icon)
val categoryText = findViewById<android.widget.TextView>(R.id.nav_category_text)
val categoryIndicator = findViewById<View>(R.id.nav_category_indicator)
val settingsIcon = findViewById<android.widget.ImageView>(R.id.nav_settings_icon)
val settingsText = findViewById<android.widget.TextView>(R.id.nav_settings_text)
val settingsIndicator = findViewById<View>(R.id.nav_settings_indicator)
when (selectedId) {
R.id.nav_home -> {
homeIcon.setColorFilter(selectedColor)
homeText.setTextColor(selectedColor)
homeIndicator.visibility = View.VISIBLE
categoryIcon.setColorFilter(unselectedColor)
categoryText.setTextColor(unselectedColor)
categoryIndicator.visibility = View.GONE
settingsIcon.setColorFilter(unselectedColor)
settingsText.setTextColor(unselectedColor)
settingsIndicator.visibility = View.GONE
}
R.id.nav_category -> {
homeIcon.setColorFilter(unselectedColor)
homeText.setTextColor(unselectedColor)
homeIndicator.visibility = View.GONE
categoryIcon.setColorFilter(selectedColor)
categoryText.setTextColor(selectedColor)
categoryIndicator.visibility = View.VISIBLE
settingsIcon.setColorFilter(unselectedColor)
settingsText.setTextColor(unselectedColor)
settingsIndicator.visibility = View.GONE
}
R.id.nav_settings -> {
homeIcon.setColorFilter(unselectedColor)
homeText.setTextColor(unselectedColor)
homeIndicator.visibility = View.GONE
categoryIcon.setColorFilter(unselectedColor)
categoryText.setTextColor(unselectedColor)
categoryIndicator.visibility = View.GONE
settingsIcon.setColorFilter(selectedColor)
settingsText.setTextColor(selectedColor)
settingsIndicator.visibility = View.VISIBLE
}
}
}
private fun navigateToDetail(KeyboardTheme: KeyboardTheme) {
val intent = Intent(this, ThemeDetailActivity::class.java).apply {
putExtra(ThemeDetailActivity.SOURCE_KEY, KeyboardTheme)
putExtra(ThemeDetailActivity.DISPLAY_URL_KEY, KeyboardTheme.imgPath)
putExtra(ThemeDetailActivity.ZIP_URL_KEY, KeyboardTheme.zipPath)
putExtra(ThemeDetailActivity.NAME_KEY, KeyboardTheme.titleName)
putExtra(ThemeDetailActivity.GIF_KEY, KeyboardTheme.imgGif)
val thumbGif = KeyboardTheme.thumbGif
val thumb = if (thumbGif != null && thumbGif.isNotEmpty()) {
thumbGif
} else {
KeyboardTheme.thumbUrl
}
putExtra(ThemeDetailActivity.THUMB_KEY, thumb)
}
startActivity(intent)
}
private fun navigateToCategory(categoryName: String) {
// 查找对应的分类(精确匹配,不区分大小写)
val categoryWrapper = App.list.find {
it.parentName.equals(categoryName, ignoreCase = true)
}
if (categoryWrapper != null) {
val intent = Intent(this, ThemeListActivity::class.java).apply {
putExtra(ThemeListActivity.KEY_NAME, categoryWrapper.parentName)
}
startActivity(intent)
} else {
// 如果找不到,跳转到分类页面(让用户自己选择)
startActivity(Intent(this, CategoryScreen::class.java))
}
}
}

View File

@ -0,0 +1,136 @@
package com.app.input.breeze.board.uiactivity;
import static com.bumptech.glide.request.RequestOptions.bitmapTransform;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.ad.tradpluslibrary.TPAdManager;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.databinding.PreviewActivityBinding;
import com.app.input.breeze.board.utils.KeyboardConstants;
import com.app.input.breeze.board.utils.AppUtils;
import com.app.input.breeze.board.utils.ThemePreferences;
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 jp.wasabeef.glide.transformations.BlurTransformation;
public class PreviewActivity extends AppCompatActivity {
private PreviewActivityBinding vb;
public static String key_name = "key_name";
private int mPreviousKeyboardHeight = -1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = PreviewActivityBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
TPAdManager.INSTANCE.showTPAD(this,()-> null);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
onInit();
}
public void onInit() {
String stringExtra = getIntent().getStringExtra(key_name);
vb.title.setText(stringExtra);
String curPath = ThemePreferences.INSTANCE.getSkinPath();
vb.idBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TPAdManager.INSTANCE.showTPAD(PreviewActivity.this,()-> {
finish();
return null;
} );
}
});
if (curPath == null) {
return;
}
String bgPath = curPath+"res/drawable-xxhdpi-v4/"+ KeyboardConstants.previewBg;
Drawable bgDraw = AppUtils.INSTANCE.getBgDrawable(this, bgPath);
if (bgDraw != null) {
Glide.with(this)
.load(bgDraw)
.apply(bitmapTransform(new BlurTransformation(15, 3))) // 设置模糊半径和模糊采样
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> target, @NonNull DataSource dataSource, boolean isFirstResource) {
vb.main.setBackground(resource);
return false;
}
})
.preload();
}
keyboardheight();
vb.et.requestFocus();
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
private void keyboardheight() {
final View rootView = getWindow().getDecorView();
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int screenHeight = rootView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight != mPreviousKeyboardHeight) {
if (mPreviousKeyboardHeight < keypadHeight) {
mPreviousKeyboardHeight = keypadHeight;
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) vb.et.getLayoutParams();
params.bottomMargin = mPreviousKeyboardHeight;
vb.et.setLayoutParams(params);
}
}
}
});
}
}

View File

@ -0,0 +1,211 @@
package com.app.input.breeze.board.uiactivity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.app.input.breeze.board.BuildConfig
import com.app.input.breeze.board.R
import com.app.input.breeze.board.databinding.SettingsActivityBinding
import com.app.input.breeze.board.utils.AppUtils
class SettingsScreen : AppCompatActivity() {
private lateinit var vb: SettingsActivityBinding
private var inputMethodReceiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vb = SettingsActivityBinding.inflate(layoutInflater)
enableEdgeToEdge()
setContentView(vb.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
setupClickListeners()
setupBottomNavigation()
registerInputMethodReceiver()
setupVersion()
}
private fun setupClickListeners() {
// 启用键盘设置
vb.layoutEnableKeyboard.setOnClickListener {
startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
}
// 设置默认键盘(只有在第一步完成后才能点击)
vb.layoutSetDefaultKeyboard.setOnClickListener {
val isEnabled = AppUtils.checkEnable(this)
if (isEnabled) {
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as android.view.inputmethod.InputMethodManager
inputMethodManager.showInputMethodPicker()
} else {
// 如果第一步未完成,提示用户先完成第一步
android.widget.Toast.makeText(
this,
getString(R.string.please_enable_keyboard_first),
android.widget.Toast.LENGTH_SHORT
).show()
}
}
// 版本(不再需要点击事件,只显示版本号)
}
private fun setupBottomNavigation() {
// 设置默认选中设置
updateBottomNavState(R.id.nav_settings)
// 主页点击
findViewById<View>(R.id.nav_home).setOnClickListener {
startActivity(Intent(this, MainActivity::class.java))
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
finish()
}
// 分类点击
findViewById<View>(R.id.nav_category).setOnClickListener {
startActivity(Intent(this, CategoryScreen::class.java))
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
finish()
}
// 设置点击
findViewById<View>(R.id.nav_settings).setOnClickListener {
// 已经在设置页,不需要操作
updateBottomNavState(R.id.nav_settings)
}
}
private fun updateBottomNavState(selectedId: Int) {
val selectedColor = getColor(R.color.blue_primary)
val unselectedColor = 0x80000000.toInt()
val homeIcon = findViewById<android.widget.ImageView>(R.id.nav_home_icon)
val homeText = findViewById<android.widget.TextView>(R.id.nav_home_text)
val homeIndicator = findViewById<View>(R.id.nav_home_indicator)
val categoryIcon = findViewById<android.widget.ImageView>(R.id.nav_category_icon)
val categoryText = findViewById<android.widget.TextView>(R.id.nav_category_text)
val categoryIndicator = findViewById<View>(R.id.nav_category_indicator)
val settingsIcon = findViewById<android.widget.ImageView>(R.id.nav_settings_icon)
val settingsText = findViewById<android.widget.TextView>(R.id.nav_settings_text)
val settingsIndicator = findViewById<View>(R.id.nav_settings_indicator)
when (selectedId) {
R.id.nav_home -> {
homeIcon.setColorFilter(selectedColor)
homeText.setTextColor(selectedColor)
homeIndicator.visibility = View.VISIBLE
categoryIcon.setColorFilter(unselectedColor)
categoryText.setTextColor(unselectedColor)
categoryIndicator.visibility = View.GONE
settingsIcon.setColorFilter(unselectedColor)
settingsText.setTextColor(unselectedColor)
settingsIndicator.visibility = View.GONE
}
R.id.nav_category -> {
homeIcon.setColorFilter(unselectedColor)
homeText.setTextColor(unselectedColor)
homeIndicator.visibility = View.GONE
categoryIcon.setColorFilter(selectedColor)
categoryText.setTextColor(selectedColor)
categoryIndicator.visibility = View.VISIBLE
settingsIcon.setColorFilter(unselectedColor)
settingsText.setTextColor(unselectedColor)
settingsIndicator.visibility = View.GONE
}
R.id.nav_settings -> {
homeIcon.setColorFilter(unselectedColor)
homeText.setTextColor(unselectedColor)
homeIndicator.visibility = View.GONE
categoryIcon.setColorFilter(unselectedColor)
categoryText.setTextColor(unselectedColor)
categoryIndicator.visibility = View.GONE
settingsIcon.setColorFilter(selectedColor)
settingsText.setTextColor(selectedColor)
settingsIndicator.visibility = View.VISIBLE
}
}
}
override fun onResume() {
super.onResume()
// 延迟更新,确保系统状态已更新
Handler(Looper.getMainLooper()).postDelayed({
updateKeyboardStatus()
}, 300)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
// 当窗口获得焦点时更新状态(用户从系统设置返回时)
Handler(Looper.getMainLooper()).postDelayed({
updateKeyboardStatus()
}, 200)
}
}
private fun registerInputMethodReceiver() {
inputMethodReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// 当输入法状态改变时自动更新
updateKeyboardStatus()
}
}
val filter = IntentFilter(Intent.ACTION_INPUT_METHOD_CHANGED)
registerReceiver(inputMethodReceiver, filter)
}
override fun onDestroy() {
super.onDestroy()
// 注销广播接收器
inputMethodReceiver?.let {
try {
unregisterReceiver(it)
} catch (e: Exception) {
// 忽略已注销的异常
}
}
}
private fun setupVersion() {
// 从 BuildConfig 获取版本号
vb.tvVersion.text = BuildConfig.VERSION_NAME
}
private fun updateKeyboardStatus() {
val isEnabled = AppUtils.checkEnable(this)
val isDefault = AppUtils.checkSetDefault(this)
vb.tvEnableStatus.text = if (isEnabled) getString(R.string.enabled) else getString(R.string.not_enabled)
vb.tvDefaultStatus.text = if (isDefault) getString(R.string.set_as_default) else getString(R.string.not_set_as_default)
// 根据第一步是否完成来控制第二步的可用性和视觉状态
if (isEnabled) {
// 第一步已完成,第二步可用
vb.layoutSetDefaultKeyboard.isEnabled = true
vb.layoutSetDefaultKeyboard.alpha = 1.0f
} else {
// 第一步未完成,第二步禁用
vb.layoutSetDefaultKeyboard.isEnabled = false
vb.layoutSetDefaultKeyboard.alpha = 0.5f
}
}
}

View File

@ -0,0 +1,197 @@
package com.app.input.breeze.board.uiactivity
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.widget.ProgressBar
import android.widget.TextView
import com.ad.tradpluslibrary.TPAdManager
import com.app.input.breeze.board.App
import com.app.input.breeze.board.R
import com.app.input.breeze.board.utils.AppUtils
import kotlin.math.roundToInt
/**
* 不要修改启动页继承Activity这点
*/
class SplashActivity : Activity() {
private lateinit var progressBar: ProgressBar
private lateinit var loadingText: TextView
private lateinit var logoView: View
private var countTime = 15000L
private lateinit var timer: CountDownTimer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 全屏沉浸式设置
setupImmersiveMode()
setContentView(R.layout.splash_activity)
progressBar = findViewById(R.id.nova_progress)
loadingText = findViewById(R.id.loading_text)
logoView = findViewById(R.id.image)
init()
}
private fun setupImmersiveMode() {
val decorView = window.decorView
// 内容延伸到系统栏下方,但保持系统栏可见
val flags = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
decorView.systemUiVisibility = flags
// 设置状态栏透明,导航栏白色
window.statusBarColor = android.graphics.Color.TRANSPARENT
window.navigationBarColor = android.graphics.Color.WHITE
}
override fun onResume() {
super.onResume()
// 保持全屏沉浸式状态
setupImmersiveMode()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
// 保持全屏沉浸式状态
setupImmersiveMode()
}
}
private fun init() {
// 启动动画
startAnimations()
TPAdManager.init(
this,
App.TAG,
"491CF93F7A3E504CC90F215A3B43D011",
"163EB8E51E42672FDBB21327BF580412",
"ACF89F38DB6757D632C0ED73E4E3F712",
"56A126D9C53FCAEAFCEADB9FA6070612"
) {
}
timer = TPAdManager.showWelcomeAd(this, countTime, { millisUntilFinished ->
//倒计时更新
val progressPercentage = (100 * millisUntilFinished) / countTime
val percentage = 100 - progressPercentage
progressBar.progress = percentage.toInt()
// 更新加载文字
updateLoadingText(percentage.toInt())
}) {
//跳转首页
progressBar.progress = 100
updateLoadingText(100)
// 延迟一点再跳转,让用户看到完成状态
Handler(Looper.getMainLooper()).postDelayed({
toHome()
}, 200)
}
timer.start()
}
private fun startAnimations() {
// Logo 淡入和缩放动画
logoView.alpha = 0f
logoView.scaleX = 0.8f
logoView.scaleY = 0.8f
val logoFadeIn = ObjectAnimator.ofFloat(logoView, "alpha", 0f, 1f)
val logoScaleX = ObjectAnimator.ofFloat(logoView, "scaleX", 0.8f, 1f)
val logoScaleY = ObjectAnimator.ofFloat(logoView, "scaleY", 0.8f, 1f)
logoFadeIn.duration = 600
logoScaleX.duration = 600
logoScaleY.duration = 600
logoFadeIn.interpolator = DecelerateInterpolator()
logoScaleX.interpolator = DecelerateInterpolator()
logoScaleY.interpolator = DecelerateInterpolator()
logoFadeIn.start()
logoScaleX.start()
logoScaleY.start()
// 应用名称淡入
val appNameView = findViewById<View>(R.id.tv)
appNameView.alpha = 0f
appNameView.translationY = 20f
Handler(Looper.getMainLooper()).postDelayed({
val nameFadeIn = ObjectAnimator.ofFloat(appNameView, "alpha", 0f, 1f)
val nameTranslate = ObjectAnimator.ofFloat(appNameView, "translationY", 20f, 0f)
nameFadeIn.duration = 500
nameTranslate.duration = 500
nameFadeIn.interpolator = DecelerateInterpolator()
nameTranslate.interpolator = DecelerateInterpolator()
nameFadeIn.start()
nameTranslate.start()
}, 300)
// 进度条淡入
progressBar.alpha = 0f
Handler(Looper.getMainLooper()).postDelayed({
val progressFadeIn = ObjectAnimator.ofFloat(progressBar, "alpha", 0f, 1f)
progressFadeIn.duration = 400
progressFadeIn.start()
loadingText.alpha = 0f
val textFadeIn = ObjectAnimator.ofFloat(loadingText, "alpha", 0f, 1f)
textFadeIn.duration = 400
textFadeIn.start()
}, 600)
}
private fun updateLoadingText(progress: Int) {
when {
progress < 30 -> loadingText.text = getString(R.string.initializing)
progress < 60 -> loadingText.text = getString(R.string.loading_themes)
progress < 90 -> loadingText.text = getString(R.string.preparing_interface)
else -> loadingText.text = getString(R.string.almost_done)
}
}
private fun toHome() {
// 淡出动画
val fadeOut = ObjectAnimator.ofFloat(findViewById<View>(R.id.main), "alpha", 1f, 0f)
fadeOut.duration = 300
fadeOut.interpolator = AccelerateDecelerateInterpolator()
fadeOut.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
finish()
}
})
fadeOut.start()
}
override fun onDestroy() {
super.onDestroy()
if (::timer.isInitialized) {
timer.cancel()
}
}
}

View File

@ -0,0 +1,376 @@
package com.app.input.breeze.board.uiactivity
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ad.tradpluslibrary.TPAdManager.showTPAD
import com.app.input.breeze.board.App
import com.app.input.breeze.board.R
import com.app.input.breeze.board.bean.KeyboardTheme
import com.app.input.breeze.board.callback.ItemClickListener
import com.app.input.breeze.board.callback.KeyboardApplyCallback
import com.app.input.breeze.board.database.FavoriteRepository
import com.app.input.breeze.board.listutils.RecommendedThemeAdapter
import com.app.input.breeze.board.uifragment.EnableKeyboardDialog
import com.app.input.breeze.board.utils.AppUtils
import com.app.input.breeze.board.utils.ThemePreferences
import com.app.input.breeze.board.utils.ZipFileHandler
import com.bumptech.glide.Glide
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import kotlinx.coroutines.launch
import java.io.File
class ThemeDetailActivity : AppCompatActivity() {
companion object {
@JvmField
var DISPLAY_URL_KEY: String = "display_url_key"
@JvmField
val ZIP_URL_KEY = "zip_url_key"
@JvmField
val NAME_KEY = "name_key"
@JvmField
val GIF_KEY = "gif_key"
@JvmField
val THUMB_KEY = "thumb_key"
@JvmField
val SOURCE_KEY = "data_key"
}
private var dialog: EnableKeyboardDialog? = null
private lateinit var displayUrl: String
private lateinit var gifUrl: String
private lateinit var zipUrl: String
private lateinit var name: String
private lateinit var applyBtn: LinearLayout
private lateinit var imgData: ImageView
private lateinit var imgBack: ImageView
private lateinit var textName: TextView
private lateinit var recommendedRecycler: RecyclerView
// private lateinit var viewAllLayout: LinearLayout
private lateinit var loadingLayout: FrameLayout
private lateinit var unzipPath: String
private lateinit var tvDownload: TextView
private lateinit var imDownload: ImageView
private lateinit var thumb: String
private lateinit var imgLike: ImageView
private lateinit var data: KeyboardTheme
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.theme_detail_activity)
this.enableEdgeToEdge()
ViewCompat.setOnApplyWindowInsetsListener(
findViewById<View?>(R.id.main),
OnApplyWindowInsetsListener { v: View?, insets: WindowInsetsCompat? ->
val systemBars = insets!!.getInsets(WindowInsetsCompat.Type.systemBars())
v!!.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
})
findViewId()
getExtraData()
displayData()
setApply()
onClick()
initLikeState()
}
private fun getExtraData() {
data = intent.getSerializableExtra(SOURCE_KEY) as KeyboardTheme
displayUrl = intent.getStringExtra(DISPLAY_URL_KEY).toString()
zipUrl = intent.getStringExtra(ZIP_URL_KEY).toString()
name = intent.getStringExtra(NAME_KEY).toString()
gifUrl = intent.getStringExtra(GIF_KEY).toString()
thumb = intent.getStringExtra(THUMB_KEY).toString()
val serviceZipName = ZipFileHandler.getServiceZipName(zipUrl)
unzipPath = ZipFileHandler.getUnzipPath(serviceZipName)
Log.d("-------------------", "-------unzipPath=" + unzipPath)
if (File(unzipPath).exists()) {
imDownload.isVisible = false
tvDownload.text = getString(R.string.apply)
} else {
imDownload.isVisible = true
tvDownload.text = getString(R.string.download_apply)
}
}
private fun findViewId() {
applyBtn = findViewById(R.id.layoutDownloadApply)
imgData = findViewById(R.id.image_data)
imgBack = findViewById(R.id.back)
textName = findViewById(R.id.textview_data_name)
recommendedRecycler = findViewById(R.id.recommended_recycler)
// viewAllLayout = findViewById(R.id.layout_view_all)
imgLike = findViewById(R.id.im_like)
loadingLayout = findViewById(R.id.loading)
imDownload = findViewById(R.id.im_download)
tvDownload = findViewById(R.id.tv_download)
}
private fun displayData() {
textName.text = name
if (gifUrl.isNotEmpty()) {
loadImgGif()
} else {
Glide.with(this)
.load(displayUrl)
.thumbnail(Glide.with(this).load(thumb))
.into(imgData)
}
}
private fun onClick() {
imgBack.setOnClickListener {
finish()
}
imgLike.setOnClickListener {
showTPAD(this) {
imgLike.isSelected = !imgLike.isSelected
lifecycleScope.launch {
if (imgLike.isSelected) {
FavoriteRepository.addLike(data)
} else {
FavoriteRepository.removeLike(data)
}
}
}
}
val forYouList = App.Companion.list.filter {
it.parentName == getString(R.string.recommend_name)
}
recommendedRecycler.run {
adapter = RecommendedThemeAdapter(
this@ThemeDetailActivity
).apply {
val shuffled = forYouList[0].keyboardList.shuffled()
setForYouList(shuffled)
}.apply {
setClickAction(object : ItemClickListener {
override fun OnItemClickListener() {
finish()
}
})
}
layoutManager = GridLayoutManager(this@ThemeDetailActivity, 2)
}
}
@SuppressLint("CheckResult")
private fun loadImgGif() {
Glide.with(this)
.load(gifUrl)
.thumbnail(Glide.with(this).load(thumb))
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(imgData)
}
@SuppressLint("ClickableViewAccessibility")
private fun setApply() {
// 添加按下态缩放动画
applyBtn.setOnTouchListener { v, event ->
when (event.action) {
android.view.MotionEvent.ACTION_DOWN -> {
val scaleX = ObjectAnimator.ofFloat(v, "scaleX", 1f, 0.95f)
val scaleY = ObjectAnimator.ofFloat(v, "scaleY", 1f, 0.95f)
val animatorSet = AnimatorSet()
animatorSet.playTogether(scaleX, scaleY)
animatorSet.duration = 100
animatorSet.start()
}
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
val scaleX = ObjectAnimator.ofFloat(v, "scaleX", 0.95f, 1f)
val scaleY = ObjectAnimator.ofFloat(v, "scaleY", 0.95f, 1f)
val animatorSet = AnimatorSet()
animatorSet.playTogether(scaleX, scaleY)
animatorSet.duration = 100
animatorSet.start()
}
}
false
}
applyBtn.setOnClickListener {
val checkEnable = AppUtils.checkEnable(this)
val checkSetDefault = AppUtils.checkSetDefault(this)
if (!checkEnable || !checkSetDefault) {
showDialog()
return@setOnClickListener
}
startDown()
}
}
private fun showDialog() {
dialog = dialog ?: EnableKeyboardDialog.newInstance() .apply{
}
dialog?.setClickListener {
startDown()
}
dialog?.show(supportFragmentManager, "")
}
private fun startDown() {
loadingLayout.isVisible = true
applyBtn.isEnabled = false
val file = File(unzipPath)
if (file.exists()) {
val findFirstDirectory = ZipFileHandler.findFirstDirectory(file)
apply("${findFirstDirectory}/")
applyBtn.isEnabled = true
loadingLayout.isVisible = false
} else {
ZipFileHandler.startDownloadZip(zipUrl, object :
KeyboardApplyCallback {
override fun OnApplySkinListener(fileList: List<File?>?) {
runOnUiThread {
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
if (fileList.isNullOrEmpty()) {
runOnUiThread {
Toast.makeText(
this@ThemeDetailActivity,
getString(R.string.download_fail),
Toast.LENGTH_SHORT
).show()
}
} else {
// lifecycleScope.launch {
// DbFunction.addDownload(data)
// }
if (file.exists()) {
val findFirstDirectory = ZipFileHandler.findFirstDirectory(file)
Log.d(
App.Companion.TAG,
"----apply------------it=$findFirstDirectory"
)
runOnUiThread {
apply("${findFirstDirectory}/")
}
}
}
}
})
}
}
private fun apply(path: String) {
var skinParentPath = path
if (path.contains("res")) {
skinParentPath = path.substringBeforeLast("res")
}
ThemePreferences.updateSkinPath(skinParentPath)
Toast.makeText(
this@ThemeDetailActivity,
getString(R.string.theme_application_successful),
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this, PreviewActivity::class.java).apply {
putExtra(PreviewActivity.key_name, name)
})
finish()
}
/**
* 初始化收藏状态
*/
private fun initLikeState() {
lifecycleScope.launch {
FavoriteRepository.getIsLike(name) { isLiked ->
imgLike.isSelected = isLiked
}
}
}
}

View File

@ -0,0 +1,74 @@
package com.app.input.breeze.board.uiactivity;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import com.ad.tradpluslibrary.TPAdManager;
import com.app.input.breeze.board.App;
import com.app.input.breeze.board.R;
import com.app.input.breeze.board.bean.KeyboardTheme;
import com.app.input.breeze.board.bean.CategoryWrapper;
import com.app.input.breeze.board.databinding.ThemeListScreenBinding;
import com.app.input.breeze.board.listutils.ThemeGridAdapter;
import com.app.input.breeze.board.utils.AppUtils;
import com.app.input.breeze.board.utils.GridItemDecoration;
import java.util.List;
public class ThemeListActivity extends AppCompatActivity {
private ThemeListScreenBinding vb;
public static final String KEY_NAME ="class_name";
private String name;
private List<KeyboardTheme> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ThemeListScreenBinding.inflate(getLayoutInflater());
EdgeToEdge.enable(this);
setContentView(vb.getRoot());
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
name = getIntent().getStringExtra(KEY_NAME);
TPAdManager.INSTANCE.showTPAD(this,()-> null);
initData();
initClick();
}
private void initData(){
vb.className.setText(name);
for (CategoryWrapper CategoryWrapper : App.list) {
if(CategoryWrapper.getParentName().equals(name)){
data = CategoryWrapper.getKeyboardList();
}
}
// 两列布局增大上下间距
GridItemDecoration GridItemDecoration = new GridItemDecoration(16, 12, 0);
ThemeGridAdapter adapterMain = new ThemeGridAdapter(this,data);
vb.recycler.setLayoutManager(new GridLayoutManager(ThemeListActivity.this,2));
vb.recycler.setAdapter(adapterMain);
vb.recycler.addItemDecoration(GridItemDecoration);
}
private void initClick(){
vb.back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TPAdManager.INSTANCE.showTPAD(ThemeListActivity.this,()-> {
finish();
return null;
} );
}
});
}
}

View File

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

View File

@ -0,0 +1,76 @@
package com.app.input.breeze.board.uifragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.app.input.breeze.board.App
import com.app.input.breeze.board.bean.KeyboardTheme
import com.app.input.breeze.board.databinding.FragmentFavoriteKeyboardBinding
import com.app.input.breeze.board.database.AppDatabase
import com.app.input.breeze.board.database.FavoriteRepository
import com.app.input.breeze.board.callback.RemoveFavoriteCallback
import com.app.input.breeze.board.listutils.FavoriteThemeAdapter
import com.app.input.breeze.board.utils.BreezeTextView
import kotlinx.coroutines.launch
class FavoriteKeyboardFragment : Fragment() {
private lateinit var vb: FragmentFavoriteKeyboardBinding
companion object {
@JvmStatic
fun newInstance() =
FavoriteKeyboardFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
vb = FragmentFavoriteKeyboardBinding.inflate(layoutInflater)
init()
return vb.root
}
private fun init() {
val mainAdapter = FavoriteThemeAdapter(
requireContext()
).apply {
setRemoveLike(object : RemoveFavoriteCallback {
override fun OnRemoveLike(data: KeyboardTheme) {
lifecycleScope.launch {
FavoriteRepository.removeLike(data)
}
}
})
}
vb.likeRecycler.run {
adapter = mainAdapter
layoutManager = GridLayoutManager(requireContext(), 2)
}
AppDatabase.instance.favoriteThemeDao().queryAllLike().observe(requireActivity()) { favorites ->
Log.d(App.Companion.TAG, "---------it=${favorites?.size}")
if(favorites.isNullOrEmpty()){
vb.likeRecycler.isVisible = false
vb.emptyTitle.isVisible = true
}else{
vb.likeRecycler.isVisible = true
vb.emptyTitle.isVisible = false
mainAdapter.setForYouList(favorites)
}
}
}
}

View File

@ -0,0 +1,73 @@
package com.app.input.breeze.board.uifragment
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.app.input.breeze.board.App
import com.app.input.breeze.board.bean.CategoryWrapper
import com.app.input.breeze.board.databinding.HomeFragmentBinding
import com.app.input.breeze.board.callback.ViewAllClickListener
import com.app.input.breeze.board.listutils.CategoryListAdapter
import com.app.input.breeze.board.uiactivity.ThemeListActivity
class HomeFragment : Fragment() {
private lateinit var vb: HomeFragmentBinding
lateinit var viewAllList: MutableList<CategoryWrapper>
private lateinit var adapterParent: CategoryListAdapter
companion object {
@JvmStatic
fun newInstance() =
HomeFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
vb = HomeFragmentBinding.inflate(layoutInflater)
setTabRecycler()
return vb.root
}
private fun setTabRecycler() {
viewAllList = App.Companion.list
adapterParent = CategoryListAdapter(
requireContext(), viewAllList
).apply {
setClickAction(object : ViewAllClickListener {
override fun OnClickSeeAll(name: String) {
startActivity(Intent(requireContext(),
ThemeListActivity::class.java).apply {
putExtra(ThemeListActivity.KEY_NAME,name)
})
}
})
}
vb.tabRecycler.run {
adapter = adapterParent
layoutManager = LinearLayoutManager(requireContext())
}
}
}

View File

@ -0,0 +1,115 @@
package com.app.input.breeze.board.utils
import android.app.Activity
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.provider.Settings
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import com.app.input.breeze.board.App
import com.bumptech.glide.Glide
import com.bumptech.glide.integration.webp.decoder.WebpDrawable
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import java.io.File
object AppUtils {
val transform = RequestOptions().transform(CenterCrop(), RoundedCorners(dpToPx(8f)))
fun initFullScreen(activity: Activity, dark: Boolean? = true) {
val window = activity.window
val decorView = window.decorView
val rootView = decorView.rootView
//
if (dark == null) return
if (dark) {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
)
} else {
decorView.setSystemUiVisibility(
(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
)
}
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
fun loadWepJif(mContext: Context, webpGifUrl: String, view: ImageView) {
Glide.with(mContext)
.load(webpGifUrl)
// .apply(transform)
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(view)
}
fun getBgDrawable(con: Context, filePath: String): Drawable? {
if (!File(filePath).exists()) {
return null
}
return BitmapDrawable(con.resources, BitmapFactory.decodeFile(filePath))
}
private val systemService =
App.Companion.appInstance.getSystemService(Context.INPUT_METHOD_SERVICE)
private val inputMethodManager = systemService as InputMethodManager
fun checkSetDefault(con: Context): Boolean {
val defaultId =
Settings.Secure.getString(con.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)
return defaultId != null && defaultId.startsWith(con.packageName)
}
fun checkEnable(con: Context): Boolean {
for (methodInfo in inputMethodManager.enabledInputMethodList) {
if (methodInfo.id.startsWith(con.packageName)) {
return true
}
}
return false
}
fun getTextForImeAction(imeOptions: Int): Int {
return imeOptions and EditorInfo.IME_MASK_ACTION
}
fun dpToPx(dpValue: Float): Int {
val scale = App.Companion.appInstance.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
}

View File

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

View File

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

View File

@ -0,0 +1,76 @@
package com.app.input.breeze.board.utils;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.app.input.breeze.board.App;
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private int v, h, ex;
public GridItemDecoration(int v, int h, int ex) {
this.v = Math.round(dpToPx(v));
this.h = Math.round(dpToPx(h));
this.ex = Math.round(dpToPx(ex));
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int spanCount = 1;
int spanSize = 1;
int spanIndex = 0;
int childAdapterPosition = parent.getChildAdapterPosition(view);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = staggeredGridLayoutManager.getSpanCount();
if (layoutParams.isFullSpan()) {
spanSize = spanCount;
}
spanIndex = layoutParams.getSpanIndex();
} else if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams) view.getLayoutParams();
spanCount = gridLayoutManager.getSpanCount();
spanSize = gridLayoutManager.getSpanSizeLookup().getSpanSize(childAdapterPosition);
spanIndex = layoutParams.getSpanIndex();
} else if (layoutManager instanceof LinearLayoutManager) {
outRect.left = v;
outRect.right = v;
outRect.bottom = h;
}
if (spanSize == spanCount) {
outRect.left = v + ex;
outRect.right = v + ex;
outRect.bottom = h;
} else {
int itemAllSpacing = (v * (spanCount + 1) + ex * 2) / spanCount;
int left = v * (spanIndex + 1) - itemAllSpacing * spanIndex + ex;
int right = itemAllSpacing - left;
outRect.left = left;
outRect.right = right;
outRect.bottom = h;
}
}
public static float dpToPx(float dpValue) {
float density = App.appInstance.getResources().getDisplayMetrics().density;
return density * dpValue + 0.5f;
}
}

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M608,736c-6.4,0 -19.2,0 -25.6,-6.4l-192,-192C384,524.8 384,499.2 390.4,486.4l192,-192c12.8,-12.8 32,-12.8 44.8,0s12.8,32 0,44.8L460.8,512l166.4,166.4c12.8,12.8 12.8,32 0,44.8C627.2,736 614.4,736 608,736z"
android:fillColor="@color/main_text_color"/>
</vector>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4,10.5c-0.83,0 -1.5,-0.67 -1.5,-1.5S3.17,7.5 4,7.5S5.5,8.17 5.5,9S4.83,10.5 4,10.5zM4,4.5c-0.83,0 -1.5,-0.67 -1.5,-1.5S3.17,1.5 4,1.5S5.5,2.17 5.5,3S4.83,4.5 4,4.5zM4,16.5c-0.83,0 -1.5,-0.67 -1.5,-1.5S3.17,13.5 4,13.5S5.5,14.17 5.5,15S4.83,16.5 4,16.5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,7h14L21,5L7,5v2z"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#30FFFFFF" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

View File

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

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#80FFFFFF" />
<size android:width="8dp" android:height="8dp" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FFFFFF" />
<size android:width="8dp" android:height="8dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/transparent"
android:strokeWidth="2"
android:strokeColor="#1A1A1A"
android:pathData="M20.84,4.61a5.5,5.5 0,0 0,-7.78 0L12,5.67 10.94,4.61a5.5,5.5 0,0 0,-7.78 7.78l1.06,1.06L12,21.23l7.78,-7.78 1.06,-1.06a5.5,5.5 0,0 0,0 -7.78z"/>
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M716.8,60.2c-81.3,0 -153.6,36.1 -204.8,96.4 -51.2,-60.2 -123.5,-96.4 -204.8,-96.4C153.6,60.2 30.1,192.8 30.1,358.4c0,45.2 6,87.3 21.1,126.5 0,0 12,36.1 21.1,57.2C189.7,798.1 512,963.8 512,963.8s322.3,-165.6 439.7,-424.7c0,0 15.1,-33.1 21.1,-57.2 12,-39.2 21.1,-81.3 21.1,-126.5C993.9,192.8 870.4,60.2 716.8,60.2zM915.6,466.8c-6,15.1 -15.1,42.2 -18.1,48.2 -87.3,192.8 -307.2,334.3 -385.5,379.5 -78.3,-45.2 -298.2,-186.7 -385.5,-379.5 -6,-15.1 -15.1,-36.1 -18.1,-51.2 -12,-33.1 -18.1,-72.3 -18.1,-105.4C90.4,225.9 189.7,120.5 307.2,120.5c60.2,0 117.5,27.1 159.6,75.3L512,247l45.2,-51.2C599.3,147.6 656.6,120.5 716.8,120.5c120.5,0 216.8,105.4 216.8,234.9 0,36.1 -6,72.3 -18.1,111.4z">
<aapt:attr name="android:fillColor" xmlns:aapt="http://schemas.android.com/aapt">
<gradient
android:startY="0"
android:startX="0"
android:endY="1024"
android:endX="0"
android:type="linear">
<item android:offset="0" android:color="#64B5F6"/>
<item android:offset="1" android:color="#FFFFFF"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M667.8,117.3C832.9,117.3 938.7,249.7 938.7,427.9c0,138.3 -125.1,290.5 -371.6,461.6a96.8,96.8 0,0 1,-110.2 0C210.4,718.4 85.3,566.1 85.3,427.9 85.3,249.7 191.1,117.3 356.2,117.3c59.6,0 100.1,20.8 155.8,68.1C567.7,138.2 608.2,117.3 667.8,117.3z">
<aapt:attr name="android:fillColor" xmlns:aapt="http://schemas.android.com/aapt">
<gradient
android:startY="0"
android:startX="0"
android:endY="1024"
android:endX="0"
android:type="linear">
<item android:offset="0" android:color="#FF6B9D"/>
<item android:offset="1" android:color="#FFB3D9"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="270"
android:startColor="#00000000"
android:centerColor="#66000000"
android:endColor="#CC000000"
android:type="linear" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="270"
android:startColor="#87CEEB"
android:centerColor="#B0E0E6"
android:endColor="#E0F6FF" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8.72,8L14.21,2.51C14.412,2.307 14.412,1.992 14.21,1.79C14.007,1.587 13.692,1.587 13.49,1.79L8,7.28L2.51,1.79C2.307,1.587 1.992,1.587 1.79,1.79C1.587,1.992 1.587,2.307 1.79,2.51L7.28,8L1.79,13.49C1.587,13.692 1.587,14.007 1.79,14.21C1.88,14.3 2.015,14.367 2.15,14.367C2.285,14.367 2.42,14.322 2.51,14.21L8,8.72L13.49,14.21C13.58,14.3 13.715,14.367 13.85,14.367C13.985,14.367 14.12,14.322 14.21,14.21C14.412,14.007 14.412,13.692 14.21,13.49L8.72,8Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="135"
android:startColor="#87CEEB"
android:centerColor="#B0E0E6"
android:endColor="#E0F6FF" />
</shape>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="#40FFFFFF" />
<corners android:radius="4dp" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="4dp" />
</shape>
</clip>
</item>
</layer-list>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/blue_primary" />
<size android:width="6dp" android:height="6dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/shape_set_button_pressed" android:state_pressed="true"/>
<item android:drawable="@drawable/shape_set_buttoon_bg" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

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

View File

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

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="horizontal"
android:background="@color/white"
android:elevation="8dp"
android:gravity="center_vertical">
<RelativeLayout
android:id="@+id/nav_home"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="@+id/nav_home_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/home_icon"
app:tint="#80000000" />
<TextView
android:id="@+id/nav_home_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/tab_home"
android:textSize="12sp"
android:textColor="#80000000" />
<!-- 閫変腑鎸囩ず锟?-->
<View
android:id="@+id/nav_home_indicator"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginTop="2dp"
android:background="@drawable/nav_indicator"
android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
<!-- Category 瀵艰埅锟?-->
<RelativeLayout
android:id="@+id/nav_category"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="@+id/nav_category_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/category_icon"
app:tint="#80000000" />
<TextView
android:id="@+id/nav_category_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/categories"
android:textSize="12sp"
android:textColor="#80000000" />
<!-- 閫変腑鎸囩ず锟?-->
<View
android:id="@+id/nav_category_indicator"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginTop="2dp"
android:background="@drawable/nav_indicator"
android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
<!-- Settings 瀵艰埅锟?-->
<RelativeLayout
android:id="@+id/nav_settings"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="@+id/nav_settings_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/settings_icon"
app:tint="#80000000" />
<TextView
android:id="@+id/nav_settings_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/settings"
android:textSize="12sp"
android:textColor="#80000000" />
<!-- 閫変腑鎸囩ず锟?-->
<View
android:id="@+id/nav_settings_indicator"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginTop="2dp"
android:background="@drawable/nav_indicator"
android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_margin="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp"
android:foreground="?attr/selectableItemBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/category_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="Category" />
<!-- 渐变遮罩 -->
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient_overlay" />
<!-- 内容 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:padding="20dp"
android:gravity="center">
<TextView
android:id="@+id/category_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Aesthetic"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#FFFFFF" />
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/home_bg_gradient">
<!-- 顶部导航栏 -->
<LinearLayout
android:id="@+id/top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/transparent"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/categories"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#1A1A1A" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="4dp"
android:text="@string/find_your_style"
android:textSize="14sp"
android:textColor="#1A1A1A"
android:alpha="0.7" />
</LinearLayout>
<!-- 分类卡片列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/category_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/top_bar"
android:layout_above="@id/bottom_navigation"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:clipToPadding="false" />
<!-- 底部导航栏 -->
<include
android:id="@+id/bottom_navigation"
layout="@layout/bottom_navigation_bar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:background="@drawable/dialog_unenable_bg"
android:paddingBottom="20dp"
android:layout_height="wrap_content">
<ImageView
android:layout_width="55dp"
android:layout_height="45dp"
android:padding="12dp"
android:layout_marginEnd="15dp"
android:id="@+id/im_close"
android:layout_marginTop="10dp"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/im_close_dialog"
app:layout_constraintRight_toRightOf="parent" />
<com.app.input.breeze.board.utils.BreezeTextView
android:id="@+id/text_open"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="50dp"
android:gravity="center"
app:apply_font="true"
android:text="@string/open_str"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/im_close" />
<LinearLayout
android:id="@+id/linear_step_one"
android:layout_width="280dp"
android:layout_height="52dp"
android:layout_marginTop="32dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:background="@drawable/selector_enable"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/text_open">
<ImageView
android:id="@+id/ok_one"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@drawable/already_enabled_icon"
android:visibility="gone" />
<com.app.input.breeze.board.utils.BreezeTextView
android:id="@+id/text_step_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/step_1"
app:apply_font="true"
android:textColor="@color/white"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/linear_step_two"
android:layout_width="280dp"
android:layout_height="52dp"
android:layout_marginTop="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:background="@drawable/selector_enable"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/linear_step_one">
<ImageView
android:id="@+id/ok_two"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@drawable/already_enabled_icon"
android:visibility="gone" />
<com.app.input.breeze.board.utils.BreezeTextView
android:id="@+id/text_step_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/step_2"
app:apply_font="true"
android:textColor="@color/white"
android:textSize="15sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/home_bg_gradient">
<!-- 椤堕儴瀵艰埅锟?-->
<LinearLayout
android:id="@+id/top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/transparent"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<ImageView
android:id="@+id/back"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/back_icon"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="6dp"
android:contentDescription="Back" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:text="@string/tab_favorite"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#1A1A1A" />
</LinearLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/like_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/top_bar"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="15dp" />
<com.app.input.breeze.board.utils.BreezeTextView
android:id="@+id/empty_title"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginStart="25dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="25dp"
android:gravity="center"
android:lineSpacingExtra="2dp"
android:text="@string/no_like"
android:textColor="@color/main_text_color"
android:textSize="16sp"
android:visibility="gone"
app:apply_font="true" />
</RelativeLayout>

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