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
This commit is contained in:
Konrad Pozniak 2018-04-22 17:20:01 +02:00 committed by GitHub
parent d17ff3eb0f
commit b4ba457d89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 196 additions and 176 deletions

View File

@ -88,6 +88,8 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA
@Inject
public MastodonApi mastodonApi;
@Inject
public AccountManager accountManager;
@Inject
public DispatchingAndroidInjector<Fragment> 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;

View File

@ -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")) {

View File

@ -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<AppCredentials> {
override fun onResponse(call: Call<AppCredentials>,
@ -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) {

View File

@ -92,11 +92,11 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity,
public MastodonApi mastodonApi;
@Inject
public DispatchingAndroidInjector<Fragment> 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);

View File

@ -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).

View File

@ -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 <http://www.gnu.org/licenses>. */
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();
}
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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()
}
}

View File

@ -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<Service> dispatchingServiceInjector;
@Inject
DispatchingAndroidInjector<BroadcastReceiver> 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<BroadcastReceiver> broadcastReceiverInjector() {
return dispatchingBroadcastReceiverInjector;
}
public interface ServiceLocator {
<T> T get(Class<T> clazz);
}

View File

@ -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
}

View File

@ -32,7 +32,8 @@ import javax.inject.Singleton
NetworkModule::class,
AndroidInjectionModule::class,
ActivitiesModule::class,
ServicesModule::class
ServicesModule::class,
BroadcastReceiverModule::class
])
interface AppComponent {
@Component.Builder

View File

@ -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 <http://www.gnu.org/licenses>. */
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
}

View File

@ -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<Interceptor>,
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<Converter.Factory>): 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

View File

@ -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<Notification> notifications) {
AccountManager accountManager = TuskyApplication.getInstance(getContext())
.getServiceLocator().get(AccountManager.class);
AccountEntity account = accountManager.getActiveAccount();
BigInteger lastNoti = new BigInteger(account.getLastNotificationId());

View File

@ -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();
}
}

View File

@ -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<List<Status>> homeTimeline(
@ -246,6 +247,7 @@ public interface MastodonApi {
@FormUrlEncoded
@POST("api/v1/apps")
Call<AppCredentials> 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<AccessToken> fetchOAuthToken(
@Header(DOMAIN_HEADER) String domain,
@Field("client_id") String clientId,
@Field("client_secret") String clientSecret,
@Field("redirect_uri") String redirectUri,

View File

@ -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 = "[]"

View File

@ -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:

View File

@ -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);
}