From a5cffe0feadccfef02540b17dc0d87234c28caec Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Tue, 27 Mar 2018 20:47:00 +0300 Subject: [PATCH] Add Dagger (#554) * Add Dagger DI * Preemptively fix tests * Add missing licenses * DI fixes * ci fixes --- app/build.gradle | 8 + app/proguard-rules.pro | 2 + .../keylesspalace/tusky/AboutActivity.java | 23 +- .../keylesspalace/tusky/AccountActivity.java | 19 +- .../tusky/AccountListActivity.java | 17 +- .../com/keylesspalace/tusky/BaseActivity.java | 77 +------ .../keylesspalace/tusky/ComposeActivity.java | 17 +- .../tusky/EditProfileActivity.kt | 34 +-- .../tusky/FavouritesActivity.java | 17 +- .../com/keylesspalace/tusky/ListsActivity.kt | 9 +- .../com/keylesspalace/tusky/MainActivity.java | 24 ++- .../tusky/ModalTimelineActivity.kt | 27 ++- .../tusky/NotificationPullJobCreator.java | 74 ++----- .../keylesspalace/tusky/ReportActivity.java | 13 +- .../keylesspalace/tusky/SearchActivity.java | 9 +- .../keylesspalace/tusky/TuskyApplication.java | 36 +++- .../keylesspalace/tusky/ViewTagActivity.java | 17 +- .../tusky/ViewThreadActivity.java | 17 +- .../keylesspalace/tusky/db/AccountManager.kt | 38 ++-- .../tusky/di/ActivitiesModule.kt | 63 ++++++ .../keylesspalace/tusky/di/AppComponent.kt | 46 ++++ .../com/keylesspalace/tusky/di/AppInjector.kt | 80 +++++++ .../com/keylesspalace/tusky/di/AppModule.kt | 68 ++++++ .../tusky/di/FragmentBuildersModule.kt | 43 ++++ .../com/keylesspalace/tusky/di/Injectable.kt | 23 ++ .../keylesspalace/tusky/di/NetworkModule.kt | 111 ++++++++++ .../tusky/fragment/AccountListFragment.java | 18 +- .../tusky/fragment/AccountMediaFragment.kt | 10 +- .../tusky/fragment/BaseFragment.java | 9 - .../tusky/fragment/NotificationsFragment.java | 25 ++- .../tusky/fragment/SFragment.java | 198 ++++++------------ .../tusky/fragment/TimelineFragment.java | 21 +- .../tusky/fragment/ViewThreadFragment.java | 21 +- .../tusky/network/AuthInterceptor.java | 42 ---- .../InstanceSwitchAuthInterceptor.java | 69 ++++++ .../tusky/network/MastodonApi.java | 3 +- .../tusky/network/TimelineCases.kt | 99 +++++++++ .../keylesspalace/tusky/util/OkHttpUtils.java | 5 + .../tusky/ComposeActivityTest.kt | 23 +- gradlew | 0 gradlew.bat | 0 41 files changed, 1040 insertions(+), 415 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt delete mode 100644 app/src/main/java/com/keylesspalace/tusky/network/AuthInterceptor.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt mode change 100755 => 100644 gradlew mode change 100644 => 100755 gradlew.bat diff --git a/app/build.gradle b/app/build.gradle index 58244690..735cd751 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,6 +44,7 @@ android { } ext.supportLibraryVersion = '27.1.0' +ext.daggerVersion = '2.15' dependencies { implementation('com.mikepenz:materialdrawer:6.0.6@aar') { @@ -77,6 +78,13 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" testImplementation 'junit:junit:4.12' + + implementation "com.google.dagger:dagger:$daggerVersion" + kapt "com.google.dagger:dagger-compiler:$daggerVersion" + implementation "com.google.dagger:dagger-android:$daggerVersion" + implementation "com.google.dagger:dagger-android-support:$daggerVersion" + kapt "com.google.dagger:dagger-android-processor:$daggerVersion" + testImplementation "org.robolectric:robolectric:3.7.1" testCompile "org.mockito:mockito-inline:2.15.0" androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 09f25523..4f6c3dfd 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -64,3 +64,5 @@ -assumenosideeffects class kotlin.jvm.internal.Intrinsics { static void checkParameterIsNotNull(java.lang.Object, java.lang.String); } + +-dontwarn com.google.errorprone.annotations.* \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java index da84b370..ed2dc41a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java @@ -2,6 +2,7 @@ package com.keylesspalace.tusky; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; @@ -10,17 +11,24 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Account; +import com.keylesspalace.tusky.network.MastodonApi; import java.util.List; +import javax.inject.Inject; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public class AboutActivity extends BaseActivity { +public class AboutActivity extends BaseActivity implements Injectable { private Button appAccountButton; + @Inject + public MastodonApi mastodonApi; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -42,12 +50,7 @@ public class AboutActivity extends BaseActivity { versionTextView.setText(String.format(versionFormat, versionName)); appAccountButton = findViewById(R.id.tusky_profile_button); - appAccountButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onAccountButtonClick(); - } - }); + appAccountButton.setOnClickListener(v -> onAccountButtonClick()); } private void onAccountButtonClick() { @@ -68,10 +71,10 @@ public class AboutActivity extends BaseActivity { private void searchForAccountThenViewIt() { Callback> callback = new Callback>() { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful()) { List accountList = response.body(); - if (!accountList.isEmpty()) { + if (accountList != null && !accountList.isEmpty()) { String id = accountList.get(0).getId(); getPrivatePreferences().edit() .putString("appAccountId", id) @@ -86,7 +89,7 @@ public class AboutActivity extends BaseActivity { } @Override - public void onFailure(Call> call, Throwable t) { + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { onSearchFailed(); } }; diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index 796f4959..61aead84 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -31,6 +31,7 @@ import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; @@ -51,6 +52,7 @@ import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Relationship; import com.keylesspalace.tusky.interfaces.ActionButtonActivity; import com.keylesspalace.tusky.interfaces.LinkListener; +import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.pager.AccountPagerAdapter; import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.util.Assert; @@ -64,11 +66,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.support.HasSupportFragmentInjector; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public final class AccountActivity extends BaseActivity implements ActionButtonActivity { +public final class AccountActivity extends BaseActivity implements ActionButtonActivity, + HasSupportFragmentInjector { private static final String TAG = "AccountActivity"; // logging tag private enum FollowState { @@ -77,6 +85,11 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA REQUESTED, } + @Inject + public MastodonApi mastodonApi; + @Inject + public DispatchingAndroidInjector dispatchingAndroidInjector; + private String accountId; private FollowState followState; private boolean blocking; @@ -690,4 +703,8 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA return null; } + @Override + public AndroidInjector supportFragmentInjector() { + return dispatchingAndroidInjector; + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java index c0720dc6..58c3e8f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; @@ -27,7 +28,16 @@ import android.view.MenuItem; import com.keylesspalace.tusky.fragment.AccountListFragment; -public final class AccountListActivity extends BaseActivity { +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.support.HasSupportFragmentInjector; + +public final class AccountListActivity extends BaseActivity implements HasSupportFragmentInjector { + + @Inject + public DispatchingAndroidInjector dispatchingAndroidInjector; private static final String TYPE_EXTRA = "type"; private static final String ARG_EXTRA = "arg"; @@ -131,4 +141,9 @@ public final class AccountListActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public AndroidInjector supportFragmentInjector() { + return dispatchingAndroidInjector; + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index a0555aeb..ef154fe8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -25,41 +25,21 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; -import android.text.Spanned; import android.util.TypedValue; import android.view.Menu; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; -import com.keylesspalace.tusky.json.SpannedTypeAdapter; -import com.keylesspalace.tusky.network.AuthInterceptor; -import com.keylesspalace.tusky.network.MastodonApi; -import com.keylesspalace.tusky.util.OkHttpUtils; import com.keylesspalace.tusky.util.ThemeUtils; -import okhttp3.Dispatcher; -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - public abstract class BaseActivity extends AppCompatActivity { - public MastodonApi mastodonApi; - protected Dispatcher mastodonApiDispatcher; - private AccountManager accountManager; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - accountManager = TuskyApplication.getInstance(this).getServiceLocator() - .get(AccountManager.class); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); /* There isn't presently a way to globally change the theme of a whole application at @@ -84,19 +64,7 @@ public abstract class BaseActivity extends AppCompatActivity { } getTheme().applyStyle(style, false); - if (redirectIfNotLoggedIn()) { - return; - } - createMastodonApi(); - - } - - @Override - protected void onDestroy() { - if (mastodonApiDispatcher != null) { - mastodonApiDispatcher.cancelAll(); - } - super.onDestroy(); + redirectIfNotLoggedIn(); } @Override @@ -123,44 +91,13 @@ public abstract class BaseActivity extends AppCompatActivity { return getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); } - protected String getBaseUrl() { - AccountEntity account = accountManager.getActiveAccount(); - if (account != null) { - return "https://" + account.getDomain(); - } else { - return ""; - } - } - - protected void createMastodonApi() { - mastodonApiDispatcher = new Dispatcher(); - - Gson gson = new GsonBuilder() - .registerTypeAdapter(Spanned.class, new SpannedTypeAdapter()) - .create(); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - - OkHttpClient.Builder okBuilder = - OkHttpUtils.getCompatibleClientBuilder(preferences) - .addInterceptor(new AuthInterceptor(accountManager)) - .dispatcher(mastodonApiDispatcher); - - if (BuildConfig.DEBUG) { - okBuilder.addInterceptor( - new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)); - } - - Retrofit retrofit = new Retrofit.Builder().baseUrl(getBaseUrl()) - .client(okBuilder.build()) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - mastodonApi = retrofit.create(MastodonApi.class); - } - protected boolean redirectIfNotLoggedIn() { - if (accountManager.getActiveAccount() == null) { + // This is very ugly but we cannot inject into parent class and injecting into every + // subclass seems inconvenient as well. + AccountEntity account = ((TuskyApplication) getApplicationContext()) + .getServiceLocator().get(AccountManager.class) + .getActiveAccount(); + if (account == null) { Intent intent = new Intent(this, LoginActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 508c5800..35061ef7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -82,10 +82,12 @@ import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.db.TootDao; import com.keylesspalace.tusky.db.TootEntity; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.fragment.ComposeOptionsFragment; +import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.ProgressRequestBody; import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.DownsizeImageTask; @@ -115,6 +117,8 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; +import javax.inject.Inject; + import okhttp3.MediaType; import okhttp3.MultipartBody; import retrofit2.Call; @@ -122,7 +126,9 @@ import retrofit2.Callback; import retrofit2.Response; public final class ComposeActivity extends BaseActivity - implements ComposeOptionsFragment.Listener, MentionAutoCompleteAdapter.AccountSearchProvider { + implements ComposeOptionsFragment.Listener, + MentionAutoCompleteAdapter.AccountSearchProvider, + Injectable { private static final String TAG = "ComposeActivity"; // logging tag private static final int STATUS_CHARACTER_LIMIT = 500; private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608; // 8MiB @@ -145,6 +151,11 @@ public final class ComposeActivity extends BaseActivity private static TootDao tootDao = TuskyApplication.getDB().tootDao(); + @Inject + public MastodonApi mastodonApi; + @Inject + public AccountManager accountManager; + private TextView replyTextView; private TextView replyContentTextView; private EditTextTyped textEditor; @@ -206,11 +217,9 @@ public final class ComposeActivity extends BaseActivity } // setup the account image - AccountEntity activeAccount = TuskyApplication.getInstance(this).getServiceLocator() - .get(AccountManager.class).getActiveAccount(); + final AccountEntity activeAccount = accountManager.getActiveAccount(); if (activeAccount != null) { - ImageView composeAvatar = findViewById(R.id.composeAvatar); if (TextUtils.isEmpty(activeAccount.getProfilePictureUrl())) { diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index 6da78305..dd309f60 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -32,7 +32,9 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View +import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.IOUtils import com.squareup.picasso.Picasso import com.theartofdev.edmodo.cropper.CropImage @@ -46,6 +48,7 @@ import retrofit2.Callback import retrofit2.Response import java.io.* import java.util.* +import javax.inject.Inject private const val TAG = "EditProfileActivity" @@ -66,7 +69,7 @@ private const val AVATAR_SIZE = 120 private const val HEADER_WIDTH = 700 private const val HEADER_HEIGHT = 335 -class EditProfileActivity : BaseActivity() { +class EditProfileActivity : BaseActivity(), Injectable { private var oldDisplayName: String? = null private var oldNote: String? = null @@ -75,6 +78,9 @@ class EditProfileActivity : BaseActivity() { private var avatarChanged: Boolean = false private var headerChanged: Boolean = false + @Inject + lateinit var mastodonApi: MastodonApi + private enum class PickType { NOTHING, AVATAR, @@ -100,11 +106,11 @@ class EditProfileActivity : BaseActivity() { avatarChanged = it.getBoolean(KEY_AVATAR_CHANGED) headerChanged = it.getBoolean(KEY_HEADER_CHANGED) - if(avatarChanged) { + if (avatarChanged) { val avatar = BitmapFactory.decodeFile(getCacheFileForName(AVATAR_FILE_NAME).absolutePath) avatarPreview.setImageBitmap(avatar) } - if(headerChanged) { + if (headerChanged) { val header = BitmapFactory.decodeFile(getCacheFileForName(HEADER_FILE_NAME).absolutePath) headerPreview.setImageBitmap(header) } @@ -135,13 +141,13 @@ class EditProfileActivity : BaseActivity() { displayNameEditText.setText(oldDisplayName) noteEditText.setText(oldNote) - if(!avatarChanged) { + if (!avatarChanged) { Picasso.with(avatarPreview.context) .load(me.avatar) .placeholder(R.drawable.avatar_default) .into(avatarPreview) } - if(!headerChanged) { + if (!headerChanged) { Picasso.with(headerPreview.context) .load(me.header) .placeholder(R.drawable.account_header_default) @@ -253,21 +259,21 @@ class EditProfileActivity : BaseActivity() { RequestBody.create(MultipartBody.FORM, newNote) } - val avatar = if(avatarChanged) { + val avatar = if (avatarChanged) { val avatarBody = RequestBody.create(MediaType.parse("image/png"), getCacheFileForName(AVATAR_FILE_NAME)) MultipartBody.Part.createFormData("avatar", getFileName(), avatarBody) } else { null } - val header = if(headerChanged) { + val header = if (headerChanged) { val headerBody = RequestBody.create(MediaType.parse("image/png"), getCacheFileForName(HEADER_FILE_NAME)) MultipartBody.Part.createFormData("header", getFileName(), headerBody) } else { null } - if(displayName == null && note == null && avatar == null && header == null) { + if (displayName == null && note == null && avatar == null && header == null) { /** if nothing has changed, there is no need to make a network request */ finish() return @@ -413,11 +419,11 @@ class EditProfileActivity : BaseActivity() { return java.lang.Long.toHexString(Random().nextLong()) } - private class ResizeImageTask (private val contentResolver: ContentResolver, - private val resizeWidth: Int, - private val resizeHeight: Int, - private val cacheFile: File, - private val listener: Listener) : AsyncTask() { + private class ResizeImageTask(private val contentResolver: ContentResolver, + private val resizeWidth: Int, + private val resizeHeight: Int, + private val cacheFile: File, + private val listener: Listener) : AsyncTask() { private var resultBitmap: Bitmap? = null override fun doInBackground(vararg uris: Uri): Boolean? { @@ -445,7 +451,7 @@ class EditProfileActivity : BaseActivity() { //dont upscale image if its smaller than the desired size val bitmap = - if(sourceBitmap.width <= resizeWidth && sourceBitmap.height <= resizeHeight) { + if (sourceBitmap.width <= resizeWidth && sourceBitmap.height <= resizeHeight) { sourceBitmap } else { Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight, true) diff --git a/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java b/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java index 470cfa2f..5dcbe2cb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java @@ -25,7 +25,17 @@ import android.view.MenuItem; import com.keylesspalace.tusky.fragment.TimelineFragment; -public class FavouritesActivity extends BaseActivity { +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.support.HasSupportFragmentInjector; + +public class FavouritesActivity extends BaseActivity implements HasSupportFragmentInjector { + + @Inject + public DispatchingAndroidInjector dispatchingAndroidInjector; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,4 +66,9 @@ public class FavouritesActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public AndroidInjector supportFragmentInjector() { + return dispatchingAndroidInjector; + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index f14d717f..f28a03ff 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -12,19 +12,19 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView +import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.MastoList import com.keylesspalace.tusky.fragment.TimelineFragment import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.ThemeUtils import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.IconicsDrawable -import com.varunest.sparkbutton.helpers.Utils import retrofit2.Call import retrofit2.Response import java.lang.ref.WeakReference +import javax.inject.Inject /** * Created by charlag on 1/4/18. @@ -83,7 +83,7 @@ class ListsViewModel(private val api: MastodonApi) { } } -class ListsActivity : BaseActivity(), ListsView { +class ListsActivity : BaseActivity(), ListsView, Injectable { companion object { @JvmStatic @@ -92,6 +92,9 @@ class ListsActivity : BaseActivity(), ListsView { } } + @Inject + lateinit var mastodonApi: MastodonApi + private lateinit var recyclerView: RecyclerView private lateinit var progressBar: ProgressBar diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index ee96f90e..25811532 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -27,6 +27,7 @@ import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; import android.support.graphics.drawable.VectorDrawableCompat; +import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.ViewPager; @@ -40,6 +41,7 @@ import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.interfaces.ActionButtonActivity; +import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.pager.TimelinePagerAdapter; import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.util.NotificationHelper; @@ -63,11 +65,18 @@ import com.squareup.picasso.Picasso; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.support.AndroidSupportInjection; +import dagger.android.support.HasSupportFragmentInjector; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public class MainActivity extends BaseActivity implements ActionButtonActivity { +public class MainActivity extends BaseActivity implements ActionButtonActivity, + HasSupportFragmentInjector { private static final String TAG = "MainActivity"; // logging tag private static final long DRAWER_ITEM_ADD_ACCOUNT = -13; private static final long DRAWER_ITEM_EDIT_PROFILE = 0; @@ -82,6 +91,11 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity { private static final long DRAWER_ITEM_SAVED_TOOT = 9; private static final long DRAWER_ITEM_LISTS = 10; + @Inject + public MastodonApi mastodonApi; + @Inject + public DispatchingAndroidInjector fragmentInjector; + private static int COMPOSE_RESULT = 1; AccountManager accountManager; @@ -93,10 +107,7 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity { @Override protected void onCreate(Bundle savedInstanceState) { - - // account switching has to be done before MastodonApi is created in super.onCreate Intent intent = getIntent(); - int tabPosition = 0; accountManager = TuskyApplication.getInstance(this).getServiceLocator() @@ -550,4 +561,9 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity { public FloatingActionButton getActionButton() { return composeButton; } + + @Override + public AndroidInjector supportFragmentInjector() { + return fragmentInjector; + } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt index fc81589a..42abea52 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt @@ -4,19 +4,29 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.support.design.widget.FloatingActionButton +import android.support.v4.app.Fragment import android.support.v7.widget.Toolbar import android.view.MenuItem import android.widget.FrameLayout import com.keylesspalace.tusky.fragment.TimelineFragment import com.keylesspalace.tusky.interfaces.ActionButtonActivity +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.support.HasSupportFragmentInjector +import javax.inject.Inject + +class ModalTimelineActivity : BaseActivity(), ActionButtonActivity, HasSupportFragmentInjector { + + @Inject + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector -class ModalTimelineActivity : BaseActivity(), ActionButtonActivity { companion object { - private const val ARG_KIND = "kind" private const val ARG_ARG = "arg" - @JvmStatic fun newIntent(context: Context, kind: TimelineFragment.Kind, - argument: String?): Intent { + + @JvmStatic + fun newIntent(context: Context, kind: TimelineFragment.Kind, + argument: String?): Intent { val intent = Intent(context, ModalTimelineActivity::class.java) intent.putExtra(ARG_KIND, kind) intent.putExtra(ARG_ARG, argument) @@ -24,6 +34,7 @@ class ModalTimelineActivity : BaseActivity(), ActionButtonActivity { } } + lateinit var contentFrame: FrameLayout override fun onCreate(savedInstanceState: Bundle?) { @@ -41,8 +52,8 @@ class ModalTimelineActivity : BaseActivity(), ActionButtonActivity { } if (supportFragmentManager.findFragmentById(R.id.content_frame) == null) { - val kind = intent?.getSerializableExtra(ARG_KIND) as? TimelineFragment.Kind ?: - TimelineFragment.Kind.HOME + val kind = intent?.getSerializableExtra(ARG_KIND) as? TimelineFragment.Kind + ?: TimelineFragment.Kind.HOME val argument = intent?.getStringExtra(ARG_ARG) supportFragmentManager.beginTransaction() .replace(R.id.content_frame, TimelineFragment.newInstance(kind, argument)) @@ -60,4 +71,8 @@ class ModalTimelineActivity : BaseActivity(), ActionButtonActivity { } return false } + + override fun supportFragmentInjector(): AndroidInjector { + return dispatchingAndroidInjector + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationPullJobCreator.java b/app/src/main/java/com/keylesspalace/tusky/NotificationPullJobCreator.java index 89a2c498..df616c92 100644 --- a/app/src/main/java/com/keylesspalace/tusky/NotificationPullJobCreator.java +++ b/app/src/main/java/com/keylesspalace/tusky/NotificationPullJobCreator.java @@ -16,23 +16,17 @@ package com.keylesspalace.tusky; import android.content.Context; -import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.Spanned; import android.util.Log; import com.evernote.android.job.Job; import com.evernote.android.job.JobCreator; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.entity.Notification; -import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.util.NotificationHelper; -import com.keylesspalace.tusky.util.OkHttpUtils; import java.io.IOException; import java.math.BigInteger; @@ -40,10 +34,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import okhttp3.OkHttpClient; +import javax.inject.Inject; + import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; /** * Created by charlag on 31/10/17. @@ -55,65 +48,53 @@ public final class NotificationPullJobCreator implements JobCreator { static final String NOTIFICATIONS_JOB_TAG = "notifications_job_tag"; - private Context context; + private final MastodonApi api; + private final Context context; + private final AccountManager accountManager; - NotificationPullJobCreator(Context context) { + @Inject NotificationPullJobCreator(MastodonApi api, Context context, + AccountManager accountManager) { + this.api = api; this.context = context; + this.accountManager = accountManager; } @Nullable @Override public Job create(@NonNull String tag) { if (tag.equals(NOTIFICATIONS_JOB_TAG)) { - return new NotificationPullJob(context); + return new NotificationPullJob(context, accountManager, api); } return null; } - private static MastodonApi createMastodonApi(String domain, Context context) { - SharedPreferences preferences = context.getSharedPreferences( - context.getString(R.string.preferences_file_key), Context.MODE_PRIVATE); - - OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder(preferences) - .build(); - - Gson gson = new GsonBuilder() - .registerTypeAdapter(Spanned.class, new SpannedTypeAdapter()) - .create(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - return retrofit.create(MastodonApi.class); - } - private final static class NotificationPullJob extends Job { - private Context context; + private final Context context; + private final AccountManager accountManager; + private final MastodonApi mastodonApi; - NotificationPullJob(Context context) { + NotificationPullJob(Context context, AccountManager accountManager, + MastodonApi mastodonApi) { this.context = context; + this.accountManager = accountManager; + this.mastodonApi = mastodonApi; } @NonNull @Override protected Result onRunJob(@NonNull Params params) { - - AccountManager accountManager = TuskyApplication.getInstance(context).getServiceLocator() - .get(AccountManager.class); List accountList = new ArrayList<>(accountManager.getAllAccountsOrderedByActive()); - for (AccountEntity account : accountList) { - if (account.getNotificationsEnabled()) { - MastodonApi api = createMastodonApi(account.getDomain(), context); try { Log.d(TAG, "getting Notifications for " + account.getFullName()); Response> notifications = - api.notificationsWithAuth(String.format("Bearer %s", account.getAccessToken())).execute(); + mastodonApi.notificationsWithAuth( + String.format("Bearer %s", account.getAccessToken()), + account.getDomain() + ) + .execute(); if (notifications.isSuccessful()) { onNotificationsReceived(account, notifications.body()); } else { @@ -127,22 +108,15 @@ public final class NotificationPullJobCreator implements JobCreator { } return Result.SUCCESS; - - } private void onNotificationsReceived(AccountEntity account, List notificationList) { - Collections.reverse(notificationList); - BigInteger newId = new BigInteger(account.getLastNotificationId()); - BigInteger newestId = BigInteger.ZERO; for (Notification notification : notificationList) { - BigInteger currentId = new BigInteger(notification.getId()); - if (isBiggerThan(currentId, newestId)) { newestId = currentId; } @@ -153,12 +127,10 @@ public final class NotificationPullJobCreator implements JobCreator { } account.setLastNotificationId(newestId.toString()); - TuskyApplication.getInstance(context).getServiceLocator() - .get(AccountManager.class).saveAccount(account); + accountManager.saveAccount(account); } private boolean isBiggerThan(BigInteger newId, BigInteger lastShownNotificationId) { - return lastShownNotificationId.compareTo(newId) == -1; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java index 3734578d..b2c97043 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java @@ -32,7 +32,9 @@ import android.view.View; import android.widget.EditText; import com.keylesspalace.tusky.adapter.ReportAdapter; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Status; +import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.util.HtmlUtils; import com.keylesspalace.tusky.util.ThemeUtils; @@ -40,14 +42,19 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javax.inject.Inject; + import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public class ReportActivity extends BaseActivity { +public class ReportActivity extends BaseActivity implements Injectable { private static final String TAG = "ReportActivity"; // logging tag + @Inject + public MastodonApi mastodonApi; + private View anyView; // what Snackbar will use to find the root view private ReportAdapter adapter; private boolean reportAlreadyInFlight; @@ -118,7 +125,7 @@ public class ReportActivity extends BaseActivity { } private void sendReport(final String accountId, final String[] statusIds, - final String comment) { + final String comment) { Callback callback = new Callback() { @Override public void onResponse(Call call, Response response) { @@ -145,7 +152,7 @@ public class ReportActivity extends BaseActivity { } private void onSendFailure(final String accountId, final String[] statusIds, - final String comment) { + final String comment) { Snackbar.make(anyView, R.string.error_generic, Snackbar.LENGTH_LONG) .setAction(R.string.action_retry, new View.OnClickListener() { @Override diff --git a/app/src/main/java/com/keylesspalace/tusky/SearchActivity.java b/app/src/main/java/com/keylesspalace/tusky/SearchActivity.java index 0df04b1e..5c37c598 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SearchActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SearchActivity.java @@ -35,17 +35,24 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.keylesspalace.tusky.adapter.SearchResultsAdapter; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.SearchResults; import com.keylesspalace.tusky.interfaces.LinkListener; +import com.keylesspalace.tusky.network.MastodonApi; + +import javax.inject.Inject; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class SearchActivity extends BaseActivity implements SearchView.OnQueryTextListener, - LinkListener { + LinkListener, Injectable { private static final String TAG = "SearchActivity"; // logging tag + @Inject + public MastodonApi mastodonApi; + private ProgressBar progressBar; private TextView messageNoResults; private SearchResultsAdapter adapter; diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java index 4dd5891b..11f16e81 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java @@ -15,6 +15,7 @@ package com.keylesspalace.tusky; +import android.app.Activity; import android.app.Application; import android.app.UiModeManager; import android.arch.persistence.room.Room; @@ -28,15 +29,26 @@ import com.evernote.android.job.JobManager; 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; -public class TuskyApplication extends Application { +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.HasActivityInjector; + +public class TuskyApplication extends Application implements HasActivityInjector { public static final String APP_THEME_DEFAULT = ThemeUtils.THEME_NIGHT; private static AppDatabase db; private AccountManager accountManager; + @Inject + DispatchingAndroidInjector dispatchingAndroidInjector; + @Inject + NotificationPullJobCreator notificationPullJobCreator; public static AppDatabase getDB() { return db; @@ -58,8 +70,12 @@ public class TuskyApplication extends Application { @Override public void onCreate() { super.onCreate(); - initPicasso(); + db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB") + .allowMainThreadQueries() + .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5) + .build(); + accountManager = new AccountManager(db); serviceLocator = new ServiceLocator() { @Override public T get(Class clazz) { @@ -72,19 +88,14 @@ public class TuskyApplication extends Application { } }; - db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB") - .allowMainThreadQueries() - .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5) - .build(); - - JobManager.create(this).addJobCreator(new NotificationPullJobCreator(this)); + AppInjector.INSTANCE.init(this); + initPicasso(); + JobManager.create(this).addJobCreator(notificationPullJobCreator); uiModeManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE); //necessary for Android < APi 21 AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); - - accountManager = new AccountManager(); } protected void initPicasso() { @@ -106,6 +117,11 @@ public class TuskyApplication extends Application { return serviceLocator; } + @Override + public AndroidInjector activityInjector() { + return dispatchingAndroidInjector; + } + public interface ServiceLocator { T get(Class clazz); } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java index dfbc6170..4d5b8fcf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java @@ -25,7 +25,17 @@ import android.view.MenuItem; import com.keylesspalace.tusky.fragment.TimelineFragment; -public class ViewTagActivity extends BaseActivity { +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.support.HasSupportFragmentInjector; + +public class ViewTagActivity extends BaseActivity implements HasSupportFragmentInjector { + + @Inject + public DispatchingAndroidInjector dispatchingAndroidInjector; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -59,4 +69,9 @@ public class ViewTagActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public AndroidInjector supportFragmentInjector() { + return dispatchingAndroidInjector; + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java index f020f667..5ebb302c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java @@ -27,7 +27,17 @@ import android.view.MenuItem; import com.keylesspalace.tusky.fragment.ViewThreadFragment; import com.keylesspalace.tusky.util.LinkHelper; -public class ViewThreadActivity extends BaseActivity { +import javax.inject.Inject; + +import dagger.android.AndroidInjector; +import dagger.android.DispatchingAndroidInjector; +import dagger.android.support.HasSupportFragmentInjector; + +public class ViewThreadActivity extends BaseActivity implements HasSupportFragmentInjector { + + @Inject + public DispatchingAndroidInjector dispatchingAndroidInjector; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -69,4 +79,9 @@ public class ViewThreadActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public AndroidInjector supportFragmentInjector() { + return dispatchingAndroidInjector; + } } 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 a6375352..e295ee1f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt @@ -15,6 +15,7 @@ package com.keylesspalace.tusky.db +import android.arch.persistence.room.Database import android.util.Log import com.keylesspalace.tusky.TuskyApplication import com.keylesspalace.tusky.entity.Account @@ -26,12 +27,13 @@ import com.keylesspalace.tusky.entity.Account private const val TAG = "AccountManager" -class AccountManager { +class AccountManager(db: AppDatabase) { - @Volatile var activeAccount: AccountEntity? = null + @Volatile + var activeAccount: AccountEntity? = null private var accounts: MutableList = mutableListOf() - private val accountDao: AccountDao = TuskyApplication.getDB().accountDao() + private val accountDao: AccountDao = db.accountDao() init { accounts = accountDao.loadAll().toMutableList() @@ -50,9 +52,9 @@ class AccountManager { */ fun addAccount(accessToken: String, domain: String) { - activeAccount?.let{ + activeAccount?.let { it.isActive = false - Log.d(TAG, "addAccount: saving account with id "+it.id) + Log.d(TAG, "addAccount: saving account with id " + it.id) accountDao.insertOrReplace(it) } @@ -67,8 +69,8 @@ class AccountManager { * @param account the account to save */ fun saveAccount(account: AccountEntity) { - if(account.id != 0L) { - Log.d(TAG, "saveAccount: saving account with id "+account.id) + if (account.id != 0L) { + Log.d(TAG, "saveAccount: saving account with id " + account.id) accountDao.insertOrReplace(account) } @@ -78,18 +80,18 @@ class AccountManager { * Logs the current account out by deleting all data of the account. * @return the new active account, or null if no other account was found */ - fun logActiveAccountOut() : AccountEntity? { + fun logActiveAccountOut(): AccountEntity? { - if(activeAccount == null) { + if (activeAccount == null) { return null } else { accounts.remove(activeAccount!!) accountDao.delete(activeAccount!!) - if(accounts.size > 0) { + if (accounts.size > 0) { accounts[0].isActive = true activeAccount = accounts[0] - Log.d(TAG, "logActiveAccountOut: saving account with id "+accounts[0].id) + Log.d(TAG, "logActiveAccountOut: saving account with id " + accounts[0].id) accountDao.insertOrReplace(accounts[0]) } else { activeAccount = null @@ -106,18 +108,18 @@ class AccountManager { * @param account the [Account] object returned from the api */ fun updateActiveAccount(account: Account) { - activeAccount?.let{ + activeAccount?.let { it.accountId = account.id it.username = account.username it.displayName = account.name it.profilePictureUrl = account.avatar - Log.d(TAG, "updateActiveAccount: saving account with id "+it.id) + Log.d(TAG, "updateActiveAccount: saving account with id " + it.id) it.id = accountDao.insertOrReplace(it) val accountIndex = accounts.indexOf(it) - if(accountIndex != -1) { + if (accountIndex != -1) { //in case the user was already logged in with this account, remove the old information accounts.removeAt(accountIndex) accounts.add(accountIndex, it) @@ -134,8 +136,8 @@ class AccountManager { */ fun setActiveAccount(accountId: Long) { - activeAccount?.let{ - Log.d(TAG, "setActiveAccount: saving account with id "+it.id) + activeAccount?.let { + Log.d(TAG, "setActiveAccount: saving account with id " + it.id) it.isActive = false saveAccount(it) } @@ -144,7 +146,7 @@ class AccountManager { acc.id == accountId } - activeAccount?.let{ + activeAccount?.let { it.isActive = true accountDao.insertOrReplace(it) } @@ -154,7 +156,7 @@ class AccountManager { * @return an immutable list of all accounts in the database with the active account first */ fun getAllAccountsOrderedByActive(): List { - accounts.sortWith (Comparator { l, r -> + accounts.sortWith(Comparator { l, r -> when { l.isActive && !r.isActive -> -1 r.isActive && !l.isActive -> 1 diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt new file mode 100644 index 00000000..1ea373b8 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -0,0 +1,63 @@ +/* Copyright 2018 charlag + * + * 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.* +import dagger.Module +import dagger.android.ContributesAndroidInjector + +/** + * Created by charlag on 3/24/18. + */ + +@Module +abstract class ActivitiesModule { + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesMainActivity(): MainActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesAccountActivity(): AccountActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesListsActivity(): ListsActivity + + @ContributesAndroidInjector + abstract fun contributesComposeActivity(): ComposeActivity + + @ContributesAndroidInjector + abstract fun contributesEditProfileActivity(): EditProfileActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesAccountListActivity(): AccountListActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesModalTimelineActivity(): ModalTimelineActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesViewTagActivity(): ViewTagActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesViewThreadActivity(): ViewThreadActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contributesFavouritesActivity(): FavouritesActivity + + @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) + abstract fun contribtutesSearchAvtivity(): SearchActivity + + @ContributesAndroidInjector() + abstract fun contributesAboutActivity(): AboutActivity +} \ 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 new file mode 100644 index 00000000..f0618676 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt @@ -0,0 +1,46 @@ +/* Copyright 2018 charlag + * + * 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.TuskyApplication +import dagger.BindsInstance +import dagger.Component +import dagger.android.AndroidInjectionModule +import javax.inject.Singleton + + +/** + * Created by charlag on 3/21/18. + */ + +@Singleton +@Component(modules = [ + AppModule::class, + NetworkModule::class, + AndroidInjectionModule::class, + ActivitiesModule::class +]) +interface AppComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun application(tuskyApp: TuskyApplication): Builder + + fun build(): AppComponent + } + + fun inject(app: TuskyApplication) +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt new file mode 100644 index 00000000..e78d352b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt @@ -0,0 +1,80 @@ +/* Copyright 2018 charlag + * + * 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 android.app.Activity +import android.app.Application +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentActivity +import android.support.v4.app.FragmentManager +import com.keylesspalace.tusky.TuskyApplication +import dagger.android.AndroidInjection +import dagger.android.support.AndroidSupportInjection +import dagger.android.support.HasSupportFragmentInjector + +/** + * Created by charlag on 3/24/18. + */ + +object AppInjector { + fun init(app: TuskyApplication) { + DaggerAppComponent.builder().application(app) + .build().inject(app) + + app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + handleActivity(activity) + } + + override fun onActivityPaused(activity: Activity?) { + } + + override fun onActivityResumed(activity: Activity?) { + } + + override fun onActivityStarted(activity: Activity?) { + } + + override fun onActivityDestroyed(activity: Activity?) { + } + + override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { + } + + override fun onActivityStopped(activity: Activity?) { + } + + }) + } + + private fun handleActivity(activity: Activity) { + if (activity is HasSupportFragmentInjector || activity is Injectable) { + AndroidInjection.inject(activity) + } + if (activity is FragmentActivity) { + activity.supportFragmentManager.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentCreated(fm: FragmentManager?, f: Fragment?, + savedInstanceState: Bundle?) { + if (f is Injectable) { + AndroidSupportInjection.inject(f) + } + } + }, true) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt new file mode 100644 index 00000000..cb02adf2 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -0,0 +1,68 @@ +/* Copyright 2018 charlag + * + * 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 android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.preference.PreferenceManager +import android.support.v4.content.LocalBroadcastManager +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.TuskyApplication +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.network.TimelineCases +import com.keylesspalace.tusky.network.TimelineCasesImpl +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + +/** + * Created by charlag on 3/21/18. + */ + +@Module +class AppModule { + + @Provides + fun providesApplication(app: TuskyApplication): Application = app + + @Provides + fun providesContext(app: Application): Context = app + + @Provides + fun providesSharedPreferences(app: Application): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(app) + } + + @Provides + fun providesBroadcastManager(app: Application): LocalBroadcastManager { + return LocalBroadcastManager.getInstance(app) + } + + @Provides + fun providesTimelineUseCases(api: MastodonApi, + broadcastManager: LocalBroadcastManager): TimelineCases { + return TimelineCasesImpl(api, broadcastManager) + } + + @Provides + @Singleton + fun providesAccountManager(app: TuskyApplication): AccountManager { + return app.serviceLocator.get(AccountManager::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt new file mode 100644 index 00000000..dc01cc2a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt @@ -0,0 +1,43 @@ +/* Copyright 2018 charlag + * + * 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.fragment.* +import dagger.Module +import dagger.android.ContributesAndroidInjector + +/** + * Created by charlag on 3/24/18. + */ + +@Module +abstract class FragmentBuildersModule { + @ContributesAndroidInjector + abstract fun accountListFragment(): AccountListFragment + + @ContributesAndroidInjector + abstract fun accountMediaFragment(): AccountMediaFragment + + @ContributesAndroidInjector + abstract fun viewThreadFragment(): ViewThreadFragment + + @ContributesAndroidInjector + abstract fun timelineFragment(): TimelineFragment + + @ContributesAndroidInjector + abstract fun notificationsFragment(): NotificationsFragment +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt b/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt new file mode 100644 index 00000000..1df715e7 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt @@ -0,0 +1,23 @@ +/* Copyright 2018 charlag + * + * 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 + +/** + * Created by charlag on 3/24/18. + */ + +interface Injectable \ 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 new file mode 100644 index 00000000..8cafab38 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -0,0 +1,111 @@ +/* Copyright 2018 charlag + * + * 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 android.content.SharedPreferences +import android.text.Spanned +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonDeserializer +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.json.SpannedTypeAdapter +import com.keylesspalace.tusky.network.InstanceSwitchAuthInterceptor +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.Interceptor +import okhttp3.OkHttpClient +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +/** + * Created by charlag on 3/24/18. + */ + +@Module +class NetworkModule { + @Provides + @IntoMap() + @ClassKey(Spanned::class) + fun providesSpannedTypeAdapter(): JsonDeserializer<*> = SpannedTypeAdapter() + + @Provides + @Singleton + fun providesGson(adapters: @JvmSuppressWildcards Map, JsonDeserializer<*>>): Gson { + return GsonBuilder() + .apply { + for ((k, v) in adapters) { + registerTypeAdapter(k, v) + } + } + .create() + } + + @Provides + @IntoSet + @Singleton + 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) + .apply { + interceptors.fold(this) { b, i -> + b.addInterceptor(i) + } + } + .build() + } + + + @Provides + @Singleton + fun providesRetrofit(httpClient: OkHttpClient, + converters: @JvmSuppressWildcards Set): Retrofit { + return Retrofit.Builder().baseUrl("https://dummy.placeholder/") + .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) + } + } + .build() + + } + + @Provides + @Singleton + fun providesApi(retrofit: Retrofit): MastodonApi = retrofit.create(MastodonApi::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java index 57c5435f..862c1109 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java @@ -39,6 +39,7 @@ import com.keylesspalace.tusky.adapter.FollowAdapter; import com.keylesspalace.tusky.adapter.FollowRequestsAdapter; import com.keylesspalace.tusky.adapter.FooterViewHolder; import com.keylesspalace.tusky.adapter.MutesAdapter; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Relationship; import com.keylesspalace.tusky.interfaces.AccountActionListener; @@ -49,11 +50,14 @@ import com.keylesspalace.tusky.view.EndlessOnScrollListener; import java.util.List; +import javax.inject.Inject; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public class AccountListFragment extends BaseFragment implements AccountActionListener { +public class AccountListFragment extends BaseFragment implements AccountActionListener, + Injectable { private static final String TAG = "AccountList"; // logging tag public AccountListFragment() { @@ -67,13 +71,15 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi FOLLOW_REQUESTS, } + @Inject + public MastodonApi api; + private Type type; private String accountId; private LinearLayoutManager layoutManager; private RecyclerView recyclerView; private EndlessOnScrollListener scrollListener; private AccountAdapter adapter; - private MastodonApi api; private boolean bottomLoading; private int bottomFetches; private boolean topLoading; @@ -146,12 +152,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - BaseActivity activity = (BaseActivity) getActivity(); - - /* MastodonApi on the base activity is only guaranteed to be initialised after the parent - * activity is created, so everything needing to access the api object has to be delayed - * until here. */ - api = activity.mastodonApi; // Just use the basic scroll listener to load more accounts. scrollListener = new EndlessOnScrollListener(layoutManager) { @Override @@ -464,7 +464,7 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi private void onLoadMore(RecyclerView recyclerView) { AccountAdapter adapter = (AccountAdapter) recyclerView.getAdapter(); //if we do not have a bottom id, we know we do not need to load more - if(adapter.getBottomId() == null) return; + if (adapter.getBottomId() == null) return; fetchAccounts(adapter.getBottomId(), null, FetchEnd.BOTTOM); } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 53f0915e..0430d982 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -29,10 +29,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView -import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.ViewVideoActivity +import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.MastodonApi @@ -43,6 +43,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.* +import javax.inject.Inject /** * Created by charlag on 26/10/2017. @@ -50,7 +51,7 @@ import java.util.* * Fragment with multiple columns of media previews for the specified account. */ -class AccountMediaFragment : BaseFragment() { +class AccountMediaFragment : BaseFragment(), Injectable { companion object { @JvmStatic @@ -66,9 +67,11 @@ class AccountMediaFragment : BaseFragment() { private const val TAG = "AccountMediaFragment" } + @Inject + lateinit var api: MastodonApi + private val adapter = MediaGridAdapter() private var currentCall: Call>? = null - private lateinit var api: MastodonApi private val statuses = mutableListOf() private var fetchingStatus = FetchingStatus.NOT_FETCHING lateinit private var swipeLayout: SwipeRefreshLayout @@ -123,7 +126,6 @@ class AccountMediaFragment : BaseFragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - api = (activity as BaseActivity).mastodonApi } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java index c0305d12..3a9db659 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java @@ -15,14 +15,10 @@ package com.keylesspalace.tusky.fragment; -import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import com.keylesspalace.tusky.R; - import java.util.ArrayList; import java.util.List; @@ -44,9 +40,4 @@ public class BaseFragment extends Fragment { } super.onDestroy(); } - - protected SharedPreferences getPrivatePreferences() { - return getContext().getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE); - } } 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 6a10f650..0f8a965a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -43,11 +43,14 @@ import com.keylesspalace.tusky.adapter.FooterViewHolder; import com.keylesspalace.tusky.adapter.NotificationsAdapter; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.interfaces.ActionButtonActivity; import com.keylesspalace.tusky.interfaces.StatusActionListener; +import com.keylesspalace.tusky.network.MastodonApi; +import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.util.CollectionUtil; import com.keylesspalace.tusky.util.Either; @@ -64,14 +67,18 @@ import java.math.BigInteger; import java.util.Iterator; import java.util.List; +import javax.inject.Inject; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class NotificationsFragment extends SFragment implements - SwipeRefreshLayout.OnRefreshListener, StatusActionListener, + SwipeRefreshLayout.OnRefreshListener, + StatusActionListener, NotificationsAdapter.NotificationActionListener, - SharedPreferences.OnSharedPreferenceChangeListener { + SharedPreferences.OnSharedPreferenceChangeListener, + Injectable { private static final String TAG = "NotificationF"; // logging tag private static final int LOAD_AT_ONCE = 30; @@ -97,6 +104,11 @@ public class NotificationsFragment extends SFragment implements } } + @Inject + public TimelineCases timelineCases; + @Inject + public MastodonApi mastodonApi; + private SwipeRefreshLayout swipeRefreshLayout; private LinearLayoutManager layoutManager; private RecyclerView recyclerView; @@ -113,6 +125,11 @@ public class NotificationsFragment extends SFragment implements private String topId; private boolean alwaysShowSensitiveMedia; + @Override + protected TimelineCases timelineCases() { + return timelineCases; + } + // Each element is either a Notification for loading data or a Placeholder private final PairedList, NotificationViewData> notifications = new PairedList<>(new Function, NotificationViewData>() { @@ -273,7 +290,7 @@ public class NotificationsFragment extends SFragment implements public void onReblog(final boolean reblog, final int position) { final Notification notification = notifications.get(position).getAsRight(); final Status status = notification.getStatus(); - reblogWithCallback(status, reblog, new Callback() { + timelineCases.reblogWithCallback(status, reblog, new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { if (response.isSuccessful()) { @@ -310,7 +327,7 @@ public class NotificationsFragment extends SFragment implements public void onFavourite(final boolean favourite, final int position) { final Notification notification = notifications.get(position).getAsRight(); final Status status = notification.getStatus(); - favouriteWithCallback(status, favourite, new Callback() { + timelineCases.favouriteWithCallback(status, favourite, new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { if (response.isSuccessful()) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 2fe48c8e..8b23caa8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -41,18 +41,22 @@ import com.keylesspalace.tusky.ViewTagActivity; import com.keylesspalace.tusky.ViewThreadActivity; import com.keylesspalace.tusky.ViewVideoActivity; import com.keylesspalace.tusky.db.AccountEntity; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Relationship; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.interfaces.AdapterItemRemover; import com.keylesspalace.tusky.network.MastodonApi; +import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.util.HtmlUtils; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; @@ -69,7 +73,8 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov protected String loggedInAccountId; protected String loggedInUsername; - protected MastodonApi mastodonApi; + + protected abstract TimelineCases timelineCases(); @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { @@ -80,8 +85,6 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov loggedInAccountId = activeAccount.getAccountId(); loggedInUsername = activeAccount.getUsername(); } - BaseActivity activity = (BaseActivity) getActivity(); - mastodonApi = activity.mastodonApi; } @Override @@ -90,6 +93,11 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left); } + protected void openReblog(@Nullable final Status status) { + if (status == null) return; + viewAccount(status.getAccount().getId()); + } + protected void reply(Status status) { String inReplyToId = status.getActionableId(); Status actionableStatus = status.getActionableStatus(); @@ -113,88 +121,6 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov startActivityForResult(intent, COMPOSE_RESULT); } - protected void reblogWithCallback(final Status status, final boolean reblog, - Callback callback) { - String id = status.getActionableId(); - - Call call; - if (reblog) { - call = mastodonApi.reblogStatus(id); - } else { - call = mastodonApi.unreblogStatus(id); - } - call.enqueue(callback); - } - - protected void favouriteWithCallback(final Status status, final boolean favourite, - final Callback callback) { - String id = status.getActionableId(); - - Call call; - if (favourite) { - call = mastodonApi.favouriteStatus(id); - } else { - call = mastodonApi.unfavouriteStatus(id); - } - call.enqueue(callback); - callList.add(call); - } - - protected void openReblog(@Nullable final Status status) { - if (status == null) return; - viewAccount(status.getAccount().getId()); - } - - private void mute(String id) { - Call call = mastodonApi.muteAccount(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - } - }); - callList.add(call); - Intent intent = new Intent(TimelineReceiver.Types.MUTE_ACCOUNT); - intent.putExtra("id", id); - LocalBroadcastManager.getInstance(getContext()) - .sendBroadcast(intent); - } - - private void block(String id) { - Call call = mastodonApi.blockAccount(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - } - }); - callList.add(call); - Intent intent = new Intent(TimelineReceiver.Types.BLOCK_ACCOUNT); - intent.putExtra("id", id); - LocalBroadcastManager.getInstance(getContext()) - .sendBroadcast(intent); - } - - private void delete(String id) { - Call call = mastodonApi.deleteStatus(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - } - }); - callList.add(call); - } - protected void more(final Status status, View view, final int position) { final String id = status.getActionableId(); final String accountId = status.getActionableStatus().getAccount().getId(); @@ -208,60 +134,56 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov } else { popup.inflate(R.menu.status_more_for_user); } - popup.setOnMenuItemClickListener( - new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.status_share_content: { - StringBuilder sb = new StringBuilder(); - sb.append(status.getAccount().getUsername()); - sb.append(" - "); - sb.append(status.getContent().toString()); + popup.setOnMenuItemClickListener(item -> { + switch (item.getItemId()) { + case R.id.status_share_content: { + StringBuilder sb = new StringBuilder(); + sb.append(status.getAccount().getUsername()); + sb.append(" - "); + sb.append(status.getContent().toString()); - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString()); - sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to))); - return true; - } - case R.id.status_share_link: { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, statusUrl); - sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_link_to))); - return true; - } - case R.id.status_copy_link: { - ClipboardManager clipboard = (ClipboardManager) - getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(null, statusUrl); - clipboard.setPrimaryClip(clip); - return true; - } - case R.id.status_mute: { - mute(accountId); - return true; - } - case R.id.status_block: { - block(accountId); - return true; - } - case R.id.status_report: { - openReportPage(accountId, accountUsename, id, content); - return true; - } - case R.id.status_delete: { - delete(id); - removeItem(position); - return true; - } - } - return false; - } - }); + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString()); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to))); + return true; + } + case R.id.status_share_link: { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, statusUrl); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_link_to))); + return true; + } + case R.id.status_copy_link: { + ClipboardManager clipboard = (ClipboardManager) + getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(null, statusUrl); + clipboard.setPrimaryClip(clip); + return true; + } + case R.id.status_mute: { + timelineCases().mute(accountId); + return true; + } + case R.id.status_block: { + timelineCases().block(accountId); + return true; + } + case R.id.status_report: { + openReportPage(accountId, accountUsename, id, content); + return true; + } + case R.id.status_delete: { + timelineCases().delete(id); + removeItem(position); + return true; + } + } + return false; + }); popup.show(); } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 331bb9d3..422fe6fd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -40,11 +40,13 @@ import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.adapter.FooterViewHolder; import com.keylesspalace.tusky.adapter.TimelineAdapter; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.interfaces.ActionButtonActivity; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.network.MastodonApi; +import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.util.CollectionUtil; import com.keylesspalace.tusky.util.Either; @@ -60,6 +62,8 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; +import javax.inject.Inject; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -67,7 +71,8 @@ import retrofit2.Response; public class TimelineFragment extends SFragment implements SwipeRefreshLayout.OnRefreshListener, StatusActionListener, - SharedPreferences.OnSharedPreferenceChangeListener { + SharedPreferences.OnSharedPreferenceChangeListener, + Injectable { private static final String TAG = "TimelineF"; // logging tag private static final String KIND_ARG = "kind"; private static final String HASHTAG_OR_ID_ARG = "hashtag_or_id"; @@ -90,6 +95,11 @@ public class TimelineFragment extends SFragment implements MIDDLE } + @Inject + TimelineCases timelineCases; + @Inject + MastodonApi mastodonApi; + private SwipeRefreshLayout swipeRefreshLayout; private TimelineAdapter adapter; private Kind kind; @@ -113,6 +123,11 @@ public class TimelineFragment extends SFragment implements private boolean alwaysShowSensitiveMedia; + @Override + protected TimelineCases timelineCases() { + return timelineCases; + } + private PairedList, StatusViewData> statuses = new PairedList<>(new Function, StatusViewData>() { @Override @@ -307,7 +322,7 @@ public class TimelineFragment extends SFragment implements @Override public void onReblog(final boolean reblog, final int position) { final Status status = statuses.get(position).getAsRight(); - super.reblogWithCallback(status, reblog, new Callback() { + timelineCases.reblogWithCallback(status, reblog, new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -342,7 +357,7 @@ public class TimelineFragment extends SFragment implements public void onFavourite(final boolean favourite, final int position) { final Status status = statuses.get(position).getAsRight(); - super.favouriteWithCallback(status, favourite, new Callback() { + timelineCases.favouriteWithCallback(status, favourite, new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index 93733bc6..24c661e6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -37,11 +37,14 @@ import android.view.ViewGroup; import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.adapter.ThreadAdapter; +import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Card; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.StatusContext; import com.keylesspalace.tusky.interfaces.StatusActionListener; +import com.keylesspalace.tusky.network.MastodonApi; +import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.receiver.TimelineReceiver; import com.keylesspalace.tusky.util.PairedList; import com.keylesspalace.tusky.util.ThemeUtils; @@ -53,14 +56,21 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; +import javax.inject.Inject; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class ViewThreadFragment extends SFragment implements - SwipeRefreshLayout.OnRefreshListener, StatusActionListener { + SwipeRefreshLayout.OnRefreshListener, StatusActionListener, Injectable { private static final String TAG = "ViewThreadFragment"; + @Inject + public TimelineCases timelineCases; + @Inject + public MastodonApi mastodonApi; + private SwipeRefreshLayout swipeRefreshLayout; private RecyclerView recyclerView; private ThreadAdapter adapter; @@ -87,6 +97,11 @@ public class ViewThreadFragment extends SFragment implements return fragment; } + @Override + protected TimelineCases timelineCases() { + return timelineCases; + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -161,7 +176,7 @@ public class ViewThreadFragment extends SFragment implements @Override public void onReblog(final boolean reblog, final int position) { final Status status = statuses.get(position); - super.reblogWithCallback(statuses.get(position), reblog, new Callback() { + timelineCases.reblogWithCallback(statuses.get(position), reblog, new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.isSuccessful()) { @@ -194,7 +209,7 @@ public class ViewThreadFragment extends SFragment implements @Override public void onFavourite(final boolean favourite, final int position) { final Status status = statuses.get(position); - super.favouriteWithCallback(statuses.get(position), favourite, new Callback() { + timelineCases.favouriteWithCallback(statuses.get(position), favourite, new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.isSuccessful()) { diff --git a/app/src/main/java/com/keylesspalace/tusky/network/AuthInterceptor.java b/app/src/main/java/com/keylesspalace/tusky/network/AuthInterceptor.java deleted file mode 100644 index 91d7c957..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/network/AuthInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.keylesspalace.tusky.network; - -import android.support.annotation.NonNull; - -import com.keylesspalace.tusky.TuskyApplication; -import com.keylesspalace.tusky.db.AccountEntity; -import com.keylesspalace.tusky.db.AccountManager; - -import java.io.IOException; - -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -/** - * Created by charlag on 31/10/17. - */ - -public final class AuthInterceptor implements Interceptor { - - AccountManager accountManager; - - public AuthInterceptor(AccountManager accountManager) { - this.accountManager = accountManager; - } - - @Override - public Response intercept(@NonNull Chain chain) throws IOException { - - Request originalRequest = chain.request(); - AccountEntity currentAccount = accountManager.getActiveAccount(); - - Request.Builder builder = originalRequest.newBuilder(); - if (currentAccount != null) { - builder.header("Authorization", String.format("Bearer %s", currentAccount.getAccessToken())); - } - Request newRequest = builder.build(); - - return chain.proceed(newRequest); - } - -} diff --git a/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java b/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java new file mode 100644 index 00000000..33f64a8a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/network/InstanceSwitchAuthInterceptor.java @@ -0,0 +1,69 @@ +/* Copyright 2018 charlag + * + * 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.network; + +import android.support.annotation.NonNull; + +import com.keylesspalace.tusky.db.AccountEntity; +import com.keylesspalace.tusky.db.AccountManager; + +import java.io.IOException; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by charlag on 31/10/17. + */ + +public final class InstanceSwitchAuthInterceptor implements Interceptor { + private AccountManager accountManager; + + public InstanceSwitchAuthInterceptor(AccountManager accountManager) { + this.accountManager = accountManager; + } + + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + + Request originalRequest = chain.request(); + AccountEntity currentAccount = accountManager.getActiveAccount(); + + 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); + } + + @NonNull + private 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 83cdc99e..351953b0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.java @@ -50,6 +50,7 @@ import retrofit2.http.Query; public interface MastodonApi { String ENDPOINT_AUTHORIZE = "/oauth/authorize"; + String DOMAIN_HEADER = "domain"; @GET("api/v1/timelines/home") Call> homeTimeline( @@ -83,7 +84,7 @@ public interface MastodonApi { @Query("limit") Integer limit); @GET("api/v1/notifications") Call> notificationsWithAuth( - @Header("Authorization") String auth); + @Header("Authorization") String auth, @Header(DOMAIN_HEADER) String domain); @POST("api/v1/notifications/clear") Call clearNotifications(); @GET("api/v1/notifications/{id}") diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt new file mode 100644 index 00000000..f670cfce --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt @@ -0,0 +1,99 @@ +/* Copyright 2018 charlag + * + * 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.network + +import android.content.Intent +import android.support.v4.content.LocalBroadcastManager +import com.keylesspalace.tusky.entity.Relationship +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.receiver.TimelineReceiver +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +/** + * Created by charlag on 3/24/18. + */ + +interface TimelineCases { + fun reblogWithCallback(status: Status, reblog: Boolean, callback: Callback) + fun favouriteWithCallback(status: Status, favourite: Boolean, callback: Callback) + fun mute(id: String) + fun block(id: String) + fun delete(id: String) +} + +class TimelineCasesImpl( + private val mastodonApi: MastodonApi, + private val broadcastManager: LocalBroadcastManager +) : TimelineCases { + override fun reblogWithCallback(status: Status, reblog: Boolean, callback: Callback) { + val id = status.actionableId + + val call = if (reblog) { + mastodonApi.reblogStatus(id) + } else { + mastodonApi.unreblogStatus(id) + } + call.enqueue(callback) + } + + override fun favouriteWithCallback(status: Status, favourite: Boolean, callback: Callback) { + val id = status.actionableId + + val call = if (favourite) { + mastodonApi.favouriteStatus(id) + } else { + mastodonApi.unfavouriteStatus(id) + } + call.enqueue(callback) + } + + override fun mute(id: String) { + val call = mastodonApi.muteAccount(id) + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) {} + + override fun onFailure(call: Call, t: Throwable) {} + }) + val intent = Intent(TimelineReceiver.Types.MUTE_ACCOUNT) + intent.putExtra("id", id) + broadcastManager.sendBroadcast(intent) + } + + override fun block(id: String) { + val call = mastodonApi.blockAccount(id) + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: retrofit2.Response) {} + + override fun onFailure(call: Call, t: Throwable) {} + }) + val intent = Intent(TimelineReceiver.Types.BLOCK_ACCOUNT) + intent.putExtra("id", id) + broadcastManager.sendBroadcast(intent) + } + + override fun delete(id: String) { + val call = mastodonApi.deleteStatus(id) + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: retrofit2.Response) {} + + override fun onFailure(call: Call, t: Throwable) {} + }) + } + +} \ No newline at end of file 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 eeafffde..36178aff 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java @@ -47,6 +47,7 @@ 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 @@ -91,6 +92,10 @@ 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); } diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt index 43ad2d3a..b4aa6abd 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt @@ -1,3 +1,19 @@ +/* Copyright 2018 charlag + * + * 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.widget.EditText @@ -51,12 +67,13 @@ class ComposeActivityTest { fun before() { val controller = Robolectric.buildActivity(ComposeActivity::class.java) activity = controller.get() - application = activity.application as FakeTuskyApplication - serviceLocator = Mockito.mock(TuskyApplication.ServiceLocator::class.java) - application.locator = serviceLocator accountManagerMock = Mockito.mock(AccountManager::class.java) + serviceLocator = Mockito.mock(TuskyApplication.ServiceLocator::class.java) `when`(serviceLocator.get(AccountManager::class.java)).thenReturn(accountManagerMock) `when`(accountManagerMock.activeAccount).thenReturn(account) + activity.accountManager = accountManagerMock + application = activity.application as FakeTuskyApplication + application.locator = serviceLocator controller.create().start() } diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755