diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index d1718bea7..363872684 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -92,7 +92,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab @Override protected void attachBaseContext(Context base) { - super.attachBaseContext(TuskyApplication.localeManager.setLocale(base)); + super.attachBaseContext(TuskyApplication.getLocaleManager().setLocale(base)); } protected boolean requiresLogin() { diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java deleted file mode 100644 index 6bf824613..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java +++ /dev/null @@ -1,158 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky 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 Tusky; if not, - * see . */ - -package com.keylesspalace.tusky; - -import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; - -import androidx.emoji.text.EmojiCompat; -import androidx.preference.PreferenceManager; -import androidx.room.Room; - -import com.evernote.android.job.JobManager; -import com.keylesspalace.tusky.db.AccountManager; -import com.keylesspalace.tusky.db.AppDatabase; -import com.keylesspalace.tusky.di.AppInjector; -import com.keylesspalace.tusky.util.EmojiCompatFont; -import com.keylesspalace.tusky.util.LocaleManager; -import com.keylesspalace.tusky.util.NotificationPullJobCreator; -import com.keylesspalace.tusky.util.ThemeUtils; -import com.uber.autodispose.AutoDisposePlugins; - -import org.conscrypt.Conscrypt; - -import java.security.Security; - -import javax.inject.Inject; - -import dagger.android.AndroidInjector; -import dagger.android.DispatchingAndroidInjector; -import dagger.android.HasAndroidInjector; - -public class TuskyApplication extends Application implements HasAndroidInjector { - @Inject - DispatchingAndroidInjector androidInjector; - - @Inject - NotificationPullJobCreator notificationPullJobCreator; - - private AppDatabase appDatabase; - private AccountManager accountManager; - - private ServiceLocator serviceLocator; - - public static LocaleManager localeManager; - - @Override - public void onCreate() { - super.onCreate(); - - initSecurityProvider(); - - appDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB") - .allowMainThreadQueries() - .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, - AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8, - AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, - AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, - AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, - AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, - AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21) - .build(); - accountManager = new AccountManager(appDatabase); - serviceLocator = new ServiceLocator() { - @Override - public T get(Class clazz) { - if (clazz.equals(AccountManager.class)) { - //noinspection unchecked - return (T) accountManager; - } else if (clazz.equals(AppDatabase.class)) { - //noinspection unchecked - return (T) appDatabase; - } else { - throw new IllegalArgumentException("Unknown service " + clazz); - } - } - }; - - AutoDisposePlugins.setHideProxies(false); - - initAppInjector(); - initEmojiCompat(); - initNightMode(); - - JobManager.create(this).addJobCreator(notificationPullJobCreator); - - } - - protected void initSecurityProvider() { - Security.insertProviderAt(Conscrypt.newProvider(), 1); - } - - @Override - protected void attachBaseContext(Context base) { - localeManager = new LocaleManager(base); - super.attachBaseContext(localeManager.setLocale(base)); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - localeManager.setLocale(this); - } - - /** - * This method will load the EmojiCompat font which has been selected. - * If this font does not work or if the user hasn't selected one (yet), it will use a - * fallback solution instead which won't make any visible difference to using no EmojiCompat at all. - */ - private void initEmojiCompat() { - int emojiSelection = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()) - .getInt(EmojiPreference.FONT_PREFERENCE, 0); - EmojiCompatFont font = EmojiCompatFont.byId(emojiSelection); - // FileEmojiCompat will handle any non-existing font and provide a fallback solution. - EmojiCompat.Config config = font.getConfig(getApplicationContext()) - // The user probably wants to get a consistent experience - .setReplaceAll(true); - EmojiCompat.init(config); - } - - protected void initAppInjector() { - AppInjector.INSTANCE.init(this); - } - - protected void initNightMode() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT); - ThemeUtils.setAppNightMode(theme); - } - - public ServiceLocator getServiceLocator() { - return serviceLocator; - } - - @Override - public AndroidInjector androidInjector() { - return androidInjector; - } - - public interface ServiceLocator { - T get(Class clazz); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt new file mode 100644 index 000000000..a86745a49 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt @@ -0,0 +1,85 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky 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 Tusky; if not, + * see . */ + +package com.keylesspalace.tusky + +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import androidx.emoji.text.EmojiCompat +import androidx.preference.PreferenceManager +import com.evernote.android.job.JobManager +import com.keylesspalace.tusky.di.AppInjector +import com.keylesspalace.tusky.util.EmojiCompatFont +import com.keylesspalace.tusky.util.LocaleManager +import com.keylesspalace.tusky.util.NotificationPullJobCreator +import com.keylesspalace.tusky.util.ThemeUtils +import com.uber.autodispose.AutoDisposePlugins +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasAndroidInjector +import org.conscrypt.Conscrypt +import java.security.Security +import javax.inject.Inject + +class TuskyApplication : Application(), HasAndroidInjector { + + @Inject + lateinit var androidInjector: DispatchingAndroidInjector + @Inject + lateinit var notificationPullJobCreator: NotificationPullJobCreator + + override fun onCreate() { + + super.onCreate() + + Security.insertProviderAt(Conscrypt.newProvider(), 1) + + AutoDisposePlugins.setHideProxies(false) // a small performance optimization + + AppInjector.init(this) + + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + + // init the custom emoji fonts + val emojiSelection = preferences.getInt(EmojiPreference.FONT_PREFERENCE, 0) + val emojiConfig = EmojiCompatFont.byId(emojiSelection) + .getConfig(this) + .setReplaceAll(true) + EmojiCompat.init(emojiConfig) + + // init night mode + val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT) + ThemeUtils.setAppNightMode(theme) + + JobManager.create(this).addJobCreator(notificationPullJobCreator) + } + + override fun attachBaseContext(base: Context) { + localeManager = LocaleManager(base) + super.attachBaseContext(localeManager.setLocale(base)) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + localeManager.setLocale(this) + } + + override fun androidInjector() = androidInjector + + companion object { + @JvmStatic + lateinit var localeManager: LocaleManager + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt index b02d8c75a..5293bd03a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt @@ -18,6 +18,10 @@ package com.keylesspalace.tusky.db import android.util.Log import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Status +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.Comparator /** * This class caches the account database and handles all account related operations @@ -26,7 +30,8 @@ import com.keylesspalace.tusky.entity.Status private const val TAG = "AccountManager" -class AccountManager(db: AppDatabase) { +@Singleton +class AccountManager @Inject constructor(db: AppDatabase) { @Volatile var activeAccount: AccountEntity? = null @@ -60,7 +65,7 @@ class AccountManager(db: AppDatabase) { val maxAccountId = accounts.maxBy { it.id }?.id ?: 0 val newAccountId = maxAccountId + 1 - activeAccount = AccountEntity(id = newAccountId, domain = domain.toLowerCase(), accessToken = accessToken, isActive = true) + activeAccount = AccountEntity(id = newAccountId, domain = domain.toLowerCase(Locale.ROOT), accessToken = accessToken, isActive = true) } @@ -146,8 +151,8 @@ class AccountManager(db: AppDatabase) { saveAccount(it) } - activeAccount = accounts.find { acc -> - acc.id == accountId + activeAccount = accounts.find { (id) -> + id == accountId } activeAccount?.let { @@ -185,8 +190,8 @@ class AccountManager(db: AppDatabase) { * @return the requested account or null if it was not found */ fun getAccountById(accountId: Long): AccountEntity? { - return accounts.find { acc -> - acc.id == accountId + return accounts.find { (id) -> + id == accountId } } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt index bbc870614..69194c795 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -21,10 +21,10 @@ import android.content.Context import android.content.SharedPreferences import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.preference.PreferenceManager +import androidx.room.Room import com.keylesspalace.tusky.TuskyApplication import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.EventHubImpl -import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.TimelineCases @@ -64,20 +64,23 @@ class AppModule { return TimelineCasesImpl(api, eventHub) } - @Provides - @Singleton - fun providesAccountManager(app: TuskyApplication): AccountManager { - return app.serviceLocator.get(AccountManager::class.java) - } - @Provides @Singleton fun providesEventHub(): EventHub = EventHubImpl @Provides @Singleton - fun providesDatabase(app: TuskyApplication): AppDatabase { - return app.serviceLocator.get(AppDatabase::class.java) + fun providesDatabase(appContext: Context): AppDatabase { + return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB") + .allowMainThreadQueries() + .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, + AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8, + AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, + AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, + AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, + AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, + AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21) + .build() } @Provides diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt index 26309be57..b349d50d9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -13,14 +13,12 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ - package com.keylesspalace.tusky.di import android.content.Context import android.text.Spanned import com.google.gson.Gson import com.google.gson.GsonBuilder -import com.google.gson.JsonDeserializer import com.keylesspalace.tusky.BuildConfig import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.json.SpannedTypeAdapter @@ -29,12 +27,8 @@ import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.OkHttpUtils import dagger.Module import dagger.Provides -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap -import dagger.multibindings.IntoSet import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Converter import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory @@ -47,32 +41,20 @@ import javax.inject.Singleton @Module class NetworkModule { - @Provides - @IntoMap - @ClassKey(Spanned::class) - fun providesSpannedTypeAdapter(): JsonDeserializer<*> = SpannedTypeAdapter() - @Provides @Singleton - fun providesGson(adapters: @JvmSuppressWildcards Map, JsonDeserializer<*>>): Gson { + fun providesGson(): Gson { return GsonBuilder() - .apply { - for ((k, v) in adapters) { - registerTypeAdapter(k, v) - } - } + .registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter()) .create() } @Provides - @IntoSet @Singleton - fun providesConverterFactory(gson: Gson): Converter.Factory = GsonConverterFactory.create(gson) - - @Provides - @Singleton - fun providesHttpClient(accountManager: AccountManager, - context: Context): OkHttpClient { + fun providesHttpClient( + accountManager: AccountManager, + context: Context + ): OkHttpClient { return OkHttpUtils.getCompatibleClientBuilder(context) .apply { addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) @@ -85,18 +67,14 @@ class NetworkModule { @Provides @Singleton - fun providesRetrofit(httpClient: OkHttpClient, - converters: @JvmSuppressWildcards Set): Retrofit { + fun providesRetrofit( + httpClient: OkHttpClient, + gson: Gson + ): Retrofit { return Retrofit.Builder().baseUrl("https://" + MastodonApi.PLACEHOLDER_DOMAIN) .client(httpClient) - .let { builder -> - // Doing it this way in case builder will be immutable so we return the final - // instance - converters.fold(builder) { b, c -> - b.addConverterFactory(c) - } - builder.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) - } + .addConverterFactory(GsonConverterFactory.create(gson)) + .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) .build() } diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt index 386018b88..ac2e955a5 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt @@ -47,7 +47,7 @@ import org.robolectric.fakes.RoboMenuItem * Created by charlag on 3/7/18. */ -@Config(application = FakeTuskyApplication::class, sdk = [28]) +@Config(sdk = [28]) @RunWith(AndroidJUnit4::class) class ComposeActivityTest { private lateinit var activity: ComposeActivity diff --git a/app/src/test/java/com/keylesspalace/tusky/FakeTuskyApplication.kt b/app/src/test/java/com/keylesspalace/tusky/FakeTuskyApplication.kt deleted file mode 100644 index 640f6826b..000000000 --- a/app/src/test/java/com/keylesspalace/tusky/FakeTuskyApplication.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.keylesspalace.tusky - -/** - * Created by charlag on 3/7/18. - */ - -class FakeTuskyApplication : TuskyApplication() { - - private lateinit var locator: ServiceLocator - - override fun initSecurityProvider() { - // No-op - } - - override fun initAppInjector() { - // No-op - } - - override fun initNightMode() { - // No-op - } - - override fun getServiceLocator(): ServiceLocator { - return locator - } -} \ No newline at end of file diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt index c94f26330..965eda674 100644 --- a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt @@ -24,7 +24,7 @@ import retrofit2.Callback import retrofit2.Response import java.util.* -@Config(application = FakeTuskyApplication::class, sdk = [28]) +@Config(sdk = [28]) @RunWith(AndroidJUnit4::class) class FilterTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt new file mode 100644 index 000000000..aaa2b349e --- /dev/null +++ b/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt @@ -0,0 +1,51 @@ +/* Copyright 2020 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky 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 Tusky; if not, + * see . */ + +package com.keylesspalace.tusky + +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import android.util.Log +import androidx.emoji.text.EmojiCompat +import com.keylesspalace.tusky.util.LocaleManager +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasAndroidInjector +import de.c1710.filemojicompat.FileEmojiCompatConfig +import javax.inject.Inject + +// override TuskyApplication for Robolectric tests, only initialize the necessary stuff +class TuskyApplication : Application() { + + override fun onCreate() { + super.onCreate() + EmojiCompat.init(FileEmojiCompatConfig(this, "")) + } + + override fun attachBaseContext(base: Context) { + localeManager = LocaleManager(base) + super.attachBaseContext(localeManager.setLocale(base)) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + localeManager.setLocale(this) + } + + companion object { + @JvmStatic + lateinit var localeManager: LocaleManager + } +} \ No newline at end of file diff --git a/app/src/test/java/com/keylesspalace/tusky/di/AppInjector.kt b/app/src/test/java/com/keylesspalace/tusky/di/AppInjector.kt deleted file mode 100644 index 8a030aa1d..000000000 --- a/app/src/test/java/com/keylesspalace/tusky/di/AppInjector.kt +++ /dev/null @@ -1,2 +0,0 @@ -package com.keylesspalace.tusky.di - diff --git a/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt index c307e5524..b9b3a3740 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt @@ -2,7 +2,6 @@ package com.keylesspalace.tusky.util import android.app.Activity import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.keylesspalace.tusky.FakeTuskyApplication import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -11,7 +10,7 @@ import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.annotation.Config -@Config(application = FakeTuskyApplication::class, sdk = [28]) +@Config(sdk = [28]) @RunWith(AndroidJUnit4::class) class RickRollTest { private lateinit var activity: Activity diff --git a/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt index acb6cf881..b85d60a1f 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt @@ -2,14 +2,13 @@ package com.keylesspalace.tusky.util import android.text.SpannableStringBuilder import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.keylesspalace.tusky.FakeTuskyApplication import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(application = FakeTuskyApplication::class, sdk = [28]) +@Config(sdk = [28]) @RunWith(AndroidJUnit4::class) class SmartLengthInputFilterTest {