From ae077dad7c2af0ecccb6d322ea0872802b88f552 Mon Sep 17 00:00:00 2001 From: Winston Sung Date: Thu, 6 Mar 2025 14:23:54 +0800 Subject: [PATCH] Add in-app language picker Add in-app language picker for languages not listed in Android system's app language settings. Bug: #100 Change-Id: I66ebdc4887d26d3ef3acff95e8af568b01ef0212 --- app/src/main/AndroidManifest.xml | 9 ++ .../app/opass/ccip/ui/DrawerMenuAdapter.kt | 4 + .../ccip/ui/LanguagePreferenceFragment.kt | 126 ++++++++++++++++++ .../java/app/opass/ccip/ui/MainActivity.kt | 5 + .../res/drawable/ic_languages_black_24dp.xml | 9 ++ .../main/res/layout/item_option_language.xml | 28 ++++ app/src/main/res/values-zh-rTW/strings.xml | 10 ++ app/src/main/res/values/strings.xml | 20 ++- 8 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/app/opass/ccip/ui/LanguagePreferenceFragment.kt create mode 100644 app/src/main/res/drawable/ic_languages_black_24dp.xml create mode 100644 app/src/main/res/layout/item_option_language.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62c9054e..89d4ad40 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -64,6 +64,15 @@ + + + + diff --git a/app/src/main/java/app/opass/ccip/ui/DrawerMenuAdapter.kt b/app/src/main/java/app/opass/ccip/ui/DrawerMenuAdapter.kt index c8072884..41bccdf3 100644 --- a/app/src/main/java/app/opass/ccip/ui/DrawerMenuAdapter.kt +++ b/app/src/main/java/app/opass/ccip/ui/DrawerMenuAdapter.kt @@ -112,6 +112,7 @@ class DrawerMenuAdapter( merged.addAll(features.map(FeatureItem.Companion::fromFeature)) merged.addAll(arrayOf( DividerItem, + MenuAction.LAUNCH_LANGUAGE_PREFERENCE_FRAGMENT, MenuAction.LAUNCH_ABOUT_SCREEN )) return merged @@ -120,12 +121,14 @@ class DrawerMenuAdapter( private fun getTitleByAction(action: MenuAction): String { return when (action) { MenuAction.SWITCH_EVENT -> context.resources.getString(R.string.switch_event) + MenuAction.LAUNCH_LANGUAGE_PREFERENCE_FRAGMENT -> context.getString(R.string.switch_language) MenuAction.LAUNCH_ABOUT_SCREEN -> context.getString(R.string.about_app) } } private fun getIconByAction(action: MenuAction): Int = when (action) { MenuAction.SWITCH_EVENT -> R.drawable.ic_swap_horiz_black_24dp + MenuAction.LAUNCH_LANGUAGE_PREFERENCE_FRAGMENT -> R.drawable.ic_languages_black_24dp MenuAction.LAUNCH_ABOUT_SCREEN -> R.drawable.ic_info_black_24dp } @@ -192,6 +195,7 @@ object DividerItem enum class MenuAction { SWITCH_EVENT, + LAUNCH_LANGUAGE_PREFERENCE_FRAGMENT, LAUNCH_ABOUT_SCREEN } diff --git a/app/src/main/java/app/opass/ccip/ui/LanguagePreferenceFragment.kt b/app/src/main/java/app/opass/ccip/ui/LanguagePreferenceFragment.kt new file mode 100644 index 00000000..72d7ce7a --- /dev/null +++ b/app/src/main/java/app/opass/ccip/ui/LanguagePreferenceFragment.kt @@ -0,0 +1,126 @@ +package app.opass.ccip.ui + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import app.opass.ccip.R +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class LanguagePreferenceFragment : DialogFragment() { + + companion object { + private const val TAG = "LanguagePreferenceFragment" + + fun show(fragmentManager: FragmentManager) { + LanguagePreferenceFragment().show(fragmentManager, TAG) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val recyclerView = RecyclerView(requireContext()).apply { + layoutParams = RecyclerView.LayoutParams( + RecyclerView.LayoutParams.MATCH_PARENT, + RecyclerView.LayoutParams.MATCH_PARENT + ) + layoutManager = LinearLayoutManager(requireContext()) + adapter = LanguagePreferenceAdapter( + items = listOf( + LanguageOptionItem( + key = "en-US", + localName = requireContext().getString(R.string.lang_local_name_en_us), + translatedName = requireContext().getString(R.string.lang_translated_name_en_us) + ), + LanguageOptionItem( + key = "nan-Hant-TW", + localName = requireContext().getString(R.string.lang_local_name_nan_hant_tw), + translatedName = requireContext().getString(R.string.lang_translated_name_nan_hant_tw) + ), + LanguageOptionItem( + key = "nan-Latn-TW-pehoeji", + localName = requireContext().getString(R.string.lang_local_name_nan_latn_tw_pehoeji), + translatedName = requireContext().getString(R.string.lang_translated_name_nan_latn_tw_pehoeji) + ), + LanguageOptionItem( + key = "nan-Latn-TW-tailo", + localName = requireContext().getString(R.string.lang_local_name_nan_latn_tw_tailo), + translatedName = requireContext().getString(R.string.lang_translated_name_nan_latn_tw_tailo) + ), + LanguageOptionItem( + key = "hi-IN", + localName = requireContext().getString(R.string.lang_local_name_hi_in), + translatedName = requireContext().getString(R.string.lang_translated_name_hi_in) + ), + LanguageOptionItem( + key = "nb-NO", + localName = requireContext().getString(R.string.lang_local_name_nb_no), + translatedName = requireContext().getString(R.string.lang_translated_name_nb_no) + ), + LanguageOptionItem( + key = "ta-IN", + localName = requireContext().getString(R.string.lang_local_name_ta_in), + translatedName = requireContext().getString(R.string.lang_translated_name_ta_in) + ), + LanguageOptionItem( + key = "zh-Hant-TW", + localName = requireContext().getString(R.string.lang_local_name_zh_hant_tw), + translatedName = requireContext().getString(R.string.lang_translated_name_zh_hant_tw) + ) + ) + ) { item -> + dialog?.dismiss() + AppCompatDelegate.setApplicationLocales( + LocaleListCompat.forLanguageTags(item.key) + ) + } + } + + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.choose_app_language) + .setView(recyclerView) + .create() + } +} + +data class LanguageOptionItem( + val key: String, + val localName: String, + val translatedName: String +) + +class LanguagePreferenceAdapter( + private val items: List, + private val onItemClick: (LanguageOptionItem) -> Unit +) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LanguagePreferenceViewHolder = + LayoutInflater.from(parent.context) + .inflate(R.layout.item_option_language, parent, false) + .let(::LanguagePreferenceViewHolder) + .apply { + itemView.setOnClickListener { + val pos = getBindingAdapterPosition() + if (pos != RecyclerView.NO_POSITION) onItemClick(items[pos]) + } + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder(holder: LanguagePreferenceViewHolder, position: Int) { + val item = items[position] + holder.localName.text = item.localName + holder.translatedName.text = item.translatedName + } +} + +class LanguagePreferenceViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val localName: TextView = view.findViewById(R.id.option_local_name) + val translatedName: TextView = view.findViewById(R.id.option_translated_name) +} diff --git a/app/src/main/java/app/opass/ccip/ui/MainActivity.kt b/app/src/main/java/app/opass/ccip/ui/MainActivity.kt index 8df18b4c..32a84636 100644 --- a/app/src/main/java/app/opass/ccip/ui/MainActivity.kt +++ b/app/src/main/java/app/opass/ccip/ui/MainActivity.kt @@ -262,6 +262,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope { this.startActivity(Intent(this, EventActivity::class.java)) finish() } + MenuAction.LAUNCH_LANGUAGE_PREFERENCE_FRAGMENT -> { + LanguagePreferenceFragment.show(supportFragmentManager) + mDrawerLayout.closeDrawers() + return + } MenuAction.LAUNCH_ABOUT_SCREEN -> { this.startActivity(Intent(this, AboutActivity::class.java)) } diff --git a/app/src/main/res/drawable/ic_languages_black_24dp.xml b/app/src/main/res/drawable/ic_languages_black_24dp.xml new file mode 100644 index 00000000..42bb09b2 --- /dev/null +++ b/app/src/main/res/drawable/ic_languages_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/item_option_language.xml b/app/src/main/res/layout/item_option_language.xml new file mode 100644 index 00000000..1eff6b46 --- /dev/null +++ b/app/src/main/res/layout/item_option_language.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6a5a7165..ed970304 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -93,6 +93,16 @@ 已儲存 Wi-Fi 網路資訊 無法儲存 Wi-Fi 網路資訊 無法儲存 Wi-Fi 網路資訊。網路密碼已複製到剪貼簿。 + 切換語言 + 選擇應用程式語言 + 英語 + 閩南語 - 傳統漢字 + 閩南語 - 白話字 + 閩南語 - 臺羅拼音 + 印地語 + 書面挪威語 + 坦米爾語 + 繁體中文 關於 &appname; 版本 %s 隱私權政策 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18e85ca2..5530bdf9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,7 +61,7 @@ Session bookmark %1$s will start in 10 minutes at %2$s Invalid QR code, invite link, or token - Tap here to scan the KKTIX QR Code ticket via the camera or gallery + Tap here to scan the KKTIX QR code ticket via the camera or gallery I have a token Enter your token @@ -94,6 +94,24 @@ Wi-Fi network saved Failed to save Wi-Fi network Failed to save the Wi-Fi information. Password copied to clipboard. + Switch language + Choose app language + English + English + 閩南語 - 傳統漢字 + Hokkien - Traditional Han script + Bân-lâm-gú - Pe̍h-ōe-jī + Hokkien - Pe̍h-ōe-jī + Bân-lâm-gú - Tâi-lô + Hokkien - Tâi-lô + हिन्दी + Hindi + norsk bokmål + Norwegian Bokmål + தமிழ் + Tamil + 繁體中文 + Mandarin - Traditional Han script About &appname; Version %s Privacy Policy