Add Dagger (#554)
* Add Dagger DI * Preemptively fix tests * Add missing licenses * DI fixes * ci fixes
This commit is contained in:
parent
720f7c6a0c
commit
a5cffe0fea
41 changed files with 1040 additions and 415 deletions
|
@ -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', {
|
||||
|
|
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
|
@ -64,3 +64,5 @@
|
|||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
||||
}
|
||||
|
||||
-dontwarn com.google.errorprone.annotations.*
|
|
@ -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<List<Account>> callback = new Callback<List<Account>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<Account>> call, Response<List<Account>> response) {
|
||||
public void onResponse(@NonNull Call<List<Account>> call, @NonNull Response<List<Account>> response) {
|
||||
if (response.isSuccessful()) {
|
||||
List<Account> 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<List<Account>> call, Throwable t) {
|
||||
public void onFailure(@NonNull Call<List<Account>> call, @NonNull Throwable t) {
|
||||
onSearchFailed();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<Fragment> 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<Fragment> supportFragmentInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Fragment> 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<Fragment> supportFragmentInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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<Uri, Void, Boolean>() {
|
||||
private class ResizeImageTask(private val contentResolver: ContentResolver,
|
||||
private val resizeWidth: Int,
|
||||
private val resizeHeight: Int,
|
||||
private val cacheFile: File,
|
||||
private val listener: Listener) : AsyncTask<Uri, Void, Boolean>() {
|
||||
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)
|
||||
|
|
|
@ -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<Fragment> 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<Fragment> supportFragmentInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<Fragment> 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<Fragment> supportFragmentInjector() {
|
||||
return fragmentInjector;
|
||||
}
|
||||
}
|
|
@ -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<Fragment>
|
||||
|
||||
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<Fragment> {
|
||||
return dispatchingAndroidInjector
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AccountEntity> 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<List<Notification>> 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<Notification> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ResponseBody> callback = new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> 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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Activity> 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> T get(Class<T> 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<Activity> activityInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
|
||||
public interface ServiceLocator {
|
||||
<T> T get(Class<T> clazz);
|
||||
}
|
||||
|
|
|
@ -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<Fragment> 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<Fragment> supportFragmentInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Fragment> 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<Fragment> supportFragmentInjector() {
|
||||
return dispatchingAndroidInjector;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AccountEntity> = 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<AccountEntity> {
|
||||
accounts.sortWith (Comparator { l, r ->
|
||||
accounts.sortWith(Comparator { l, r ->
|
||||
when {
|
||||
l.isActive && !r.isActive -> -1
|
||||
r.isActive && !l.isActive -> 1
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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
|
||||
}
|
46
app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt
Normal file
46
app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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)
|
||||
}
|
80
app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt
Normal file
80
app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
68
app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
Normal file
68
app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
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
|
||||
}
|
23
app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt
Normal file
23
app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
package com.keylesspalace.tusky.di
|
||||
|
||||
/**
|
||||
* Created by charlag on 3/24/18.
|
||||
*/
|
||||
|
||||
interface Injectable
|
111
app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
Normal file
111
app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
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<Class<*>, 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<Interceptor>,
|
||||
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<Converter.Factory>): 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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<List<Status>>? = null
|
||||
private lateinit var api: MastodonApi
|
||||
private val statuses = mutableListOf<Status>()
|
||||
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?,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Either<Placeholder, Notification>, NotificationViewData> notifications
|
||||
= new PairedList<>(new Function<Either<Placeholder, Notification>, 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<Status>() {
|
||||
timelineCases.reblogWithCallback(status, reblog, new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Status> call, @NonNull retrofit2.Response<Status> 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<Status>() {
|
||||
timelineCases.favouriteWithCallback(status, favourite, new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Status> call, @NonNull retrofit2.Response<Status> response) {
|
||||
if (response.isSuccessful()) {
|
||||
|
|
|
@ -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<Status> callback) {
|
||||
String id = status.getActionableId();
|
||||
|
||||
Call<Status> 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<Status> callback) {
|
||||
String id = status.getActionableId();
|
||||
|
||||
Call<Status> 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<Relationship> call = mastodonApi.muteAccount(id);
|
||||
call.enqueue(new Callback<Relationship>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Relationship> 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<Relationship> call = mastodonApi.blockAccount(id);
|
||||
call.enqueue(new Callback<Relationship>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull retrofit2.Response<Relationship> response) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Relationship> 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<ResponseBody> call = mastodonApi.deleteStatus(id);
|
||||
call.enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull retrofit2.Response<ResponseBody> response) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ResponseBody> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Either<Placeholder, Status>, StatusViewData> statuses =
|
||||
new PairedList<>(new Function<Either<Placeholder, Status>, 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<Status>() {
|
||||
timelineCases.reblogWithCallback(status, reblog, new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> 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<Status>() {
|
||||
timelineCases.favouriteWithCallback(status, favourite, new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
||||
|
||||
|
|
|
@ -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<Status>() {
|
||||
timelineCases.reblogWithCallback(statuses.get(position), reblog, new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> 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<Status>() {
|
||||
timelineCases.favouriteWithCallback(statuses.get(position), favourite, new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
||||
if (response.isSuccessful()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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<List<Status>> homeTimeline(
|
||||
|
@ -83,7 +84,7 @@ public interface MastodonApi {
|
|||
@Query("limit") Integer limit);
|
||||
@GET("api/v1/notifications")
|
||||
Call<List<Notification>> notificationsWithAuth(
|
||||
@Header("Authorization") String auth);
|
||||
@Header("Authorization") String auth, @Header(DOMAIN_HEADER) String domain);
|
||||
@POST("api/v1/notifications/clear")
|
||||
Call<ResponseBody> clearNotifications();
|
||||
@GET("api/v1/notifications/{id}")
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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<Status>)
|
||||
fun favouriteWithCallback(status: Status, favourite: Boolean, callback: Callback<Status>)
|
||||
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<Status>) {
|
||||
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<Status>) {
|
||||
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<Relationship> {
|
||||
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {}
|
||||
|
||||
override fun onFailure(call: Call<Relationship>, 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<Relationship> {
|
||||
override fun onResponse(call: Call<Relationship>, response: retrofit2.Response<Relationship>) {}
|
||||
|
||||
override fun onFailure(call: Call<Relationship>, 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<ResponseBody> {
|
||||
override fun onResponse(call: Call<ResponseBody>, response: retrofit2.Response<ResponseBody>) {}
|
||||
|
||||
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
0
gradlew
vendored
Executable file → Normal file
0
gradlew
vendored
Executable file → Normal file
0
gradlew.bat
vendored
Normal file → Executable file
0
gradlew.bat
vendored
Normal file → Executable file
Loading…
Reference in a new issue