Add Dagger (#554)

* Add Dagger DI

* Preemptively fix tests

* Add missing licenses

* DI fixes

* ci fixes
This commit is contained in:
Ivan Kupalov 2018-03-27 20:47:00 +03:00 committed by Konrad Pozniak
commit a5cffe0fea
41 changed files with 1040 additions and 415 deletions

View file

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

View file

@ -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?,

View file

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

View file

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

View file

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

View file

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

View file

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