This commit is contained in:
litingting 2026-01-16 15:23:32 +08:00
commit 77ae2d96ea
128 changed files with 30698 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/EuphoriaBoard Normal file

Binary file not shown.

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

@ -0,0 +1,103 @@
import java.io.FileInputStream
import java.util.Date
import java.text.SimpleDateFormat
import java.util.Properties
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id ("kotlin-android")
}
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties().apply {
load(FileInputStream(keystorePropertiesFile))
}
val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date())
android {
namespace = "com.joyful.euphoria.board"
compileSdk = 36
defaultConfig {
applicationId = "com.joyful.euphoria.board"
minSdk = 24
targetSdk = 36
versionCode = 2
versionName = "1.1"
setProperty(
"archivesBaseName",
"EuphoriaBoard_V" + versionName + "(${versionCode})_$timestamp"
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
create("release") {
storeFile = file(keystoreProperties["keystoreFile"] as String)
storePassword = keystoreProperties["key_store_password"] as String
keyAlias = keystoreProperties["key_alias"] as String
keyPassword = keystoreProperties["key_password"] as String
}
}
buildTypes {
release {
isMinifyEnabled = true
signingConfig = signingConfigs.getByName("release")
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.code.gson:gson:2.10.1")
implementation("com.google.android.material:material:1.9.0")
// Kotlin标准库
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21")
// 协程核心库
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
// Android平台协程支持
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
}

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

@ -0,0 +1,56 @@
# 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.** { *; }
-keep class com.google.gson.** { *; }
-keepattributes Signature
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
-keepattributes AnnotationDefault,RuntimeVisibleAnnotations
-keep class com.joyful.euphoria.board.database.CategoryData { *; }
-keep class com.joyful.euphoria.board.database.BoardData { *; }
-keep class com.joyful.euphoria.board.database.BoardData$Detail { *; }
-keep class com.joyful.euphoria.board.database.BoardData$ThemeContent { *; }

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,58 @@
<?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" />
<application
android:name=".EuphoriaBoard"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:theme="@style/MyKeyBoard"
tools:targetApi="31">
<activity
android:name=".xmlactivity.CategoryXML"
android:exported="false" />
<activity
android:name=".xmlactivity.BeginXML"
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=".xmlactivity.DownloadXML"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".xmlactivity.PreviewXML"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".xmlactivity.HomePageXML"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<service
android:name=".assistant.ServiceAssistant"
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.

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
package com.joyful.euphoria.board
import android.app.Application
import android.graphics.Typeface
import com.joyful.euphoria.board.beaned.BeanDetails
import com.joyful.euphoria.board.beaned.BeanWrapper
//import com.pretty.keyboard.theme.keyboard.helper.ObjectBox
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 EuphoriaBoard : Application() {
companion object {
lateinit var euphoriaBoardInstance: EuphoriaBoard
lateinit var list: MutableList<BeanWrapper>
const val TAG = "-----------------"
var defaultFont: Typeface? = null
const val DB_VERSION = 4
const val DB_NAME = "db_name"
}
override fun onCreate() {
super.onCreate()
euphoriaBoardInstance = this
defaultFont = Typeface.createFromAsset(assets, "my_font.ttf")
dealFile()
}
private fun dealFile() {
val openFile = euphoriaBoardInstance.assets.open("res.json")
val jsonString = getJsonString(openFile)
if (jsonString != null) {
resolveJsonString(jsonString)
}
}
private fun resolveJsonString(string: String) {
val jsonData = JSONArray(string)
var dataList: MutableList<BeanWrapper> = mutableListOf()
for (i in 0 until jsonData.length()) {
jsonData.getJSONObject(i).run {
val pName = getString("parent_name")
val listArray = getJSONArray("keyboard_list")
var beanDetailsList: MutableList<BeanDetails> = 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 {
}
beanDetailsList.add(
BeanDetails().apply {
setImgPath(imgPath)
setZipPath(zipPath)
setTitleName(title)
setImgGif(imgGif)
thumbUrl = thUrl
thumbGif = thGif
})
}
}
val shuffled = beanDetailsList.shuffled()
val dataBeanWrapper = BeanWrapper()
.apply {
parentName = pName
keyboardList = shuffled
}
dataList.add(dataBeanWrapper)
}
}
updateDataList(dataList)
}
private fun updateDataList(mainList: MutableList<BeanWrapper>) {
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,36 @@
package com.joyful.euphoria.board.assistant
import com.joyful.euphoria.board.beaned.BeanDetails
import com.joyful.euphoria.board.database.BoardDataBase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object DatabaseAssistant {
suspend fun addLike(data: BeanDetails) {
withContext(Dispatchers.IO) {
BoardDataBase.Companion.boardDataBase.ThemesDao().insertData(data)
}
}
suspend fun removeLike(data: BeanDetails) {
withContext(Dispatchers.IO) {
val queryIsLike =
BoardDataBase.Companion.boardDataBase.ThemesDao().queryIsLike(data.titleName)
if (queryIsLike != null) {
BoardDataBase.Companion.boardDataBase.ThemesDao().delete(queryIsLike)
}
}
}
suspend fun getIsLike(name: String, action: (isLike: Boolean) -> Unit) {
withContext(Dispatchers.IO) {
val query = BoardDataBase.Companion.boardDataBase.ThemesDao().queryIsLike(name)
withContext(Dispatchers.Main) {
action.invoke(query != null)
}
}
}
}

View File

@ -0,0 +1,100 @@
package com.joyful.euphoria.board.assistant
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.joyful.euphoria.board.EuphoriaBoard
import com.joyful.euphoria.board.boardcode.BoardCode
import com.joyful.euphoria.board.utils.KeyNamesUtils
import com.joyful.euphoria.board.utils.SaveCurrentThemeUtils
import java.io.File
object KeyboardActionAssistant {
fun keyToUpper(mKeyBoard: BoardCode) {
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: BoardCode) {
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
) {
SaveCurrentThemeUtils.getSkinPath()?.let { resPath ->
val videoPath = "${resPath}res/raw/${KeyNamesUtils.videoName}"
val videoPath2 = "${resPath}res/raw/${KeyNamesUtils.video}"
val backgroundPath = "${resPath}res/drawable-xxhdpi-v4/${KeyNamesUtils.bgName}"
val backgroundPath_png = "${resPath}res/drawable-xxhdpi-v4/${KeyNamesUtils.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(EuphoriaBoard.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(
EuphoriaBoard.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.joyful.euphoria.board.assistant;
// 按键对象模型
public class KeyboardKeyModelAssistant {
private String name;
private String background;
private String label;
public KeyboardKeyModelAssistant(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,168 @@
package com.joyful.euphoria.board.assistant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class ResponseConfigAssistant {
private String version;
private String supportLayouts;
private int hideHint;
private String layoutStyle;
private List<ResponseLayoutAssistant> layouts = new ArrayList<>();
private List<KeyboardKeyModelAssistant> keyboardKeyModelAssistantList = 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<ResponseLayoutAssistant> getLayouts() {
return layouts;
}
public void addLayout(ResponseLayoutAssistant layout) {
this.layouts.add(layout);
}
public List<KeyboardKeyModelAssistant> getKeyList() {
return keyboardKeyModelAssistantList;
}
public KeyboardKeyModelAssistant getLastKeyList() {
return keyboardKeyModelAssistantList.isEmpty() ? null : keyboardKeyModelAssistantList.get(keyboardKeyModelAssistantList.size() - 1);
}
public void addKey(KeyboardKeyModelAssistant keyboardKeyModelAssistant) {
this.keyboardKeyModelAssistantList.add(keyboardKeyModelAssistant);
}
public LinkedHashMap<String, String> getMaps() {
return maps;
}
public void setMaps(LinkedHashMap<String, String> maps) {
this.maps = maps;
}
public String getKeyDefault() {
return KeyDefault;
}
public void setKeyDefault(String keyDefault) {
KeyDefault = keyDefault;
}
public String getKeyMarkDefault() {
return KeyMarkDefault;
}
public void setKeyMarkDefault(String keyMarkDefault) {
KeyMarkDefault = keyMarkDefault;
}
public String getKeyFuncDefault() {
return KeyFuncDefault;
}
public void setKeyFuncDefault(String keyFuncDefault) {
KeyFuncDefault = keyFuncDefault;
}
public String getKeyShift() {
return KeyShift;
}
public void setKeyShift(String keyShift) {
KeyShift = keyShift;
}
public String getKeyDelete() {
return KeyDelete;
}
public void setKeyDelete(String keyDelete) {
KeyDelete = keyDelete;
}
public String getKeyAlphaSymbol() {
return KeyAlphaSymbol;
}
public void setKeyAlphaSymbol(String keyAlphaSymbol) {
KeyAlphaSymbol = keyAlphaSymbol;
}
public String getKeyEmoji() {
return KeyEmoji;
}
public void setKeyEmoji(String keyEmoji) {
KeyEmoji = keyEmoji;
}
public String getKeyMark() {
return KeyMark;
}
public void setKeyMark(String keyMark) {
KeyMark = keyMark;
}
public String getKeySpace() {
return KeySpace;
}
public void setKeySpace(String keySpace) {
KeySpace = keySpace;
}
public String getKeyEnter() {
return KeyEnter;
}
public void setKeyEnter(String keyEnter) {
KeyEnter = keyEnter;
}
}

View File

@ -0,0 +1,35 @@
package com.joyful.euphoria.board.assistant;
import java.util.ArrayList;
import java.util.List;
public class ResponseLayoutAssistant {
private String name;
private List<KeyboardKeyModelAssistant> keyboardKeyModelAssistants = new ArrayList<>();
public ResponseLayoutAssistant(String name) {
this.name = name;
}
// Getters and Setters
public String getName() {
return name;
}
public List<KeyboardKeyModelAssistant> getKeys() {
return keyboardKeyModelAssistants;
}
public void addKey(KeyboardKeyModelAssistant keyboardKeyModelAssistant) {
this.keyboardKeyModelAssistants.add(keyboardKeyModelAssistant);
}
public KeyboardKeyModelAssistant getLastKey() {
return keyboardKeyModelAssistants.isEmpty() ? null : keyboardKeyModelAssistants.get(keyboardKeyModelAssistants.size() - 1);
}
}

View File

@ -0,0 +1,286 @@
package com.joyful.euphoria.board.assistant;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
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.joyful.euphoria.board.EuphoriaBoard;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.utils.KeyNamesUtils;
import com.joyful.euphoria.board.utils.Normal;
import com.joyful.euphoria.board.utils.SaveCurrentThemeUtils;
import com.joyful.euphoria.board.boardcode.BoardCode;
import com.joyful.euphoria.board.boardcode.BoardViewCode;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
public class ServiceAssistant extends android.inputmethodservice.InputMethodService implements BoardViewCode.OnKeyboardActionListener {
private ViewCodeAssistant keyBoardView;
private BoardCode 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.default_keyboard, null);
findView();
return parentView;
}
private void findView() {
imBG = parentView.findViewById(R.id.gif_bg);
videoView = parentView.findViewById(R.id.video_view);
mKeyBoard = new BoardCode(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 = Normal.INSTANCE.getTextForImeAction(currentInputEditorInfo.imeOptions);
String skinPath = SaveCurrentThemeUtils.INSTANCE.getSkinPath();
if (skinPath == null || skinPath.isEmpty()) {
Log.d(EuphoriaBoard.TAG, "---------skinPath= bull");
keyBoardView.updateUi(curImeAction);
} else {
Log.d(EuphoriaBoard.TAG, "---------skinPath= 1111");
KeyboardActionAssistant.INSTANCE.readBgOrVideo(this, new Function2<String, Drawable, Unit>() {
@Override
public Unit invoke(String s, Drawable drawable) {
Log.d(EuphoriaBoard.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(ServiceAssistant.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();
if (videoView != null)
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 KeyNamesUtils.KEY_CODE_DELETE:
curInputConnect.deleteSurroundingText(1, 0);
break;
case KeyNamesUtils.KEY_CODE_SHIFT:
switchShift();
break;
case KeyNamesUtils.KEY_CODE_NUMBER_SHIFT:
case KeyNamesUtils.KEY_CODE_SYMBOL_SHIFT:
switchMoreOrNumber();
break;
case KeyNamesUtils.KEY_CODE_CHANGE_NUMBER:
case KeyNamesUtils.KEY_CODE_BACK:
switchNormalOrNumber();
break;
case KeyNamesUtils.KEY_CODE_COMPLETE:
case KeyNamesUtils.KEY_CODE_CANCEL:
curInputConnect.performEditorAction(curImeAction);
// curInputConnect.performEditorAction(EditorInfo.IME_ACTION_DONE);
break;
default:
String codeToChar = KeyboardActionAssistant.INSTANCE.primaryCodeToChar(primaryCode);
curInputConnect.commitText(codeToChar, 1);
if (keyBoardView.isLowerCase() == 1) {
//自动转小写
keyBoardView.setLowerCase(0);
KeyboardActionAssistant.INSTANCE.keyToLowerCase(mKeyBoard);
keyBoardView.setKeyboard(mKeyBoard);
}
break;
}
}
private void switchMoreOrNumber() {
int mode = keyBoardView.getMode();
switch (mode) {
case 1:
mKeyBoard = new BoardCode(this, c);
keyBoardView.setMode(2);
keyBoardView.setKeyboard(mKeyBoard);
break;
case 2:
mKeyBoard = new BoardCode(this, b);
keyBoardView.setMode(1);
keyBoardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchNormalOrNumber() {
int mode = keyBoardView.getMode();
switch (mode) {
case 0:
mKeyBoard = new BoardCode(this, b);
keyBoardView.setMode(1);
keyBoardView.setKeyboard(mKeyBoard);
break;
case 1:
case 2:
mKeyBoard = new BoardCode(this, a);
keyBoardView.setMode(0);
keyBoardView.setKeyboard(mKeyBoard);
break;
}
}
private void switchShift() {
int lowerCase = keyBoardView.isLowerCase();
switch (lowerCase) {
case 0:
//当前小写转大写
keyBoardView.setLowerCase(1);
KeyboardActionAssistant.INSTANCE.keyToUpper(mKeyBoard);
keyBoardView.setKeyboard(mKeyBoard);
break;
case 1:
//当前大写转锁定大写
keyBoardView.setLowerCase(2);
break;
case 2:
//当前锁定大写转小写
keyBoardView.setLowerCase(0);
KeyboardActionAssistant.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,325 @@
package com.joyful.euphoria.board.assistant;
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.joyful.euphoria.board.EuphoriaBoard;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.boardcode.BoardCode;
import com.joyful.euphoria.board.boardcode.BoardViewCode;
import com.joyful.euphoria.board.utils.KeyNamesUtils;
import com.joyful.euphoria.board.utils.ThemesUtils;
import java.util.ArrayList;
import java.util.List;
public class ViewCodeAssistant extends BoardViewCode {
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 ThemesUtils themesUtils;
private int curImeAction = EditorInfo.IME_ACTION_UNSPECIFIED;
public ViewCodeAssistant(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setAttribute(attrs, context);
}
public ViewCodeAssistant(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
setAttribute(attrs, context);
}
public ViewCodeAssistant(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(EuphoriaBoard.TAG, "----------ime=" + ime);
curImeAction = ime;
themesUtils.updateSkinConfig();
invalidate();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
}
private void setAttribute(AttributeSet attrs, Context con) {
themesUtils = new ThemesUtils(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);
ResponseConfigAssistant config = themesUtils.getConfig();
List<KeyboardKeyModelAssistant> keyboardKeyModelAssistants = new ArrayList<>();
int i = 0;
for (BoardCode.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;
ResponseLayoutAssistant responseLayoutAssistant = config.getLayouts().get(0);
keyboardKeyModelAssistants = responseLayoutAssistant.getKeys();
} else if (code == 97||code == 65 || code == 33||code == 126) {
i = 0;
ResponseLayoutAssistant responseLayoutAssistant = config.getLayouts().get(1);
keyboardKeyModelAssistants = responseLayoutAssistant.getKeys();
}else if (code == -1 || code == -103||code==-101) {
i = 0;
ResponseLayoutAssistant responseLayoutAssistant = config.getLayouts().get(2);
keyboardKeyModelAssistants = responseLayoutAssistant.getKeys();
}else if (code == -2 || code == -102) {
i = 0;
ResponseLayoutAssistant responseLayoutAssistant = config.getLayouts().get(3);
keyboardKeyModelAssistants = responseLayoutAssistant.getKeys();
}
String background = keyboardKeyModelAssistants.get(i).getBackground()+".9.png";
i++;
Drawable configBg = themesUtils.getConfigBg(background);
realNewDraw(configBg, curKey, canvas, code);
} else {
realDraw(curKey, canvas, code);
}
}
}
private void realNewDraw(Drawable configBg, BoardCode.Key curKey, Canvas canvas, int code) {
switch (code) {
case KeyNamesUtils.KEY_CODE_SHIFT:
// drawAllShift(curKey, canvas);
onDrawCurKey(curKey, canvas, "Shift", configBg, null);
break;
case KeyNamesUtils.KEY_CODE_NUMBER_SHIFT:
onDrawCurKey(curKey, canvas, "More", configBg, null);
break;
case KeyNamesUtils.KEY_CODE_DELETE:
onDrawCurKey(curKey, canvas, "Delete", configBg, null);
break;
case KeyNamesUtils.KEY_CODE_SYMBOL_SHIFT:
onDrawCurKey(curKey, canvas, "123", configBg, null);
break;
case KeyNamesUtils.KEY_CODE_CHANGE_NUMBER:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
case KeyNamesUtils.KEY_CODE_BACK:
onDrawCurKey(curKey, canvas, "Back", configBg, null);
break;
case KeyNamesUtils.KEY_CODE_SPACE:
onDrawCurKey(curKey, canvas, null, configBg, null);
break;
case KeyNamesUtils.KEY_CODE_COMPLETE, KeyNamesUtils.KEY_CODE_CANCEL:
Log.d(EuphoriaBoard.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(BoardCode.Key curKey, Canvas canvas, int code) {
switch (code) {
case KeyNamesUtils.KEY_CODE_SHIFT:
drawAllShift(curKey, canvas);
break;
case KeyNamesUtils.KEY_CODE_NUMBER_SHIFT:
onDrawCurKey(curKey, canvas, "More", themesUtils.getFunctionDraw(), null);
break;
case KeyNamesUtils.KEY_CODE_DELETE:
onDrawCurKey(curKey, canvas, "Delete", themesUtils.getFunctionDraw(), null);
break;
case KeyNamesUtils.KEY_CODE_SYMBOL_SHIFT:
onDrawCurKey(curKey, canvas, "123", themesUtils.getFunctionDraw(), null);
break;
case KeyNamesUtils.KEY_CODE_CHANGE_NUMBER:
onDrawCurKey(curKey, canvas, null, themesUtils.getToDraw(), null);
break;
case KeyNamesUtils.KEY_CODE_BACK:
onDrawCurKey(curKey, canvas, "Back", themesUtils.getToDraw(), null);
break;
case KeyNamesUtils.KEY_CODE_SPACE:
onDrawCurKey(curKey, canvas, null, themesUtils.getSpaceDraw(), null);
break;
case KeyNamesUtils.KEY_CODE_COMPLETE, KeyNamesUtils.KEY_CODE_CANCEL:
Log.d(EuphoriaBoard.TAG, "-11111111111---------curImeAction=" + curImeAction);
if (curImeAction == EditorInfo.IME_ACTION_SEARCH) {
onDrawCurKey(curKey, canvas, "Search", themesUtils.getFunctionDraw(), null);
} else {
onDrawCurKey(curKey, canvas, "Done", themesUtils.getFunctionDraw(), null);
}
break;
default:
onDrawCurKey(curKey, canvas, null, themesUtils.getGeneralDraw(), null);
break;
}
}
private void drawAllShift(BoardCode.Key curKey, Canvas canvas) {
if (isLowerCase == 0) {
onDrawCurKey(curKey, canvas, "Shift", themesUtils.getFunctionDraw(), null);
} else if (isLowerCase == 1) {
onDrawCurKey(curKey, canvas, "Shift", themesUtils.getFunctionDraw(), null);
} else if (isLowerCase == 2) {
onDrawCurKey(curKey, canvas, "Shift", themesUtils.getFunctionDraw(), null);
}
}
private void onDrawCurKey(BoardCode.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(BoardCode.Key curKey, Canvas curCanvas, String label) {
mPaint.setColor(themesUtils.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(BoardCode.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(BoardCode.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,28 @@
package com.joyful.euphoria.board.beaned;
public class BeanCate {
private String categoryName;
private String categoryCoverUrl;
public BeanCate(String categoryName, String categoryCoverUrl) {
this.categoryName = categoryName;
this.categoryCoverUrl = categoryCoverUrl;
}
// Getter/Setter
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getCategoryCoverUrl() {
return categoryCoverUrl;
}
public void setCategoryCoverUrl(String categoryCoverUrl) {
this.categoryCoverUrl = categoryCoverUrl;
}
}

View File

@ -0,0 +1,105 @@
package com.joyful.euphoria.board.beaned;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.io.Serializable;
import java.util.List;
@Entity
public class BeanDetails 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;
private List<String> previewImages;
private boolean isFavorite;
private String zipUrl; // 下载链接字段
// Getter & Setter
public String getZipUrl() { return zipUrl != null ? zipUrl : ""; }
public void setZipUrl(String zipUrl) { this.zipUrl = zipUrl; }
public List<String> getPreviewImages() { return previewImages; }
public void setPreviewImages(List<String> previewImages) {
this.previewImages = previewImages;
}
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;
}
public boolean isFavorite() {
return isFavorite;
}
public void setFavorite(boolean favorite) {
isFavorite = favorite;
}
}

View File

@ -0,0 +1,27 @@
package com.joyful.euphoria.board.beaned;
import java.util.List;
public class BeanWrapper {
private String parentName;
private List<BeanDetails> keyboardList;
public String getParentName() {
return parentName;
}
public List<BeanDetails> getKeyboardList() {
return keyboardList;
}
public void setParentName(String name) {
this.parentName = name;
}
public void setKeyboardList(List<BeanDetails> keyboardList) {
this.keyboardList = keyboardList;
}
}

View File

@ -0,0 +1,32 @@
package com.joyful.euphoria.board.beaned;
import java.io.Serializable;
import java.util.List;
public class CateFullData implements Serializable { // 新增Serializable
private String categoryName;
private List<BeanDetails> keyboardList;
// 构造方法
public CateFullData(String categoryName, List<BeanDetails> keyboardList) {
this.categoryName = categoryName;
this.keyboardList = keyboardList;
}
// Getter & Setter
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public List<BeanDetails> getKeyboardList() {
return keyboardList;
}
public void setKeyboardList(List<BeanDetails> keyboardList) {
this.keyboardList = keyboardList;
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
package com.joyful.euphoria.board.database;
public class BoardData {
private String key;
private String thumbUrl;
private String thumbUrlGif;
private String title;
private Detail detail;
// Getter & Setter
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public String getThumbUrl() { return thumbUrl; }
public void setThumbUrl(String thumbUrl) { this.thumbUrl = thumbUrl; }
public String getThumbUrlGif() { return thumbUrlGif; }
public void setThumbUrlGif(String thumbUrlGif) { this.thumbUrlGif = thumbUrlGif; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public Detail getDetail() { return detail; }
public void setDetail(Detail detail) { this.detail = detail; }
// Detail子实体类
public static class Detail {
private String key;
private ThemeContent themeContent;
private String title;
// Getter & Setter
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public ThemeContent getThemeContent() { return themeContent; }
public void setThemeContent(ThemeContent themeContent) { this.themeContent = themeContent; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}
// ThemeContent子实体类
public static class ThemeContent {
private String androidRawZipUrl;
private String img;
private String imgBanner;
private String imgPreviewGif;
private String pushBanner;
private String pushIcon;
// Getter & Setter
public String getAndroidRawZipUrl() { return androidRawZipUrl; }
public void setAndroidRawZipUrl(String androidRawZipUrl) { this.androidRawZipUrl = androidRawZipUrl; }
public String getImg() { return img; }
public void setImg(String img) { this.img = img; }
public String getImgBanner() { return imgBanner; }
public void setImgBanner(String imgBanner) { this.imgBanner = imgBanner; }
public String getImgPreviewGif() { return imgPreviewGif; }
public void setImgPreviewGif(String imgPreviewGif) { this.imgPreviewGif = imgPreviewGif; }
public String getPushBanner() { return pushBanner; }
public void setPushBanner(String pushBanner) { this.pushBanner = pushBanner; }
public String getPushIcon() { return pushIcon; }
public void setPushIcon(String pushIcon) { this.pushIcon = pushIcon; }
}
}

View File

@ -0,0 +1,33 @@
package com.joyful.euphoria.board.database
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.joyful.euphoria.board.EuphoriaBoard
import com.joyful.euphoria.board.beaned.BeanDetails
import androidx.room.TypeConverters
@Database(
entities = [BeanDetails::class],
version = EuphoriaBoard.Companion.DB_VERSION,
exportSchema = false
)
@TypeConverters(TypeTransform::class)
abstract class BoardDataBase : RoomDatabase() {
abstract fun ThemesDao(): DetailsDao
companion object {
val boardDataBase: BoardDataBase by lazy {
Room.databaseBuilder(
EuphoriaBoard.Companion.euphoriaBoardInstance,
BoardDataBase::class.java,
EuphoriaBoard.Companion.DB_NAME
).fallbackToDestructiveMigration().build()
}
}
}

View File

@ -0,0 +1,16 @@
package com.joyful.euphoria.board.database;
import java.util.List;
public class CategoryData {
private String parent_name;
private List<BoardData> keyboard_list;
// Getter & Setter
public String getParent_name() { return parent_name; }
public void setParent_name(String parent_name) { this.parent_name = parent_name; }
public List<BoardData> getKeyboard_list() { return keyboard_list; }
public void setKeyboard_list(List<BoardData> keyboard_list) { this.keyboard_list = keyboard_list; }
}

View File

@ -0,0 +1,31 @@
package com.joyful.euphoria.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.joyful.euphoria.board.beaned.BeanDetails
@Dao
interface DetailsDao {
@Insert(onConflict = OnConflictStrategy.Companion.IGNORE)
suspend fun insertData(data: BeanDetails): Long
@Query("select * from BeanDetails ")
fun queryAllLike(): LiveData<List<BeanDetails?>?>
@Query("select * from BeanDetails where titleName = :title ")
suspend fun queryIsLike(title: String ): BeanDetails?
@Delete
suspend fun delete(data: BeanDetails)
@Update
suspend fun updateLike(data: BeanDetails)
}

View File

@ -0,0 +1,47 @@
package com.joyful.euphoria.board.database;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* 专门处理List<String>与String之间的转换供Room数据库使用
*/
public class TypeTransform {
// 初始化Gson实例
private static final Gson gson = new Gson();
/**
* 序列化将List<String>转换为JSON格式的String
*/
@androidx.room.TypeConverter // 单数注解标记这是一个类型转换方法
public static String listToString(List<String> stringList) {
// 处理空列表避免序列化异常
if (stringList == null || stringList.isEmpty()) {
return null;
}
// 获取List<String>的类型令牌
Type type = new TypeToken<List<String>>() {}.getType();
// 转换为JSON字符串
return gson.toJson(stringList, type);
}
/**
* 反序列化将JSON格式的String转换为List<String>
*/
@androidx.room.TypeConverter // 单数注解标记这是一个类型转换方法
public static List<String> stringToList(String jsonString) {
// 处理空字符串避免反序列化异常
if (jsonString == null || jsonString.trim().isEmpty()) {
return new ArrayList<>(); // 返回空列表避免后续空指针
}
// 获取List<String>的类型令牌
Type type = new TypeToken<List<String>>() {}.getType();
// 转换为List<String>
return gson.fromJson(jsonString, type);
}
}

View File

@ -0,0 +1,134 @@
package com.joyful.euphoria.board.frag;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.recyclerview.RecyclerviewDialogCollect;
import com.joyful.euphoria.board.beaned.CateFullData;
import com.joyful.euphoria.board.xmlactivity.DownloadXML;
import com.joyful.euphoria.board.utils.ListDecorationUtils;
public class Category extends DialogFragment {
private static final String ARG_CATE_DATA = "arg_cate_data";
private CateFullData mCateFullData;
// 静态工厂方法传递参数并创建实例
public static Category newInstance(CateFullData cateFullData) {
Category fragment = new Category();
Bundle args = new Bundle();
args.putSerializable(ARG_CATE_DATA, cateFullData); // 需确保CateFullData实现Serializable
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 接收传递的分类数据
if (getArguments() != null) {
mCateFullData = (CateFullData) getArguments().getSerializable(ARG_CATE_DATA);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_category, container, false);
initView(view);
return view;
}
private void initView(View view) {
if (mCateFullData == null) {
dismiss(); // 无数据则关闭弹窗
return;
}
// 绑定弹窗标题
TextView tvTitle = view.findViewById(R.id.tv_dialog_title);
tvTitle.setText(mCateFullData.getCategoryName());
// 弹窗RecyclerView两列网格
RecyclerView rvDialog = view.findViewById(R.id.rv_dialog_category);
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2);
rvDialog.setLayoutManager(layoutManager);
rvDialog.addItemDecoration(new ListDecorationUtils(10, 10, 0));
// 弹窗适配器
RecyclerviewDialogCollect dialogAdapter = new RecyclerviewDialogCollect(getContext(), mCateFullData.getKeyboardList());
rvDialog.setAdapter(dialogAdapter);
// 弹窗中键盘点击 跳转Download+关闭弹窗
dialogAdapter.setOnKeyboardClickListener(bean -> {
if (bean == null || bean.getTitleName() == null) {
Toast.makeText(getActivity(), "键盘数据异常", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(getActivity(), DownloadXML.class);
intent.putExtra(DownloadXML.SOURCE_KEY, bean);
intent.putExtra(DownloadXML.NAME_KEY, bean.getTitleName());
intent.putExtra(DownloadXML.DISPLAY_URL_KEY, bean.getImgPath());
intent.putExtra(DownloadXML.GIF_KEY, bean.getImgGif());
intent.putExtra(DownloadXML.THUMB_KEY, bean.getThumbUrl());
intent.putExtra(DownloadXML.ZIP_URL_KEY, bean.getZipPath());
startActivity(intent);
dismiss(); // 关闭弹窗
});
}
@Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog == null) return;
Window window = dialog.getWindow();
if (window == null) return;
// 窗口配置宽度全屏底部对齐
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.gravity = Gravity.BOTTOM;
params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
window.setAttributes(params);
// 适配虚拟按键
View decorView = window.getDecorView();
decorView.setOnApplyWindowInsetsListener((v, insets) -> {
int navBarHeight = insets.getSystemWindowInsetBottom();
int screenHeight = getResources().getDisplayMetrics().heightPixels;
int dialogHeight = (int) (screenHeight * 0.75) - navBarHeight;
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.height = dialogHeight;
window.setAttributes(layoutParams);
View rootView = getView();
if (rootView != null) {
rootView.setPadding(0, 0, 0, navBarHeight);
}
return insets.consumeSystemWindowInsets();
});
// 设置弹窗背景
window.setBackgroundDrawableResource(R.drawable.bg_euphoria_dialog_download);
}
}

View File

@ -0,0 +1,180 @@
package com.joyful.euphoria.board.frag
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.joyful.euphoria.board.EuphoriaBoard
import com.joyful.euphoria.board.R
import com.joyful.euphoria.board.databinding.DialogDownloaderBinding
import com.joyful.euphoria.board.utils.Normal
class Dialog : DialogFragment() {
private lateinit var vb: DialogDownloaderBinding
private lateinit var layoutStepOne: LinearLayout
private lateinit var layoutStepTwo: LinearLayout
private lateinit var imgStepOkOne: ImageView
private lateinit var imgStepOkTwo: ImageView
private lateinit var intentFilter: IntentFilter
private var myreceiver: BroadcastReceiver? = null
private lateinit var stepOne: TextView
private lateinit var stepTwo: TextView
private lateinit var context: Context
private var clickAction: (() -> Unit )? = null
companion object {
fun newInstance(): Dialog {
val fragment = Dialog()
return fragment
}
}
fun setClickListener(action:() -> Unit){
clickAction = action
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
vb = DialogDownloaderBinding.inflate(layoutInflater)
context = EuphoriaBoard.Companion.euphoriaBoardInstance
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 = Normal.checkEnable(EuphoriaBoard.Companion.euphoriaBoardInstance)
val checkSetDefault = Normal.checkSetDefault(EuphoriaBoard.Companion.euphoriaBoardInstance)
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,74 @@
package com.joyful.euphoria.board.frag
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.joyful.euphoria.board.EuphoriaBoard
import com.joyful.euphoria.board.beaned.BeanDetails
import com.joyful.euphoria.board.database.BoardDataBase
import com.joyful.euphoria.board.assistant.DatabaseAssistant
import com.joyful.euphoria.board.remember.DeleteCollectRemember
import com.joyful.euphoria.board.recyclerview.RecyclerviewCollect
import kotlinx.coroutines.launch
import com.joyful.euphoria.board.databinding.FragCollectBinding
class FragmentCollect : Fragment() {
private lateinit var vb: FragCollectBinding
companion object {
@JvmStatic
fun newInstance() =
FragmentCollect()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
vb = FragCollectBinding.inflate(layoutInflater)
init()
return vb.root
}
private fun init() {
val mainAdapter = RecyclerviewCollect(
requireContext()
).apply {
setRemoveLike(object : DeleteCollectRemember {
override fun OnRemoveLike(data: BeanDetails) {
lifecycleScope.launch {
DatabaseAssistant.removeLike(data)
}
}
})
}
vb.likeRecycler.run {
adapter = mainAdapter
layoutManager = GridLayoutManager(requireContext(), 2)
}
BoardDataBase.Companion.boardDataBase.ThemesDao().queryAllLike().observe(requireActivity()) {
Log.d(EuphoriaBoard.Companion.TAG, "---------it=${it?.size}")
if(it.isNullOrEmpty()){
vb.likeRecycler.isVisible = false
vb.emptyTitle.isVisible = true
}else{
vb.likeRecycler.isVisible = true
vb.emptyTitle.isVisible = false
mainAdapter.setForYouList(it)
}
}
}
}

View File

@ -0,0 +1,159 @@
package com.joyful.euphoria.board.frag;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.recyclerview.RecyclerviewHomePage;
import com.joyful.euphoria.board.beaned.BeanDetails;
import com.joyful.euphoria.board.beaned.CateFullData;
import com.joyful.euphoria.board.database.CategoryData;
import com.joyful.euphoria.board.database.BoardData;
import com.joyful.euphoria.board.xmlactivity.DownloadXML;
import com.joyful.euphoria.board.utils.FileUtils;
import com.joyful.euphoria.board.utils.ListDecorationUtils;
import java.util.ArrayList;
import java.util.List;
public class HomePage extends Fragment {
private static final String TAG = "FragmentHome";
private RecyclerView rvHomeCategory;
private View llHomeEmpty;
private RecyclerviewHomePage mAdapter;
private List<CateFullData> mCateFullList = new ArrayList<>();
public static HomePage newInstance() {
return new HomePage();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.frag_home_page, container, false);
initView(rootView);
initAdapter();
loadHomeCategoryData();
return rootView;
}
private void initView(View rootView) {
rvHomeCategory = rootView.findViewById(R.id.rv_home_category);
llHomeEmpty = rootView.findViewById(R.id.ll_home_empty);
}
private void initAdapter() {
mAdapter = new RecyclerviewHomePage(getContext(), mCateFullList);
// 主布局网格布局
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2);
rvHomeCategory.setLayoutManager(layoutManager);
rvHomeCategory.addItemDecoration(new ListDecorationUtils(10, 10, 0));
rvHomeCategory.setAdapter(mAdapter);
// 分类标题/箭头点击 显示弹窗
mAdapter.setOnCateTitleClickListener(cateFullData -> {
showCategoryDialog(cateFullData);
});
// 键盘图片点击 跳转Download界面
mAdapter.setOnKeyboardImgClickListener(bean -> {
jumpToDownloadActivity(bean);
});
}
// 加载分类完整数据含所有键盘
private void loadHomeCategoryData() {
try {
List<CategoryData> categoryDataList = FileUtils.readCategoryJsonFromAssets(getContext(), "res.json");
if (categoryDataList == null || categoryDataList.isEmpty()) {
showEmptyState();
return;
}
mCateFullList.clear();
for (CategoryData categoryData : categoryDataList) {
if (categoryData == null || categoryData.getParent_name() == null) continue;
String cateName = categoryData.getParent_name().trim();
if (cateName.isEmpty()) continue;
// 转换为BeanDetails列表
List<BeanDetails> keyboardList = convertToBeanDetails(categoryData.getKeyboard_list());
if (keyboardList.isEmpty()) continue;
// 添加完整分类数据
mCateFullList.add(new CateFullData(cateName, keyboardList));
}
mAdapter.notifyDataSetChanged();
hideEmptyState();
} catch (Exception e) {
Log.e(TAG, "加载Home分类数据失败", e);
showEmptyState();
}
}
// 将BoardData列表转换为BeanDetails列表
private List<BeanDetails> convertToBeanDetails(List<BoardData> boardDataList) {
List<BeanDetails> detailsList = new ArrayList<>();
if (boardDataList == null) return detailsList;
for (BoardData boardData : boardDataList) {
if (boardData == null) continue;
BeanDetails details = new BeanDetails();
details.setTitleName(boardData.getTitle());
// 提取图片路径和下载链接
if (boardData.getDetail() != null && boardData.getDetail().getThemeContent() != null) {
details.setImgPath(boardData.getDetail().getThemeContent().getImg());
String zipUrl = boardData.getDetail().getThemeContent().getAndroidRawZipUrl();
details.setZipUrl(zipUrl != null ? zipUrl.trim() : "");
}
details.setThumbUrl(boardData.getThumbUrl());
detailsList.add(details);
}
return detailsList;
}
// 显示分类弹窗
private void showCategoryDialog(CateFullData cateFullData) {
Category dialog = Category.newInstance(cateFullData);
dialog.show(getChildFragmentManager(), "CategoryDialog");
}
// 跳转Download界面
private void jumpToDownloadActivity(BeanDetails bean) {
if (getActivity() == null || bean == null) return;
Intent intent = new Intent(getActivity(), DownloadXML.class);
intent.putExtra(DownloadXML.ZIP_URL_KEY, bean.getZipUrl());
intent.putExtra(DownloadXML.DISPLAY_URL_KEY, bean.getImgPath());
intent.putExtra(DownloadXML.ZIP_URL_KEY, bean.getZipPath());
intent.putExtra(DownloadXML.NAME_KEY, bean.getTitleName());
intent.putExtra(DownloadXML.KEYBOARD_ID, bean.getId());
intent.putExtra(DownloadXML.KEYBOARD_IMG, bean.getImgPath());
intent.putExtra(DownloadXML.SOURCE_KEY, bean);
startActivity(intent);
}
// 空状态控制
private void showEmptyState() {
rvHomeCategory.setVisibility(View.GONE);
llHomeEmpty.setVisibility(View.VISIBLE);
}
private void hideEmptyState() {
rvHomeCategory.setVisibility(View.VISIBLE);
llHomeEmpty.setVisibility(View.GONE);
}
}

View File

@ -0,0 +1,67 @@
package com.joyful.euphoria.board.recyclerview;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.beaned.BeanDetails;
import java.util.List;
public class RecyclerviewBoardPager extends RecyclerView.Adapter<RecyclerviewBoardPager.ViewHolder> {
private List<BeanDetails> mBeans;
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(BeanDetails bean);
}
public RecyclerviewBoardPager(List<BeanDetails> beans) {
mBeans = beans;
}
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_keyboard_pager, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
BeanDetails bean = mBeans.get(position);
Glide.with(holder.itemView.getContext())
.load(bean.getImgPath())
.placeholder(R.drawable.euphoria_place_holder)
.error(R.drawable.euphoria_place_holder)
.into(holder.ivKeyboard);
holder.itemView.setOnClickListener(v -> {
if (mListener != null) {
mListener.onItemClick(bean);
}
});
}
@Override
public int getItemCount() {
return mBeans.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivKeyboard;
public ViewHolder(@NonNull View itemView) {
super(itemView);
ivKeyboard = itemView.findViewById(R.id.iv_keyboard);
}
}
}

View File

@ -0,0 +1,86 @@
package com.joyful.euphoria.board.recyclerview;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.beaned.BeanDetails;
import java.util.List;
public class RecyclerviewCategory extends RecyclerView.Adapter<RecyclerviewCategory.ViewHolder> {
private Context mContext;
private List<BeanDetails> mKeyboardList;
private OnCategoryKeyboardClickListener mKeyboardClickListener;
// 仅保留键盘点击回调接口
public interface OnCategoryKeyboardClickListener {
void onKeyboardClick(BeanDetails bean);
}
public RecyclerviewCategory(Context context, List<BeanDetails> keyboardList) {
mContext = context;
mKeyboardList = keyboardList;
}
public void setOnCategoryKeyboardClickListener(OnCategoryKeyboardClickListener listener) {
mKeyboardClickListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_category, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (mKeyboardList == null || mKeyboardList.isEmpty() || position >= mKeyboardList.size()) {
return;
}
BeanDetails bean = mKeyboardList.get(position);
if (bean == null) {
return;
}
// 绑定键盘名称
holder.tvKeyboardName.setText(bean.getTitleName() != null ? bean.getTitleName() : "未知键盘");
// 加载键盘图片
Glide.with(mContext)
.load(bean.getImgPath())
.placeholder(R.drawable.euphoria_place_holder)
.error(R.drawable.euphoria_place_holder)
.into(holder.ivKeyboardImg);
// 键盘项点击跳转Set界面
holder.itemView.setOnClickListener(v -> {
if (mKeyboardClickListener != null) {
mKeyboardClickListener.onKeyboardClick(bean);
}
});
}
@Override
public int getItemCount() {
return mKeyboardList == null ? 0 : mKeyboardList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivKeyboardImg;
TextView tvKeyboardName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
ivKeyboardImg = itemView.findViewById(R.id.iv_cate_keyboard_img);
tvKeyboardName = itemView.findViewById(R.id.tv_cate_keyboard_name);
}
}
}

View File

@ -0,0 +1,129 @@
package com.joyful.euphoria.board.recyclerview;
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.joyful.euphoria.board.xmlactivity.DownloadXML;
import com.bumptech.glide.Glide;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.beaned.BeanDetails;
import com.joyful.euphoria.board.utils.Normal;
import com.joyful.euphoria.board.remember.DeleteCollectRemember;
import java.util.ArrayList;
import java.util.List;
public class RecyclerviewCollect extends RecyclerView.Adapter<RecyclerviewCollect.ForYouViewHolder> {
private Context mContext;
private List<BeanDetails> mList = new ArrayList<>();
private DeleteCollectRemember mCallBack;
public RecyclerviewCollect(Context context) {
mContext = context;
}
public void setForYouList(List<BeanDetails> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setRemoveLike(DeleteCollectRemember callback) {
mCallBack = callback;
}
@NonNull
@Override
public ForYouViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_collect, parent, false);
return new ForYouViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ForYouViewHolder holder, int position) {
BeanDetails beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
if (thumbGif != null && !thumbGif.isEmpty()) {
Normal.INSTANCE.loadWepJif(mContext, thumbGif, holder.itemImg);
} else {
// 加载图片时也对thumb做非空处理
Glide.with(mContext)
.load(thumb != null ? thumb : R.drawable.euphoria_place_holder) // 增加null判断
.error(R.drawable.euphoria_place_holder)
.placeholder(R.drawable.euphoria_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(beanDetails);
}
}
});
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentApply = new Intent(mContext, DownloadXML.class);
intentApply.putExtra(DownloadXML.SOURCE_KEY, beanDetails);
intentApply.putExtra(DownloadXML.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(DownloadXML.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(DownloadXML.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(DownloadXML.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (thumbGif != null && !thumbGif.isEmpty()) { // 同样增加非空判断
intent_thumb = thumbGif;
} else {
intent_thumb = thumb != null ? thumb : ""; // 避免thumb为null的情况
}
intentApply.putExtra(DownloadXML.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,75 @@
package com.joyful.euphoria.board.recyclerview;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.beaned.BeanDetails;
import java.util.List;
public class RecyclerviewDialogCollect extends RecyclerView.Adapter<RecyclerviewDialogCollect.ViewHolder> {
private Context mContext;
private List<BeanDetails> mKeyboardList;
private OnKeyboardClickListener mListener;
public interface OnKeyboardClickListener {
void onKeyboardClick(BeanDetails bean); // 跳转Download
}
public RecyclerviewDialogCollect(Context context, List<BeanDetails> keyboardList) {
mContext = context;
mKeyboardList = keyboardList;
}
public void setOnKeyboardClickListener(OnKeyboardClickListener listener) {
mListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_dialog_keyboard, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
BeanDetails bean = mKeyboardList.get(position);
// 绑定键盘名称
holder.tvName.setText(bean.getTitleName() != null ? bean.getTitleName() : "未知键盘");
// 加载键盘图片
Glide.with(mContext)
.load(bean.getImgPath())
.placeholder(R.drawable.euphoria_place_holder)
.error(R.drawable.euphoria_place_holder)
.into(holder.ivImg);
// 点击跳转Download
holder.itemView.setOnClickListener(v -> {
if (mListener != null) {
mListener.onKeyboardClick(bean);
}
});
}
@Override
public int getItemCount() {
return mKeyboardList == null ? 0 : mKeyboardList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivImg;
TextView tvName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
ivImg = itemView.findViewById(R.id.iv_dialog_keyboard_img);
tvName = itemView.findViewById(R.id.tv_dialog_keyboard_name);
}
}
}

View File

@ -0,0 +1,108 @@
package com.joyful.euphoria.board.recyclerview;
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.joyful.euphoria.board.R;
import com.joyful.euphoria.board.beaned.BeanDetails;
import com.joyful.euphoria.board.remember.OnItemClickRemember;
import com.joyful.euphoria.board.xmlactivity.DownloadXML;
import com.bumptech.glide.Glide;
import com.joyful.euphoria.board.utils.Normal;
import java.util.ArrayList;
import java.util.List;
public class RecyclerviewDownload extends RecyclerView.Adapter<RecyclerviewDownload.ForYouViewHolder> {
private Context mContext;
private List<BeanDetails> mList = new ArrayList<>();
private OnItemClickRemember mCallBack;
public RecyclerviewDownload(Context context) {
mContext = context;
}
public void setForYouList(List<BeanDetails> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setClickAction(OnItemClickRemember callback) {
mCallBack = callback;
}
@NonNull
@Override
public ForYouViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_download, parent, false);
return new ForYouViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ForYouViewHolder holder, int position) {
BeanDetails beanDetails = mList.get(position);
String thumbGif = beanDetails.getThumbGif();
String thumb = beanDetails.getThumbUrl();
if (!thumbGif.isEmpty()) {
Normal.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, DownloadXML.class);
intentApply.putExtra(DownloadXML.SOURCE_KEY, beanDetails);
intentApply.putExtra(DownloadXML.DISPLAY_URL_KEY, beanDetails.getImgPath());
intentApply.putExtra(DownloadXML.ZIP_URL_KEY, beanDetails.getZipPath());
intentApply.putExtra(DownloadXML.NAME_KEY, beanDetails.getTitleName());
intentApply.putExtra(DownloadXML.GIF_KEY, beanDetails.getImgGif());
String intent_thumb;
if (!thumbGif.isEmpty()) {
intent_thumb = thumbGif;
} else {
intent_thumb = thumb;
}
intentApply.putExtra(DownloadXML.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);
}
}
}

View File

@ -0,0 +1,135 @@
package com.joyful.euphoria.board.recyclerview;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.beaned.BeanDetails;
import com.joyful.euphoria.board.beaned.CateFullData;
import java.util.List;
public class RecyclerviewHomePage extends RecyclerView.Adapter<RecyclerviewHomePage.ViewHolder> {
private Context mContext;
private List<CateFullData> mCateFullList; // 存储分类完整数据含所有键盘
private OnCateTitleClickListener mTitleListener; // 分类标题/箭头点击事件
private OnKeyboardImgClickListener mImgListener; // 键盘图片点击事件
// 移除Handler声明因为不再需要自动滑动
// 点击事件接口
public interface OnCateTitleClickListener {
void onCateTitleClick(CateFullData cateFullData); // 传递完整分类数据
}
public interface OnKeyboardImgClickListener {
void onKeyboardImgClick(BeanDetails bean); // 传递单个键盘数据
}
public RecyclerviewHomePage(Context context, List<CateFullData> cateFullList) {
mContext = context;
mCateFullList = cateFullList;
}
// Setter方法
public void setOnCateTitleClickListener(OnCateTitleClickListener listener) {
mTitleListener = listener;
}
public void setOnKeyboardImgClickListener(OnKeyboardImgClickListener listener) {
mImgListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_home_page, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
CateFullData cateFullData = mCateFullList.get(position);
List<BeanDetails> keyboardList = cateFullData.getKeyboardList();
if (keyboardList == null || keyboardList.isEmpty()) return;
// 1. 绑定分类名称
holder.tvCateName.setText(cateFullData.getCategoryName());
// 2. 设置ViewPager2适配器
RecyclerviewBoardPager pagerAdapter = new RecyclerviewBoardPager(keyboardList);
holder.vpKeyboard.setAdapter(pagerAdapter);
// 3. 初始显示第一个键盘名称
holder.tvKeyboardName.setText(keyboardList.get(0).getTitleName() != null ?
keyboardList.get(0).getTitleName() : "unknown keyboard");
// 4. 监听滑动事件更新标题
holder.vpKeyboard.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// 增加索引校验确保position在有效范围内
if (position >= 0 && position < keyboardList.size()) {
BeanDetails currentBean = keyboardList.get(position);
holder.tvKeyboardName.setText(currentBean.getTitleName() != null ?
currentBean.getTitleName() : "unknown keyboard");
}
}
});
// 5. 分类标题点击事件
holder.llCateTitle.setOnClickListener(v -> {
if (mTitleListener != null) {
mTitleListener.onCateTitleClick(cateFullData);
}
});
// 6. 图片点击事件
pagerAdapter.setOnItemClickListener(bean -> {
if (mImgListener != null) {
mImgListener.onKeyboardImgClick(bean);
}
});
}
@Override
public int getItemCount() {
return mCateFullList == null ? 0 : mCateFullList.size();
}
// ViewHolder绑定新布局控件
public static class ViewHolder extends RecyclerView.ViewHolder {
LinearLayout llCateTitle;
TextView tvCateName;
ImageView ivArrowRight;
ViewPager2 vpKeyboard;
TextView tvKeyboardName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
llCateTitle = itemView.findViewById(R.id.ll_cate_title);
tvCateName = itemView.findViewById(R.id.tv_home_cate_name);
ivArrowRight = itemView.findViewById(R.id.iv_arrow_right);
vpKeyboard = itemView.findViewById(R.id.vp_keyboard); // 初始化ViewPager2
tvKeyboardName = itemView.findViewById(R.id.tv_keyboard_name);
}
}
// 销毁
@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
}
}

View File

@ -0,0 +1,8 @@
package com.joyful.euphoria.board.remember
import com.joyful.euphoria.board.beaned.BeanDetails
interface DeleteCollectRemember {
fun OnRemoveLike(data: BeanDetails)
}

View File

@ -0,0 +1,8 @@
package com.joyful.euphoria.board.remember
import java.io.File
interface DownloadBoardRemember {
fun OnApplySkinListener(fileList: List<File?>?)
}

View File

@ -0,0 +1,6 @@
package com.joyful.euphoria.board.remember
interface OnItemClickRemember {
fun OnItemClickListener( )
}

View File

@ -0,0 +1,6 @@
package com.joyful.euphoria.board.remember
interface ViewAllRemember {
fun OnClickSeeAll(name: String)
}

View File

@ -0,0 +1,37 @@
package com.joyful.euphoria.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.joyful.euphoria.board.EuphoriaBoard;
import com.joyful.euphoria.board.R;
public class CustomUtils extends androidx.appcompat.widget.AppCompatTextView {
public CustomUtils(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(EuphoriaBoard.Companion.getDefaultFont());
}
}

View File

@ -0,0 +1,106 @@
package com.joyful.euphoria.board.utils;
import com.joyful.euphoria.board.assistant.KeyboardKeyModelAssistant;
import com.joyful.euphoria.board.assistant.ResponseConfigAssistant;
import com.joyful.euphoria.board.assistant.ResponseLayoutAssistant;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class DealConfUtils {
public static ResponseConfigAssistant initConfig(String path) {
String filePath = "keyboard.conf"; // 文件路径
ResponseConfigAssistant config = parseConfig(path);
return config;
}
public static ResponseConfigAssistant parseConfig(String filePath) {
// InputStream open = App.appInstance.getAssets().open(filePath);
ResponseConfigAssistant config = new ResponseConfigAssistant();
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
ResponseLayoutAssistant 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 ResponseLayoutAssistant(line.split(":")[0].trim());
config.addLayout(currentLayout);
} else if (currentLayout != null) {
if (line.equals("Key")) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
KeyboardKeyModelAssistant keyboardKeyModelAssistant = new KeyboardKeyModelAssistant(keyName);
currentLayout.addKey(keyboardKeyModelAssistant);
} else if (line.contains(":") && currentLayout.getLastKey().getBackground() == null) {
// 解析按键的其他属性 Label
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
KeyboardKeyModelAssistant keyboardKeyModelAssistant = currentLayout.getLastKey();
if (keyName.equals("Label")) {
keyboardKeyModelAssistant.setLabel(keyValue);
}
if (keyName.equals("Background")) {
keyboardKeyModelAssistant.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")) {
KeyboardKeyModelAssistant funcationKeyboardKeyModelAssistant = new KeyboardKeyModelAssistant(line);
config.addKey(funcationKeyboardKeyModelAssistant);
} else if (line.contains(":")) {
String[] parts = line.split(":");
String keyName = parts[0].trim();
String keyValue = parts[1].trim();
KeyboardKeyModelAssistant lastKeyboardKeyModelAssistant = config.getLastKeyList();
if (keyName.equals("Label")) {
lastKeyboardKeyModelAssistant.setLabel(keyValue);
}
if (keyName.equals("Background")) {
lastKeyboardKeyModelAssistant.setBackground(keyValue);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return config;
}
}

View File

@ -0,0 +1,274 @@
package com.joyful.euphoria.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.joyful.euphoria.board.EuphoriaBoard;
import com.joyful.euphoria.board.remember.DownloadBoardRemember;
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 DealZipUtils {
public static void startDownloadZip(String zipPath, DownloadBoardRemember callback) {
// 新增下载前校验链接
if (!isValidZipUrl(zipPath)) {
if (callback != null) {
callback.OnApplySkinListener(null);
}
return;
}
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) {
// 处理空字符串情况
if (zipPath == null || zipPath.isEmpty()) {
Log.e("DealZipUtils", "zipPath is empty or null");
return "default_unzip_folder"; // 返回默认文件夹名
}
String pointStr = ".";
int lastIndexOf = zipPath.lastIndexOf(pointStr);
// 处理不包含"."的情况
if (lastIndexOf == -1) {
Log.w("DealZipUtils", "zipPath does not contain '.': " + zipPath);
return zipPath; // 直接返回原字符串作为文件夹名
}
// 确保截取范围合法
if (lastIndexOf == 0) {
Log.w("DealZipUtils", "zipPath starts with '.': " + zipPath);
return "dot_prefix_folder"; // 处理以"."开头的特殊情况
}
return zipPath.substring(0, lastIndexOf);
}
private static void saveZipFile(InputStream inputStream, String zipFileName, DownloadBoardRemember callback) {
File zipfFile = new File(EuphoriaBoard.euphoriaBoardInstance.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 EuphoriaBoard.euphoriaBoardInstance.getFilesDir().getPath() + "/" + replace;
}
private static void un7ZZipFile(File saveZipFile, DownloadBoardRemember 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();
}
// 新增校验下载链接合法性
public static boolean isValidZipUrl(String zipUrl) {
if (zipUrl == null || zipUrl.trim().isEmpty()) {
Log.e("DealZipUtils", "URL为空");
return false;
}
// 校验协议头必须是http/https
if (!zipUrl.startsWith("http://") && !zipUrl.startsWith("https://")) {
Log.e("DealZipUtils", "URL缺少http/https协议头" + zipUrl);
return false;
}
// 校验链接长度避免过短非法链接
if (zipUrl.length() < 10) {
Log.e("DealZipUtils", "URL过短非法" + zipUrl);
return false;
}
return true;
}
}

View File

@ -0,0 +1,31 @@
package com.joyful.euphoria.board.utils;
import android.content.Context;
import com.joyful.euphoria.board.database.CategoryData;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class FileUtils {
/**
* 读取JSON并返回List<CategoryData>关键类型匹配
*/
public static List<CategoryData> readCategoryJsonFromAssets(Context context, String fileName) {
List<CategoryData> dataList = new ArrayList<>();
if (context == null) {
return dataList;
}
try (InputStream is = context.getAssets().open(fileName);
InputStreamReader isr = new InputStreamReader(is)) {
Gson gson = new Gson();
dataList = gson.fromJson(isr, new TypeToken<List<CategoryData>>() {}.getType());
} catch (Exception e) {
e.printStackTrace();
}
return dataList != null ? dataList : new ArrayList<>();
}
}

View File

@ -0,0 +1,60 @@
package com.joyful.euphoria.board.utils
object KeyNamesUtils {
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,76 @@
package com.joyful.euphoria.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.joyful.euphoria.board.EuphoriaBoard;
public class ListDecorationUtils extends RecyclerView.ItemDecoration {
private int v, h, ex;
public ListDecorationUtils(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 = EuphoriaBoard.euphoriaBoardInstance.getResources().getDisplayMetrics().density;
return density * dpValue + 0.5f;
}
}

View File

@ -0,0 +1,115 @@
package com.joyful.euphoria.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.joyful.euphoria.board.EuphoriaBoard
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 Normal {
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 =
EuphoriaBoard.Companion.euphoriaBoardInstance.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 = EuphoriaBoard.Companion.euphoriaBoardInstance.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
}

View File

@ -0,0 +1,19 @@
package com.joyful.euphoria.board.utils
import android.content.Context
import com.joyful.euphoria.board.EuphoriaBoard
object SaveCurrentThemeUtils {
val SP_NAME = "keyboard_skin"
val SKIN_PATH = "skin_path"
val spSkin = EuphoriaBoard.Companion.euphoriaBoardInstance.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,181 @@
package com.joyful.euphoria.board.utils
import com.joyful.euphoria.board.R
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.joyful.euphoria.board.EuphoriaBoard
import com.joyful.euphoria.board.assistant.ResponseConfigAssistant
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.StringReader
import kotlin.collections.iterator
class ThemesUtils(var context: Context) {
private var textSize = 13f
var functionDraw: Drawable =
getDefaultDrawList(R.drawable.euphoria_default_normal_key, R.drawable.euphoria_default_pressed_key)
var generalDraw: Drawable =
getDefaultDrawList(R.drawable.euphoria_default_normal_key, R.drawable.euphoria_default_pressed_key)
var toDraw: Drawable = getDefaultDrawList(R.drawable.euphoria_default_normal_key, R.drawable.euphoria_default_pressed_key)
var spaceDraw: Drawable = getDefaultDrawList(R.drawable.euphoria_default_normal_key, R.drawable.euphoria_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(): ResponseConfigAssistant? {
val skinPath = SaveCurrentThemeUtils.getSkinPath()
val configFilePath = skinPath + "assets/keyboard.conf"
val file = File(configFilePath)
return if (file.exists()) {
DealConfUtils.initConfig(configFilePath)
} else {
null
}
}
fun getConfigBg(name: String): Drawable? {
SaveCurrentThemeUtils.getSkinPath()?.let { resPath ->
val pPath = "${resPath}res/drawable-xhdpi-v4/"
return getDrawList(
pPath + name,
pPath + name
)
}
return null
}
fun updateSkinConfig() {
SaveCurrentThemeUtils.getSkinPath()?.let { resPath ->
val pPath = "${resPath}res/drawable-xhdpi-v4/"
pPath.let {
readColors(resPath) {
for ((name, value) in it) {
if (name == KeyNamesUtils.keyTextColorName) {
keyTextColor = value
}
if (name == KeyNamesUtils.keyTextColorFunctionName) {
keyTextColorFunction = value
}
}
}
functionDraw = getDrawList(
it + KeyNamesUtils.functionNormalName,
it + KeyNamesUtils.functionPressName
)
generalDraw = getDrawList(it + KeyNamesUtils.normalName, it + KeyNamesUtils.pressName)
toDraw = getDrawList(it + KeyNamesUtils.toNormalName, it + KeyNamesUtils.toPressName)
spaceDraw =
getDrawList(it + KeyNamesUtils.spaceNormalName, it + KeyNamesUtils.spacePressName)
switchDraw =
getDrawList(it + KeyNamesUtils.imeSwitchName, it + KeyNamesUtils.imeSwitchName)
deleteDraw = getDrawList(
it + KeyNamesUtils.deleteNormalName,
it + KeyNamesUtils.deletePressName
)
backDraw = getDrawList(it + KeyNamesUtils.backName, it + KeyNamesUtils.backName)
searchDraw = getDrawList(it + KeyNamesUtils.searchName, it + KeyNamesUtils.searchName)
shiftDraw = getDrawList(
it + KeyNamesUtils.shiftNormalName,
it + KeyNamesUtils.shiftNormalName
)
shiftLockDraw =
getDrawList(it + KeyNamesUtils.shiftLockName, it + KeyNamesUtils.shiftLockName)
}
}
}
private fun getDefaultDrawList(normalDrawId: Int, pressDrawId: Int): StateListDrawable {
val normalDraw = ContextCompat.getDrawable(EuphoriaBoard.Companion.euphoriaBoardInstance, normalDrawId)
val pressDraw = ContextCompat.getDrawable(EuphoriaBoard.Companion.euphoriaBoardInstance, 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 == KeyNamesUtils.keyTextColorName
val b4 = attributeName == KeyNamesUtils.keyTextColorFunctionName
if (b3 || b4) {
resMaps[attributeName] = Color.parseColor(nextTextValue)
}
}
curType = xmlPullParser.next()
}
}
callBack.invoke(resMaps)
}
}

View File

@ -0,0 +1,61 @@
package com.joyful.euphoria.board.xmlactivity
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.ProgressBar
import com.joyful.euphoria.board.R
import com.joyful.euphoria.board.utils.Normal
import kotlin.math.roundToInt
/**
* 不要修改启动页继承Activity这点
*/
class BeginXML : Activity() {
private lateinit var progressBar: ProgressBar
private var countTime = 1500L
private lateinit var timer: CountDownTimer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_begin_xml)
Normal.initFullScreen(this,true)
progressBar = findViewById<ProgressBar>(R.id.nova_progress)
init()
}
private fun init() {
timer = object : CountDownTimer(countTime, 100) {
override fun onTick(millisUntilFinished: Long) {
val percentage: Float = 100 - millisUntilFinished.toFloat() / countTime * 100
val round = percentage.roundToInt()
progressBar.progress = round
}
override fun onFinish() {
progressBar.progress = 100
toHome()
}
}
timer.start()
}
private fun toHome() {
startActivity(Intent(this, HomePageXML::class.java))
finish()
}
override fun onDestroy() {
super.onDestroy()
timer.cancel()
}
}

View File

@ -0,0 +1,172 @@
package com.joyful.euphoria.board.xmlactivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
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 androidx.recyclerview.widget.RecyclerView;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.recyclerview.RecyclerviewCategory;
import com.joyful.euphoria.board.beaned.BeanDetails;
import com.joyful.euphoria.board.beaned.BeanWrapper;
import com.joyful.euphoria.board.database.CategoryData;
import com.joyful.euphoria.board.database.BoardData;
import com.joyful.euphoria.board.EuphoriaBoard;
import com.joyful.euphoria.board.utils.FileUtils;
import com.joyful.euphoria.board.utils.ListDecorationUtils;
import java.util.ArrayList;
import java.util.List;
public class CategoryXML extends AppCompatActivity {
private static final String TAG = "CategoryActivity";
public static final String KEY_CATEGORY_NAME = "category_name";
private RecyclerView rvCateKeyboard;
private View llCateEmpty;
private View ivCateBack;
private TextView tvCateTitle;
private RecyclerviewCategory mCateKeyboardAdapter;
private List<BeanDetails> mKeyboardList = new ArrayList<>();
private List<CategoryData> mCategoryDataList = new ArrayList<>();
private String mCurrentCategoryName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_category_xml);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.category), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
initView();
getIntentData();
initAdapter();
loadCategoryData();
loadKeyboardDataByCategory();
}
private void initView() {
rvCateKeyboard = findViewById(R.id.rv_cate_keyboard);
llCateEmpty = findViewById(R.id.ll_cate_empty);
ivCateBack = findViewById(R.id.iv_cate_back);
tvCateTitle = findViewById(R.id.tv_cate_title);
ivCateBack.setOnClickListener(v -> finish());
}
private void getIntentData() {
mCurrentCategoryName = getIntent().getStringExtra(KEY_CATEGORY_NAME);
tvCateTitle.setText(mCurrentCategoryName == null ? "未知分类" : mCurrentCategoryName);
}
private void initAdapter() {
mCateKeyboardAdapter = new RecyclerviewCategory(this, mKeyboardList);
GridLayoutManager layoutManager = new GridLayoutManager(this, 1);
rvCateKeyboard.setLayoutManager(layoutManager);
ListDecorationUtils decoration = new ListDecorationUtils(10, 10, 0);
rvCateKeyboard.addItemDecoration(decoration);
rvCateKeyboard.setAdapter(mCateKeyboardAdapter);
// 键盘点击跳转Set界面
mCateKeyboardAdapter.setOnCategoryKeyboardClickListener(bean -> {
if (bean == null || bean.getTitleName() == null) {
Toast.makeText(CategoryXML.this, "键盘数据异常", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(CategoryXML.this, DownloadXML.class);
intent.putExtra(DownloadXML.SOURCE_KEY, bean);
intent.putExtra(DownloadXML.NAME_KEY, bean.getTitleName());
intent.putExtra(DownloadXML.DISPLAY_URL_KEY, bean.getImgPath());
intent.putExtra(DownloadXML.GIF_KEY, bean.getImgGif());
intent.putExtra(DownloadXML.THUMB_KEY, bean.getThumbUrl());
intent.putExtra(DownloadXML.ZIP_URL_KEY, bean.getZipPath());
startActivity(intent);
});
}
private void loadCategoryData() {
try {
mCategoryDataList = FileUtils.readCategoryJsonFromAssets(this, "res.json");
Log.d(TAG, "加载分类原始数据条数:" + mCategoryDataList.size());
} catch (Exception e) {
Log.e(TAG, "加载分类原始数据失败", e);
}
}
private void loadKeyboardDataByCategory() {
if (mCurrentCategoryName == null || mCategoryDataList.isEmpty()) {
showEmptyState();
return;
}
mKeyboardList.clear();
// 从JSON原始数据加载
for (CategoryData categoryData : mCategoryDataList) {
if (categoryData == null || !mCurrentCategoryName.equalsIgnoreCase(categoryData.getParent_name())) {
continue;
}
if (categoryData.getKeyboard_list() != null && !categoryData.getKeyboard_list().isEmpty()) {
for (BoardData boardData : categoryData.getKeyboard_list()) {
if (boardData == null) {
continue;
}
BeanDetails bean = new BeanDetails();
bean.setTitleName(boardData.getTitle() != null ? boardData.getTitle() : "未知键盘");
bean.setThumbUrl(boardData.getThumbUrl() != null ? boardData.getThumbUrl() : "");
bean.setImgGif(boardData.getThumbUrlGif() != null ? boardData.getThumbUrlGif() : "");
if (boardData.getDetail() != null && boardData.getDetail().getThemeContent() != null) {
bean.setImgPath(boardData.getDetail().getThemeContent().getImg() != null ?
boardData.getDetail().getThemeContent().getImg() : "");
bean.setZipPath(boardData.getDetail().getThemeContent().getAndroidRawZipUrl() != null ?
boardData.getDetail().getThemeContent().getAndroidRawZipUrl() : "");
} else {
bean.setImgPath("");
bean.setZipPath("");
}
mKeyboardList.add(bean);
}
}
break;
}
// 备选从DelightBoard全局数据加载
if (mKeyboardList.isEmpty() && EuphoriaBoard.list != null && !EuphoriaBoard.list.isEmpty()) {
for (BeanWrapper wrapper : EuphoriaBoard.list) {
if (wrapper == null || !mCurrentCategoryName.equals(wrapper.getParentName())) {
continue;
}
mKeyboardList.addAll(wrapper.getKeyboardList());
break;
}
}
mCateKeyboardAdapter.notifyDataSetChanged();
if (mKeyboardList.isEmpty()) {
showEmptyState();
} else {
hideEmptyState();
}
Log.d(TAG, "分类[" + mCurrentCategoryName + "]下加载键盘条数:" + mKeyboardList.size());
}
private void showEmptyState() {
rvCateKeyboard.setVisibility(View.GONE);
llCateEmpty.setVisibility(View.VISIBLE);
}
private void hideEmptyState() {
rvCateKeyboard.setVisibility(View.VISIBLE);
llCateEmpty.setVisibility(View.GONE);
}
}

View File

@ -0,0 +1,433 @@
package com.joyful.euphoria.board.xmlactivity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Environment
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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.joyful.euphoria.board.EuphoriaBoard
import com.joyful.euphoria.board.R
import com.joyful.euphoria.board.beaned.BeanDetails
import com.joyful.euphoria.board.remember.OnItemClickRemember
import com.joyful.euphoria.board.remember.DownloadBoardRemember
import com.joyful.euphoria.board.assistant.DatabaseAssistant
import com.joyful.euphoria.board.recyclerview.RecyclerviewDownload
import com.joyful.euphoria.board.frag.Dialog
import com.joyful.euphoria.board.utils.Normal
import com.joyful.euphoria.board.utils.DealZipUtils
import com.joyful.euphoria.board.utils.SaveCurrentThemeUtils
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 DownloadXML : 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"
@JvmField
val KEYBOARD_ID = "KEYBOARD_ID"
@JvmField
val KEYBOARD_IMG = "KEYBOARD_IMG"
private const val STORAGE_PERMISSION_REQUEST = 1001
}
private var dialog: Dialog? = null
private var displayUrl: String? = null
private lateinit var gifUrl: String
private lateinit var zipUrl: String
private 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 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 var data: BeanDetails? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_download_xml)
this.enableEdgeToEdge()
ViewCompat.setOnApplyWindowInsetsListener(
findViewById<View?>(R.id.set),
OnApplyWindowInsetsListener { v: View?, insets: WindowInsetsCompat? ->
val systemBars = insets!!.getInsets(WindowInsetsCompat.Type.systemBars())
v!!.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
})
// 检查存储权限
checkStoragePermission()
findViewId()
getExtraData() // 先赋值
displayData() // 后使用
setApply()
onClick()
}
// 检查存储权限
private fun checkStoragePermission() {
if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
Toast.makeText(this, getString(R.string.storage_not_available), Toast.LENGTH_SHORT).show()
return
}
}
/**
* 修复3完善数据获取逻辑确保所有属性都被赋值避免空指针
*/
private fun getExtraData() {
// 安全获取BeanDetails对象
data = intent.getSerializableExtra(SOURCE_KEY) as? BeanDetails
// 强制容错:数据为空则关闭页面
if (data == null) {
Toast.makeText(this, getString(R.string.data_error), Toast.LENGTH_SHORT).show()
finish()
return
}
// 为所有属性赋值使用orEmpty()避免空值
displayUrl = intent.getStringExtra(DISPLAY_URL_KEY).orEmpty()
zipUrl = intent.getStringExtra(ZIP_URL_KEY).orEmpty()
name = intent.getStringExtra(NAME_KEY).orEmpty() // 确保name被赋值即使为空字符串
gifUrl = intent.getStringExtra(GIF_KEY).orEmpty()
thumb = intent.getStringExtra(THUMB_KEY).orEmpty()
if (zipUrl.isEmpty()) {
zipUrl = data?.getZipUrl().orEmpty()
}
// 3. 新增:链接为空时的处理
if (zipUrl.isEmpty()) {
tvDownload.text = getString(R.string.download_url_missing) // 提示“下载链接缺失”
applyBtn.isEnabled = false // 禁用下载按钮
}
// 从data中补充name防止Intent未传递NAME_KEY
if (name.isEmpty()) {
name = data?.getTitleName().orEmpty()
}
val serviceZipName = DealZipUtils.getServiceZipName(zipUrl)
unzipPath = DealZipUtils.getUnzipPath(serviceZipName)
Log.d("SetActivity", "unzipPath=$unzipPath")
lifecycleScope.launch {
DatabaseAssistant.getIsLike(name) {
imgLike.isSelected = it
}
}
updateDownloadStatus()
}
// 更新下载状态显示
private fun updateDownloadStatus() {
val file = File(unzipPath)
if (file.exists() && file.isDirectory) {
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)
imgLike = findViewById(R.id.im_like)
loadingLayout = findViewById(R.id.loading)
imDownload = findViewById(R.id.im_download)
tvDownload = findViewById(R.id.tv_download)
}
/**
* 修复4displayData中增加name空判断避免访问未初始化属性
*/
private fun displayData() {
textName.text = if (name.isNotEmpty()) name else getString(R.string.unknown_keyboard)
// 安全获取图片路径,多重备份方案
val imgPath = displayUrl ?: data?.getImgPath() ?: thumb ?: ""
val gifPath = gifUrl.takeIf { it.isNotEmpty() } ?: data?.getImgGif() ?: ""
// 加载图片/GIF
if (gifPath.isNotEmpty()) {
loadImgGif(gifPath)
} else if (imgPath.isNotEmpty()) {
Glide.with(this)
.load(imgPath)
.thumbnail(Glide.with(this).load(thumb))
.placeholder(R.drawable.euphoria_place_holder)
.error(R.drawable.euphoria_place_holder)
.into(imgData)
} else {
imgData.setImageResource(R.drawable.euphoria_place_holder)
}
}
private fun onClick() {
imgBack.setOnClickListener {
finish()
}
imgLike.setOnClickListener {
imgLike.isSelected = !imgLike.isSelected
lifecycleScope.launch {
try {
val currentData = data ?: return@launch // 安全获取data
if (imgLike.isSelected) {
DatabaseAssistant.addLike(currentData)
} else {
DatabaseAssistant.removeLike(currentData)
}
} catch (e: Exception) {
Log.e("SetActivity", "Like operation error", e)
Toast.makeText(this@DownloadXML, getString(R.string.operation_failed), Toast.LENGTH_SHORT).show()
}
}
}
val forYouList = EuphoriaBoard.list.filter {
it.parentName == getString(R.string.recommend_name)
}
recommendedRecycler.run {
adapter = RecyclerviewDownload(this@DownloadXML).apply {
val shuffled = forYouList.getOrNull(0)?.keyboardList?.shuffled() ?: emptyList()
setForYouList(shuffled)
setClickAction(object : OnItemClickRemember {
override fun OnItemClickListener() {
finish()
}
})
}
layoutManager = LinearLayoutManager(this@DownloadXML,
LinearLayoutManager.HORIZONTAL, false)
addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent.getChildAdapterPosition(view)
if (position > 0) {
outRect.left = 8.dpToPx()
}
}
})
}
}
private fun Int.dpToPx(): Int {
return (this * resources.displayMetrics.density).toInt()
}
@SuppressLint("CheckResult")
private fun loadImgGif(gifPath: String) {
Glide.with(this)
.load(gifPath)
.thumbnail(Glide.with(this).load(thumb))
.placeholder(R.drawable.euphoria_place_holder)
.error(R.drawable.euphoria_place_holder)
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
Log.e("SetActivity", "GIF load failed", e)
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is WebpDrawable) {
resource.loopCount = WebpDrawable.LOOP_FOREVER
}
return false
}
}).into(imgData)
}
private fun setApply() {
applyBtn.setOnClickListener {
val checkEnable = Normal.checkEnable(this)
val checkSetDefault = Normal.checkSetDefault(this)
if (!checkEnable || !checkSetDefault) {
showDialog()
return@setOnClickListener
}
startDown()
}
}
private fun showDialog() {
if (dialog == null) {
dialog = Dialog.newInstance().apply {
setClickListener {
startDown()
}
}
}
dialog?.show(supportFragmentManager, "")
}
private fun startDown() {
loadingLayout.isVisible = true
applyBtn.isEnabled = false
if (!DealZipUtils.isValidZipUrl(zipUrl)) { // 调用工具类校验
runOnUiThread {
Toast.makeText(this, getString(R.string.invalid_download_url), Toast.LENGTH_SHORT).show()
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
return
}
val file = File(unzipPath)
if (file.exists() && file.isDirectory) {
val findFirstDirectory = DealZipUtils.findFirstDirectory(file)
findFirstDirectory?.let {
apply("${it}/")
} ?: run {
runOnUiThread {
Toast.makeText(this, getString(R.string.file_error), Toast.LENGTH_SHORT).show()
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
}
} else {
if (zipUrl.isEmpty()) {
runOnUiThread {
Toast.makeText(this, getString(R.string.download_url_error), Toast.LENGTH_SHORT).show()
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
return
}
DealZipUtils.startDownloadZip(zipUrl, object : DownloadBoardRemember {
override fun OnApplySkinListener(fileList: List<File?>?) {
runOnUiThread {
applyBtn.isEnabled = true
loadingLayout.isVisible = false
}
if (fileList.isNullOrEmpty()) {
runOnUiThread {
Toast.makeText(
this@DownloadXML,
getString(R.string.download_fail),
Toast.LENGTH_SHORT
).show()
}
} else {
if (file.exists()) {
val findFirstDirectory = DealZipUtils.findFirstDirectory(file)
Log.d(EuphoriaBoard.TAG, "apply path: $findFirstDirectory")
runOnUiThread {
findFirstDirectory?.let {
apply("${it}/")
} ?: run {
Toast.makeText(this@DownloadXML, getString(R.string.file_error), Toast.LENGTH_SHORT).show()
}
}
}
}
}
})
}
}
private fun apply(path: String) {
var skinParentPath = path
if (path.contains("res")) {
skinParentPath = path.substringBeforeLast("res")
}
try {
SaveCurrentThemeUtils.updateSkinPath(skinParentPath)
Toast.makeText(
this@DownloadXML,
getString(R.string.theme_application_successful),
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this, PreviewXML::class.java).apply {
putExtra(PreviewXML.key_name, name)
})
finish()
} catch (e: Exception) {
Log.e("SetActivity", "Apply theme error", e)
Toast.makeText(this, getString(R.string.apply_failed), Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
super.onDestroy()
dialog?.dismiss()
}
}

View File

@ -0,0 +1,146 @@
package com.joyful.euphoria.board.xmlactivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.MarginPageTransformer;
import com.joyful.euphoria.board.R;
import com.joyful.euphoria.board.databinding.ActivityHomePageXmlBinding;
import com.joyful.euphoria.board.utils.CustomUtils;
import com.joyful.euphoria.board.frag.FragmentCollect;
import com.joyful.euphoria.board.frag.HomePage;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
public class HomePageXML extends AppCompatActivity {
private ActivityHomePageXmlBinding vb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityHomePageXmlBinding.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() {
List<Fragment> listFragment = new ArrayList<>();
listFragment.add(HomePage.newInstance());
listFragment.add(FragmentCollect.newInstance());
List<TabLayout.Tab> tabList = new ArrayList<>();
for (int i = 0; i < listFragment.size(); i++) {
TabLayout.Tab tab = vb.tabLayout.newTab();
View inflate = LayoutInflater.from(this).inflate(R.layout.tab_content, null, false);
ImageView icon = inflate.findViewById(R.id.im_icon);
CustomUtils textView = inflate.findViewById(R.id.textView);
if (i == 0) {
textView.setSelected(true);
textView.setText(getString(R.string.tab_home));
textView.setTextColor(ContextCompat.getColor(this, R.color.white));
icon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.selector_euphoria_tab_main));
} else if(i ==1){
textView.setSelected(false);
textView.setText(getString(R.string.tab_love));
textView.setTextColor(ContextCompat.getColor(this, R.color.tab_text));
icon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.selector_euphoria_tab_love));
}
tab.setCustomView(inflate);
vb.tabLayout.addTab(tab);
}
vb.viewpager.setUserInputEnabled(false);
vb.viewpager.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
return listFragment.get(position);
}
@Override
public int getItemCount() {
return listFragment.size();
}
});
vb.viewpager.setPageTransformer(new MarginPageTransformer(20));
bing();
}
private void bing() {
vb.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
View customView = tab.getCustomView();
if (customView != null) {
ImageView icon = customView.findViewById(R.id.im_icon);
CustomUtils textView = customView.findViewById(R.id.textView);
icon.setSelected(true);
textView.setTextColor(ContextCompat.getColor(HomePageXML.this, R.color.white));
}
vb.viewpager.setCurrentItem(tab.getPosition(), true); // 添加平滑滚动
// 更新标题
updateTitle(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
View customView = tab.getCustomView();
if (customView != null) {
ImageView icon = customView.findViewById(R.id.im_icon);
CustomUtils textView = customView.findViewById(R.id.textView);
icon.setSelected(false);
textView.setTextColor(ContextCompat.getColor(HomePageXML.this, R.color.tab_text));
}
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
// 初始化第一个Tab为选中状态
if (vb.tabLayout.getTabCount() > 0) {
TabLayout.Tab firstTab = vb.tabLayout.getTabAt(0);
if (firstTab != null) {
firstTab.select();
}
}
}
private void updateTitle(int position) {
if (position == 0) {
vb.titleTv.setText(getString(R.string.app_name));
} else if(position == 1) {
vb.titleTv.setText(getString(R.string.my_favorite));
}
}
}

View File

@ -0,0 +1,131 @@
package com.joyful.euphoria.board.xmlactivity;
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.joyful.euphoria.board.R;
import com.joyful.euphoria.board.databinding.ActivityPreviewXmlBinding;
import com.joyful.euphoria.board.utils.KeyNamesUtils;
import com.joyful.euphoria.board.utils.Normal;
import com.joyful.euphoria.board.utils.SaveCurrentThemeUtils;
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 PreviewXML extends AppCompatActivity {
private ActivityPreviewXmlBinding vb;
public static String key_name = "key_name";
private int mPreviousKeyboardHeight = -1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vb = ActivityPreviewXmlBinding.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;
});
onInit();
}
public void onInit() {
String stringExtra = getIntent().getStringExtra(key_name);
vb.title.setText(stringExtra);
String curPath = SaveCurrentThemeUtils.INSTANCE.getSkinPath();
vb.idBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
if (curPath == null) {
return;
}
String bgPath = curPath+"res/drawable-xxhdpi-v4/"+ KeyNamesUtils.previewBg;
Drawable bgDraw = Normal.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,12 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="@color/gray" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="@color/transparent" />
</shape>
</item>
</selector>

View File

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

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/main"/>
<corners android:radius="20dp"/>
</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="oval">
<solid android:color="@color/white"/>
</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/btn" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/black" />
</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">
<solid android:color="@color/color_gray" />
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,15 @@
<?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" />
<stroke
android:color="@color/main"
android:width="5dp"/>
</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/btn" />
<corners android:radius="32dp" />
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="@color/transparent"
android:startColor="@color/mask_set" />
<corners android:radius="10dp"/>
</shape>

View File

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

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/background0"
android:endColor="@color/background1"
android:angle="270"
android:type="linear"/>
</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/tab"/>
<corners android:radius="30dp"/>
<stroke
android:width="1dp"
android:color="@color/black"/>
</shape>

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,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/bg_euphoria_dialog_btn0"
android:state_selected="false" />
<item
android:drawable="@drawable/bg_euphoria_dialog_btn1" />
</selector>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@drawable/bg_euphoria_normal"
android:paddingHorizontal="24dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/image"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="220dp"
android:src="@mipmap/logo"
android:scaleType="centerCrop"
app:roundPercent="0.25"
tools:ignore="MissingConstraints" />
<com.joyful.euphoria.board.utils.CustomUtils
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/image"
android:layout_centerHorizontal="true"
android:layout_marginTop="12dp"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="22sp"
android:textStyle="bold"
android:letterSpacing="0.03"
app:apply_font="true" />
<ProgressBar
android:id="@+id/nova_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="280dp"
android:layout_height="6dp"
android:layout_below="@id/tv"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
android:progress="1"
android:progressDrawable="@drawable/bg_euphoria_begin_pb"
android:elevation="2dp"
android:clipToPadding="false" />
</RelativeLayout>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/category"
android:background="@drawable/bg_euphoria_normal">
<!-- 标题栏(返回+分类名称) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:id="@+id/iv_cate_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/euphoria_back"
android:background="@drawable/bg_euphoria_back" />
<TextView
android:id="@+id/tv_cate_title"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:text="@string/category"
android:textSize="24sp"
android:textColor="@color/black"
android:textStyle="bold" />
</LinearLayout>
<!-- 键盘列表RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_cate_keyboard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_euphoria_frag_viewpager"
android:layout_margin="10dp"
android:padding="8dp" />
<!-- 空状态布局 -->
<LinearLayout
android:id="@+id/ll_cate_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂无键盘数据"
android:textSize="16sp"
android:textColor="@color/gray" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,146 @@
<?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:id="@+id/set"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_euphoria_normal"
android:orientation="vertical">
<!-- 顶部操作栏:返回+标题+收藏 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:id="@+id/back"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/euphoria_back"
android:background="@drawable/bg_euphoria_back" />
<com.joyful.euphoria.board.utils.CustomUtils
android:id="@+id/textview_data_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
app:apply_font="true" />
<ImageView
android:id="@+id/im_like"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/selector_euphoria_downloader_love"
android:background="@drawable/bg_euphoria_back" />
</LinearLayout>
<!-- 中间预览区:带阴影卡片+大图预览 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="280dp"
android:layout_margin="24dp"
android:layout_gravity="center_horizontal">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:cardUseCompatPadding="true">
<ImageView
android:id="@+id/image_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:background="@color/gray" />
</androidx.cardview.widget.CardView>
<!-- 预览图顶部渐变遮罩 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_euphoria_download_mask"
android:orientation="vertical">
</LinearLayout>
</FrameLayout>
<!-- 底部功能区:下载/应用按钮 -->
<LinearLayout
android:id="@+id/layoutDownloadApply"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_euphoria_download_btn"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/im_download"
android:layout_width="28dp"
android:layout_height="28dp"
android:src="@drawable/euphoria_download" />
<com.joyful.euphoria.board.utils.CustomUtils
android:id="@+id/tv_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="@string/download_apply"
android:textColor="@color/white"
android:textSize="22sp"
android:textStyle="bold"
app:apply_font="true" />
</LinearLayout>
<!-- 推荐标题 -->
<com.joyful.euphoria.board.utils.CustomUtils
android:id="@+id/text_for_you"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="40dp"
android:text="@string/recommended"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
app:apply_font="true" />
<!-- 推荐列表:横向滚动 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recommended_recycler"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_marginTop="16dp"
android:paddingStart="20dp"
android:clipToPadding="false" />
<!-- 加载中遮罩 -->
<FrameLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent8"
android:clickable="true"
android:focusable="true"
android:visibility="gone">
<ProgressBar
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:indeterminateTint="@color/white"
android:background="@drawable/bg_euphoria_begin_pb" />
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:background="@drawable/bg_euphoria_normal">
<!-- 标题栏添加背景和阴影效果 -->
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="28sp"
android:textColor="@color/black"
android:layout_centerHorizontal="true"
android:textStyle="bold|italic"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"/>
<!-- ViewPager2 -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/tab_layout"
android:layout_below="@id/title_tv"
android:elevation="2dp"/>
<!-- 底部TabLayout优化 -->
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@drawable/euphoria_tab_shape"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_margin="16dp"
android:elevation="4dp"
app:tabIndicatorHeight="0dp"
app:tabPaddingStart="16dp"
app:tabPaddingEnd="16dp"
app:tabContentStart="0dp"
app:tabMode="fixed" />
</RelativeLayout>

View File

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

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/gif_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignTop="@id/custom_input_view"
android:layout_alignBottom="@id/custom_input_view" />
<VideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignTop="@id/custom_input_view"
android:layout_alignBottom="@id/custom_input_view" />
<com.joyful.euphoria.board.assistant.ViewCodeAssistant
android:id="@+id/custom_input_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:keyBackground="@drawable/selector_euphoria_default_key"
android:keyTextColor="@color/white"
android:keyTextSize="0sp"
android:labelTextSize="12sp"
android:paddingStart="5dp"
android:paddingTop="5dp"
android:paddingEnd="5dp"
android:paddingBottom="5dp" />
</RelativeLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="@drawable/bg_euphoria_normal">
<!-- 弹窗标题 -->
<TextView
android:id="@+id/tv_dialog_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:layout_gravity="center"
android:textColor="@android:color/black"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" />
<!-- 两列网格RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_dialog_category"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

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/bg_euphoria_dialog_download"
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/euphoria_close"
app:layout_constraintRight_toRightOf="parent" />
<com.joyful.euphoria.board.utils.CustomUtils
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_euphoria_downloader"
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/euphoria_enable"
android:visibility="gone" />
<com.joyful.euphoria.board.utils.CustomUtils
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_euphoria_downloader"
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/euphoria_enable"
android:visibility="gone" />
<com.joyful.euphoria.board.utils.CustomUtils
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,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/like_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="15dp" />
<com.joyful.euphoria.board.utils.CustomUtils
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/bg_loading"
android:textSize="16sp"
android:visibility="gone"
app:apply_font="true" />
</RelativeLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 空状态布局 -->
<LinearLayout
android:id="@+id/ll_home_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
android:background="?android:windowBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_category_data"
android:textSize="16sp"
android:textColor="#999999"/>
</LinearLayout>
<!-- 分类列表RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_home_category"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"/>
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/iv_dialog_keyboard_img"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:src="@drawable/euphoria_place_holder"
android:background="@drawable/euphoria_round_corner"
android:clipToOutline="true"/>
<TextView
android:id="@+id/tv_dialog_keyboard_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:gravity="center"
android:layout_marginTop="4dp" />
</LinearLayout>

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