From b4ba457d89ec7bb6d21ef9a291b4671b8d4c5426 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 22 Apr 2018 17:20:01 +0200 Subject: [PATCH] Dependency injection improvement (#596) * inject MastodonApi into LoginActivity * inject AccountManager into MainActivity * inject AccountManager into SplashActivity, convert to Kotlin * inject AccountManager into AccountActivity * inject AccountManager into LoginActivity * inject AccountManager into NotificationsFragment and NotificationClearBroadcastReceiver, fix MainActivity * ooops * use same OkHttpClient for Retrofit & Picasso * fix ordering of okhttp interceptors * remove dependencies on TuskyApplication * bugfix --- .../keylesspalace/tusky/AccountActivity.java | 5 +- .../com/keylesspalace/tusky/BaseActivity.java | 4 +- .../com/keylesspalace/tusky/LoginActivity.kt | 64 ++++++++----------- .../com/keylesspalace/tusky/MainActivity.java | 7 +- .../tusky/PreferencesActivity.java | 4 +- .../keylesspalace/tusky/SplashActivity.java | 48 -------------- .../com/keylesspalace/tusky/SplashActivity.kt | 50 +++++++++++++++ .../keylesspalace/tusky/TuskyApplication.java | 38 ++++------- .../tusky/di/ActivitiesModule.kt | 7 ++ .../keylesspalace/tusky/di/AppComponent.kt | 3 +- .../tusky/di/BroadcastReceiverModule.kt | 26 ++++++++ .../keylesspalace/tusky/di/NetworkModule.kt | 27 +++----- .../tusky/fragment/NotificationsFragment.java | 6 +- .../InstanceSwitchAuthInterceptor.java | 39 ++++++----- .../tusky/network/MastodonApi.java | 3 + .../NotificationClearBroadcastReceiver.kt | 10 ++- .../keylesspalace/tusky/util/OkHttpUtils.java | 22 +++---- .../keylesspalace/tusky/util/ThemeUtils.java | 9 +-- 18 files changed, 196 insertions(+), 176 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/SplashActivity.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index 61aead849..ef8e93d8b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -88,6 +88,8 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA @Inject public MastodonApi mastodonApi; @Inject + public AccountManager accountManager; + @Inject public DispatchingAndroidInjector dispatchingAndroidInjector; private String accountId; @@ -207,8 +209,7 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA // Obtain information to fill out the profile. obtainAccount(); - AccountEntity activeAccount = TuskyApplication.getInstance(this).getServiceLocator() - .get(AccountManager.class).getActiveAccount(); + AccountEntity activeAccount = accountManager.getActiveAccount(); if (accountId.equals(activeAccount.getAccountId())) { isSelf = true; diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index ef154fe8b..0b691831d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -45,8 +45,8 @@ public abstract class BaseActivity extends AppCompatActivity { /* There isn't presently a way to globally change the theme of a whole application at * runtime, just individual activities. So, each activity has to set its theme before any * views are created. */ - String theme = preferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT); - ThemeUtils.setAppNightMode(theme); + String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT); + ThemeUtils.setAppNightMode(theme, this); int style; switch (preferences.getString("statusTextSize", "medium")) { diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index 9460918f7..856ab4343 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -32,21 +32,26 @@ import android.view.View import android.widget.EditText import android.widget.TextView import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.AccessToken import com.keylesspalace.tusky.entity.AppCredentials import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.CustomTabsHelper -import com.keylesspalace.tusky.util.OkHttpUtils import com.keylesspalace.tusky.util.ThemeUtils import kotlinx.android.synthetic.main.activity_login.* +import okhttp3.HttpUrl import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Inject -class LoginActivity : AppCompatActivity() { +class LoginActivity : AppCompatActivity(), Injectable { + + @Inject + lateinit var mastodonApi: MastodonApi + @Inject + lateinit var accountManager: AccountManager private lateinit var preferences: SharedPreferences private var domain: String = "" @@ -64,8 +69,8 @@ class LoginActivity : AppCompatActivity() { super.onCreate(savedInstanceState) preferences = PreferenceManager.getDefaultSharedPreferences(this) - val theme = preferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT) - ThemeUtils.setAppNightMode(theme) + val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT) + ThemeUtils.setAppNightMode(theme, this) setContentView(R.layout.activity_login) @@ -114,16 +119,6 @@ class LoginActivity : AppCompatActivity() { super.onSaveInstanceState(outState) } - private fun getApiFor(domain: String): MastodonApi { - val retrofit = Retrofit.Builder() - .baseUrl("https://" + domain) - .client(OkHttpUtils.getCompatibleClient(preferences)) - .addConverterFactory(GsonConverterFactory.create()) - .build() - - return retrofit.create(MastodonApi::class.java) - } - /** * Obtain the oauth client credentials for this app. This is only necessary the first time the * app is run on a given server instance. So, after the first authentication, they are @@ -133,7 +128,15 @@ class LoginActivity : AppCompatActivity() { loginButton.isEnabled = false - domain = validateDomain(domainEditText.text.toString()) + domain = canonicalizeDomain(domainEditText.text.toString()) + + try { + HttpUrl.Builder().host(domain).scheme("https").build() + } catch (e: IllegalArgumentException) { + setLoading(false) + domainEditText.error = getString(R.string.error_invalid_domain) + return + } val callback = object : Callback { override fun onResponse(call: Call, @@ -159,16 +162,11 @@ class LoginActivity : AppCompatActivity() { } } - try { - getApiFor(domain) - .authenticateApp(getString(R.string.app_name), oauthRedirectUri, - OAUTH_SCOPES, getString(R.string.app_website)) - .enqueue(callback) - setLoading(true) - } catch (e: IllegalArgumentException) { - setLoading(false) - domainEditText.error = getString(R.string.error_invalid_domain) - } + mastodonApi + .authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri, + OAUTH_SCOPES, getString(R.string.app_website)) + .enqueue(callback) + setLoading(true) } @@ -250,7 +248,7 @@ class LoginActivity : AppCompatActivity() { } } - getApiFor(domain).fetchOAuthToken(clientId, clientSecret, redirectUri, code, + mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code, "authorization_code").enqueue(callback) } else if (error != null) { /* Authorization failed. Put the error response where the user can read it and they @@ -290,9 +288,7 @@ class LoginActivity : AppCompatActivity() { setLoading(true) - TuskyApplication.getInstance(this).serviceLocator - .get(AccountManager::class.java) - .addAccount(accessToken, domain) + accountManager.addAccount(accessToken, domain) val intent = Intent(this, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -316,14 +312,10 @@ class LoginActivity : AppCompatActivity() { } /** Make sure the user-entered text is just a fully-qualified domain name. */ - private fun validateDomain(domain: String): String { + private fun canonicalizeDomain(domain: String): String { // Strip any schemes out. var s = domain.replaceFirst("http://", "") s = s.replaceFirst("https://", "") - - //strip out any slashes that might have been added - s = s.replace("/", "") - // If a username was included (e.g. username@example.com), just take what's after the '@'. val at = s.lastIndexOf('@') if (at != -1) { diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index b24517a61..06b97772f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -92,11 +92,11 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity, public MastodonApi mastodonApi; @Inject public DispatchingAndroidInjector fragmentInjector; + @Inject + public AccountManager accountManager; private static int COMPOSE_RESULT = 1; - AccountManager accountManager; - private FloatingActionButton composeButton; private AccountHeader headerResult; private Drawer drawer; @@ -107,9 +107,6 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity, Intent intent = getIntent(); int tabPosition = 0; - accountManager = TuskyApplication.getInstance(this).getServiceLocator() - .get(AccountManager.class); - if (intent != null) { long accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1); diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java index df7ccd67e..2db70373b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java @@ -104,8 +104,8 @@ public class PreferencesActivity extends BaseActivity public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { switch (key) { case "appTheme": { - String theme = sharedPreferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT); - ThemeUtils.setAppNightMode(theme); + String theme = sharedPreferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT); + ThemeUtils.setAppNightMode(theme, this); restartActivitiesOnExit = true; // recreate() could be used instead, but it doesn't have an animation B). diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java deleted file mode 100644 index 0e15899f4..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java +++ /dev/null @@ -1,48 +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.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; - -import com.keylesspalace.tusky.db.AccountEntity; -import com.keylesspalace.tusky.db.AccountManager; -import com.keylesspalace.tusky.util.NotificationHelper; - -public class SplashActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - /* Determine whether the user is currently logged in, and if so go ahead and load the - * timeline. Otherwise, start the activity_login screen. */ - - NotificationHelper.deleteLegacyNotificationChannels(this); - - AccountEntity activeAccount = TuskyApplication.getInstance(this).getServiceLocator() - .get(AccountManager.class).getActiveAccount(); - - Intent intent; - if (activeAccount != null) { - intent = new Intent(this, MainActivity.class); - } else { - intent = LoginActivity.getIntent(this, false); - } - startActivity(intent); - finish(); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt new file mode 100644 index 000000000..c1a229ab9 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt @@ -0,0 +1,50 @@ +/* Copyright 2018 Conny Duck + * + * 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.content.Intent +import android.os.Bundle +import android.support.v7.app.AppCompatActivity + +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.di.Injectable +import com.keylesspalace.tusky.util.NotificationHelper + +import javax.inject.Inject + +class SplashActivity : AppCompatActivity(), Injectable { + + @Inject + lateinit var accountManager: AccountManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + /** delete old notification channels that were in use in Tusky 1.4 */ + NotificationHelper.deleteLegacyNotificationChannels(this) + + /** Determine whether the user is currently logged in, and if so go ahead and load the + * timeline. Otherwise, start the activity_login screen. */ + + val intent = if (accountManager.activeAccount != null) { + Intent(this, MainActivity::class.java) + } else { + LoginActivity.getIntent(this, false) + } + startActivity(intent) + finish() + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java index a7be757c4..7596a993e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java @@ -18,11 +18,9 @@ package com.keylesspalace.tusky; import android.app.Activity; import android.app.Application; import android.app.Service; -import android.app.UiModeManager; import android.arch.persistence.room.Room; +import android.content.BroadcastReceiver; import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatDelegate; @@ -31,8 +29,6 @@ import com.jakewharton.picasso.OkHttp3Downloader; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.db.AppDatabase; import com.keylesspalace.tusky.di.AppInjector; -import com.keylesspalace.tusky.util.OkHttpUtils; -import com.keylesspalace.tusky.util.ThemeUtils; import com.squareup.picasso.Picasso; import javax.inject.Inject; @@ -40,13 +36,11 @@ import javax.inject.Inject; import dagger.android.AndroidInjector; import dagger.android.DispatchingAndroidInjector; import dagger.android.HasActivityInjector; +import dagger.android.HasBroadcastReceiverInjector; import dagger.android.HasServiceInjector; -import okhttp3.Cache; import okhttp3.OkHttpClient; -public class TuskyApplication extends Application implements HasActivityInjector, HasServiceInjector { - public static final String APP_THEME_DEFAULT = ThemeUtils.THEME_NIGHT; - +public class TuskyApplication extends Application implements HasActivityInjector, HasServiceInjector, HasBroadcastReceiverInjector { private static AppDatabase db; private AccountManager accountManager; @Inject @@ -54,23 +48,19 @@ public class TuskyApplication extends Application implements HasActivityInjector @Inject DispatchingAndroidInjector dispatchingServiceInjector; @Inject + DispatchingAndroidInjector dispatchingBroadcastReceiverInjector; + @Inject NotificationPullJobCreator notificationPullJobCreator; + @Inject OkHttpClient okHttpClient; public static AppDatabase getDB() { return db; } - private static UiModeManager uiModeManager; - - public static UiModeManager getUiModeManager() { - return uiModeManager; - } - public static TuskyApplication getInstance(@NonNull Context context) { return (TuskyApplication) context.getApplicationContext(); } - private ServiceLocator serviceLocator; @Override @@ -98,7 +88,6 @@ public class TuskyApplication extends Application implements HasActivityInjector initPicasso(); JobManager.create(this).addJobCreator(notificationPullJobCreator); - uiModeManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE); //necessary for Android < APi 21 AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); @@ -111,15 +100,7 @@ public class TuskyApplication extends Application implements HasActivityInjector protected void initPicasso() { // Initialize Picasso configuration Picasso.Builder builder = new Picasso.Builder(this); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - - OkHttpClient.Builder okHttpBuilder = OkHttpUtils.getCompatibleClientBuilder(preferences); - - int cacheSize = 10*1024*1024; // 10 MiB - - okHttpBuilder.cache(new Cache(getCacheDir(), cacheSize)); - - builder.downloader(new OkHttp3Downloader(okHttpBuilder.build())); + builder.downloader(new OkHttp3Downloader(okHttpClient)); if (BuildConfig.DEBUG) { builder.listener((picasso, uri, exception) -> exception.printStackTrace()); } @@ -141,6 +122,11 @@ public class TuskyApplication extends Application implements HasActivityInjector return dispatchingServiceInjector; } + @Override + public AndroidInjector broadcastReceiverInjector() { + return dispatchingBroadcastReceiverInjector; + } + public interface ServiceLocator { T get(Class clazz); } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index e4a78f67a..e36ff9ebb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -61,6 +61,13 @@ abstract class ActivitiesModule { @ContributesAndroidInjector() abstract fun contributesAboutActivity(): AboutActivity + @ContributesAndroidInjector() + abstract fun contributesLoginActivity(): LoginActivity + + @ContributesAndroidInjector() + abstract fun contributesSplashActivity(): SplashActivity + @ContributesAndroidInjector abstract fun contributesReportActivity(): ReportActivity + } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt index b2ad4d12d..c3851ad58 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt @@ -32,7 +32,8 @@ import javax.inject.Singleton NetworkModule::class, AndroidInjectionModule::class, ActivitiesModule::class, - ServicesModule::class + ServicesModule::class, + BroadcastReceiverModule::class ]) interface AppComponent { @Component.Builder diff --git a/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt new file mode 100644 index 000000000..335da5599 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt @@ -0,0 +1,26 @@ +/* Copyright 2018 Conny Duck + * + * 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.di + +import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class BroadcastReceiverModule { + @ContributesAndroidInjector + abstract fun contributeNotificationClearBroadcastReceiver() : NotificationClearBroadcastReceiver +} \ No newline at end of file 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 8cafab389..d7418ad69 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -16,11 +16,12 @@ package com.keylesspalace.tusky.di -import android.content.SharedPreferences +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 import com.keylesspalace.tusky.network.InstanceSwitchAuthInterceptor @@ -31,8 +32,8 @@ import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet -import okhttp3.Interceptor import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -67,32 +68,24 @@ class NetworkModule { fun providesConverterFactory(gson: Gson): Converter.Factory = GsonConverterFactory.create(gson) @Provides - @IntoSet @Singleton - fun providesAuthInterceptor(accountManager: AccountManager): Interceptor { - // should accept AccountManager here probably but I don't want to break things yet - return InstanceSwitchAuthInterceptor(accountManager) - } - - @Provides - @Singleton - fun providesHttpClient(interceptors: @JvmSuppressWildcards Set, - preferences: SharedPreferences): OkHttpClient { - return OkHttpUtils.getCompatibleClientBuilder(preferences) + fun providesHttpClient(accountManager: AccountManager, + context: Context): OkHttpClient { + return OkHttpUtils.getCompatibleClientBuilder(context) .apply { - interceptors.fold(this) { b, i -> - b.addInterceptor(i) + addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) + if (BuildConfig.DEBUG) { + addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)) } } .build() } - @Provides @Singleton fun providesRetrofit(httpClient: OkHttpClient, converters: @JvmSuppressWildcards Set): Retrofit { - return Retrofit.Builder().baseUrl("https://dummy.placeholder/") + 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 diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 0f8a965a0..d7b068fcf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -38,7 +38,6 @@ import android.view.ViewGroup; import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.R; -import com.keylesspalace.tusky.TuskyApplication; import com.keylesspalace.tusky.adapter.FooterViewHolder; import com.keylesspalace.tusky.adapter.NotificationsAdapter; import com.keylesspalace.tusky.db.AccountEntity; @@ -108,6 +107,8 @@ public class NotificationsFragment extends SFragment implements public TimelineCases timelineCases; @Inject public MastodonApi mastodonApi; + @Inject + AccountManager accountManager; private SwipeRefreshLayout swipeRefreshLayout; private LinearLayoutManager layoutManager; @@ -606,8 +607,7 @@ public class NotificationsFragment extends SFragment implements } private void saveNewestNotificationId(List notifications) { - AccountManager accountManager = TuskyApplication.getInstance(getContext()) - .getServiceLocator().get(AccountManager.class); + AccountEntity account = accountManager.getActiveAccount(); BigInteger lastNoti = new BigInteger(account.getLastNotificationId()); diff --git a/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java b/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java index 33f64a8a9..afcda3de4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java @@ -42,28 +42,35 @@ public final class InstanceSwitchAuthInterceptor implements Interceptor { public Response intercept(@NonNull Chain chain) throws IOException { Request originalRequest = chain.request(); - AccountEntity currentAccount = accountManager.getActiveAccount(); - Request.Builder builder = originalRequest.newBuilder(); + // only switch domains if the request comes from retrofit + if (originalRequest.url().host().equals(MastodonApi.PLACEHOLDER_DOMAIN)) { + AccountEntity currentAccount = accountManager.getActiveAccount(); - String instanceHeader = originalRequest.header(MastodonApi.DOMAIN_HEADER); - if (instanceHeader != null) { - // use domain explicitly specified in custom header - builder.url(swapHost(originalRequest.url(), instanceHeader)); - builder.removeHeader(MastodonApi.DOMAIN_HEADER); - } else if (currentAccount != null) { - //use domain of current account - builder.url(swapHost(originalRequest.url(), currentAccount.getDomain())) - .header("Authorization", - String.format("Bearer %s", currentAccount.getAccessToken())); + Request.Builder builder = originalRequest.newBuilder(); + + String instanceHeader = originalRequest.header(MastodonApi.DOMAIN_HEADER); + if (instanceHeader != null) { + // use domain explicitly specified in custom header + builder.url(swapHost(originalRequest.url(), instanceHeader)); + builder.removeHeader(MastodonApi.DOMAIN_HEADER); + } else if (currentAccount != null) { + //use domain of current account + builder.url(swapHost(originalRequest.url(), currentAccount.getDomain())) + .header("Authorization", + String.format("Bearer %s", currentAccount.getAccessToken())); + } + Request newRequest = builder.build(); + + return chain.proceed(newRequest); + + } else { + return chain.proceed(originalRequest); } - Request newRequest = builder.build(); - - return chain.proceed(newRequest); } @NonNull - private HttpUrl swapHost(@NonNull HttpUrl url, @NonNull String host) { + private static HttpUrl swapHost(@NonNull HttpUrl url, @NonNull String host) { return url.newBuilder().host(host).build(); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java index dacbe4833..aeb82d85b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java @@ -53,6 +53,7 @@ import retrofit2.http.Query; public interface MastodonApi { String ENDPOINT_AUTHORIZE = "/oauth/authorize"; String DOMAIN_HEADER = "domain"; + String PLACEHOLDER_DOMAIN = "dummy.placeholder"; @GET("api/v1/timelines/home") Call> homeTimeline( @@ -246,6 +247,7 @@ public interface MastodonApi { @FormUrlEncoded @POST("api/v1/apps") Call authenticateApp( + @Header(DOMAIN_HEADER) String domain, @Field("client_name") String clientName, @Field("redirect_uris") String redirectUris, @Field("scopes") String scopes, @@ -254,6 +256,7 @@ public interface MastodonApi { @FormUrlEncoded @POST("oauth/token") Call fetchOAuthToken( + @Header(DOMAIN_HEADER) String domain, @Field("client_id") String clientId, @Field("client_secret") String clientSecret, @Field("redirect_uri") String redirectUri, diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt index eda26998d..9e6d0dc52 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt @@ -19,17 +19,21 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.keylesspalace.tusky.TuskyApplication import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.util.NotificationHelper +import dagger.android.AndroidInjection +import javax.inject.Inject class NotificationClearBroadcastReceiver : BroadcastReceiver() { + + @Inject + lateinit var accountManager: AccountManager + override fun onReceive(context: Context, intent: Intent) { + AndroidInjection.inject(this, context) val accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1) - val accountManager = TuskyApplication.getInstance(context) - .serviceLocator.get(AccountManager::class.java) val account = accountManager.getAccountById(accountId) if (account != null) { account.activeNotifications = "[]" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java index 36178aff5..f714ec091 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java @@ -15,8 +15,10 @@ package com.keylesspalace.tusky.util; +import android.content.Context; import android.content.SharedPreferences; import android.os.Build; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.util.Log; @@ -43,11 +45,11 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import okhttp3.Cache; import okhttp3.ConnectionSpec; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.logging.HttpLoggingInterceptor; public class OkHttpUtils { private static final String TAG = "OkHttpUtils"; // logging tag @@ -65,7 +67,11 @@ public class OkHttpUtils { * TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20. */ @NonNull - public static OkHttpClient.Builder getCompatibleClientBuilder(SharedPreferences preferences) { + public static OkHttpClient.Builder getCompatibleClientBuilder(@NonNull Context context) { + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + + boolean httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false); String httpServer = preferences.getString("httpProxyServer", ""); int httpPort = Integer.parseInt(preferences.getString("httpProxyPort", "-1")); @@ -81,10 +87,13 @@ public class OkHttpUtils { specList.add(fallback); specList.add(ConnectionSpec.CLEARTEXT); + int cacheSize = 10*1024*1024; // 10 MiB + OkHttpClient.Builder builder = new OkHttpClient.Builder() .addInterceptor(getUserAgentInterceptor()) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) + .cache(new Cache(context.getCacheDir(), cacheSize)) .connectionSpecs(specList); if (httpProxyEnabled && !httpServer.isEmpty() && (httpPort > 0) && (httpPort < 65535)) { @@ -92,18 +101,9 @@ public class OkHttpUtils { builder.proxy(new Proxy(Proxy.Type.HTTP, address)); } - if(BuildConfig.DEBUG) { - builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)); - } - return enableHigherTlsOnPreLollipop(builder); } - @NonNull - public static OkHttpClient getCompatibleClient(SharedPreferences preferences) { - return getCompatibleClientBuilder(preferences).build(); - } - /** * Add a custom User-Agent that contains Tusky & Android Version to all requests * Example: diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java index f2c1a2502..de643c954 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java @@ -30,13 +30,13 @@ import android.support.v7.app.AppCompatDelegate; import android.util.TypedValue; import android.widget.ImageView; -import com.keylesspalace.tusky.TuskyApplication; - /** * Provides runtime compatibility to obtain theme information and re-theme views, especially where * the ability to do so is not supported in resource files. */ public class ThemeUtils { + public static final String APP_THEME_DEFAULT = ThemeUtils.THEME_NIGHT; + public static final String THEME_NIGHT = "night"; public static final String THEME_DAY = "day"; public static final String THEME_AUTO = "auto"; @@ -91,7 +91,7 @@ public class ThemeUtils { drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN); } - public static void setAppNightMode(String flavor) { + public static void setAppNightMode(String flavor, Context context) { int mode; switch (flavor) { default: @@ -107,7 +107,8 @@ public class ThemeUtils { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - TuskyApplication.getUiModeManager().setNightMode(mode); + UiModeManager uiModeManager = (UiModeManager)context.getApplicationContext().getSystemService(Context.UI_MODE_SERVICE); + uiModeManager.setNightMode(mode); } else { AppCompatDelegate.setDefaultNightMode(mode); }