From 7250a3e1e1ccff7bb4f28dba3d56ab138d680afd Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Thu, 3 May 2018 21:48:17 +0200 Subject: [PATCH] Allow choosing language in-app Signed-off-by: Janne Koschinski <janne@kuschku.de> --- .../de/kuschku/quasseldroid/Quasseldroid.kt | 6 ++ .../settings/AppearanceSettings.kt | 3 +- .../kuschku/quasseldroid/settings/Settings.kt | 4 + .../ui/chat/input/AutoCompleteHelper.kt | 9 ++- .../client/ClientSettingsFragment.kt | 4 +- .../quasseldroid/ui/setup/SetupActivity.kt | 10 +++ .../quasseldroid/util/ui/LocaleHelper.kt | 48 ++++++++++++ .../quasseldroid/util/ui/ThemedActivity.kt | 9 +++ .../main/res/values/strings_preferences.xml | 19 +++++ app/src/main/res/xml/preferences.xml | 7 ++ .../libquassel/util/helpers/AnyHelper.kt | 22 ++++++ .../util/helpers/ObservableHelper.kt | 12 +++ .../quasseldroid/viewmodel/EditorViewModel.kt | 9 ++- .../viewmodel/QuasselViewModel.kt | 74 +++++++++---------- 14 files changed, 187 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/LocaleHelper.kt create mode 100644 lib/src/main/java/de/kuschku/libquassel/util/helpers/AnyHelper.kt diff --git a/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt b/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt index dc686bcd5..a729a2fa8 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt @@ -19,6 +19,7 @@ package de.kuschku.quasseldroid +import android.content.Context import android.os.Build import android.os.StrictMode import com.squareup.leakcanary.LeakCanary @@ -35,6 +36,7 @@ import de.kuschku.quasseldroid.util.backport.AndroidThreeTenBackport import de.kuschku.quasseldroid.util.compatibility.AndroidCompatibilityUtils import de.kuschku.quasseldroid.util.compatibility.AndroidLoggingHandler import de.kuschku.quasseldroid.util.compatibility.AndroidStreamChannelFactory +import de.kuschku.quasseldroid.util.ui.LocaleHelper class Quasseldroid : DaggerApplication() { override fun applicationInjector(): AndroidInjector<Quasseldroid> = @@ -226,4 +228,8 @@ class Quasseldroid : DaggerApplication() { ) } } + + override fun attachBaseContext(base: Context) { + super.attachBaseContext(LocaleHelper.setLocale(base)) + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/AppearanceSettings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/AppearanceSettings.kt index 92a98eebc..2720317de 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/settings/AppearanceSettings.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/settings/AppearanceSettings.kt @@ -25,7 +25,8 @@ import de.kuschku.quasseldroid.R data class AppearanceSettings( val inputEnter: InputEnterMode = InputEnterMode.EMOJI, val showLag: Boolean = true, - val theme: Theme = Theme.MATERIAL_LIGHT + val theme: Theme = Theme.MATERIAL_LIGHT, + val language: String = "" ) { enum class InputEnterMode { EMOJI, diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt index 997e99d8e..b690bb6a8 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt @@ -43,6 +43,10 @@ object Settings { showLag = getBoolean( context.getString(R.string.preference_show_lag_key), AppearanceSettings.DEFAULT.showLag + ), + language = getString( + context.getString(R.string.preference_language_key), + AppearanceSettings.DEFAULT.language ) ) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt index 814dc3432..602cd42d5 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt @@ -29,6 +29,7 @@ import de.kuschku.libquassel.protocol.Buffer_Type import de.kuschku.libquassel.quassel.syncables.IrcChannel import de.kuschku.libquassel.util.IrcUserUtils import de.kuschku.libquassel.util.flag.hasFlag +import de.kuschku.libquassel.util.helpers.nullIf import de.kuschku.libquassel.util.helpers.value import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AutoCompleteSettings @@ -152,15 +153,15 @@ class AutoCompleteHelper( }.mapNotNull { info -> networks[info.networkId]?.let { info to it } }.map { (info, network) -> - val channel = network.ircChannel(info.bufferName) ?: IrcChannel.NULL + val channel = network.ircChannel(info.bufferName).nullIf { it == IrcChannel.NULL } AutoCompleteItem.ChannelItem( info = info, network = network.networkInfo(), bufferStatus = when (channel) { - IrcChannel.NULL -> BufferStatus.OFFLINE - else -> BufferStatus.ONLINE + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE }, - description = channel.topic() + description = channel?.topic() ?: "" ) } val nicks = users.asSequence().map { user -> diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt index f0d72afeb..c330f0d0b 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt @@ -63,7 +63,9 @@ class ClientSettingsFragment : DaggerPreferenceFragmentCompat(), override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { updateSummary(findPreference(key)) - if (appearanceSettings.theme != Settings.appearance(context!!).theme) { + val appearanceSettings = Settings.appearance(context!!) + if (this.appearanceSettings.theme != appearanceSettings.theme || + this.appearanceSettings.language != appearanceSettings.language) { activity?.recreate() } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt index ef8887c5a..345417488 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt @@ -21,6 +21,8 @@ package de.kuschku.quasseldroid.ui.setup import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer +import android.content.Context +import android.content.pm.PackageManager import android.os.Bundle import android.os.Parcelable import android.support.annotation.ColorRes @@ -35,6 +37,7 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import dagger.android.support.DaggerAppCompatActivity +import de.kuschku.libquassel.util.helpers.nullIf import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.ui.clientsettings.about.AboutActivity import de.kuschku.quasseldroid.ui.clientsettings.client.ClientSettingsActivity @@ -44,6 +47,7 @@ import de.kuschku.quasseldroid.util.helper.observeSticky import de.kuschku.quasseldroid.util.helper.or import de.kuschku.quasseldroid.util.helper.switchMap import de.kuschku.quasseldroid.util.helper.updateRecentsHeaderIfExisting +import de.kuschku.quasseldroid.util.ui.LocaleHelper abstract class SetupActivity : DaggerAppCompatActivity() { @BindView(R.id.menu_view) @@ -104,6 +108,8 @@ abstract class SetupActivity : DaggerAppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.Theme_SetupTheme) super.onCreate(savedInstanceState) + packageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA).labelRes + .nullIf { it == 0 }?.let(this::setTitle) setContentView(R.layout.activity_setup) ButterKnife.bind(this) @@ -158,6 +164,10 @@ abstract class SetupActivity : DaggerAppCompatActivity() { updateRecentsHeader() } + override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } + private fun onDoneInternal() { onDone(adapter.result) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/LocaleHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/LocaleHelper.kt new file mode 100644 index 000000000..cf95a5ebf --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/LocaleHelper.kt @@ -0,0 +1,48 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.util.ui + +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import de.kuschku.quasseldroid.settings.Settings +import java.util.* + + +object LocaleHelper { + fun setLocale(context: Context): Context { + return updateResources(context, Settings.appearance(context).language) + } + + private fun updateResources(context: Context, language: String) = if (language.isNotEmpty()) { + val locale = Locale(language) + Locale.setDefault(locale) + + val config = Configuration(context.resources.configuration) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + config.setLocale(locale) + context.createConfigurationContext(config) + } else { + config.locale = locale + context.resources.updateConfiguration(config, context.resources.displayMetrics) + context + } + } else context +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/ThemedActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/ThemedActivity.kt index 54e9858f0..26d763b3f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/ui/ThemedActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/ThemedActivity.kt @@ -19,6 +19,8 @@ package de.kuschku.quasseldroid.util.ui +import android.content.Context +import android.content.pm.PackageManager import android.os.Bundle import android.support.v4.app.Fragment import android.support.v7.app.AppCompatActivity @@ -27,6 +29,7 @@ import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasFragmentInjector import dagger.android.support.HasSupportFragmentInjector +import de.kuschku.libquassel.util.helpers.nullIf import de.kuschku.quasseldroid.settings.AppearanceSettings import javax.inject.Inject @@ -45,6 +48,12 @@ abstract class ThemedActivity : AppCompatActivity(), HasSupportFragmentInjector, AndroidInjection.inject(this) setTheme(appearanceSettings.theme.style) super.onCreate(savedInstanceState) + packageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA).labelRes + .nullIf { it == 0 }?.let(this::setTitle) + } + + override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) } override fun supportFragmentInjector(): AndroidInjector<Fragment>? { diff --git a/app/src/main/res/values/strings_preferences.xml b/app/src/main/res/values/strings_preferences.xml index 7e4851b8f..049db0118 100644 --- a/app/src/main/res/values/strings_preferences.xml +++ b/app/src/main/res/values/strings_preferences.xml @@ -74,6 +74,25 @@ <string name="preference_show_lag_title">Show lag</string> <string name="preference_show_lag_summary">Displays the lag between client and core in the action bar</string> + <string name="preference_language_key" translatable="false">language</string> + <string name="preference_language_title">Language</string> + <string name="preference_language_entry_auto">System Default</string> + <string name="preference_language_entry_en" translatable="false">English</string> + <string name="preference_language_entry_de" translatable="false">Deutsch</string> + <string name="preference_language_entry_lt" translatable="false">Lithuanian</string> + <string-array name="preference_language_entries"> + <item>@string/preference_language_entry_auto</item> + <item>@string/preference_language_entry_en</item> + <item>@string/preference_language_entry_de</item> + <item>@string/preference_language_entry_lt</item> + </string-array> + <string-array name="preference_language_entryvalues" translatable="false"> + <item /> + <item>en</item> + <item>de</item> + <item>lt</item> + </string-array> + <string name="preference_notifications_title">Notifications</string> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 37ea55553..08bcb2591 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -39,6 +39,13 @@ android:key="@string/preference_show_lag_key" android:summary="@string/preference_show_lag_summary" android:title="@string/preference_show_lag_title" /> + + <DropDownPreference + android:defaultValue="" + android:entries="@array/preference_language_entries" + android:entryValues="@array/preference_language_entryvalues" + android:key="@string/preference_language_key" + android:title="@string/preference_language_title" /> </PreferenceCategory> <PreferenceCategory android:title="@string/preference_notifications_title"> diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helpers/AnyHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helpers/AnyHelper.kt new file mode 100644 index 000000000..1059c8a6e --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/util/helpers/AnyHelper.kt @@ -0,0 +1,22 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2018 Janne Koschinski + * Copyright (c) 2018 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.libquassel.util.helpers + +inline fun <T> T.nullIf(f: (T) -> Boolean): T? = if (f(this)) null else this diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt index 53135c57b..1bc49af7e 100644 --- a/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt +++ b/lib/src/main/java/de/kuschku/libquassel/util/helpers/ObservableHelper.kt @@ -41,6 +41,18 @@ fun <T : Any, U : Any> Observable<Optional<T>>.mapMapNullable( } } +fun <T : Any, U : Any> Observable<T>.mapNullable( + nullableValue: T, + mapper: (T?) -> U): Observable<U> = map { + mapper(it.nullIf { it == nullableValue }) +} + +fun <T : Any, U : Any> Observable<T>.switchMapNullable( + nullableValue: T, + mapper: (T?) -> Observable<U>): Observable<U> = switchMap { + mapper(it.nullIf { it == nullableValue }) +} + fun <T : Any, U : Any> Observable<Optional<T>>.mapSwitchMap( mapper: (T) -> Observable<U>): Observable<Optional<U>> = switchMap { if (it.isPresent()) { diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt index 9551dc070..63f4296d0 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt @@ -26,6 +26,7 @@ import de.kuschku.libquassel.quassel.syncables.IrcUser import de.kuschku.libquassel.session.ISession import de.kuschku.libquassel.util.Optional import de.kuschku.libquassel.util.flag.hasFlag +import de.kuschku.libquassel.util.helpers.mapNullable import de.kuschku.quasseldroid.util.helper.combineLatest import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem import de.kuschku.quasseldroid.viewmodel.data.BufferStatus @@ -76,15 +77,15 @@ class EditorViewModel : ViewModel() { network.liveIrcChannel( info.bufferName ).switchMap { channel -> - channel.updates().map { + channel.updates().mapNullable(IrcChannel.NULL) { AutoCompleteItem.ChannelItem( info = info, network = network.networkInfo(), bufferStatus = when (it) { - IrcChannel.NULL -> BufferStatus.OFFLINE - else -> BufferStatus.ONLINE + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE }, - description = it.topic() + description = it?.topic() ?: "" ) } } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt index b91e5d814..2a2b52f5d 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt @@ -201,35 +201,31 @@ class QuasselViewModel : ViewModel() { if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { session.liveNetworks().switchMap { networks -> val network = networks[bufferInfo.networkId] - network?.liveIrcChannel(bufferInfo.bufferName)?.switchMap { ircChannel -> - if (ircChannel != IrcChannel.NULL) { - ircChannel.liveIrcUsers().switchMap { users -> - combineLatest<IrcUserItem>( - users.map<IrcUser, Observable<IrcUserItem>?> { - it.updates().map { user -> - val userModes = ircChannel.userModes(user) - val prefixModes = network.prefixModes() - - val lowestMode = userModes.asSequence().mapNotNull { - prefixModes.indexOf(it) - }.min() ?: prefixModes.size - - IrcUserItem( - user.nick(), - network.modesToPrefixes(userModes), - lowestMode, - user.realName(), - user.hostMask(), - user.isAway(), - network.support("CASEMAPPING") - ) - } + network?.liveIrcChannel(bufferInfo.bufferName)?.switchMapNullable(IrcChannel.NULL) { ircChannel -> + ircChannel?.liveIrcUsers()?.switchMap { users -> + combineLatest<IrcUserItem>( + users.map<IrcUser, Observable<IrcUserItem>?> { + it.updates().map { user -> + val userModes = ircChannel.userModes(user) + val prefixModes = network.prefixModes() + + val lowestMode = userModes.asSequence().mapNotNull { + prefixModes.indexOf(it) + }.min() ?: prefixModes.size + + IrcUserItem( + user.nick(), + network.modesToPrefixes(userModes), + lowestMode, + user.realName(), + user.hostMask(), + user.isAway(), + network.support("CASEMAPPING") + ) } - ) - } - } else { - Observable.just(emptyList()) - } + } + ) + } ?: Observable.just(emptyList()) } } } else { @@ -294,10 +290,10 @@ class QuasselViewModel : ViewModel() { } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) } Buffer_Type.ChannelBuffer -> { - network?.liveIrcChannel(info.bufferName)?.map { + network?.liveIrcChannel(info.bufferName)?.mapNullable(IrcChannel.NULL) { SelectedBufferItem( info, - joined = it != IrcChannel.NULL, + joined = it != null, hiddenState = hiddenState ) } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) @@ -359,17 +355,17 @@ class QuasselViewModel : ViewModel() { network.liveNetworkInfo().switchMap { networkInfo -> network.liveConnectionState().switchMap { connectionState -> network.liveIrcUser(info.bufferName).switchMap { - it.updates().map { user -> + it.updates().mapNullable(IrcUser.NULL) { user -> BufferProps( info = info, network = networkInfo, networkConnectionState = connectionState, bufferStatus = when { - user == IrcUser.NULL -> BufferStatus.OFFLINE - user.isAway() -> BufferStatus.AWAY - else -> BufferStatus.ONLINE + user == null -> BufferStatus.OFFLINE + user.isAway() -> BufferStatus.AWAY + else -> BufferStatus.ONLINE }, - description = user.realName(), + description = user?.realName() ?: "", activity = activity, highlights = highlights, hiddenState = state @@ -383,16 +379,16 @@ class QuasselViewModel : ViewModel() { network.liveNetworkInfo().switchMap { networkInfo -> network.liveConnectionState().switchMap { connectionState -> network.liveIrcChannel(info.bufferName).switchMap { channel -> - channel.updates().map { + channel.updates().mapNullable(IrcChannel.NULL) { BufferProps( info = info, network = networkInfo, networkConnectionState = connectionState, bufferStatus = when (it) { - IrcChannel.NULL -> BufferStatus.OFFLINE - else -> BufferStatus.ONLINE + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE }, - description = it.topic(), + description = it?.topic() ?: "", activity = activity, highlights = highlights, hiddenState = state -- GitLab