From 0f360abc4caa74b4579f351927e889f8efc302d2 Mon Sep 17 00:00:00 2001 From: Sean Mac Gillicuddy Date: Thu, 5 Sep 2019 14:57:43 +0100 Subject: [PATCH] #1399 rewrite to kotlin --- .../di/modules/ApplicationModule.java | 22 +- .../extensions/ContextExtensions.kt | 8 + .../kiwixmobile/main/KiwixTextToSpeech.java | 2 +- .../kiwix/kiwixmobile/utils/BookUtils.java | 6 +- .../kiwixmobile/utils/LanguageContainer.kt | 19 ++ .../kiwix/kiwixmobile/utils/LanguageUtils.kt | 226 ++++++------------ .../kiwix/kiwixmobile/utils/BookUtilsTest.kt | 4 +- 7 files changed, 111 insertions(+), 176 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageContainer.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java index 9441330aa..4ae28e0d2 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java @@ -34,20 +34,18 @@ import org.kiwix.kiwixmobile.di.qualifiers.IO; import org.kiwix.kiwixmobile.di.qualifiers.MainThread; import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter; import org.kiwix.kiwixmobile.utils.BookUtils; -import org.kiwix.kiwixmobile.utils.LanguageUtils; @Module(includes = { - ActivityBindingModule.class, - AndroidInjectionModule.class, - DownloaderModule.class, - ViewModelModule.class, - DatabaseModule.class + ActivityBindingModule.class, + AndroidInjectionModule.class, + DownloaderModule.class, + ViewModelModule.class, + DatabaseModule.class }) public class ApplicationModule { @Provides @Singleton Application provideApplication(Context context) { return (Application) context; - } @Provides @@ -62,14 +60,8 @@ public class ApplicationModule { @Provides @Singleton - BookUtils provideBookUtils(LanguageUtils.LanguageContainer container) { - return new BookUtils(container); - } - - @Provides - @Singleton - LanguageUtils.LanguageContainer provideLanguageContainer() { - return new LanguageUtils.LanguageContainer(); + BookUtils provideBookUtils() { + return new BookUtils(); } @IO diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt index 7b019f9c4..a24f03a6f 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt @@ -3,8 +3,11 @@ package org.kiwix.kiwixmobile.extensions import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.widget.Toast import org.kiwix.kiwixmobile.zim_manager.BaseBroadcastReceiver +import java.util.Locale fun Context?.toast( stringId: Int, @@ -28,3 +31,8 @@ fun Context?.toast( fun Context.registerReceiver(baseBroadcastReceiver: BaseBroadcastReceiver): Intent? = registerReceiver(baseBroadcastReceiver, IntentFilter(baseBroadcastReceiver.action)) + +val Context.locale: Locale + get() = + if (VERSION.SDK_INT >= VERSION_CODES.N) resources.configuration.locales.get(0) + else resources.configuration.locale diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixTextToSpeech.java b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixTextToSpeech.java index 44aec75b2..718067a3b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixTextToSpeech.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixTextToSpeech.java @@ -110,7 +110,7 @@ public class KiwixTextToSpeech { onSpeakingListener.onSpeakingEnded(); } } else { - Locale locale = LanguageUtils.ISO3ToLocale(ZimContentProvider.getLanguage()); + Locale locale = LanguageUtils.iSO3ToLocale(ZimContentProvider.getLanguage()); int result; if ("mul".equals(ZimContentProvider.getLanguage())) { Log.d(TAG_KIWIX, "TextToSpeech: disabled " + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/BookUtils.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/BookUtils.java index 7c282f425..17ffb67fa 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/BookUtils.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/BookUtils.java @@ -28,11 +28,9 @@ import java.util.Map; public class BookUtils { public final Map localeMap; - public LanguageUtils.LanguageContainer container; // Create a map of ISO 369-2 language codes - public BookUtils(LanguageUtils.LanguageContainer container) { - this.container = container; + public BookUtils() { String[] languages = Locale.getISOLanguages(); localeMap = new HashMap<>(languages.length); for (String language : languages) { @@ -49,7 +47,7 @@ public class BookUtils { } if (languageCode.length() == 2) { - return container.findLanguageName(languageCode).getLanguageName(); + return new LanguageContainer(languageCode).getLanguageName(); } else if (languageCode.length() == 3) { try { return localeMap.get(languageCode).getDisplayLanguage(); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageContainer.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageContainer.kt new file mode 100644 index 000000000..20653c4f4 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageContainer.kt @@ -0,0 +1,19 @@ +package org.kiwix.kiwixmobile.utils + +import java.util.Locale + +class LanguageContainer private constructor(val languageCode: String, val languageName: String) { + constructor(languageCode: String) : this(languageCode, chooseLanguageName(languageCode)) + + companion object { + private fun chooseLanguageName( + languageCode: String + ): String { + val displayLanguage = Locale(languageCode).displayLanguage + return if (displayLanguage.length == 2 || displayLanguage.isEmpty()) + Locale.ENGLISH.displayLanguage + else + displayLanguage + } + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.kt index 94c0de3da..6a2fd38c9 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.kt @@ -31,70 +31,46 @@ import android.view.InflateException import android.view.LayoutInflater import android.view.View import android.widget.TextView +import org.kiwix.kiwixmobile.extensions.locale import org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX import org.kiwix.kiwixmobile.utils.files.FileUtils import java.text.Collator -import java.util.Collections -import java.util.HashMap import java.util.Locale -import java.util.MissingResourceException -class LanguageUtils(private val mContext: Context) { - private val mLanguageList: List = sortWithCollator(setupLanguageList()) - private val mLocaleLanguageCodes: List = getLanguageCodesFromAssets()) +class LanguageUtils(private val context: Context) { + private val localeLanguageCodes: List = languageCodesFromAssets() + private val languageList: List = languageContainersFrom(localeLanguageCodes) + val keys = languageList.map(LanguageContainer::languageCode) - private fun sortWithCollator(languageCodesFromAssets: List): List { - val localeCollator = Collator.getInstance(mContext.resources.configuration.locale) - localeCollator.strength = Collator.SECONDARY - Collections.sort(languageCodesFromAssets) { o1, o2 -> localeCollator.compare(o1.languageName, o2) } + private fun languageContainersFrom(languageCodes: List) = + sortWithCollator(languageCodes.map(::LanguageContainer).toMutableList()) + + private fun languageCodesFromAssets(): List { + return FileUtils.readLocalesFromAssets(context) + .filter(String::isNotEmpty) + .map { locale -> locale.trim { it <= ' ' } } } - // Get a list of all the language names - val values: List - get() = mLanguageList.map(LanguageContainer::languageName) - - // Get a list of all the language codes - val keys: List - get() = mLanguageList.map(LanguageContainer::languageCode) - - init { - - sortLanguageList() + private fun sortWithCollator(languageCodesFromAssets: MutableList): + MutableList { + val localeCollator = + Collator.getInstance(context.locale).apply { strength = Collator.SECONDARY } + languageCodesFromAssets.sortWith(Comparator { o1, o2 -> + localeCollator.compare( + o1.languageName, + o2.languageName + ) + }) + return languageCodesFromAssets } - // Read the language codes, that are supported in this app from the locales.txt file - private fun getLanguageCodesFromAssets() = FileUtils.readLocalesFromAssets(mContext) - .filterNot(String::isEmpty) - .map { locale -> locale.trim { it <= ' ' } } - - // Create a list containing the language code and the corresponding (english) langauge name - private fun setupLanguageList() = mLocaleLanguageCodes.map(::LanguageContainer) - - // Sort the language list by the language name - private fun sortLanguageList(locale: Locale) { - - Collections.sort( - mLanguageList - ) { a, b -> localeCollator.compare(a.languageName, b.languageName) } - } - - // Check, if the selected Locale is supported and weather we actually need to change our font. - // We do this by checking, if our Locale is available in the List, that Locale.getAvailableLocales() returns. private fun haveToChangeFont(sharedPreferenceUtil: SharedPreferenceUtil): Boolean { - - for (s in Locale.getAvailableLocales()) { - if (s.language == Locale.getDefault().toString()) { - return false - } - - // Don't change the language, if the options hasn't been set - val language = sharedPreferenceUtil.getPrefLanguage("") - - if (language.isEmpty()) { - return false - } + if (sharedPreferenceUtil.getPrefLanguage("").isEmpty()) { + return false } - return true + return Locale.getAvailableLocales().firstOrNull { locale -> + locale.language == Locale.getDefault().toString() + } == null } // Change the font of all the TextViews and its subclasses in our whole app by attaching a custom @@ -115,10 +91,11 @@ class LanguageUtils(private val mContext: Context) { val field = LayoutInflater::class.java.getDeclaredField("mFactorySet") field.isAccessible = true field.setBoolean(layoutInflater, false) - layoutInflater.factory = LayoutInflaterFactory(mContext, layoutInflater) + layoutInflater.factory = LayoutInflaterFactory(context, layoutInflater) } catch (e: NoSuchFieldException) { Log.w( - TAG_KIWIX, "Font Change Failed: Could not access private field of the LayoutInflater", + TAG_KIWIX, + "Font Change Failed: Could not access private field of the LayoutInflater", e ) } catch (e: IllegalAccessException) { @@ -143,34 +120,25 @@ class LanguageUtils(private val mContext: Context) { private val mLayoutInflater: LayoutInflater ) : LayoutInflater.Factory { + @SuppressWarnings("ImplicitSamInstance") override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { - // Apply the custom font, if the xml tag equals "TextView", "EditText" or "AutoCompleteTextView" - if (name.equals("TextView", ignoreCase = true) - || name.equals("EditText", ignoreCase = true) - || name.equals("AutoCompleteTextView", ignoreCase = true) + // Apply the custom font, if the xml equals "TextView", "EditText" or "AutoCompleteTextView" + if (name.equals("TextView", ignoreCase = true) || + name.equals("EditText", ignoreCase = true) || + name.equals("AutoCompleteTextView", ignoreCase = true) ) { - try { - val inflater = mLayoutInflater - val view = inflater.createView(name, null, attrs) + val view = mLayoutInflater.createView(name, null, attrs) Handler().post { - val textView = view as TextView - - // Set the custom typeface - textView.typeface = Typeface.createFromAsset( - mContext.assets, - getTypeface(Locale.getDefault().language) - ) - Log.d(TAG_KIWIX, "Applying custom font") - - // Reduce the text size - textView.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - textView.textSize - 2f - ) + (view as TextView).apply { + typeface = Typeface.createFromAsset( + mContext.assets, + getTypeface(Locale.getDefault().language) + ) + setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize - 2f) + } } - return view } catch (e: InflateException) { Log.w(TAG_KIWIX, "Could not apply the custom font to $name", e) @@ -178,51 +146,47 @@ class LanguageUtils(private val mContext: Context) { Log.w(TAG_KIWIX, "Could not apply the custom font to $name", e) } } - return null } } - class LanguageContainer(val languageCode: String, val languageName: String) { - constructor(languageCode: String) : this(languageCode, chooseLanguageName(languageCode)) - - companion object { - private fun chooseLanguageName( - languageCode: String - ): String { - val displayLanguage = Locale(languageCode).displayLanguage - return if (displayLanguage.length == 2 || displayLanguage.isEmpty()) { - Locale.ENGLISH.displayLanguage - } else { - displayLanguage - } - } - } - } - companion object { - private var mLocaleMap: HashMap? = null + private var mLocaleMap = + Locale.getAvailableLocales().associateBy { it.isO3Language.toUpperCase(Locale.ROOT) } + private var fontExceptions = mapOf( + "km" to "fonts/KhmerOS.ttf", + "my" to "fonts/Parabaik.ttf", + "guj" to "fonts/Lohit-Gujarati.ttf", + "ori" to "fonts/Lohit-Odia.ttf", + "pan" to "fonts/Lohit-Punjabi.ttf", + "dzo" to "fonts/DDC_Uchen.ttf", + "bod" to "fonts/DDC_Uchen.ttf", + "sin" to "fonts/Kaputa-Regular.ttf", + // http://scriptsource.org/cms/scripts/page.php?item_id=entry_detail&uid=kstzk8hbg4 + // Link above shows that we are allowed to distribute this font + "chr" to "fonts/Digohweli.ttf" + ) + + @JvmStatic fun handleLocaleChange( context: Context, sharedPreferenceUtil: SharedPreferenceUtil ) { val language = sharedPreferenceUtil.getPrefLanguage("") - if (language.isEmpty()) { return } - handleLocaleChange(context, language) } + @JvmStatic fun handleLocaleChange(context: Context, language: String) { - val locale = Locale(language) Locale.setDefault(locale) val config = Configuration() - if (Build.VERSION.SDK_INT >= 17) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { config.setLocale(locale) config.setLayoutDirection(locale) } else { @@ -238,64 +202,18 @@ class LanguageUtils(private val mContext: Context) { * @param iso3 ISO3 language code * @return [java.util.Locale] that represents the language of the provided code */ - fun ISO3ToLocale(iso3: String): Locale? { - if (mLocaleMap == null) { - val locales = Locale.getAvailableLocales() - mLocaleMap = HashMap() - for (locale in locales) { - try { - mLocaleMap!![locale.isO3Language.toUpperCase()] = locale - } catch (e: MissingResourceException) { - // Do nothing - } - } - } - return mLocaleMap!![iso3.toUpperCase()] - } + @JvmStatic + fun iSO3ToLocale(iso3: String?): Locale? = + iso3?.let { mLocaleMap[it.toUpperCase(Locale.ROOT)] } - fun getCurrentLocale(context: Context): Locale { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - context.resources.configuration.locales.get(0) - } else { + @JvmStatic + fun getCurrentLocale(context: Context) = context.locale - context.resources.configuration.locale - } - } - - // This method will determine which font will be applied to the not-supported-locale. - // You can define exceptions to the default DejaVu font in the 'exceptions' Hashmap: - fun getTypeface(languageCode: String): String? { - - // Define the exceptions to the rule. The font has to be placed in the assets folder. - // Key: the language code; Value: the name of the font. - val exceptions = HashMap() - exceptions["km"] = "fonts/KhmerOS.ttf" - exceptions["my"] = "fonts/Parabaik.ttf" - exceptions["guj"] = "fonts/Lohit-Gujarati.ttf" - exceptions["ori"] = "fonts/Lohit-Odia.ttf" - exceptions["pan"] = "fonts/Lohit-Punjabi.ttf" - exceptions["dzo"] = "fonts/DDC_Uchen.ttf" - exceptions["bod"] = "fonts/DDC_Uchen.ttf" - exceptions["sin"] = "fonts/Kaputa-Regular.ttf" - - // http://scriptsource.org/cms/scripts/page.php?item_id=entry_detail&uid=kstzk8hbg4 - // Link above shows that we are allowed to distribute this font - exceptions["chr"] = "fonts/Digohweli.ttf" - - // These scripts could be supported via more Lohit fonts if DejaVu doesn't - // support them. That is untested now as they aren't even in the language - // menu: - // * (no ISO code?) (Devanagari/Nagari) -- at 0% in translatewiki - // * mr (Marathi) -- at 21% in translatewiki - - // Check, if an exception applies to our current locale - return if (exceptions.containsKey(languageCode)) { - exceptions[languageCode] - } else "fonts/DejaVuSansCondensed.ttf" - - // Return the default font - } + @JvmStatic + fun getTypeface(languageCode: String) = + fontExceptions[languageCode] ?: "fonts/DejaVuSansCondensed.ttf" + @JvmStatic fun getResourceString(appContext: Context, str: String): String { var resourceName = str if (resourceName.contains("REPLACE_")) { diff --git a/app/src/test/java/org/kiwix/kiwixmobile/utils/BookUtilsTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/utils/BookUtilsTest.kt index 070f363b8..ff2b1835d 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/utils/BookUtilsTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/utils/BookUtilsTest.kt @@ -24,12 +24,12 @@ import org.junit.Assert.assertEquals import org.junit.jupiter.api.Test class BookUtilsTest { - private val container: LanguageUtils.LanguageContainer = mockk() + private val container: LanguageContainer = mockk() // Test that the language returned for the given language code is correct @Test fun testLanguageFromCode() { - val t = BookUtils(container) + val t = BookUtils() // testing trivial cases assertEquals("null is passed", "", t.getLanguage(null))