diff --git a/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt b/app/src/main/java/de/kuschku/quasseldroid/Quasseldroid.kt index dc686bcd5109accdf326ad4e4699ca40bb61dc81..a729a2fa8295b4186046e7ee1bb85862fe176af6 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 92a98eebcac2cdec5a079a599648a0203ea5701c..2720317ded3da4d982748be838ae6b2746145595 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 997e99d8ee00696fb90c5104890423a3d8679ac8..b690bb6a8df4524b391db0a41f140598dd2b2e71 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 814dc343298a24ca3ecfed75f77f1a1ef95dff26..602cd42d50bfd59564126f6a8ec7e8c71dab01d3 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 f0d72afebde116eddc2cc5ef412c84be45adfc73..c330f0d0baa2779a7a5184b754c7d21925ff8e89 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 ef8887c5a17510a4a0c085c692e6fefbf082be92..345417488297fad86b4983ca983eca68d1234b88 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 0000000000000000000000000000000000000000..cf95a5ebf7a7c50bb21da1e2b1d9d061c022ac8a --- /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 54e9858f0abd5d10877b18996107b507850d8afc..26d763b3f98136dd82b76f6059e125beae21b6bf 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 7e4851b8fa5ac320af4fb84941935d4840dec77b..049db01181c4f1a41480d7028fced5308de6fc04 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 37ea55553816d0fd6861793df0f50dc02a9318ce..08bcb2591c2d59340214fd54ee2115ee598f17ea 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 0000000000000000000000000000000000000000..1059c8a6edc467e20fd2876817bd158323e1ba2d --- /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 53135c57b6f1b80b6e9f8f9f1243ebad497e83b1..1bc49af7edce6a3c100c933fbaeada582b734ae6 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 9551dc07078928cb6593856991cab9e8282d2cc5..63f4296d049e543be934152163fbe80865663fe6 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 b91e5d814a57783553cdd5e59448c966d6c46578..2a2b52f5db451f8745ba99058b988c4afb51699a 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