Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aba3c54

Browse files
rockaberberman
authored andcommitted
Migrate navigation graph xml to kotlin DSL (fcitx5-android#754)
* Migrate navigation graph xml to kotlin DSL see also: https://developer.android.com/guide/navigation/design/kotlin-dsl * Make ConfigDescriptor serializable * Make QuickPhrase serializable --------- Co-authored-by: Potato Hatsue <[email protected]>
1 parent 0e7d622 commit aba3c54

26 files changed

+667
-620
lines changed

app/src/main/java/org/fcitx/fcitx5/android/core/Types.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/*
22
* SPDX-License-Identifier: LGPL-2.1-or-later
3-
* SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors
3+
* SPDX-FileCopyrightText: Copyright 2021-2025 Fcitx5 for Android Contributors
44
*/
55
package org.fcitx.fcitx5.android.core
66

77
import android.os.Parcelable
88
import kotlinx.parcelize.Parcelize
9+
import kotlinx.serialization.Serializable
910

1011
data class InputMethodSubMode(val name: String, val label: String, val icon: String) {
1112
constructor() : this("", "", "")
@@ -77,6 +78,7 @@ data class InputMethodEntry(
7778
}
7879

7980
@Parcelize
81+
@Serializable
8082
data class RawConfig(
8183
val name: String,
8284
val comment: String,
@@ -161,6 +163,7 @@ data class AddonInfo(
161163
val dependencies: Array<String> = arrayOf(),
162164
val optionalDependencies: Array<String> = arrayOf(),
163165
) {
166+
@Suppress("UNUSED") // used in JNI
164167
constructor(
165168
uniqueName: String,
166169
name: String,

app/src/main/java/org/fcitx/fcitx5/android/data/quickphrase/BuiltinQuickPhrase.kt

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,13 @@ class BuiltinQuickPhrase(
1414

1515
init {
1616
ensureFileExists()
17+
evaluateOverride()
1718
}
1819

19-
var override: CustomQuickPhrase? =
20-
if (overrideFile.exists())
21-
CustomQuickPhrase(overrideFile)
22-
else {
23-
val disabledOverride = File(overrideFile.path + ".$DISABLE")
24-
if (disabledOverride.exists())
25-
CustomQuickPhrase(disabledOverride)
26-
else
27-
null
28-
}
20+
val overrideFilePath: String
21+
get() = overrideFile.absolutePath
22+
23+
var override: CustomQuickPhrase? = null
2924
private set
3025

3126
override val isEnabled: Boolean
@@ -35,6 +30,7 @@ class BuiltinQuickPhrase(
3530
if (override != null)
3631
return
3732
file.copyTo(overrideFile, overwrite = true)
33+
// Update override
3834
override = CustomQuickPhrase(overrideFile)
3935
}
4036

@@ -66,6 +62,21 @@ class BuiltinQuickPhrase(
6662
override = null
6763
}
6864

65+
/**
66+
* Make sure [override] is set correctly.
67+
*/
68+
fun evaluateOverride() {
69+
override = if (overrideFile.exists())
70+
CustomQuickPhrase(overrideFile)
71+
else {
72+
val disabledOverride = File(overrideFile.path + ".$DISABLE")
73+
if (disabledOverride.exists())
74+
CustomQuickPhrase(disabledOverride)
75+
else
76+
null
77+
}
78+
}
79+
6980
override fun toString(): String {
7081
return "BuiltinQuickPhrase(file=$file, overrideFile=$overrideFile, override=$override, isEnabled=$isEnabled)"
7182
}

app/src/main/java/org/fcitx/fcitx5/android/data/quickphrase/QuickPhrase.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,25 @@
44
*/
55
package org.fcitx.fcitx5.android.data.quickphrase
66

7+
import android.os.Parcel
8+
import android.os.Parcelable
9+
import kotlinx.serialization.Serializable
710
import java.io.File
8-
import java.io.Serializable
911

10-
abstract class QuickPhrase : Serializable {
12+
@Serializable(QuickPhraseSerializer::class)
13+
abstract class QuickPhrase : Parcelable {
14+
15+
override fun describeContents(): Int = 0
16+
17+
override fun writeToParcel(dest: Parcel, flags: Int) {
18+
dest.writeString(file.absolutePath)
19+
dest.writeByte(if (this is BuiltinQuickPhrase) 1 else 0)
20+
if (this is BuiltinQuickPhrase) {
21+
dest.writeString(overrideFilePath)
22+
} else {
23+
dest.writeString(null)
24+
}
25+
}
1126

1227
abstract val file: File
1328

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* SPDX-FileCopyrightText: Copyright 2025 Fcitx5 for Android Contributors
4+
*/
5+
6+
package org.fcitx.fcitx5.android.data.quickphrase
7+
8+
import kotlinx.serialization.ExperimentalSerializationApi
9+
import kotlinx.serialization.KSerializer
10+
import kotlinx.serialization.builtins.serializer
11+
import kotlinx.serialization.descriptors.SerialDescriptor
12+
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
13+
import kotlinx.serialization.descriptors.element
14+
import kotlinx.serialization.encoding.Decoder
15+
import kotlinx.serialization.encoding.Encoder
16+
import kotlinx.serialization.encoding.decodeStructure
17+
import kotlinx.serialization.encoding.encodeStructure
18+
import java.io.File
19+
20+
object QuickPhraseSerializer : KSerializer<QuickPhrase> {
21+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("quickphrase") {
22+
element<String>("path")
23+
element<Boolean>("isBuiltin")
24+
element<String?>("override")
25+
}
26+
27+
@OptIn(ExperimentalSerializationApi::class)
28+
override fun serialize(
29+
encoder: Encoder,
30+
value: QuickPhrase
31+
) = encoder.encodeStructure(descriptor) {
32+
encodeStringElement(descriptor, 0, value.file.absolutePath)
33+
encodeBooleanElement(descriptor, 1, value is BuiltinQuickPhrase)
34+
encodeNullableSerializableElement(
35+
descriptor,
36+
2,
37+
String.serializer(),
38+
value.let { it as? BuiltinQuickPhrase }?.overrideFilePath
39+
)
40+
41+
}
42+
43+
@OptIn(ExperimentalSerializationApi::class)
44+
override fun deserialize(decoder: Decoder): QuickPhrase =
45+
decoder.decodeStructure(descriptor) {
46+
var path: String? = null
47+
var isBuiltin = false
48+
var overridePath: String? = null
49+
50+
while (true) {
51+
when (decodeElementIndex(descriptor)) {
52+
0 -> path = decodeStringElement(descriptor, 0)
53+
1 -> isBuiltin = decodeBooleanElement(descriptor, 1)
54+
2 -> overridePath = decodeNullableSerializableElement(
55+
descriptor, 2, String.serializer()
56+
)
57+
else -> break
58+
}
59+
}
60+
61+
val file = File(path ?: throw IllegalStateException("Path cannot be null"))
62+
if (isBuiltin) {
63+
BuiltinQuickPhrase(
64+
file,
65+
File(
66+
overridePath ?: throw IllegalStateException("Override path cannot be null")
67+
)
68+
)
69+
} else {
70+
CustomQuickPhrase(file)
71+
}
72+
}
73+
}

app/src/main/java/org/fcitx/fcitx5/android/ui/main/AboutFragment.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
/*
22
* SPDX-License-Identifier: LGPL-2.1-or-later
3-
* SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors
3+
* SPDX-FileCopyrightText: Copyright 2021-2025 Fcitx5 for Android Contributors
44
*/
55
package org.fcitx.fcitx5.android.ui.main
66

7+
import android.annotation.SuppressLint
78
import android.content.Intent
89
import android.net.Uri
910
import android.os.Bundle
10-
import androidx.navigation.fragment.findNavController
1111
import org.fcitx.fcitx5.android.BuildConfig
1212
import org.fcitx.fcitx5.android.R
1313
import org.fcitx.fcitx5.android.ui.common.PaddingPreferenceFragment
14+
import org.fcitx.fcitx5.android.ui.main.settings.SettingsRoute
1415
import org.fcitx.fcitx5.android.utils.Const
1516
import org.fcitx.fcitx5.android.utils.addCategory
1617
import org.fcitx.fcitx5.android.utils.addPreference
1718
import org.fcitx.fcitx5.android.utils.formatDateTime
19+
import org.fcitx.fcitx5.android.utils.navigateWithAnim
1820

1921
class AboutFragment : PaddingPreferenceFragment() {
2022

23+
@SuppressLint("UseKtx")
2124
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
2225
preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()).apply {
2326
addPreference(R.string.privacy_policy) {
@@ -27,7 +30,7 @@ class AboutFragment : PaddingPreferenceFragment() {
2730
R.string.open_source_licenses,
2831
R.string.licenses_of_third_party_libraries
2932
) {
30-
findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
33+
navigateWithAnim(SettingsRoute.License)
3134
}
3235
addPreference(R.string.source_code, R.string.github_repo) {
3336
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(Const.githubRepo)))

app/src/main/java/org/fcitx/fcitx5/android/ui/main/MainActivity.kt

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/*
22
* SPDX-License-Identifier: LGPL-2.1-or-later
3-
* SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors
3+
* SPDX-FileCopyrightText: Copyright 2021-2025 Fcitx5 for Android Contributors
44
*/
55
package org.fcitx.fcitx5.android.ui.main
66

77
import android.Manifest
8+
import android.annotation.SuppressLint
89
import android.content.Intent
910
import android.content.pm.PackageManager
1011
import android.net.Uri
@@ -17,20 +18,23 @@ import androidx.activity.viewModels
1718
import androidx.appcompat.app.AlertDialog
1819
import androidx.appcompat.app.AppCompatActivity
1920
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
20-
import androidx.core.os.bundleOf
2121
import androidx.core.view.ViewCompat
2222
import androidx.core.view.WindowInsetsCompat
2323
import androidx.core.view.forEach
2424
import androidx.core.view.updateLayoutParams
2525
import androidx.navigation.NavController
26+
import androidx.navigation.NavDestination.Companion.hasRoute
2627
import androidx.navigation.fragment.NavHostFragment
28+
import org.fcitx.fcitx5.android.BuildConfig
2729
import org.fcitx.fcitx5.android.R
2830
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
2931
import org.fcitx.fcitx5.android.databinding.ActivityMainBinding
30-
import org.fcitx.fcitx5.android.ui.main.settings.PinyinDictionaryFragment
32+
import org.fcitx.fcitx5.android.ui.main.settings.SettingsRoute
3133
import org.fcitx.fcitx5.android.ui.setup.SetupActivity
3234
import org.fcitx.fcitx5.android.utils.Const
3335
import org.fcitx.fcitx5.android.utils.item
36+
import org.fcitx.fcitx5.android.utils.navigateWithAnim
37+
import org.fcitx.fcitx5.android.utils.parcelable
3438
import org.fcitx.fcitx5.android.utils.startActivity
3539
import splitties.dimensions.dp
3640
import splitties.resources.styledColor
@@ -65,6 +69,7 @@ class MainActivity : AppCompatActivity() {
6569
// because navController would change toolbar title, we need to control it by ourselves
6670
setupToolbarMenu(binding.toolbar.menu)
6771
navController = binding.navHostFragment.getFragment<NavHostFragment>().navController
72+
navController.graph = SettingsRoute.createGraph(navController)
6873
binding.toolbar.setNavigationOnClickListener {
6974
// prevent navigate up when child fragment has enabled `OnBackPressedCallback`
7075
if (onBackPressedDispatcher.hasEnabledCallbacks()) {
@@ -82,38 +87,43 @@ class MainActivity : AppCompatActivity() {
8287
}
8388
navController.addOnDestinationChangedListener { _, dest, _ ->
8489
dest.label?.let { viewModel.setToolbarTitle(it.toString()) }
85-
when (dest.id) {
86-
R.id.themeFragment -> viewModel.disableToolbarShadow()
87-
else -> viewModel.enableToolbarShadow()
90+
if (dest.hasRoute<SettingsRoute.Theme>()) {
91+
viewModel.disableToolbarShadow()
92+
} else {
93+
viewModel.enableToolbarShadow()
8894
}
8995
}
90-
if (intent?.action == Intent.ACTION_MAIN && SetupActivity.shouldShowUp()) {
91-
startActivity<SetupActivity>()
92-
} else {
93-
processIntent(intent)
94-
}
95-
addOnNewIntentListener {
96-
processIntent(it)
97-
}
96+
processIntent(intent)
9897
checkNotificationPermission()
9998
}
10099

100+
override fun onNewIntent(intent: Intent) {
101+
super.onNewIntent(intent)
102+
processIntent(intent)
103+
}
104+
101105
private fun processIntent(intent: Intent?) {
102-
if (intent?.action == Intent.ACTION_VIEW) {
103-
intent.data?.let {
106+
val action = intent?.action ?: return
107+
when (action) {
108+
Intent.ACTION_MAIN -> if (SetupActivity.shouldShowUp()) {
109+
startActivity<SetupActivity>()
110+
}
111+
Intent.ACTION_VIEW -> intent.data?.let {
104112
AlertDialog.Builder(this)
105113
.setTitle(R.string.pinyin_dict)
106114
.setMessage(R.string.whether_import_dict)
107115
.setNegativeButton(android.R.string.cancel) { _, _ -> }
108116
.setPositiveButton(android.R.string.ok) { _, _ ->
109-
navController.popBackStack(R.id.mainFragment, false)
110-
navController.navigate(
111-
R.id.action_mainFragment_to_pinyinDictionaryFragment,
112-
bundleOf(PinyinDictionaryFragment.INTENT_DATA_URI to it)
113-
)
117+
navController.popBackStack(SettingsRoute.Index, false)
118+
navController.navigateWithAnim(SettingsRoute.PinyinDict(it))
114119
}
115120
.show()
116121
}
122+
Intent.ACTION_RUN -> {
123+
val route = intent.parcelable<SettingsRoute>(EXTRA_SETTINGS_ROUTE) ?: return
124+
navController.popBackStack(SettingsRoute.Index, false)
125+
navController.navigateWithAnim(route)
126+
}
117127
}
118128
}
119129

@@ -127,13 +137,14 @@ class MainActivity : AppCompatActivity() {
127137
}
128138
val aboutMenuItems = listOf(
129139
menu.item(R.string.faq) {
140+
@SuppressLint("UseKtx")
130141
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(Const.faqUrl)))
131142
},
132143
menu.item(R.string.developer) {
133-
navController.navigate(R.id.action_mainFragment_to_developerFragment)
144+
navController.navigateWithAnim(SettingsRoute.Developer)
134145
},
135146
menu.item(R.string.about) {
136-
navController.navigate(R.id.action_mainFragment_to_aboutFragment)
147+
navController.navigateWithAnim(SettingsRoute.About)
137148
}
138149
)
139150
viewModel.aboutButton.observe(this@MainActivity) { enabled ->
@@ -199,4 +210,8 @@ class MainActivity : AppCompatActivity() {
199210
super.onStop()
200211
}
201212

213+
companion object {
214+
const val EXTRA_SETTINGS_ROUTE = "${BuildConfig.APPLICATION_ID}.EXTRA_SETTINGS_ROUTE"
215+
}
216+
202217
}

0 commit comments

Comments
 (0)