diff --git a/.github/workflows/nightly-ci.yml b/.github/workflows/nightly-ci.yml index 76068ce874..a9edfe95cb 100644 --- a/.github/workflows/nightly-ci.yml +++ b/.github/workflows/nightly-ci.yml @@ -14,6 +14,11 @@ jobs: build: if: ${{ github.repository == 'osfans/trime' && github.ref == 'refs/heads/develop' }} runs-on: ubuntu-24.04 + env: + SIGN_KEY_BASE64: ${{ secrets.SIGNING_KEY }} + SIGN_KEY_STORE_PWD: ${{ secrets.KEY_STORE_PASSWORD }} + SIGN_KEY_ALIAS: ${{ secrets.ALIAS }} + SIGN_KEY_PWD: ${{ secrets.KEY_PASSWORD }} steps: - name: Checkout uses: actions/checkout@v4 @@ -44,16 +49,6 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 - - name: Setup keystore - run: | - echo ${{ secrets.SIGNING_KEY }} | base64 --decode | cat >> $(pwd)/signingkey.jks - cat << EOF > keystore.properties - storeFile=$(pwd)/signingkey.jks - storePassword=${{ secrets.KEY_STORE_PASSWORD }} - keyAlias=${{ secrets.ALIAS }} - keyPassword=${{ secrets.KEY_PASSWORD }} - EOF - - name: Build Trime run: make release diff --git a/.github/workflows/release-ci.yml b/.github/workflows/release-ci.yml index 48f70acc5b..656304c73d 100644 --- a/.github/workflows/release-ci.yml +++ b/.github/workflows/release-ci.yml @@ -13,6 +13,11 @@ env: jobs: build: runs-on: ubuntu-24.04 + env: + SIGN_KEY_BASE64: ${{ secrets.SIGNING_KEY }} + SIGN_KEY_STORE_PWD: ${{ secrets.KEY_STORE_PASSWORD }} + SIGN_KEY_ALIAS: ${{ secrets.ALIAS }} + SIGN_KEY_PWD: ${{ secrets.KEY_PASSWORD }} steps: - name: Checkout uses: actions/checkout@v4 @@ -43,16 +48,6 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 - - name: Setup keystore - run: | - echo ${{ secrets.SIGNING_KEY }} | base64 --decode | cat >> $(pwd)/signingkey.jks - cat << EOF > keystore.properties - storeFile=$(pwd)/signingkey.jks - storePassword=${{ secrets.KEY_STORE_PASSWORD }} - keyAlias=${{ secrets.ALIAS }} - keyPassword=${{ secrets.KEY_PASSWORD }} - EOF - - name: Build Trime run: make release diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ad5c3c857..7a19e987f1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,16 +41,12 @@ android { isMinifyEnabled = false // proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-android.txt" signingConfig = - with(ApkRelease) { - if (project.buildApkRelease) { - signingConfigs.create("release") { - storeFile = file(project.storeFile!!) - storePassword = project.storePassword - keyAlias = project.keyAlias - keyPassword = project.keyPassword - } - } else { - null + project.signKeyFile?.let { + signingConfigs.create("release") { + storeFile = it + storePassword = project.signKeyStorePwd + keyAlias = project.signKeyAlias + keyPassword = project.signKeyPwd } } diff --git a/app/src/main/java/com/osfans/trime/core/Rime.kt b/app/src/main/java/com/osfans/trime/core/Rime.kt index 384ec77362..077c4e1f85 100644 --- a/app/src/main/java/com/osfans/trime/core/Rime.kt +++ b/app/src/main/java/com/osfans/trime/core/Rime.kt @@ -397,9 +397,6 @@ class Rime : @JvmStatic external fun forgetRimeCandidate(index: Int): Boolean - @JvmStatic - external fun getLibrimeVersion(): String - // module @JvmStatic external fun runRimeTask(taskName: String?): Boolean diff --git a/app/src/main/java/com/osfans/trime/data/opencc/OpenCCDictManager.kt b/app/src/main/java/com/osfans/trime/data/opencc/OpenCCDictManager.kt index 78f64b02fd..1def62987d 100644 --- a/app/src/main/java/com/osfans/trime/data/opencc/OpenCCDictManager.kt +++ b/app/src/main/java/com/osfans/trime/data/opencc/OpenCCDictManager.kt @@ -115,9 +115,6 @@ object OpenCCDictManager { configFileName: String, ): String - @JvmStatic - external fun getOpenCCVersion(): String - const val MODE_BIN_TO_TXT = true // OCD(2) to TXT const val MODE_TXT_TO_BIN = false // TXT to OCD2 } diff --git a/app/src/main/java/com/osfans/trime/data/theme/mapper/GeneralStyleMapper.kt b/app/src/main/java/com/osfans/trime/data/theme/mapper/GeneralStyleMapper.kt index ea57842602..1c2acbc02c 100644 --- a/app/src/main/java/com/osfans/trime/data/theme/mapper/GeneralStyleMapper.kt +++ b/app/src/main/java/com/osfans/trime/data/theme/mapper/GeneralStyleMapper.kt @@ -141,7 +141,7 @@ class GeneralStyleMapper( val textFont = getStringList("text_font") - val textSize = getInt("text_size") + val textSize = getFloat("text_size") val verticalCorrection = getInt("vertical_correction") diff --git a/app/src/main/java/com/osfans/trime/data/theme/mapper/LayoutStyleMapper.kt b/app/src/main/java/com/osfans/trime/data/theme/mapper/LayoutStyleMapper.kt index b9dc9fab09..8b5b486eba 100644 --- a/app/src/main/java/com/osfans/trime/data/theme/mapper/LayoutStyleMapper.kt +++ b/app/src/main/java/com/osfans/trime/data/theme/mapper/LayoutStyleMapper.kt @@ -53,7 +53,7 @@ class LayoutStyleMapper( val roundCorner = getFloat("round_corner") - val alpha = getInt("alpha") + val alpha = getFloat("alpha", 0.8f) val elevation = getInt("elevation") val movable = getString("movable") diff --git a/app/src/main/java/com/osfans/trime/data/theme/model/GeneralStyle.kt b/app/src/main/java/com/osfans/trime/data/theme/model/GeneralStyle.kt index 3f57cd8af5..c4b42a1929 100644 --- a/app/src/main/java/com/osfans/trime/data/theme/model/GeneralStyle.kt +++ b/app/src/main/java/com/osfans/trime/data/theme/model/GeneralStyle.kt @@ -69,7 +69,7 @@ data class GeneralStyle( val symbolFont: List, val symbolTextSize: Float, val textFont: List, - val textSize: Int, + val textSize: Float, val verticalCorrection: Int, val verticalGap: Int, val longTextFont: List, diff --git a/app/src/main/java/com/osfans/trime/data/theme/model/Layout.kt b/app/src/main/java/com/osfans/trime/data/theme/model/Layout.kt index 73e832493a..31c9e99614 100644 --- a/app/src/main/java/com/osfans/trime/data/theme/model/Layout.kt +++ b/app/src/main/java/com/osfans/trime/data/theme/model/Layout.kt @@ -26,7 +26,7 @@ data class Layout( val realMargin: Int, val spacing: Int, val roundCorner: Float, - val alpha: Int, + val alpha: Float, val elevation: Int, val movable: String, ) diff --git a/app/src/main/java/com/osfans/trime/ime/composition/ComposingPopupWindow.kt b/app/src/main/java/com/osfans/trime/ime/composition/ComposingPopupWindow.kt index 5c6e475692..472672f35a 100644 --- a/app/src/main/java/com/osfans/trime/ime/composition/ComposingPopupWindow.kt +++ b/app/src/main/java/com/osfans/trime/ime/composition/ComposingPopupWindow.kt @@ -55,7 +55,8 @@ class ComposingPopupWindow( theme.generalStyle.layout.border, "border_color", theme.generalStyle.layout.roundCorner, - theme.generalStyle.layout.alpha, + theme.generalStyle.layout.alpha + .toInt(), ), ) width = WindowManager.LayoutParams.WRAP_CONTENT diff --git a/app/src/main/java/com/osfans/trime/ime/composition/PreeditModule.kt b/app/src/main/java/com/osfans/trime/ime/composition/PreeditModule.kt index 815453f68b..3155f5f4cf 100644 --- a/app/src/main/java/com/osfans/trime/ime/composition/PreeditModule.kt +++ b/app/src/main/java/com/osfans/trime/ime/composition/PreeditModule.kt @@ -6,17 +6,23 @@ package com.osfans.trime.ime.composition import android.content.Context +import android.graphics.Outline +import android.graphics.Rect import android.view.Gravity +import android.view.View +import android.view.ViewOutlineProvider import android.view.WindowManager import android.widget.PopupWindow import com.osfans.trime.core.RimeProto import com.osfans.trime.daemon.RimeSession import com.osfans.trime.daemon.launchOnReady +import com.osfans.trime.data.theme.ColorManager import com.osfans.trime.data.theme.Theme import com.osfans.trime.ime.bar.QuickBar import com.osfans.trime.ime.broadcast.InputBroadcastReceiver import com.osfans.trime.ime.dependency.InputScope import me.tatarka.inject.annotations.Inject +import splitties.views.backgroundColor @InputScope @Inject @@ -26,8 +32,29 @@ class PreeditModule( rime: RimeSession, private val bar: QuickBar, ) : InputBroadcastReceiver { + private val textBackColor = ColorManager.getColor("text_back_color") + + private val topLeftCornerRadiusOutlineProvider = + object : ViewOutlineProvider() { + override fun getOutline( + view: View, + outline: Outline, + ) { + val radius = theme.generalStyle.layout.roundCorner + val width = view.width + val height = view.height + val rect = Rect(-radius.toInt(), 0, width, (height + radius).toInt()) + outline.setRoundRect(rect, radius) + } + } + val ui = - PreeditUi(context, theme).apply { + PreeditUi(context, theme, setupPreeditView = { + textBackColor?.let { backgroundColor = it } + }).apply { + root.alpha = theme.generalStyle.layout.alpha + root.outlineProvider = topLeftCornerRadiusOutlineProvider + root.clipToOutline = true preedit.setOnCursorMoveListener { position -> rime.launchOnReady { it.moveCursorPos(position) } } @@ -42,8 +69,8 @@ class PreeditModule( override fun onInputContextUpdate(ctx: RimeProto.Context) { ui.update(ctx.composition) if (ctx.composition.length > 0) { + window.showAtLocation(bar.view, Gravity.START or Gravity.TOP, 0, 0) val (x, y) = intArrayOf(0, 0).also { bar.view.getLocationInWindow(it) } - window.showAtLocation(bar.view, Gravity.START or Gravity.TOP, x, y) ui.root.post { window.update(x, y - ui.root.height, -1, -1) } @@ -51,4 +78,8 @@ class PreeditModule( window.dismiss() } } + + fun onDetached() { + window.dismiss() + } } diff --git a/app/src/main/java/com/osfans/trime/ime/composition/PreeditUi.kt b/app/src/main/java/com/osfans/trime/ime/composition/PreeditUi.kt index 40e3f8f540..ff4812a2bb 100644 --- a/app/src/main/java/com/osfans/trime/ime/composition/PreeditUi.kt +++ b/app/src/main/java/com/osfans/trime/ime/composition/PreeditUi.kt @@ -11,6 +11,7 @@ import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.view.MotionEvent import android.widget.LinearLayout +import android.widget.TextView import androidx.core.text.buildSpannedString import com.osfans.trime.core.RimeProto import com.osfans.trime.data.theme.ColorManager @@ -25,6 +26,7 @@ import splitties.views.setPaddingDp open class PreeditUi( final override val ctx: Context, private val theme: Theme, + private val setupPreeditView: (TextView.() -> Unit)? = null, ) : Ui { private val textColor = ColorManager.getColor("text_color") private val highlightTextColor = ColorManager.getColor("hilited_text_color") @@ -34,8 +36,9 @@ open class PreeditUi( view(::PreeditTextView) { setPaddingDp(3, 1, 3, 1) textColor?.let { setTextColor(it) } - textSize = theme.generalStyle.textSize.toFloat() + textSize = theme.generalStyle.textSize typeface = FontManager.getTypeface("text_font") + setupPreeditView?.invoke(this) } override val root = diff --git a/app/src/main/java/com/osfans/trime/ime/core/InputView.kt b/app/src/main/java/com/osfans/trime/ime/core/InputView.kt index 2781ca20e4..150d30b10e 100644 --- a/app/src/main/java/com/osfans/trime/ime/core/InputView.kt +++ b/app/src/main/java/com/osfans/trime/ime/core/InputView.kt @@ -5,12 +5,10 @@ package com.osfans.trime.ime.core import android.annotation.SuppressLint -import android.app.Dialog import android.graphics.Color import android.os.Build import android.view.View import android.view.View.OnClickListener -import android.view.WindowManager import android.view.inputmethod.EditorInfo import android.widget.ImageView import androidx.constraintlayout.widget.ConstraintLayout @@ -37,7 +35,6 @@ import com.osfans.trime.ime.keyboard.KeyboardWindow import com.osfans.trime.ime.preview.KeyPreviewChoreographer import com.osfans.trime.ime.symbol.LiquidKeyboard import com.osfans.trime.util.ColorUtils -import com.osfans.trime.util.styledFloat import kotlinx.coroutines.Job import kotlinx.coroutines.launch import splitties.dimensions.dp @@ -372,40 +369,13 @@ class InputView( broadcaster.onSelectionUpdate(start, end) } - private var showingDialog: Dialog? = null - - fun showDialog(dialog: Dialog) { - showingDialog?.dismiss() - val windowToken = windowToken - check(windowToken != null) { "InputView Token is null." } - val window = dialog.window!! - window.attributes.apply { - token = windowToken - type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG - } - window.addFlags( - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or - WindowManager.LayoutParams.FLAG_DIM_BEHIND, - ) - window.setDimAmount(themedContext.styledFloat(android.R.attr.backgroundDimAmount)) - showingDialog = - dialog.apply { - setOnDismissListener { this@InputView.showingDialog = null } - show() - } - } - - fun finishInput() { - showingDialog?.dismiss() - } - override fun onDetachedFromWindow() { ViewCompat.setOnApplyWindowInsetsListener(this, null) - showingDialog?.dismiss() // cancel the notification job and clear all broadcast receivers, // implies that InputView should not be attached again after detached. baseCallbackHandler.cancelJob() updateWindowViewHeightJob.cancel() + preedit.onDetached() preview.root.removeAllViews() broadcaster.clear() super.onDetachedFromWindow() diff --git a/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt b/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt index 7f0f457d2a..feeeacd6dc 100644 --- a/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt +++ b/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt @@ -7,6 +7,7 @@ package com.osfans.trime.ime.core import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.AlarmManager +import android.app.Dialog import android.app.PendingIntent import android.content.Intent import android.content.res.Configuration @@ -20,6 +21,7 @@ import android.view.KeyEvent import android.view.View import android.view.ViewGroup import android.view.Window +import android.view.WindowManager import android.view.inputmethod.CursorAnchorInfo import android.view.inputmethod.EditorInfo import android.view.inputmethod.ExtractedTextRequest @@ -51,11 +53,11 @@ import com.osfans.trime.ime.broadcast.IntentReceiver import com.osfans.trime.ime.composition.ComposingPopupWindow import com.osfans.trime.ime.keyboard.InitializationUi import com.osfans.trime.ime.keyboard.InputFeedbackManager -import com.osfans.trime.util.ShortcutUtils import com.osfans.trime.util.findSectionFrom import com.osfans.trime.util.forceShowSelf import com.osfans.trime.util.isNightMode import com.osfans.trime.util.monitorCursorAnchor +import com.osfans.trime.util.styledFloat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers @@ -64,6 +66,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.launch import splitties.bitflags.hasFlag +import splitties.systemservices.clipboardManager import splitties.systemservices.inputMethodManager import timber.log.Timber import java.util.Locale @@ -259,10 +262,6 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { } } - fun pasteByChar() { - commitTextByChar(checkNotNull(ShortcutUtils.pasteFromClipboard(this)).toString()) - } - private fun setupInputViews(theme: Theme): InputView { composingPopupWindow?.cancelJob() val newInputView = InputView(this, rime, theme) @@ -535,7 +534,6 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { clearComposition() } InputFeedbackManager.finishInput() - inputView?.finishInput() } // 直接commit不做任何处理 @@ -877,7 +875,8 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { val et = ic.getExtractedText(etr, 0) if (et == null) { Timber.d("hookKeyboard paste, et == null, try commitText") - if (ic.commitText(ShortcutUtils.pasteFromClipboard(this), 1)) { + val clipboardText = clipboardManager.primaryClip?.getItemAt(0)?.coerceToText(this) + if (ic.commitText(clipboardText, 1)) { return true } } else if (ic.performContextMenuAction(android.R.id.paste)) { @@ -934,6 +933,27 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { override fun onEvaluateFullscreenMode(): Boolean = false + private var showingDialog: Dialog? = null + + fun showDialog(dialog: Dialog) { + showingDialog?.dismiss() + dialog.window?.also { + it.attributes.apply { + token = decorView.windowToken + type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG + } + it.addFlags( + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or WindowManager.LayoutParams.FLAG_DIM_BEHIND, + ) + it.setDimAmount(styledFloat(android.R.attr.backgroundDimAmount)) + } + dialog.setOnDismissListener { + showingDialog = null + } + dialog.show() + showingDialog = dialog + } + companion object { /** Delimiter regex to split language/locale tags. */ private val DELIMITER_SPLITTER = """[-_]""".toRegex() diff --git a/app/src/main/java/com/osfans/trime/ime/keyboard/CommonKeyboardActionListener.kt b/app/src/main/java/com/osfans/trime/ime/keyboard/CommonKeyboardActionListener.kt index 4eb0c7e8d5..aa11791455 100644 --- a/app/src/main/java/com/osfans/trime/ime/keyboard/CommonKeyboardActionListener.kt +++ b/app/src/main/java/com/osfans/trime/ime/keyboard/CommonKeyboardActionListener.kt @@ -7,6 +7,7 @@ package com.osfans.trime.ime.keyboard import android.app.Dialog import android.content.Context +import android.content.Intent import android.view.KeyEvent import androidx.lifecycle.lifecycleScope import com.osfans.trime.R @@ -20,7 +21,6 @@ import com.osfans.trime.daemon.launchOnReady import com.osfans.trime.data.prefs.AppPrefs import com.osfans.trime.data.theme.ColorManager import com.osfans.trime.data.theme.KeyActionManager -import com.osfans.trime.ime.core.InputView import com.osfans.trime.ime.core.Speech import com.osfans.trime.ime.core.TrimeInputMethodService import com.osfans.trime.ime.dependency.InputScope @@ -34,11 +34,14 @@ import com.osfans.trime.ime.window.BoardWindowManager import com.osfans.trime.ui.main.settings.ColorPickerDialog import com.osfans.trime.ui.main.settings.KeySoundEffectPickerDialog import com.osfans.trime.ui.main.settings.ThemePickerDialog -import com.osfans.trime.util.ShortcutUtils -import com.osfans.trime.util.ShortcutUtils.openCategory +import com.osfans.trime.util.AppUtils +import com.osfans.trime.util.buildIntentFromAction +import com.osfans.trime.util.buildIntentFromArgument +import com.osfans.trime.util.customFormatDateTime import com.osfans.trime.util.isAsciiPrintable import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Inject +import splitties.systemservices.clipboardManager import splitties.systemservices.inputMethodManager import timber.log.Timber @@ -48,7 +51,6 @@ class CommonKeyboardActionListener( private val context: Context, private val service: TrimeInputMethodService, private val rime: RimeSession, - private val inputView: InputView, private val liquidKeyboard: LiquidKeyboard, private val windowManager: BoardWindowManager, ) { @@ -67,20 +69,24 @@ class CommonKeyboardActionListener( private fun showDialog(dialog: suspend (RimeApi) -> Dialog) { rime.launchOnReady { api -> service.lifecycleScope.launch { - inputView.showDialog(dialog(api)) + service.showDialog(dialog(api)) } } } private fun showThemePicker() { - showDialog { - ThemePickerDialog.build(service.lifecycleScope, context) + showDialog { api -> + ThemePickerDialog.build(service.lifecycleScope, context) { + api.commitComposition() + } } } private fun showColorPicker() { - showDialog { - ColorPickerDialog.build(service.lifecycleScope, context) + showDialog { api -> + ColorPickerDialog.build(service.lifecycleScope, context) { + api.commitComposition() + } } } @@ -103,7 +109,7 @@ class CommonKeyboardActionListener( showAvailableSchemaPicker() } setNegativeButton(R.string.set_ime) { _, _ -> - ShortcutUtils.launchMainActivity(context) + AppUtils.launchMainActivity(context) } } } @@ -196,13 +202,29 @@ class CommonKeyboardActionListener( windowManager.attachWindow(KeyboardWindow) } } - "paste_by_char" -> service.pasteByChar() "set_color_scheme" -> ColorManager.setColorScheme(arg) - else -> { - ShortcutUtils.call(service, action.command, arg)?.let { + "broadcast" -> service.sendBroadcast(Intent(arg)) + "clipboard" -> { + clipboardManager.primaryClip?.getItemAt(0)?.coerceToText(service)?.let { service.commitText(it) } } + "commit" -> service.commitText(arg) + "date" -> service.commitText(customFormatDateTime(arg)) + "run" -> + service.startActivity( + buildIntentFromArgument(arg)?.apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY + }, + ) + "share_text" -> service.shareText() + else -> { + service.startActivity( + buildIntentFromAction(action.command, arg)?.apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY + }, + ) + } } } KeyEvent.KEYCODE_VOICE_ASSIST -> Speech(service).startListening() // Speech Recognition @@ -212,7 +234,7 @@ class CommonKeyboardActionListener( "color" -> showColorPicker() "schema" -> showAvailableSchemaPicker() "sound" -> showSoundEffectPicker() - else -> ShortcutUtils.launchMainActivity(service) + else -> AppUtils.launchMainActivity(service) } } KeyEvent.KEYCODE_PROG_RED -> showColorPicker() @@ -269,7 +291,7 @@ class CommonKeyboardActionListener( Timber.d("handleKey: hook") return@postRimeJob } - if (context.openCategory(keyEventCode)) { + if (AppUtils.launchKeyCategory(service, keyEventCode)) { Timber.d("handleKey: openCategory") return@postRimeJob } diff --git a/app/src/main/java/com/osfans/trime/ime/symbol/DbAdapter.kt b/app/src/main/java/com/osfans/trime/ime/symbol/DbAdapter.kt index ffffbe21d4..b726ba16de 100644 --- a/app/src/main/java/com/osfans/trime/ime/symbol/DbAdapter.kt +++ b/app/src/main/java/com/osfans/trime/ime/symbol/DbAdapter.kt @@ -14,7 +14,7 @@ import com.osfans.trime.data.db.DatabaseBean import com.osfans.trime.data.db.DraftHelper import com.osfans.trime.data.theme.Theme import com.osfans.trime.ime.core.TrimeInputMethodService -import com.osfans.trime.util.ShortcutUtils +import com.osfans.trime.util.AppUtils import kotlinx.coroutines.launch class DbAdapter( @@ -65,7 +65,7 @@ class DbAdapter( } override fun onEdit(bean: DatabaseBean) { - bean.text?.let { ShortcutUtils.launchLiquidKeyboardEdit(ctx, type, bean.id, it) } + bean.text?.let { AppUtils.launchLiquidKeyboardEdit(ctx, type, bean.id, it) } } override fun onCollect(bean: DatabaseBean) { @@ -91,7 +91,7 @@ class DbAdapter( } }.setNegativeButton(R.string.cancel, null) .create() - service.inputView?.showDialog(confirm) + service.showDialog(confirm) } override val showCollectButton: Boolean = type != SymbolBoardType.COLLECTION diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/AboutFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/AboutFragment.kt index e8b23405a2..754fb91f89 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/AboutFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/AboutFragment.kt @@ -4,25 +4,18 @@ package com.osfans.trime.ui.fragments -import android.content.ClipData import android.content.Intent import android.net.Uri import android.os.Bundle -import android.widget.Toast import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.get import com.osfans.trime.R -import com.osfans.trime.core.Rime -import com.osfans.trime.data.opencc.OpenCCDictManager import com.osfans.trime.ui.components.PaddingPreferenceFragment import com.osfans.trime.ui.main.MainViewModel import com.osfans.trime.util.Const import com.osfans.trime.util.formatDateTime -import com.osfans.trime.util.thirdPartySummary -import com.osfans.trime.util.toast -import splitties.systemservices.clipboardManager class AboutFragment : PaddingPreferenceFragment() { private val viewModel: MainViewModel by activityViewModels() @@ -34,34 +27,41 @@ class AboutFragment : PaddingPreferenceFragment() { setPreferencesFromResource(R.xml.about_preference, rootKey) with(preferenceScreen) { get("about__changelog")?.apply { - summary = Const.displayVersionName + summary = Const.VERSION_NAME isCopyingEnabled = true intent = Intent( Intent.ACTION_VIEW, - Uri.parse("${Const.currentGitRepo}/commits/${Const.buildCommitHash}"), + Uri.parse("${Const.GIT_REPO}/commits/${Const.BUILD_COMMIT_HASH}"), ) } get("about__build_info")?.apply { summary = requireContext().getString( R.string.about__build_info_format, - Const.builder, - Const.currentGitRepo, - Const.buildCommitHash, - formatDateTime(Const.buildTimestamp), + Const.BUILDER, + Const.GIT_REPO, + Const.BUILD_COMMIT_HASH, + formatDateTime(Const.BUILD_TIMESTAMP), ) - setOnPreferenceClickListener { - val info = ClipData.newPlainText("BuildInfo", summary) - clipboardManager.setPrimaryClip(info) - context.toast(R.string.copy_done, Toast.LENGTH_LONG) - true + isCopyingEnabled = true + } + get("about__librime_version")?.apply { + val code = Const.LIBRIME_VERSION + val hash = extractCommitHash(code) + summary = code + intent?.data?.also { + intent!!.data = Uri.withAppendedPath(it, "commit/$hash") + } + } + get("about__opencc_version")?.apply { + val code = Const.OPENCC_VERSION + val hash = extractCommitHash(code) + summary = code + intent?.data?.also { + intent!!.data = Uri.withAppendedPath(it, "commit/$hash") } } - get("about__librime_version") - ?.thirdPartySummary(Rime.getLibrimeVersion()) - get("about__opencc_version") - ?.thirdPartySummary(OpenCCDictManager.getOpenCCVersion()) get("about__open_source_licenses")?.apply { setOnPreferenceClickListener { findNavController().navigate(R.id.action_aboutFragment_to_licenseFragment) @@ -71,8 +71,13 @@ class AboutFragment : PaddingPreferenceFragment() { } } - override fun onResume() { - super.onResume() - viewModel.setToolbarTitle(getString(R.string.pref_about)) + companion object { + private val DASH_G_PATTERN = Regex("^(.*-g)([0-9a-f]+)(.*)$") + private val COMMON_PATTERN = Regex("^([^-]*)(-.*)$") + + private fun extractCommitHash(versionCode: String): String = + DASH_G_PATTERN.find(versionCode)?.groupValues?.get(2) + ?: COMMON_PATTERN.find(versionCode)?.groupValues?.get(1) + ?: versionCode } } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/ClipboardFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/ClipboardFragment.kt index 806c8f29a9..4db84052c3 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/ClipboardFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/ClipboardFragment.kt @@ -22,7 +22,6 @@ class ClipboardFragment : PaddingPreferenceFragment() { override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.clipboard)) viewModel.disableTopOptionsMenu() } } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/KeyboardFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/KeyboardFragment.kt index 0725a864b1..c965af1967 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/KeyboardFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/KeyboardFragment.kt @@ -54,7 +54,6 @@ class KeyboardFragment : override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.pref_keyboard)) viewModel.disableTopOptionsMenu() preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/LicenseFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/LicenseFragment.kt index 31aaf69391..c200f88ce7 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/LicenseFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/LicenseFragment.kt @@ -54,7 +54,6 @@ class LicenseFragment : PaddingPreferenceFragment() { override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.about__license)) viewModel.disableTopOptionsMenu() } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/OtherFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/OtherFragment.kt index 88ff04acf7..89e1bbdeed 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/OtherFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/OtherFragment.kt @@ -40,7 +40,6 @@ class OtherFragment : PaddingPreferenceFragment() { override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.pref_other)) viewModel.disableTopOptionsMenu() } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/PrefFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/PrefFragment.kt index 028e556341..e51a98eae8 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/PrefFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/PrefFragment.kt @@ -23,7 +23,6 @@ class PrefFragment : PaddingPreferenceFragment() { override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.trime_app_name)) viewModel.enableTopOptionsMenu() } @@ -70,7 +69,7 @@ class PrefFragment : PaddingPreferenceFragment() { true } get("pref_theme_and_color")?.setOnPreferenceClickListener { - findNavController().navigate(R.id.action_prefFragment_to_themeColorFragment) + findNavController().navigate(R.id.action_prefFragment_to_themeFragment) true } get("pref_clipboard")?.setOnPreferenceClickListener { diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/ProfileFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/ProfileFragment.kt index e578c2ac55..0d4565b2c2 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/ProfileFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/ProfileFragment.kt @@ -174,7 +174,6 @@ class ProfileFragment : override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.pref_profile)) viewModel.disableTopOptionsMenu() preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/ThemeColorFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/ThemeFragment.kt similarity index 91% rename from app/src/main/java/com/osfans/trime/ui/fragments/ThemeColorFragment.kt rename to app/src/main/java/com/osfans/trime/ui/fragments/ThemeFragment.kt index a85f73538b..8794dde1b2 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/ThemeColorFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/ThemeFragment.kt @@ -16,7 +16,7 @@ import com.osfans.trime.ui.main.settings.ColorPickerDialog import com.osfans.trime.ui.main.settings.ThemePickerDialog import kotlinx.coroutines.launch -class ThemeColorFragment : PaddingPreferenceFragment() { +class ThemeFragment : PaddingPreferenceFragment() { private val viewModel: MainViewModel by activityViewModels() override fun onCreatePreferences( @@ -38,7 +38,6 @@ class ThemeColorFragment : PaddingPreferenceFragment() { override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.pref_theme_and_color)) viewModel.disableTopOptionsMenu() } } diff --git a/app/src/main/java/com/osfans/trime/ui/fragments/ToolkitFragment.kt b/app/src/main/java/com/osfans/trime/ui/fragments/ToolkitFragment.kt index efda66806d..db4e7dd349 100644 --- a/app/src/main/java/com/osfans/trime/ui/fragments/ToolkitFragment.kt +++ b/app/src/main/java/com/osfans/trime/ui/fragments/ToolkitFragment.kt @@ -11,8 +11,8 @@ import androidx.preference.Preference import com.osfans.trime.R import com.osfans.trime.ui.components.PaddingPreferenceFragment import com.osfans.trime.ui.main.MainViewModel +import com.osfans.trime.util.AppUtils import com.osfans.trime.util.Logcat -import com.osfans.trime.util.ShortcutUtils class ToolkitFragment : PaddingPreferenceFragment() { private val viewModel: MainViewModel by activityViewModels() @@ -28,7 +28,7 @@ class ToolkitFragment : PaddingPreferenceFragment() { setTitle(R.string.real_time_logs) isIconSpaceReserved = false setOnPreferenceClickListener { - ShortcutUtils.launchLogActivity(context) + AppUtils.launchLogActivity(context) true } }, @@ -54,7 +54,6 @@ class ToolkitFragment : PaddingPreferenceFragment() { override fun onResume() { super.onResume() - viewModel.setToolbarTitle(getString(R.string.pref_toolkit)) viewModel.disableTopOptionsMenu() } } diff --git a/app/src/main/java/com/osfans/trime/ui/main/PrefMainActivity.kt b/app/src/main/java/com/osfans/trime/ui/main/PrefMainActivity.kt index f665056195..bac9e160be 100644 --- a/app/src/main/java/com/osfans/trime/ui/main/PrefMainActivity.kt +++ b/app/src/main/java/com/osfans/trime/ui/main/PrefMainActivity.kt @@ -107,6 +107,12 @@ class PrefMainActivity : AppCompatActivity() { } navHostFragment.navController.addOnDestinationChangedListener { _, dest, _ -> dest.label?.let { viewModel.setToolbarTitle(it.toString()) } + binding.prefToolbar.toolbar.subtitle = + if (dest.id == R.id.prefFragment) { + getString(R.string.trime_app_slogan) + } else { + "" + } } supportActionBar?.setDisplayHomeAsUpEnabled(true) diff --git a/app/src/main/java/com/osfans/trime/ui/main/settings/ColorPickerDialog.kt b/app/src/main/java/com/osfans/trime/ui/main/settings/ColorPickerDialog.kt index ba033e86c4..b61a26cc78 100644 --- a/app/src/main/java/com/osfans/trime/ui/main/settings/ColorPickerDialog.kt +++ b/app/src/main/java/com/osfans/trime/ui/main/settings/ColorPickerDialog.kt @@ -17,6 +17,7 @@ object ColorPickerDialog { suspend fun build( scope: LifecycleCoroutineScope, context: Context, + afterConfirm: (suspend () -> Unit)? = null, ): AlertDialog { val all = withContext(Dispatchers.Default) { ColorManager.presetColorSchemes } val allIds = all.keys @@ -35,6 +36,7 @@ object ColorPickerDialog { currentIndex, ) { dialog, which -> scope.launch { + afterConfirm?.invoke() if (which != currentIndex) { ColorManager.setColorScheme(allIds.elementAt(which)) } diff --git a/app/src/main/java/com/osfans/trime/ui/main/settings/ThemePickerDialog.kt b/app/src/main/java/com/osfans/trime/ui/main/settings/ThemePickerDialog.kt index f992322c17..3606486ceb 100644 --- a/app/src/main/java/com/osfans/trime/ui/main/settings/ThemePickerDialog.kt +++ b/app/src/main/java/com/osfans/trime/ui/main/settings/ThemePickerDialog.kt @@ -18,6 +18,7 @@ object ThemePickerDialog { suspend fun build( scope: LifecycleCoroutineScope, context: Context, + afterConfirm: (suspend () -> Unit)? = null, ): AlertDialog { val all = withContext(Dispatchers.IO) { @@ -46,6 +47,7 @@ object ThemePickerDialog { currentIndex, ) { dialog, which -> scope.launch { + afterConfirm?.invoke() ThemeManager.setNormalTheme(all[which]) dialog.dismiss() } diff --git a/app/src/main/java/com/osfans/trime/util/AppContext.kt b/app/src/main/java/com/osfans/trime/util/AppContext.kt new file mode 100644 index 0000000000..324622302f --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/AppContext.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.content.Context +import com.osfans.trime.TrimeApplication + +val appContext: Context get() = TrimeApplication.getInstance().applicationContext diff --git a/app/src/main/java/com/osfans/trime/util/AppUtils.kt b/app/src/main/java/com/osfans/trime/util/AppUtils.kt new file mode 100644 index 0000000000..6e76f54254 --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/AppUtils.kt @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2015 - 2024 Rime community +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.osfans.trime.util + +import android.content.Context +import android.content.Intent +import android.util.SparseArray +import android.view.KeyEvent +import com.osfans.trime.ime.symbol.SymbolBoardType +import com.osfans.trime.ui.main.LiquidKeyboardEditActivity +import com.osfans.trime.ui.main.LogActivity +import com.osfans.trime.ui.main.PrefMainActivity +import timber.log.Timber + +object AppUtils { + private val applicationLaunchKeyCategories = + SparseArray().apply { + append(KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER) + append(KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL) + append(KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS) + append(KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR) + append(KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC) + append(KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR) + } + + fun launchKeyCategory( + context: Context, + keyCode: Int, + ): Boolean = + applicationLaunchKeyCategories[keyCode]?.let { + Timber.d("launchKeyCategory: $it") + try { + context.startActivity( + Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, it).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY) + }, + ) + true + } catch (_: Exception) { + false + } + } ?: false + + fun launchMainActivity(context: Context) { + context.startActivity { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + } + } + + fun launchLogActivity(context: Context) { + context.startActivity() + } + + fun launchLiquidKeyboardEdit( + context: Context, + type: SymbolBoardType, + id: Int, + text: String, + ) { + context.startActivity { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(LiquidKeyboardEditActivity.DB_BEAN_ID, id) + putExtra(LiquidKeyboardEditActivity.DB_BEAN_TEXT, text) + putExtra(LiquidKeyboardEditActivity.LIQUID_KEYBOARD_TYPE, type.name) + } + } +} diff --git a/app/src/main/java/com/osfans/trime/util/Bundle.kt b/app/src/main/java/com/osfans/trime/util/Bundle.kt new file mode 100644 index 0000000000..f0ff816519 --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/Bundle.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.os.Build +import android.os.Bundle +import java.io.Serializable + +inline fun Bundle.serializable(key: String): T? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getSerializable(key, T::class.java) + } else { + @Suppress("DEPRECATION") + getSerializable(key) as? T + } diff --git a/app/src/main/java/com/osfans/trime/util/Const.kt b/app/src/main/java/com/osfans/trime/util/Const.kt index b516e18bf6..11dcb3d990 100644 --- a/app/src/main/java/com/osfans/trime/util/Const.kt +++ b/app/src/main/java/com/osfans/trime/util/Const.kt @@ -7,10 +7,12 @@ package com.osfans.trime.util import com.osfans.trime.BuildConfig object Const { - val builder = BuildConfig.BUILDER - val buildTimestamp = BuildConfig.BUILD_TIMESTAMP - val buildCommitHash = BuildConfig.BUILD_COMMIT_HASH - val displayVersionName = "${BuildConfig.BUILD_VERSION_NAME}-${BuildConfig.BUILD_TYPE}" - val originalGitRepo = "https://github.com/osfans/trime" - val currentGitRepo = BuildConfig.BUILD_GIT_REPO + const val BUILDER = BuildConfig.BUILDER + const val BUILD_TIMESTAMP = BuildConfig.BUILD_TIMESTAMP + const val BUILD_COMMIT_HASH = BuildConfig.BUILD_COMMIT_HASH + const val VERSION_NAME = "${BuildConfig.BUILD_VERSION_NAME}-${BuildConfig.BUILD_TYPE}" + const val GIT_REPO = BuildConfig.BUILD_GIT_REPO + + const val LIBRIME_VERSION = BuildConfig.LIBRIME_VERSION + const val OPENCC_VERSION = BuildConfig.OPENCC_VERSION } diff --git a/app/src/main/java/com/osfans/trime/util/DateTime.kt b/app/src/main/java/com/osfans/trime/util/DateTime.kt new file mode 100644 index 0000000000..b5af80fc25 --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/DateTime.kt @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.icu.text.DateFormat +import android.icu.util.Calendar +import android.icu.util.ULocale +import android.os.Build +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +fun formatDateTime(timeMillis: Long? = null): String = SimpleDateFormat.getDateTimeInstance().format(timeMillis?.let { Date(it) } ?: Date()) + +private val iso8601DateFormat by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } +} + +fun iso8601UTCDateTime(timeMillis: Long? = null): String = iso8601DateFormat.format(timeMillis?.let { Date(it) } ?: Date()) + +fun customFormatDateTime( + pattern: String, + timeMillis: Long? = null, +): String { + val (locale, option) = + if ("@" in pattern) { + val parts = pattern.split(" ", limit = 2) + when (parts.size) { + 2 -> if ("@" in parts[0]) parts[0] to parts[1] else parts[0] to "" + 1 -> parts[0] to "" + else -> "" to "" + } + } else { + "" to pattern + } + val date = timeMillis?.let { Date(it) } ?: Date() + val loc = Locale(locale) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (option.isEmpty()) { + DateFormat.getDateInstance(DateFormat.LONG, loc).format(date) + } else { + val cal = Calendar.getInstance(ULocale(locale)) + android.icu.text + .SimpleDateFormat(option, loc) + .format(cal) + } + } else { + SimpleDateFormat(option, loc).format(date) + } +} diff --git a/app/src/main/java/com/osfans/trime/util/DeviceInfo.kt b/app/src/main/java/com/osfans/trime/util/DeviceInfo.kt index ae7ac0c98c..fd8b86d5e2 100644 --- a/app/src/main/java/com/osfans/trime/util/DeviceInfo.kt +++ b/app/src/main/java/com/osfans/trime/util/DeviceInfo.kt @@ -39,10 +39,10 @@ object DeviceInfo { ) appendLine("--------- Build Info") appendLine("Package Name: ${BuildConfig.APPLICATION_ID}") - appendLine("Builder: ${Const.builder}") + appendLine("Builder: ${Const.BUILDER}") appendLine("Version Code: ${BuildConfig.VERSION_CODE}") - appendLine("Version Name: ${Const.displayVersionName}") - appendLine("Build Time: ${iso8601UTCDateTime(Const.buildTimestamp)}") - appendLine("Build Git Hash: ${Const.buildCommitHash}") + appendLine("Version Name: ${Const.VERSION_NAME}") + appendLine("Build Time: ${iso8601UTCDateTime(Const.BUILD_TIMESTAMP)}") + appendLine("Build Git Hash: ${Const.BUILD_COMMIT_HASH}") } } diff --git a/app/src/main/java/com/osfans/trime/util/Intent.kt b/app/src/main/java/com/osfans/trime/util/Intent.kt new file mode 100644 index 0000000000..c0e9aab717 --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/Intent.kt @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.content.ComponentName +import android.content.Intent +import android.net.Uri + +private const val ANDROID_INTENT_ACTION_PREFIX = "android.intent.action" + +fun buildIntentFromArgument(argument: String): Intent? = + if (argument.contains(':')) { // URI + Intent.parseUri(argument, Intent.URI_INTENT_SCHEME) + } else if (argument.contains('/')) { + Intent(Intent.ACTION_MAIN).apply { + addCategory(Intent.CATEGORY_LAUNCHER) + component = ComponentName.unflattenFromString(argument) + } + } else { + appContext.packageManager.getLaunchIntentForPackage(argument) // Package name + } + +fun buildIntentFromAction( + action: String, + argument: String = "", +): Intent? = + when (val fullAction = "$ANDROID_INTENT_ACTION_PREFIX.${action.uppercase()}") { + Intent.ACTION_WEB_SEARCH, Intent.ACTION_SEARCH -> { + buildIntentFromArgument(argument) + } + Intent.ACTION_SEND -> { + Intent(fullAction).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, argument) + } + } + else -> Intent(fullAction, Uri.parse(argument)) + } diff --git a/app/src/main/java/com/osfans/trime/util/NinePatchBitmapFactory.kt b/app/src/main/java/com/osfans/trime/util/NinePatchBitmapFactory.kt index 7e879b843c..1d0e8355b9 100644 --- a/app/src/main/java/com/osfans/trime/util/NinePatchBitmapFactory.kt +++ b/app/src/main/java/com/osfans/trime/util/NinePatchBitmapFactory.kt @@ -12,10 +12,7 @@ import android.graphics.Rect import android.graphics.drawable.NinePatchDrawable import android.util.DisplayMetrics import timber.log.Timber -import java.io.BufferedInputStream import java.io.File -import java.io.FileInputStream -import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -44,9 +41,9 @@ object NinePatchBitmapFactory { private fun createNinePatchWithCapInsets( res: Resources?, - bitmap: Bitmap?, - rangeListX: List?, - rangeListY: List?, + bitmap: Bitmap, + rangeListX: List, + rangeListY: List, srcName: String?, ): NinePatchDrawable { val buffer = @@ -55,12 +52,12 @@ object NinePatchBitmapFactory { } private fun getByteBuffer( - rangeListX: List?, - rangeListY: List?, + rangeListX: List, + rangeListY: List, ): ByteBuffer { val buffer = ByteBuffer - .allocate(4 + 4 * 7 + 4 * 2 * rangeListX!!.size + 4 * 2 * rangeListY!!.size + 4 * 9) + .allocate(4 + 4 * 7 + 4 * 2 * rangeListX.size + 4 * 2 * rangeListY.size + 4 * 9) .order( ByteOrder.nativeOrder(), ) @@ -104,7 +101,7 @@ object NinePatchBitmapFactory { private fun checkBitmap(bitmap: Bitmap): RangeLists { val width = bitmap.width val height = bitmap.height - val rangeListX: MutableList = ArrayList() + val rangeListX = arrayListOf() var pos = -1 for (i in 1 until width - 1) { val color = bitmap.getPixel(i, 0) @@ -119,19 +116,13 @@ object NinePatchBitmapFactory { } } else { if (pos != -1) { - val range = Range() - range.start = pos - range.end = i - 1 - rangeListX.add(range) + rangeListX.add(Range(pos, i - 1)) pos = -1 } } } if (pos != -1) { - val range = Range() - range.start = pos - range.end = width - 2 - rangeListX.add(range) + rangeListX.add(Range(pos, width - 2)) } for (range in rangeListX) { Timber.v("(" + range.start + "," + range.end + ")") @@ -150,27 +141,18 @@ object NinePatchBitmapFactory { } } else { if (pos != -1) { - val range = Range() - range.start = pos - range.end = i - 1 - rangeListY.add(range) + rangeListY.add(Range(pos, i - 1)) pos = -1 } } } if (pos != -1) { - val range = Range() - range.start = pos - range.end = height - 2 - rangeListY.add(range) + rangeListY.add(Range(pos, height - 2)) } for (range in rangeListY) { Timber.v("(" + range.start + "," + range.end + ")") } - val rangeLists = RangeLists() - rangeLists.rangeListX = rangeListX - rangeLists.rangeListY = rangeListY - return rangeLists + return RangeLists(rangeListX, rangeListY) } private fun trimBitmap(bitmap: Bitmap): Bitmap { @@ -179,42 +161,31 @@ object NinePatchBitmapFactory { return Bitmap.createBitmap(bitmap, 1, 1, width - 2, height - 2) } - fun loadBitmap(file: File?): Bitmap? { - var bis: BufferedInputStream? = null - try { - bis = BufferedInputStream(FileInputStream(file)) - return BitmapFactory.decodeStream(bis) - } catch (e: IOException) { - e.printStackTrace() - } finally { - try { - bis!!.close() - } catch (e: Exception) { + fun loadBitmap(file: File): Bitmap? = + runCatching { + file.inputStream().buffered().use { + BitmapFactory.decodeStream(it) } - } - return null - } + }.getOrNull() - fun getDensityPostfix(res: Resources): String? { - var result: String? = null + fun getDensityPostfix(res: Resources): String? = when (res.displayMetrics.densityDpi) { - DisplayMetrics.DENSITY_LOW -> result = "ldpi" - DisplayMetrics.DENSITY_MEDIUM -> result = "mdpi" - DisplayMetrics.DENSITY_HIGH -> result = "hdpi" - DisplayMetrics.DENSITY_XHIGH -> result = "xhdpi" - DisplayMetrics.DENSITY_XXHIGH -> result = "xxhdpi" - DisplayMetrics.DENSITY_XXXHIGH -> result = "xxxhdpi" + DisplayMetrics.DENSITY_LOW -> "ldpi" + DisplayMetrics.DENSITY_MEDIUM -> "mdpi" + DisplayMetrics.DENSITY_HIGH -> "hdpi" + DisplayMetrics.DENSITY_XHIGH -> "xhdpi" + DisplayMetrics.DENSITY_XXHIGH -> "xxhdpi" + DisplayMetrics.DENSITY_XXXHIGH -> "xxxhdpi" + else -> null } - return result - } - class RangeLists { - var rangeListX: List? = null - var rangeListY: List? = null - } + class RangeLists( + val rangeListX: List, + val rangeListY: List, + ) - class Range { - var start = 0 - var end = 0 - } + data class Range( + val start: Int, + val end: Int, + ) } diff --git a/app/src/main/java/com/osfans/trime/util/Permissions.kt b/app/src/main/java/com/osfans/trime/util/Permissions.kt new file mode 100644 index 0000000000..88b6eca47f --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/Permissions.kt @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.content.Context +import android.os.Environment +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.Permission +import com.hjq.permissions.XXPermissions +import com.osfans.trime.R + +@Suppress("NOTHING_TO_INLINE") +inline fun Context.isStorageAvailable(): Boolean = + XXPermissions.isGranted(this, Permission.MANAGE_EXTERNAL_STORAGE) && + Environment.getExternalStorageDirectory().absolutePath.isNotEmpty() + +fun Context.requestExternalStoragePermission() { + XXPermissions + .with(this) + .permission(Permission.MANAGE_EXTERNAL_STORAGE) + .request( + object : OnPermissionCallback { + override fun onGranted( + permissions: List, + all: Boolean, + ) { + if (all) { + toast(R.string.external_storage_permission_granted) + } + } + + override fun onDenied( + permissions: List, + never: Boolean, + ) { + if (never) { + toast(R.string.external_storage_permission_denied) + XXPermissions.startPermissionActivity( + this@requestExternalStoragePermission, + permissions, + ) + } else { + toast(R.string.external_storage_permission_denied) + } + } + }, + ) +} diff --git a/app/src/main/java/com/osfans/trime/util/RecyclerView.kt b/app/src/main/java/com/osfans/trime/util/RecyclerView.kt new file mode 100644 index 0000000000..f1664cb385 --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/RecyclerView.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.recyclerview.widget.RecyclerView + +fun RecyclerView.applyNavBarInsetsBottomPadding() { + clipToPadding = false + ViewCompat.setOnApplyWindowInsetsListener(this) { _, windowInsets -> + windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).also { + setPadding(paddingLeft, paddingTop, paddingRight, it.bottom) + } + windowInsets + } +} diff --git a/app/src/main/java/com/osfans/trime/util/ShortcutUtils.kt b/app/src/main/java/com/osfans/trime/util/ShortcutUtils.kt deleted file mode 100644 index 6002671b1c..0000000000 --- a/app/src/main/java/com/osfans/trime/util/ShortcutUtils.kt +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-FileCopyrightText: 2015 - 2024 Rime community -// -// SPDX-License-Identifier: GPL-3.0-or-later - -package com.osfans.trime.util - -import android.app.SearchManager -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.icu.text.DateFormat -import android.icu.util.Calendar -import android.icu.util.ULocale -import android.net.Uri -import android.os.Build -import android.text.TextUtils -import android.util.SparseArray -import android.view.KeyEvent -import com.osfans.trime.core.Rime -import com.osfans.trime.daemon.RimeDaemon -import com.osfans.trime.data.prefs.AppPrefs -import com.osfans.trime.ime.core.TrimeInputMethodService -import com.osfans.trime.ime.symbol.SymbolBoardType -import com.osfans.trime.ui.main.LiquidKeyboardEditActivity -import com.osfans.trime.ui.main.LogActivity -import com.osfans.trime.ui.main.PrefMainActivity -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import splitties.systemservices.clipboardManager -import timber.log.Timber -import java.text.FieldPosition -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -/** - * Implementation to open/call specified application/function - */ -object ShortcutUtils { - fun call( - context: Context, - command: String, - option: String, - ): CharSequence? { - when (command) { - "broadcast" -> context.sendBroadcast(Intent(option)) - "clipboard" -> return pasteFromClipboard(context) - "date" -> return getDate(option) - "commit" -> return option - "run" -> context.startIntent(option) - "share_text" -> TrimeInputMethodService.getService().shareText() - else -> context.startIntent(command, option) - } - return null - } - - private fun Context.startIntent(arg: String) { - when { - arg.contains(':') -> { // URI - Intent.parseUri(arg, Intent.URI_INTENT_SCHEME) - } - arg.contains('/') -> { // Component name - Intent(Intent.ACTION_MAIN).apply { - addCategory(Intent.CATEGORY_LAUNCHER) - component = ComponentName.unflattenFromString(arg) - } - } - else -> packageManager.getLaunchIntentForPackage(arg) // Package name - }?.apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY - }?.let { - runCatching { - Timber.d("startIntent: arg=$arg") - startActivity(it) - }.getOrElse { Timber.e(it, "Error on starting activity with intent") } - } - } - - private fun Context.startIntent( - action: String, - arg: String, - ) { - val longAction = "android.intent.action.${action.uppercase()}" - val intent = Intent(longAction) - when (longAction) { - // Search or open link - // Note that web_search cannot directly open link - Intent.ACTION_WEB_SEARCH, Intent.ACTION_SEARCH -> { - if (arg.startsWith("http")) { - startIntent(arg) - return - } else { - intent.putExtra(SearchManager.QUERY, arg) - } - } - Intent.ACTION_SEND -> { // Share text - intent.apply { - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, arg) - } - } - else -> { // Stage the data - if (arg.isNotEmpty()) intent.data = Uri.parse(arg) - } - } - intent.flags = (Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY) - runCatching { - Timber.d("startIntent: action=$longAction, arg=$arg") - startActivity(intent) - }.getOrElse { Timber.e(it, "Error on starting activity with intent") } - } - - private fun getDate(string: String): CharSequence { - var option = "" - var locale = "" - if (string.contains("@")) { - val opt = string.split(" ".toRegex(), 2) - if (opt.size == 2 && opt[0].contains("@")) { - locale = opt[0] - option = opt[1] - } else { - locale = opt[0] - } - } - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !TextUtils.isEmpty(locale)) { - val ul = ULocale(locale) - val cc = Calendar.getInstance(ul) - val df = - if (option.isEmpty()) { - DateFormat.getDateInstance(DateFormat.LONG, ul) - } else { - android.icu.text.SimpleDateFormat(option, ul.toLocale()) - } - df.format(cc, StringBuffer(256), FieldPosition(0)).toString() - } else { - SimpleDateFormat(string, Locale.getDefault()).format(Date()) // Time - } - } - - @JvmStatic - fun pasteFromClipboard(context: Context): CharSequence? = clipboardManager.primaryClip?.getItemAt(0)?.coerceToText(context) - - fun syncInBackground() { - val prefs = AppPrefs.defaultInstance() - prefs.profile.lastBackgroundSyncTime = Date().time - CoroutineScope(Dispatchers.IO).launch { - prefs.profile.lastSyncStatus = Rime.syncRimeUserData().also { RimeDaemon.restartRime() } - } - } - - fun Context.openCategory(keyCode: Int): Boolean { - val category = applicationLaunchKeyCategories[keyCode] - return if (!category.isNullOrEmpty()) { - Timber.d("openCategory: keyEvent=${KeyEvent.keyCodeToString(keyCode)}, category=$category") - val intent = - Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY - } - runCatching { - startActivity(intent) - }.getOrElse { Timber.e(it, "Error on starting activity with category") } - true - } else { - false - } - } - - private val applicationLaunchKeyCategories = - SparseArray().apply { - append(KeyEvent.KEYCODE_EXPLORER, "android.intent.category.APP_BROWSER") - append(KeyEvent.KEYCODE_ENVELOPE, "android.intent.category.APP_EMAIL") - append(KeyEvent.KEYCODE_CONTACTS, "android.intent.category.APP_CONTACTS") - append(KeyEvent.KEYCODE_CALENDAR, "android.intent.category.APP_CALENDAR") - append(KeyEvent.KEYCODE_MUSIC, "android.intent.category.APP_MUSIC") - append(KeyEvent.KEYCODE_CALCULATOR, "android.intent.category.APP_CALCULATOR") - } - - fun launchMainActivity(context: Context) { - context.startActivity( - Intent(context, PrefMainActivity::class.java).apply { - addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK - or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - or Intent.FLAG_ACTIVITY_CLEAR_TOP, - ) - }, - ) - } - - fun launchLogActivity(context: Context) { - context.startActivity( - Intent(context, LogActivity::class.java), - ) - } - - fun launchLiquidKeyboardEdit( - context: Context, - type: SymbolBoardType, - id: Int, - text: String, - ) { - context.startActivity( - Intent(context, LiquidKeyboardEditActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - putExtra(LiquidKeyboardEditActivity.DB_BEAN_ID, id) - putExtra(LiquidKeyboardEditActivity.DB_BEAN_TEXT, text) - putExtra(LiquidKeyboardEditActivity.LIQUID_KEYBOARD_TYPE, type.name) - }, - ) - } -} diff --git a/app/src/main/java/com/osfans/trime/util/Splitties.kt b/app/src/main/java/com/osfans/trime/util/Splitties.kt new file mode 100644 index 0000000000..c15d8cc81f --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/Splitties.kt @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.content.Context +import android.util.TypedValue +import android.view.View +import androidx.annotation.AttrRes +import androidx.fragment.app.Fragment +import splitties.experimental.InternalSplittiesApi +import splitties.resources.withResolvedThemeAttribute + +@OptIn(InternalSplittiesApi::class) +fun Context.styledFloat( + @AttrRes attrRes: Int, +) = withResolvedThemeAttribute(attrRes) { + when (type) { + TypedValue.TYPE_FLOAT -> float + else -> throw IllegalArgumentException("float attribute expected") + } +} + +@Suppress("NOTHING_TO_INLINE") +inline fun View.styledFloat( + @AttrRes attrRes: Int, +) = context.styledFloat(attrRes) + +@Suppress("NOTHING_TO_INLINE") +inline fun Fragment.styledFloat( + @AttrRes attrRes: Int, +) = context!!.styledFloat(attrRes) diff --git a/app/src/main/java/com/osfans/trime/util/StartActivity.kt b/app/src/main/java/com/osfans/trime/util/StartActivity.kt new file mode 100644 index 0000000000..6476f59f34 --- /dev/null +++ b/app/src/main/java/com/osfans/trime/util/StartActivity.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.osfans.trime.util + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.fragment.app.Fragment + +inline fun Context.startActivity(setupIntent: Intent.() -> Unit = {}) { + startActivity(Intent(this, T::class.java).apply(setupIntent)) +} + +inline fun Fragment.startActivity(setupIntent: Intent.() -> Unit = {}) { + requireContext().startActivity(setupIntent) +} diff --git a/app/src/main/java/com/osfans/trime/util/Utils.kt b/app/src/main/java/com/osfans/trime/util/Utils.kt deleted file mode 100644 index e37cc1f631..0000000000 --- a/app/src/main/java/com/osfans/trime/util/Utils.kt +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-FileCopyrightText: 2015 - 2024 Rime community -// -// SPDX-License-Identifier: GPL-3.0-or-later - -package com.osfans.trime.util - -import android.content.Context -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.util.TypedValue -import android.view.View -import androidx.annotation.AttrRes -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.fragment.app.Fragment -import androidx.preference.Preference -import androidx.recyclerview.widget.RecyclerView -import com.hjq.permissions.OnPermissionCallback -import com.hjq.permissions.Permission -import com.hjq.permissions.XXPermissions -import com.osfans.trime.R -import com.osfans.trime.TrimeApplication -import splitties.experimental.InternalSplittiesApi -import splitties.resources.withResolvedThemeAttribute -import java.io.Serializable -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import java.util.TimeZone - -val appContext: Context get() = TrimeApplication.getInstance().applicationContext - -@OptIn(InternalSplittiesApi::class) -fun Context.styledFloat( - @AttrRes attrRes: Int, -) = withResolvedThemeAttribute(attrRes) { - when (type) { - TypedValue.TYPE_FLOAT -> float - else -> throw IllegalArgumentException("float attribute expected") - } -} - -@Suppress("NOTHING_TO_INLINE") -inline fun View.styledFloat( - @AttrRes attrRes: Int, -) = context.styledFloat(attrRes) - -@Suppress("NOTHING_TO_INLINE") -inline fun Fragment.styledFloat( - @AttrRes attrRes: Int, -) = context!!.styledFloat(attrRes) - -fun formatDateTime(timeMillis: Long? = null): String = SimpleDateFormat.getDateTimeInstance().format(timeMillis?.let { Date(it) } ?: Date()) - -private val iso8601DateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply { - timeZone = TimeZone.getTimeZone("UTC") - } -} - -fun iso8601UTCDateTime(timeMillis: Long? = null): String = iso8601DateFormat.format(timeMillis?.let { Date(it) } ?: Date()) - -@Suppress("NOTHING_TO_INLINE") -inline fun CharSequence.startsWithAsciiChar(): Boolean { - val firstCodePoint = this.toString().codePointAt(0) - return firstCodePoint in 0x20 until 0x80 -} - -fun RecyclerView.applyNavBarInsetsBottomPadding() { - clipToPadding = false - ViewCompat.setOnApplyWindowInsetsListener(this) { _, windowInsets -> - windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).also { - setPadding(paddingLeft, paddingTop, paddingRight, it.bottom) - } - windowInsets - } -} - -inline fun Bundle.serializable(key: String): T? = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - getSerializable(key, T::class.java) - } else { - @Suppress("DEPRECATION") - getSerializable(key) as? T - } - -fun Preference.thirdPartySummary(versionCode: String) { - summary = versionCode - intent?.let { - val commitHash = - if (versionCode.contains("-g")) { - versionCode.replace("^(.*-g)([0-9a-f]+)(.*)$".toRegex(), "$2") - } else { - versionCode.replace("^([^-]*)(-.*)$".toRegex(), "$1") - } - it.data = Uri.withAppendedPath(it.data, "commits/$commitHash") - } -} - -fun Preference.optionalPreference() { - isVisible = summary.isNullOrBlank() || intent?.data == null -} - -@Suppress("NOTHING_TO_INLINE") -inline fun Context.isStorageAvailable(): Boolean = - XXPermissions.isGranted(this, Permission.MANAGE_EXTERNAL_STORAGE) && - Environment.getExternalStorageDirectory().absolutePath.isNotEmpty() - -fun Context.requestExternalStoragePermission() { - XXPermissions - .with(this) - .permission(Permission.MANAGE_EXTERNAL_STORAGE) - .request( - object : OnPermissionCallback { - override fun onGranted( - permissions: List, - all: Boolean, - ) { - if (all) { - toast(R.string.external_storage_permission_granted) - } - } - - override fun onDenied( - permissions: List, - never: Boolean, - ) { - if (never) { - toast(R.string.external_storage_permission_denied) - XXPermissions.startPermissionActivity( - this@requestExternalStoragePermission, - permissions, - ) - } else { - toast(R.string.external_storage_permission_denied) - } - } - }, - ) -} diff --git a/app/src/main/java/com/osfans/trime/util/WeakHashSet.kt b/app/src/main/java/com/osfans/trime/util/WeakHashSet.kt index ab92aa9a2e..2942b63734 100644 --- a/app/src/main/java/com/osfans/trime/util/WeakHashSet.kt +++ b/app/src/main/java/com/osfans/trime/util/WeakHashSet.kt @@ -4,33 +4,8 @@ package com.osfans.trime.util +import java.util.Collections import java.util.WeakHashMap -class WeakHashSet : MutableSet { - private val core = WeakHashMap() - - private object PlaceHolder - - val view = object : Set by core.keys {} - override val size get() = core.size - - override fun iterator(): MutableIterator = core.keys.iterator() - - override fun add(element: T) = core.put(element, PlaceHolder) == null - - override fun addAll(elements: Collection) = elements.all(::add) - - override fun remove(element: T) = core.remove(element) != null - - override fun removeAll(elements: Collection) = elements.all(::remove) - - override fun clear() = core.clear() - - override fun retainAll(elements: Collection) = removeAll(core.keys.filter { it !in elements }) - - override operator fun contains(element: T) = core.containsKey(element) - - override fun containsAll(elements: Collection) = elements.all(::contains) - - override fun isEmpty() = core.isEmpty() -} +@Suppress("FunctionName") +fun WeakHashSet(): MutableSet = Collections.newSetFromMap(WeakHashMap()) diff --git a/app/src/main/jni/librime_jni/CMakeLists.txt b/app/src/main/jni/librime_jni/CMakeLists.txt index fccc7f9ae7..e219de3117 100644 --- a/app/src/main/jni/librime_jni/CMakeLists.txt +++ b/app/src/main/jni/librime_jni/CMakeLists.txt @@ -26,20 +26,6 @@ execute_process( ) string(STRIP ${TRIME_VERSION} TRIME_VERSION) -execute_process( - COMMAND git --git-dir ${CMAKE_SOURCE_DIR}/OpenCC/.git describe --tags - OUTPUT_VARIABLE OPENCC_VERSION -) -string(STRIP ${OPENCC_VERSION} OPENCC_VERSION) - -execute_process( - COMMAND git --git-dir ${CMAKE_SOURCE_DIR}/librime/.git describe --tags --exclude "latest" - OUTPUT_VARIABLE LIBRIME_VERSION -) -string(STRIP ${LIBRIME_VERSION} LIBRIME_VERSION) - target_compile_definitions(rime_jni PRIVATE TRIME_VERSION="${TRIME_VERSION}" - OPENCC_VERSION="${OPENCC_VERSION}" - LIBRIME_VERSION="${LIBRIME_VERSION}" ) diff --git a/app/src/main/jni/librime_jni/opencc.cc b/app/src/main/jni/librime_jni/opencc.cc index faf6579a24..52a67db31d 100644 --- a/app/src/main/jni/librime_jni/opencc.cc +++ b/app/src/main/jni/librime_jni/opencc.cc @@ -12,12 +12,6 @@ // opencc -extern "C" JNIEXPORT jstring JNICALL -Java_com_osfans_trime_data_opencc_OpenCCDictManager_getOpenCCVersion( - JNIEnv *env, jclass clazz) { - return env->NewStringUTF(OPENCC_VERSION); -} - extern "C" JNIEXPORT jstring JNICALL Java_com_osfans_trime_data_opencc_OpenCCDictManager_openCCLineConv( JNIEnv *env, jclass clazz, jstring input, jstring config_file_name) { diff --git a/app/src/main/jni/librime_jni/rime_jni.cc b/app/src/main/jni/librime_jni/rime_jni.cc index 77b579bac8..e801dfc9e9 100644 --- a/app/src/main/jni/librime_jni/rime_jni.cc +++ b/app/src/main/jni/librime_jni/rime_jni.cc @@ -474,12 +474,6 @@ Java_com_osfans_trime_core_Rime_forgetRimeCandidate(JNIEnv *env, return Rime::Instance().forgetCandidate(index); } -extern "C" JNIEXPORT jstring JNICALL -Java_com_osfans_trime_core_Rime_getLibrimeVersion(JNIEnv *env, - jclass /* thiz */) { - return env->NewStringUTF(LIBRIME_VERSION); -} - extern "C" JNIEXPORT jboolean JNICALL Java_com_osfans_trime_core_Rime_runRimeTask(JNIEnv *env, jclass /* thiz */, jstring task_name) { diff --git a/app/src/main/res/drawable/candidate_scrollbar_thumb.xml b/app/src/main/res/drawable/candidate_scrollbar_thumb.xml deleted file mode 100644 index 8c4145b730..0000000000 --- a/app/src/main/res/drawable/candidate_scrollbar_thumb.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/checkable_item.xml b/app/src/main/res/layout/checkable_item.xml deleted file mode 100644 index ab49aa8ef4..0000000000 --- a/app/src/main/res/layout/checkable_item.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/keyboard_key_preview.xml b/app/src/main/res/layout/keyboard_key_preview.xml deleted file mode 100644 index e2d3df3413..0000000000 --- a/app/src/main/res/layout/keyboard_key_preview.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/app/src/main/res/navigation/pref_nav.xml b/app/src/main/res/navigation/pref_nav.xml index 80430d05e8..503b90f903 100644 --- a/app/src/main/res/navigation/pref_nav.xml +++ b/app/src/main/res/navigation/pref_nav.xml @@ -14,7 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later + android:label="@string/trime_app_name" > + android:label="@string/profile" /> + android:label="@string/keyboard" /> + android:id="@+id/themeFragment" + android:name="com.osfans.trime.ui.fragments.ThemeFragment" + android:label="@string/theme" /> + android:label="@string/others" /> + android:label="@string/toolkit" /> + android:label="@string/about" > + android:label="@string/license" /> + android:label="@string/clipboard" /> RIME版本 OpenCC版本 更新日志 - 许可证 + 许可证 贡献代码 微软平台PRIME输入法 隐私策略 构建者:%1$s\nGit 仓库:%2$s\n构建 Git 哈希:%3$s\n构建时间:%4$s - 启用 - 启用同文输入法 - 选取软键盘 - 选取同文输入法 主题 选取主题 配色 @@ -61,32 +57,23 @@ SPDX-License-Identifier: GPL-3.0-or-later 悬浮窗编码区使用插入符号(^) 部署 - 修改设置后需要再次部署同文输入法平台 同步用户数据 重置成功 重置失败 正在部署… - 正在同步… - 正在应用主题… - 正在加载方案… 设置 其他输入法 夜间模式 - 其他设置 + 其他 显示通知栏图标 退出时清除缓存 - 同文风 - 默认 - 共享文件夹 用户文件夹 - 点击以开启后台同步 后台定时同步 上次同步时间:%1$s(%2$s)\n下次同步时间: %3$s 后台定时同步已禁用 应用市场 用户社区 按键效果 - 显示在其他应用上方 视图 在启动器中显示图标 此选项仅对部分 ROM 有效 @@ -95,7 +82,6 @@ SPDX-License-Identifier: GPL-3.0-or-later 剪贴板历史记录上限 剪贴板去重规则 剪贴板过滤规则 - 保存剪贴板内容的数量 为开关显示箭头符号(→) 横屏时全屏编辑 部署完成 @@ -120,15 +106,11 @@ SPDX-License-Identifier: GPL-3.0-or-later 系统默认 触发按键滑动手势的距离 点击候选词 - 点击候选词后,模拟空格键完成上屏(暂未实现) 击响中文之韵 - 同文输入法需要存储权限以部署或读取方案和配置,点击“确定”前往授权。 存储权限不可用 已授予存储权限 已拒绝授权存储权限,部署或读取方案和配置功能或不可用 - 同文输入法需要允许显示在其他应用上方以能显示应用外悬浮窗或对话框,点击“确定”以授权。 按键音效 - 正在应用音效... 草稿箱历史记录上限 草稿箱过滤规则 下一步 @@ -138,7 +120,6 @@ SPDX-License-Identifier: GPL-3.0-or-later 启用输入法 选择输入法 上一步 - 同文输入法设置向导 请完成键盘设置 第 1 步 第 2 步 @@ -155,24 +136,17 @@ SPDX-License-Identifier: GPL-3.0-or-later 事件不发送给被编辑的 App 及 librime 直接上屏候选词 发送数字键模拟键盘输入 - 连续击键时触发滑动手势的距离 - 判定为连续击键的时间间隔 - 拦截字母键的快速输入 - 合并多次按键输入为一次,从而缩减出候选的时间,但交互的反馈感会变差(暂未实现此功能) 允许触发按键的滑动手势 触发按键滑动手势的速度(距离/速度满足其一即可) - 连续击键时触发滑动手势的速度 构建信息 连接实体键盘时,显示迷你软键盘 Telegram群组 候选栏 已复制 - 已删除 点击空格时,忽略Shift的锁定状态 点击0-9时,忽略Shift的锁定状态 点击符号键时,忽略Shift的锁定状态 - 工具箱 - 系统 + 工具箱 导出 跳到最后 清除画面 @@ -183,26 +157,22 @@ SPDX-License-Identifier: GPL-3.0-or-later 应用程序崩溃了 Logcat 进程已创建 抱歉,但我们提供了日志以供调查。 - 键盘设置 - 关于 - 主题与配色 - 配置管理 + 键盘 + 关于 + 主题 + 配置 存储 维护 正在加载 恢复默认设置 成功 失败 - 设定存储位置和修改同步设置等 + 设定存储位置和修改同步设置等 用默认的内置配置覆盖共享目录中的相同文件 备份配置文件和同步用户词典 - 剪贴板内容 - 管理草稿箱内容 - 管理收藏夹内容 删除 编辑 收藏 - 搜索(暂未实现) 在此处编辑文本 删除全部? 确定 @@ -256,9 +226,7 @@ SPDX-License-Identifier: GPL-3.0-or-later 忘记该词 同步 设置后台同步时间 - 当前页候选词 - 仅预编辑码 - 候选词窗口 + 候选窗口 候选词显示模式 常规 预编辑码 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b9921f9657..4c47aab8a5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -24,15 +24,11 @@ SPDX-License-Identifier: GPL-3.0-or-later RIME版本 OpenCC版本 更新日誌 - 許可證 + 許可證 貢獻代碼 Win10 PRIME輸入法平臺 隱私權政策 建構者:%1$s\nGit 倉庫:%2$s\n建構 Git 雜湊值:%3$s\n建構時間:%4$s - 啓用 - 啓用同文輸入法平臺 - 選取軟鍵盤 - 選取同文輸入法平臺 主題 選取想要的主題 配色 @@ -64,26 +60,18 @@ SPDX-License-Identifier: GPL-3.0-or-later 懸浮窗編碼區使用插入符號(^) 部署 - 修改設定後需要再次部署同文輸入法平臺 同步使用者資料 備份配置檔案和同步使用者詞典 回廠成功 回廠失敗 正在部署… - 正在同步… - 正在應用主題… - 正在加載方案… 設定 其他輸入法 夜間模式 - 其他設置 + 其他 顯示通知欄圖標 離開時清理記憶體 - 同文風 - 默認 - 共享資料夾 使用者資料夾 - 點擊以啟用後台同步 後台定時同步 上次同步時間:%1$s(%2$s)\n下次同步時間: %3$s 後台定時同步已禁用 @@ -91,14 +79,12 @@ SPDX-License-Identifier: GPL-3.0-or-later 使用者社群 檢視 按键效果 - 顯示在其他應用上方 此選項僅對部分 ROM 有效 在啟動器中顯示圖示 在某些 ROM 中,您仍會看到圖示顯示,並可能在>點選它後轉到系統的應用資訊頁 預設 剪貼板去重規則 剪貼板過濾規則 - 保存剪貼板內容的數量 爲開關顯示箭頭符號(→) 橫屏時全屏編輯 部署完成 @@ -123,15 +109,11 @@ SPDX-License-Identifier: GPL-3.0-or-later 系統預設 觸發滑動手勢的最短距離 點擊候選詞 - 點擊候選詞後,模擬空格鍵完成上屏(暫未實現) 擊響中文之韻 - 同文輸入法需要儲存許可權以部署或讀取方案和配置,點選“確定”前往授權。 儲存許可權不可用 已授予儲存許可權 已拒絕授權儲存許可權,部署或讀取方案和配置功能或不可用 - 同文輸入法需要允許顯示在其他應用上方以能顯示應用外懸浮窗或對話方塊,點選“確定”以授權。 按鍵音效 - 正在應用音效... 草稿箱歷史記錄上限 草稿箱過濾規則 下一步 @@ -141,7 +123,6 @@ SPDX-License-Identifier: GPL-3.0-or-later 啟用輸入法 選擇輸入法 上一步 - 同文輸入法設定嚮導 請完成鍵盤設定 第 1 步 第 2 步 @@ -158,19 +139,13 @@ SPDX-License-Identifier: GPL-3.0-or-later 事件不發送給被編輯的 App 及 librime 直接上屏候選詞 發送數字鍵模擬鍵盤錄入 -連續擊鍵時觸發滑動手勢的距離 - 判定為連續擊鍵的時間間隔 - 攔截字母鍵的快速輸入 - 合併多次按鍵輸入為一次,從而縮減出候選的時間,但交互的反饋感會變差(暫未實現此功能) 允許觸發按鍵的滑動手勢 觸發按鍵滑動手勢的速度(距離/速度滿足其一即可) - 連續擊鍵時觸發滑動手勢的速度 建構資訊 連接實體鍵盤時,顯示迷你軟鍵盤 Telegram群組 候選欄 已複製 - 已刪除 點擊空格時,忽略Shift的鎖定狀態 點擊0-9時,忽略Shift的鎖定狀態 點擊符號鍵時,忽略Shift的鎖定狀態 @@ -183,26 +158,21 @@ SPDX-License-Identifier: GPL-3.0-or-later 清除所有紀錄檔 要清除有紀錄檔嗎?(此動作不能復原) 清除畫面 - 系統 - 工具箱 + 工具箱 Logcat 行程已建立 - 關於 - 主題與配色 - 配置管理 - 設定儲存位置和修改同步設定等 - 鍵盤設定 + 關於 + 主題 + 配置 + 設定儲存位置和修改同步設定等 + 鍵盤 儲存 維護 正在載入 成功 失敗 - 剪貼板內容 - 管理草稿箱內容 - 管理收藏夾內容 刪除 編輯 收藏 - 搜索(暫未實現) 在此處編輯文本 刪除全部? 確定 @@ -222,7 +192,6 @@ SPDX-License-Identifier: GPL-3.0-or-later 收藏夾和草稿箱也在這裡管理 一行一條正則表示式 使用自訂按鍵音 - 橫向模式 啟用橫屏模式 自動分割鍵盤比例 @@ -257,9 +226,7 @@ SPDX-License-Identifier: GPL-3.0-or-later 忘記該詞 同步 設定後台同步時間 - 當前頁候選詞 - 僅預編輯碼 - 候选词窗口 + 候选窗口 候選詞顯示模式 常規 預編輯碼 diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 0ffae985f8..42afc6623c 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -7,12 +7,6 @@ SPDX-License-Identifier: GPL-3.0-or-later --> - - preview - composition - input - none - NEVER LANDSCAPE @@ -24,23 +18,11 @@ SPDX-License-Identifier: GPL-3.0-or-later LIGHT DARK - - 0 - 20 - 50 - 100 - 200 - 500 - 1000 - -1 - auto_show always_show never_show - /sdcard/rime - /sdcard/rime NONE COLOR_ONLY diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7056c12ae8..0a7edf4402 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,19 +24,13 @@ SPDX-License-Identifier: GPL-3.0-or-later Rime Version OpenCC Version Change Log - License + License Contribute Win10 PRIME Platform Privacy Policy Builder: %1$s\nGit Repo: %2$s\nBuild Git Hash: %3$s\nBuild Time: %4$s - Enable - Enable Trime - Change Keyboard - Select Trime Themes Select Desired Theme - Tongwenfeng - Default Colors Select Desired Color for Keyboard Reset @@ -66,24 +60,18 @@ SPDX-License-Identifier: GPL-3.0-or-later Use soft cursor(^) in composition Deploy - Gotta Deploy after Modifying Settings Sync user data Backup config files and user dict sync Reset Succeeded! Reset Failed! Deploying… - Syncing… - Applying theme… - Loading schemas… Preferences Other IMEs Night Mode - Others + Others Show icon in notification bar Free memory on quit - Shared directory User directory - Click to enable Timing background sync Last: %1$s (%2$s)\nNext: %3$s Timing background sync is disabled @@ -91,7 +79,6 @@ SPDX-License-Identifier: GPL-3.0-or-later User Community View Keypress Effect - Display Over Other Apps Show app icon in launcher This option is only available on some ROMs In some ROMs, you will still see this icon in the launcher and may go to the system\'s app information page after tapping it @@ -99,7 +86,6 @@ SPDX-License-Identifier: GPL-3.0-or-later Clipboard History Limit Clipboard Compare Rules Clipboard Output Rules - Number of clipboard saved items Show arrow for switch (→) Landscape fullscreen input Deploy finish @@ -124,16 +110,12 @@ SPDX-License-Identifier: GPL-3.0-or-later Open Source Licenses System default Min travel for gesture - Click candidate - Send Space when click candidate (coming soon) - Trimes with Your Keystrokes - Trime needs to access external storage to deploy or read schemas and config, tap OK to grant the permission. + Click candidatec + Rimes with Your Keystrokes on Android External storage permission is not available External storage permission is granted External storage permission was denied, deployment or reading schemas and config may not be available - Trime needs alert window permission to show up popup window or dialog, tap OK to grant the permission. Key Sound Effect - Loading sound... Draft History Limit Draft Filter Next @@ -143,7 +125,6 @@ SPDX-License-Identifier: GPL-3.0-or-later Enable Input Method Select Input Method Prev - Trime Setup Wizard Finish setting up the keyboard Step 1 Step 2 @@ -160,19 +141,13 @@ SPDX-License-Identifier: GPL-3.0-or-later Not handled by the App being edited or librime Commit directly Send number key to librime -The distance to trigger the swipe gesture - Determine the time interval for consecutive keystrokes - Time interval as fast input - Merge multiple key inputs into one, thereby reducing the time for candidates, but the interactive feedback will become worse (It will be comming soon) Allow swipe gestures to trigger keys The speed of triggering the button swipe gesture (the distance/velocity is sufficient) - The velocity of the swipe gesture on consecutive keystrokes Build Info Show mini keyboard with real keyboard attached Telegram Group Candidate Copied to clipboard! - Deleted Ignore Shift locked for Space Ignore Shift locked for 0-9 Ignore Shift locked for Symbol keys @@ -186,25 +161,20 @@ SPDX-License-Identifier: GPL-3.0-or-later Clear Screen Export Jump to Bottom - System - Toolkit - Profile - Keyboard settings - - About + Toolkit + Profile + Keyboard + Theme + About Storage Maintenance Loading success failure - Define storage locations and change sync settings, etc. - Manage Clipboard Data - Manage Draft Data - Manage Collection Data + Define storage locations and change sync settings, etc. Delete Edit Collect - Search ( comming soon ) Edit text here Delete All? OK @@ -222,7 +192,6 @@ SPDX-License-Identifier: GPL-3.0-or-later Collection and draft are also managed here A regular expression per line Use custom key sound - Landscape Mode Enable Landscape Mode Auto Split Keyboard Space Ratio @@ -257,8 +226,6 @@ SPDX-License-Identifier: GPL-3.0-or-later Forget this word Synchronization Set background sync time - Current page of candidates - Preedit only Candidates Window Candidates mode General diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index 9bf997a58a..0000000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/xml/about_preference.xml b/app/src/main/res/xml/about_preference.xml index e1b7b422fd..424d998c9b 100644 --- a/app/src/main/res/xml/about_preference.xml +++ b/app/src/main/res/xml/about_preference.xml @@ -51,7 +51,7 @@ SPDX-License-Identifier: GPL-3.0-or-later app:iconSpaceReserved="false"> + android:title="@string/keyboard"> + android:summary="@string/profile_summary"/> diff --git a/build-logic/convention/src/main/kotlin/ApkRelease.kt b/build-logic/convention/src/main/kotlin/ApkRelease.kt deleted file mode 100644 index bccf3ef841..0000000000 --- a/build-logic/convention/src/main/kotlin/ApkRelease.kt +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2015 - 2024 Rime community -// -// SPDX-License-Identifier: GPL-3.0-or-later - -import org.gradle.api.Project -import java.io.File -import java.util.Properties - -object ApkRelease { - private const val KEYSTORE_PROPERTIES = "keystore.properties" - - private val Project.props: Properties - get() = - rootProject - .file(KEYSTORE_PROPERTIES) - .takeIf { it.exists() } - ?.let { Properties().apply { load(it.inputStream()) } } - ?: Properties() - - val Project.storeFile - get() = props["storeFile"] as? String - - val Project.storePassword - get() = props["storePassword"] as? String - - val Project.keyAlias - get() = props["keyAlias"] as? String - - val Project.keyPassword - get() = props["keyPassword"] as? String - - val Project.buildApkRelease - get() = storeFile?.let { File(it).exists() } ?: false -} diff --git a/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt index 3247438f87..f521f58b7d 100644 --- a/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt @@ -7,6 +7,20 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.configure class NativeAppConventionPlugin : NativeBaseConventionPlugin() { + private val Project.librimeVersion: String + get() = + runCmd( + "git describe --tags --long --always --exclude 'latest'", + workingDir = file("src/main/jni/librime"), + ) + + private val Project.openccVersion: String + get() = + runCmd( + "git describe --tags --long --always", + workingDir = file("src/main/jni/OpenCC"), + ) + override fun apply(target: Project) { super.apply(target) @@ -16,6 +30,10 @@ class NativeAppConventionPlugin : NativeBaseConventionPlugin() { useLegacyPackaging = true } } + defaultConfig { + buildConfigField("String", "LIBRIME_VERSION", "\"${target.librimeVersion}\"") + buildConfigField("String", "OPENCC_VERSION", "\"${target.openccVersion}\"") + } } } } diff --git a/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt index c8b8abb11f..e24befc9b1 100644 --- a/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import Versions.cmakeVersion -import Versions.ndkVersion import com.android.build.api.dsl.CommonExtension import org.gradle.api.Plugin import org.gradle.api.Project @@ -31,16 +29,12 @@ open class NativeBaseConventionPlugin : Plugin { } } - splits { - abi { - isEnable = true - reset() - if (ApkRelease.run { target.buildApkRelease }) { - include("x86", "x86_64", "armeabi-v7a", "arm64-v8a") - } else { - include(*target.buildABI.split(',').toTypedArray()) - } - isUniversalApk = false + splits.abi { + isEnable = true + isUniversalApk = false + reset() + (target.buildAbiOverride?.split(",") ?: Versions.supportedAbis).forEach { + include(it) } } } diff --git a/build-logic/convention/src/main/kotlin/NativeCacheHashPlugin.kt b/build-logic/convention/src/main/kotlin/NativeCacheHashPlugin.kt index 46693cb367..52c802f203 100644 --- a/build-logic/convention/src/main/kotlin/NativeCacheHashPlugin.kt +++ b/build-logic/convention/src/main/kotlin/NativeCacheHashPlugin.kt @@ -2,9 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import ApkRelease.buildApkRelease -import Versions.cmakeVersion -import Versions.ndkVersion import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project @@ -34,9 +31,7 @@ class NativeCacheHashPlugin : Plugin { buildString { appendLine(cmakeVersion) appendLine(ndkVersion) - if (!buildApkRelease) { - appendLine(buildABI) - } + appendLine(buildAbiOverride) appendLine(runCmd("git submodule status")) fileTree("src/main/jni/cmake").forEach { module -> appendLine(sha256(module)) diff --git a/build-logic/convention/src/main/kotlin/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt new file mode 100644 index 0000000000..e1204202c6 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2015 - 2024 Rime community + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import org.gradle.api.Project +import org.gradle.api.Task +import java.io.ByteArrayOutputStream +import java.io.File +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +fun Project.runCmd( + cmd: String, + default: String = "", + workingDir: File? = null, +): String { + val stdout = ByteArrayOutputStream() + val result = + stdout.use { + project.exec { + commandLine = cmd.split(" ") + standardOutput = stdout + workingDir?.let { this.workingDir = it } + } + } + return if (result.exitValue == 0) stdout.toString().trim() else default +} + +val Project.assetsDir: File + get() = file("src/main/assets").also { it.mkdirs() } + +val Project.cleanTask: Task + get() = tasks.getByName("clean") + +val Project.cmakeVersion + get() = envOrProp("CMAKE_VERSION", "cmakeVersion") { Versions.DEFAULT_CMAKE } + +val Project.ndkVersion + get() = envOrProp("NDK_VERSION", "ndkVersion") { Versions.DEFAULT_NDK } + +val Project.buildAbiOverride + get() = envOrPropOrNull("BUILD_ABI", "buildABI") + +val Project.builder + get() = + envOrProp("CI_NAME", "ciName") { + runCatching { runCmd("git config user.name").ifEmpty { "(Unknown)" } }.getOrElse { "(Unknown)" } + } + +val Project.buildGitRepo + get() = + envOrProp("BUILD_GIT_REPO", "buildGitRepo") { + runCmd("git remote get-url origin") + .replace("git@([^:]+):(.+)/(.+)\\.git".toRegex(), "https://$1/$2/$3") + } + +val Project.buildVersionName + get() = + envOrProp("BUILD_VERSION_NAME", "buildVersionName") { + // 构建正式版时过滤掉 nightly 标签 + val cmd = + if (builder.contains("nightly", ignoreCase = true)) { + "git describe --tags --long --always --match nightly" + } else { + "git describe --tags --long --always --match v*" + } + runCmd(cmd) + } + +val Project.buildCommitHash + get() = + envOrProp("BUILD_COMMIT_HASH", "buildCommitHash") { + runCmd("git rev-parse HEAD") + } + +val Project.buildTimestamp + get() = + envOrProp("BUILD_TIMESTAMP", "buildTimestamp") { + System.currentTimeMillis().toString() + } + +val Project.signKeyBase64: String? + get() = envOrPropOrNull("SIGN_KEY_BASE64", "signKeyBase64") + +val Project.signKeyStore + get() = envOrPropOrNull("SIGN_KEY_STORE", "signKeyStore") + +val Project.signKeyStorePwd + get() = envOrPropOrNull("SIGN_KEY_STORE_PWD", "signKeyStorePwd") + +val Project.signKeyAlias + get() = envOrPropOrNull("SIGN_KEY_ALIAS", "signKeyAlias") + +val Project.signKeyPwd + get() = envOrPropOrNull("SIGN_KEY_PWD", "signKeyPwd") + +val Project.signKeyFile: File? + get() { + signKeyStore?.let { + val file = File(it) + if (file.exists()) return file + } + + @OptIn(ExperimentalEncodingApi::class) + signKeyBase64?.let { + val buildDir = layout.buildDirectory.asFile.get() + buildDir.mkdirs() + val file = File.createTempFile("sign-", ".ks", buildDir) + try { + file.writeBytes(Base64.decode(it)) + return file + } catch (e: Exception) { + println(e.localizedMessage ?: e.stackTraceToString()) + file.delete() + } + } + return null + } diff --git a/build-logic/convention/src/main/kotlin/Utils.kt b/build-logic/convention/src/main/kotlin/Utils.kt index 27131b2a6d..3ed666f9bb 100644 --- a/build-logic/convention/src/main/kotlin/Utils.kt +++ b/build-logic/convention/src/main/kotlin/Utils.kt @@ -4,9 +4,8 @@ import kotlinx.serialization.json.Json import org.gradle.api.Project -import org.gradle.api.Task -import java.io.ByteArrayOutputStream -import java.io.File + +val json = Json { prettyPrint = true } inline fun envOrDefault( env: String, @@ -20,17 +19,6 @@ inline fun Project.propertyOrDefault( default() } -fun Project.runCmd(cmd: String): String = - ByteArrayOutputStream().use { - project.exec { - commandLine = cmd.split(" ") - standardOutput = it - } - it.toString().trim() - } - -val json = Json { prettyPrint = true } - internal inline fun Project.envOrProp( env: String, prop: String, @@ -41,57 +29,8 @@ internal inline fun Project.envOrProp( } } -val Project.assetsDir: File - get() = file("src/main/assets").also { it.mkdirs() } - -val Project.cleanTask: Task - get() = tasks.getByName("clean") - -// Change default ABI here -val Project.buildABI - get() = - envOrProp("BUILD_ABI", "buildABI") { -// "armeabi-v7a" - "arm64-v8a" -// "x86" -// "x86_64" - } - -val Project.builder - get() = - envOrProp("CI_NAME", "ciName") { - runCatching { runCmd("git config user.name").ifEmpty { "(Unknown)" } }.getOrElse { "(Unknown)" } - } - -val Project.buildGitRepo - get() = - envOrProp("BUILD_GIT_REPO", "buildGitRepo") { - runCmd("git remote get-url origin") - .replaceFirst("^git@github\\.com:", "https://github.com/") - .replaceFirst("\\.git\$", "") - } - -val Project.buildVersionName - get() = - envOrProp("BUILD_VERSION_NAME", "buildVersionName") { - // 构建正式版时过滤掉 nightly 标签 - val cmd = - if (builder.contains("nightly", ignoreCase = true)) { - "git describe --tags --long --always --match nightly" - } else { - "git describe --tags --long --always --match v*" - } - runCmd(cmd) - } - -val Project.buildCommitHash - get() = - envOrProp("BUILD_COMMIT_HASH", "buildCommitHash") { - runCmd("git rev-parse HEAD") - } - -val Project.buildTimestamp - get() = - envOrProp("BUILD_TIMESTAMP", "buildTimestamp") { - System.currentTimeMillis().toString() - } +fun Project.envOrPropOrNull( + env: String, + prop: String, +) = System.getenv(env)?.takeIf { it.isNotBlank() } + ?: runCatching { property(prop)!!.toString() }.getOrNull() diff --git a/build-logic/convention/src/main/kotlin/Versions.kt b/build-logic/convention/src/main/kotlin/Versions.kt index 9334801cae..7ac18de207 100644 --- a/build-logic/convention/src/main/kotlin/Versions.kt +++ b/build-logic/convention/src/main/kotlin/Versions.kt @@ -2,15 +2,9 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import org.gradle.api.Project - object Versions { - private const val DEFAULT_CMAKE = "3.22.1" - private const val DEFAULT_NDK = "25.2.9519653" - - val Project.cmakeVersion - get() = envOrProp("CMAKE_VERSION", "cmakeVersion") { DEFAULT_CMAKE } + const val DEFAULT_CMAKE = "3.22.1" + const val DEFAULT_NDK = "25.2.9519653" - val Project.ndkVersion - get() = envOrProp("NDK_VERSION", "ndkVersion") { DEFAULT_NDK } + val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") }