get rid of BaseFragment by using RxJava instead of Retrofit Calls (#2055)
* get rid of BaseFragment by using RxJava instead of Retrofit Calls * fix tests
This commit is contained in:
parent
2d2b79aa47
commit
886ff2f06b
13 changed files with 170 additions and 309 deletions
|
@ -57,7 +57,7 @@ class DraftsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getToot(tootId: String): Single<Status> {
|
fun getToot(tootId: String): Single<Status> {
|
||||||
return api.statusSingle(tootId)
|
return api.status(tootId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -14,7 +15,6 @@ import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter
|
import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter
|
||||||
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
|
import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.fragment.BaseFragment
|
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
|
@ -30,7 +30,7 @@ import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InstanceListFragment: BaseFragment(), Injectable, InstanceActionListener {
|
class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
|
@ -39,10 +39,6 @@ class InstanceListFragment: BaseFragment(), Injectable, InstanceActionListener {
|
||||||
private var adapter = DomainMutesAdapter(this)
|
private var adapter = DomainMutesAdapter(this)
|
||||||
private lateinit var scrollListener: EndlessOnScrollListener
|
private lateinit var scrollListener: EndlessOnScrollListener
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
return inflater.inflate(R.layout.fragment_instance_list, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,8 @@ package com.keylesspalace.tusky.fragment
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -45,14 +44,12 @@ import com.uber.autodispose.autoDispose
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_account_list.*
|
import kotlinx.android.synthetic.main.fragment_account_list.*
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountActionListener, Injectable {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
@ -71,10 +68,6 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
||||||
id = arguments?.getString(ARG_ID)
|
id = arguments?.getString(ARG_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
return inflater.inflate(R.layout.fragment_account_list, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -202,27 +195,23 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
||||||
override fun onRespondToFollowRequest(accept: Boolean, accountId: String,
|
override fun onRespondToFollowRequest(accept: Boolean, accountId: String,
|
||||||
position: Int) {
|
position: Int) {
|
||||||
|
|
||||||
val callback = object : Callback<Relationship> {
|
if (accept) {
|
||||||
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
onRespondToFollowRequestSuccess(position)
|
|
||||||
} else {
|
|
||||||
onRespondToFollowRequestFailure(accept, accountId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
|
||||||
onRespondToFollowRequestFailure(accept, accountId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val call = if (accept) {
|
|
||||||
api.authorizeFollowRequest(accountId)
|
api.authorizeFollowRequest(accountId)
|
||||||
} else {
|
} else {
|
||||||
api.rejectFollowRequest(accountId)
|
api.rejectFollowRequest(accountId)
|
||||||
}
|
}.observeOn(AndroidSchedulers.mainThread())
|
||||||
callList.add(call)
|
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
call.enqueue(callback)
|
.subscribe({
|
||||||
|
onRespondToFollowRequestSuccess(position)
|
||||||
|
}, { throwable ->
|
||||||
|
val verb = if (accept) {
|
||||||
|
"accept"
|
||||||
|
} else {
|
||||||
|
"reject"
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Failed to $verb account id $accountId.", throwable)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRespondToFollowRequestSuccess(position: Int) {
|
private fun onRespondToFollowRequestSuccess(position: Int) {
|
||||||
|
@ -230,15 +219,6 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
||||||
followRequestsAdapter.removeItem(position)
|
followRequestsAdapter.removeItem(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRespondToFollowRequestFailure(accept: Boolean, accountId: String) {
|
|
||||||
val verb = if (accept) {
|
|
||||||
"accept"
|
|
||||||
} else {
|
|
||||||
"reject"
|
|
||||||
}
|
|
||||||
Log.e(TAG, "Failed to $verb account id $accountId.")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFetchCallByListType(fromId: String?): Single<Response<List<Account>>> {
|
private fun getFetchCallByListType(fromId: String?): Single<Response<List<Account>>> {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
Type.FOLLOWS -> {
|
Type.FOLLOWS -> {
|
||||||
|
|
|
@ -18,12 +18,13 @@ package com.keylesspalace.tusky.fragment
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
@ -40,9 +41,11 @@ import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
import com.keylesspalace.tusky.view.SquareImageView
|
import com.keylesspalace.tusky.view.SquareImageView
|
||||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||||
|
import com.uber.autodispose.android.lifecycle.autoDispose
|
||||||
|
import io.reactivex.SingleObserver
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.android.synthetic.main.fragment_timeline.*
|
import kotlinx.android.synthetic.main.fragment_timeline.*
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -54,7 +57,7 @@ import javax.inject.Inject
|
||||||
* Fragment with multiple columns of media previews for the specified account.
|
* Fragment with multiple columns of media previews for the specified account.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFragment, Injectable {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
||||||
|
@ -78,14 +81,13 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
private val adapter = MediaGridAdapter()
|
private val adapter = MediaGridAdapter()
|
||||||
private var currentCall: Call<List<Status>>? = null
|
|
||||||
private val statuses = mutableListOf<Status>()
|
private val statuses = mutableListOf<Status>()
|
||||||
private var fetchingStatus = FetchingStatus.NOT_FETCHING
|
private var fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
|
|
||||||
private lateinit var accountId: String
|
private lateinit var accountId: String
|
||||||
|
|
||||||
private val callback = object : Callback<List<Status>> {
|
private val callback = object : SingleObserver<Response<List<Status>>> {
|
||||||
override fun onFailure(call: Call<List<Status>>?, t: Throwable?) {
|
override fun onError(t: Throwable) {
|
||||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
|
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
|
@ -107,7 +109,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
Log.d(TAG, "Failed to fetch account media", t)
|
Log.d(TAG, "Failed to fetch account media", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call<List<Status>>, response: Response<List<Status>>) {
|
override fun onSuccess(response: Response<List<Status>>) {
|
||||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
swipeRefreshLayout.isRefreshing = false
|
swipeRefreshLayout.isRefreshing = false
|
||||||
|
@ -128,22 +130,23 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
|
|
||||||
if (statuses.isEmpty()) {
|
if (statuses.isEmpty()) {
|
||||||
statusView.show()
|
statusView.show()
|
||||||
statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
|
statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty)
|
||||||
null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSubscribe(d: Disposable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val bottomCallback = object : Callback<List<Status>> {
|
private val bottomCallback = object : SingleObserver<Response<List<Status>>> {
|
||||||
override fun onFailure(call: Call<List<Status>>?, t: Throwable?) {
|
override fun onError(t: Throwable) {
|
||||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
|
|
||||||
Log.d(TAG, "Failed to fetch account media", t)
|
Log.d(TAG, "Failed to fetch account media", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call<List<Status>>, response: Response<List<Status>>) {
|
override fun onSuccess(response: Response<List<Status>>) {
|
||||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||||
val body = response.body()
|
val body = response.body()
|
||||||
body?.let { fetched ->
|
body?.let { fetched ->
|
||||||
|
@ -160,6 +163,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSubscribe(d: Disposable) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -167,10 +171,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,true) == true
|
isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,true) == true
|
||||||
accountId = arguments?.getString(ACCOUNT_ID_ARG)!!
|
accountId = arguments?.getString(ACCOUNT_ID_ARG)!!
|
||||||
}
|
}
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View? {
|
|
||||||
return inflater.inflate(R.layout.fragment_timeline, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -202,8 +202,10 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
statuses.lastOrNull()?.let { (id) ->
|
statuses.lastOrNull()?.let { (id) ->
|
||||||
Log.d(TAG, "Requesting statuses with max_id: ${id}, (bottom)")
|
Log.d(TAG, "Requesting statuses with max_id: ${id}, (bottom)")
|
||||||
fetchingStatus = FetchingStatus.FETCHING_BOTTOM
|
fetchingStatus = FetchingStatus.FETCHING_BOTTOM
|
||||||
currentCall = api.accountStatuses(accountId, id, null, null, null, true, null)
|
api.accountStatuses(accountId, id, null, null, null, true, null)
|
||||||
currentCall?.enqueue(bottomCallback)
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY)
|
||||||
|
.subscribe(bottomCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,14 +218,15 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
private fun refresh() {
|
private fun refresh() {
|
||||||
statusView.hide()
|
statusView.hide()
|
||||||
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return
|
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return
|
||||||
currentCall = if (statuses.isEmpty()) {
|
if (statuses.isEmpty()) {
|
||||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||||
api.accountStatuses(accountId, null, null, null, null, true, null)
|
api.accountStatuses(accountId, null, null, null, null, true, null)
|
||||||
} else {
|
} else {
|
||||||
fetchingStatus = FetchingStatus.REFRESHING
|
fetchingStatus = FetchingStatus.REFRESHING
|
||||||
api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null)
|
api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null)
|
||||||
}
|
}.observeOn(AndroidSchedulers.mainThread())
|
||||||
currentCall?.enqueue(callback)
|
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
||||||
|
.subscribe(callback)
|
||||||
|
|
||||||
if (!isSwipeToRefreshEnabled)
|
if (!isSwipeToRefreshEnabled)
|
||||||
topProgressBar?.show()
|
topProgressBar?.show()
|
||||||
|
@ -235,8 +238,10 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
}
|
}
|
||||||
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
||||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||||
currentCall = api.accountStatuses(accountId, null, null, null, null, true, null)
|
api.accountStatuses(accountId, null, null, null, null, true, null)
|
||||||
currentCall?.enqueue(callback)
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY)
|
||||||
|
.subscribe(callback)
|
||||||
}
|
}
|
||||||
else if (needToRefresh)
|
else if (needToRefresh)
|
||||||
refresh()
|
refresh()
|
||||||
|
@ -339,5 +344,4 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||||
needToRefresh = true
|
needToRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
/* Copyright 2017 Andrew Dawson
|
|
||||||
*
|
|
||||||
* This file is a part of Tusky.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
||||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
||||||
* Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.fragment;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import retrofit2.Call;
|
|
||||||
|
|
||||||
public class BaseFragment extends Fragment {
|
|
||||||
protected List<Call> callList;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
callList = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
for (Call call : callList) {
|
|
||||||
call.cancel();
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -102,13 +102,11 @@ import at.connyduck.sparkbutton.helpers.Utils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlin.collections.CollectionsKt;
|
import kotlin.collections.CollectionsKt;
|
||||||
import kotlin.jvm.functions.Function1;
|
import kotlin.jvm.functions.Function1;
|
||||||
import okhttp3.ResponseBody;
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
|
import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
|
||||||
import static com.uber.autodispose.AutoDispose.autoDisposable;
|
import static com.uber.autodispose.AutoDispose.autoDisposable;
|
||||||
|
@ -125,8 +123,9 @@ public class NotificationsFragment extends SFragment implements
|
||||||
private static final int LOAD_AT_ONCE = 30;
|
private static final int LOAD_AT_ONCE = 30;
|
||||||
private int maxPlaceholderId = 0;
|
private int maxPlaceholderId = 0;
|
||||||
|
|
||||||
|
private final Set<Notification.Type> notificationFilter = new HashSet<>();
|
||||||
|
|
||||||
private Set<Notification.Type> notificationFilter = new HashSet<>();
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
|
||||||
private enum FetchEnd {
|
private enum FetchEnd {
|
||||||
TOP,
|
TOP,
|
||||||
|
@ -685,32 +684,21 @@ public class NotificationsFragment extends SFragment implements
|
||||||
updateAdapter();
|
updateAdapter();
|
||||||
|
|
||||||
//Execute clear notifications request
|
//Execute clear notifications request
|
||||||
Call<ResponseBody> call = mastodonApi.clearNotifications();
|
mastodonApi.clearNotifications()
|
||||||
call.enqueue(new Callback<ResponseBody>() {
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
@Override
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
|
.subscribe(
|
||||||
if (isAdded()) {
|
response -> {
|
||||||
if (!response.isSuccessful()) {
|
// nothing to do
|
||||||
//Reload notifications on failure
|
},
|
||||||
fullyRefreshWithProgressBar(true);
|
throwable -> {
|
||||||
}
|
//Reload notifications on failure
|
||||||
}
|
fullyRefreshWithProgressBar(true);
|
||||||
}
|
});
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
|
|
||||||
//Reload notifications on failure
|
|
||||||
fullyRefreshWithProgressBar(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callList.add(call);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetNotificationsLoad() {
|
private void resetNotificationsLoad() {
|
||||||
for (Call callItem : callList) {
|
disposables.clear();
|
||||||
callItem.cancel();
|
|
||||||
}
|
|
||||||
callList.clear();
|
|
||||||
bottomLoading = false;
|
bottomLoading = false;
|
||||||
topLoading = false;
|
topLoading = false;
|
||||||
|
|
||||||
|
@ -840,8 +828,8 @@ public class NotificationsFragment extends SFragment implements
|
||||||
@Override
|
@Override
|
||||||
public void onRespondToFollowRequest(boolean accept, String id, int position) {
|
public void onRespondToFollowRequest(boolean accept, String id, int position) {
|
||||||
Single<Relationship> request = accept ?
|
Single<Relationship> request = accept ?
|
||||||
mastodonApi.authorizeFollowRequestObservable(id) :
|
mastodonApi.authorizeFollowRequest(id) :
|
||||||
mastodonApi.rejectFollowRequestObservable(id);
|
mastodonApi.rejectFollowRequest(id);
|
||||||
request.observeOn(AndroidSchedulers.mainThread())
|
request.observeOn(AndroidSchedulers.mainThread())
|
||||||
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
@ -959,27 +947,20 @@ public class NotificationsFragment extends SFragment implements
|
||||||
bottomLoading = true;
|
bottomLoading = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Call<List<Notification>> call = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE, showNotificationsFilter ? notificationFilter : null);
|
Disposable notificationCall = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE, showNotificationsFilter ? notificationFilter : null)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
call.enqueue(new Callback<List<Notification>>() {
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
@Override
|
.subscribe(
|
||||||
public void onResponse(@NonNull Call<List<Notification>> call,
|
response -> {
|
||||||
@NonNull Response<List<Notification>> response) {
|
if (response.isSuccessful()) {
|
||||||
if (response.isSuccessful()) {
|
String linkHeader = response.headers().get("Link");
|
||||||
String linkHeader = response.headers().get("Link");
|
onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos);
|
||||||
onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos);
|
} else {
|
||||||
} else {
|
onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos);
|
||||||
onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos);
|
}
|
||||||
}
|
},
|
||||||
}
|
throwable -> onFetchNotificationsFailure(throwable, fetchEnd, pos));
|
||||||
|
disposables.add(notificationCall);
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<List<Notification>> call, @NonNull Throwable t) {
|
|
||||||
if (!call.isCanceled())
|
|
||||||
onFetchNotificationsFailure((Exception) t, fetchEnd, pos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callList.add(call);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFetchNotificationsSuccess(List<Notification> notifications, String linkHeader,
|
private void onFetchNotificationsSuccess(List<Notification> notifications, String linkHeader,
|
||||||
|
@ -1038,7 +1019,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) {
|
private void onFetchNotificationsFailure(Throwable throwable, FetchEnd fetchEnd, int position) {
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) {
|
if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) {
|
||||||
Placeholder placeholder = notifications.get(position).asLeft();
|
Placeholder placeholder = notifications.get(position).asLeft();
|
||||||
|
@ -1050,7 +1031,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
this.statusView.setVisibility(View.VISIBLE);
|
this.statusView.setVisibility(View.VISIBLE);
|
||||||
swipeRefreshLayout.setEnabled(false);
|
swipeRefreshLayout.setEnabled(false);
|
||||||
this.showingError = true;
|
this.showingError = true;
|
||||||
if (exception instanceof IOException) {
|
if (throwable instanceof IOException) {
|
||||||
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
||||||
this.progressBar.setVisibility(View.VISIBLE);
|
this.progressBar.setVisibility(View.VISIBLE);
|
||||||
this.onRefresh();
|
this.onRefresh();
|
||||||
|
@ -1065,7 +1046,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
updateFilterVisibility();
|
updateFilterVisibility();
|
||||||
}
|
}
|
||||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
Log.e(TAG, "Fetch failure: " + throwable.getMessage());
|
||||||
|
|
||||||
if (fetchEnd == FetchEnd.TOP) {
|
if (fetchEnd == FetchEnd.TOP) {
|
||||||
topLoading = false;
|
topLoading = false;
|
||||||
|
|
|
@ -20,7 +20,6 @@ import android.app.DownloadManager;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -30,8 +29,6 @@ import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -41,14 +38,14 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
import androidx.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import com.keylesspalace.tusky.BaseActivity;
|
import com.keylesspalace.tusky.BaseActivity;
|
||||||
import com.keylesspalace.tusky.BottomSheetActivity;
|
import com.keylesspalace.tusky.BottomSheetActivity;
|
||||||
import com.keylesspalace.tusky.MainActivity;
|
import com.keylesspalace.tusky.MainActivity;
|
||||||
import com.keylesspalace.tusky.R;
|
|
||||||
import com.keylesspalace.tusky.PostLookupFallbackBehavior;
|
import com.keylesspalace.tusky.PostLookupFallbackBehavior;
|
||||||
|
import com.keylesspalace.tusky.R;
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity;
|
import com.keylesspalace.tusky.ViewMediaActivity;
|
||||||
import com.keylesspalace.tusky.ViewTagActivity;
|
import com.keylesspalace.tusky.ViewTagActivity;
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity;
|
import com.keylesspalace.tusky.components.compose.ComposeActivity;
|
||||||
|
@ -76,9 +73,8 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import kotlin.Unit;
|
|
||||||
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import kotlin.Unit;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
@ -92,7 +88,7 @@ import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvid
|
||||||
* adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also
|
* adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also
|
||||||
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
||||||
* up what needs to be where. */
|
* up what needs to be where. */
|
||||||
public abstract class SFragment extends BaseFragment implements Injectable {
|
public abstract class SFragment extends Fragment implements Injectable {
|
||||||
|
|
||||||
protected abstract void removeItem(int position);
|
protected abstract void removeItem(int position);
|
||||||
|
|
||||||
|
@ -103,7 +99,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
private static List<Filter> filters;
|
private static List<Filter> filters;
|
||||||
private boolean filterRemoveRegex;
|
private boolean filterRemoveRegex;
|
||||||
private Matcher filterRemoveRegexMatcher;
|
private Matcher filterRemoveRegexMatcher;
|
||||||
private static Matcher alphanumeric = Pattern.compile("^\\w+$").matcher("");
|
private static final Matcher alphanumeric = Pattern.compile("^\\w+$").matcher("");
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MastodonApi mastodonApi;
|
public MastodonApi mastodonApi;
|
||||||
|
|
|
@ -101,12 +101,11 @@ import javax.inject.Inject;
|
||||||
|
|
||||||
import at.connyduck.sparkbutton.helpers.Utils;
|
import at.connyduck.sparkbutton.helpers.Utils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlin.collections.CollectionsKt;
|
import kotlin.collections.CollectionsKt;
|
||||||
import kotlin.jvm.functions.Function1;
|
import kotlin.jvm.functions.Function1;
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
import static com.uber.autodispose.AutoDispose.autoDisposable;
|
import static com.uber.autodispose.AutoDispose.autoDisposable;
|
||||||
|
@ -1004,7 +1003,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Call<List<Status>> getFetchCallByTimelineType(String fromId, String uptoId) {
|
private Single<Response<List<Status>>> getFetchCallByTimelineType(String fromId, String uptoId) {
|
||||||
MastodonApi api = mastodonApi;
|
MastodonApi api = mastodonApi;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
default:
|
default:
|
||||||
|
@ -1051,37 +1050,31 @@ public class TimelineFragment extends SFragment implements
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(result) -> onFetchTimelineSuccess(result, fetchEnd, pos),
|
result -> onFetchTimelineSuccess(result, fetchEnd, pos),
|
||||||
(err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos)
|
err -> onFetchTimelineFailure(err, fetchEnd, pos)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Callback<List<Status>> callback = new Callback<List<Status>>() {
|
getFetchCallByTimelineType(maxId, sinceId)
|
||||||
@Override
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
public void onResponse(@NonNull Call<List<Status>> call, @NonNull Response<List<Status>> response) {
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
if (response.isSuccessful()) {
|
.subscribe(
|
||||||
@Nullable
|
response -> {
|
||||||
String newNextId = extractNextId(response);
|
if (response.isSuccessful()) {
|
||||||
if (newNextId != null) {
|
@Nullable
|
||||||
// when we reach the bottom of the list, we won't have a new link. If
|
String newNextId = extractNextId(response);
|
||||||
// we blindly write `null` here we will start loading from the top
|
if (newNextId != null) {
|
||||||
// again.
|
// when we reach the bottom of the list, we won't have a new link. If
|
||||||
nextId = newNextId;
|
// we blindly write `null` here we will start loading from the top
|
||||||
}
|
// again.
|
||||||
onFetchTimelineSuccess(liftStatusList(response.body()), fetchEnd, pos);
|
nextId = newNextId;
|
||||||
} else {
|
}
|
||||||
onFetchTimelineFailure(new Exception(response.message()), fetchEnd, pos);
|
onFetchTimelineSuccess(liftStatusList(response.body()), fetchEnd, pos);
|
||||||
}
|
} else {
|
||||||
}
|
onFetchTimelineFailure(new Exception(response.message()), fetchEnd, pos);
|
||||||
|
}
|
||||||
@Override
|
},
|
||||||
public void onFailure(@NonNull Call<List<Status>> call, @NonNull Throwable t) {
|
err -> onFetchTimelineFailure(err, fetchEnd, pos)
|
||||||
onFetchTimelineFailure((Exception) t, fetchEnd, pos);
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Call<List<Status>> listCall = getFetchCallByTimelineType(maxId, sinceId);
|
|
||||||
callList.add(listCall);
|
|
||||||
listCall.enqueue(callback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1158,7 +1151,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) {
|
private void onFetchTimelineFailure(Throwable throwable, FetchEnd fetchEnd, int position) {
|
||||||
if (isAdded()) {
|
if (isAdded()) {
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
topProgressBar.hide();
|
topProgressBar.hide();
|
||||||
|
@ -1177,7 +1170,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
} else if (this.statuses.isEmpty()) {
|
} else if (this.statuses.isEmpty()) {
|
||||||
swipeRefreshLayout.setEnabled(false);
|
swipeRefreshLayout.setEnabled(false);
|
||||||
this.statusView.setVisibility(View.VISIBLE);
|
this.statusView.setVisibility(View.VISIBLE);
|
||||||
if (exception instanceof IOException) {
|
if (throwable instanceof IOException) {
|
||||||
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
||||||
this.progressBar.setVisibility(View.VISIBLE);
|
this.progressBar.setVisibility(View.VISIBLE);
|
||||||
this.onRefresh();
|
this.onRefresh();
|
||||||
|
@ -1192,7 +1185,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.e(TAG, "Fetch Failure: " + exception.getMessage());
|
Log.e(TAG, "Fetch Failure: " + throwable.getMessage());
|
||||||
updateBottomLoadingState(fetchEnd);
|
updateBottomLoadingState(fetchEnd);
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,11 @@ package com.keylesspalace.tusky.fragment
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
|
|
||||||
abstract class ViewMediaFragment : BaseFragment() {
|
abstract class ViewMediaFragment : Fragment() {
|
||||||
private var toolbarVisibiltyDisposable: Function0<Boolean>? = null
|
private var toolbarVisibiltyDisposable: Function0<Boolean>? = null
|
||||||
|
|
||||||
abstract fun setupMediaView(
|
abstract fun setupMediaView(
|
||||||
|
|
|
@ -55,7 +55,6 @@ import com.keylesspalace.tusky.di.Injectable;
|
||||||
import com.keylesspalace.tusky.entity.Filter;
|
import com.keylesspalace.tusky.entity.Filter;
|
||||||
import com.keylesspalace.tusky.entity.Poll;
|
import com.keylesspalace.tusky.entity.Poll;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.entity.StatusContext;
|
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||||
import com.keylesspalace.tusky.network.MastodonApi;
|
import com.keylesspalace.tusky.network.MastodonApi;
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys;
|
import com.keylesspalace.tusky.settings.PrefKeys;
|
||||||
|
@ -75,9 +74,6 @@ import java.util.Locale;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
import static com.uber.autodispose.AutoDispose.autoDisposable;
|
import static com.uber.autodispose.AutoDispose.autoDisposable;
|
||||||
import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
|
import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from;
|
||||||
|
@ -463,49 +459,32 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendStatusRequest(final String id) {
|
private void sendStatusRequest(final String id) {
|
||||||
Call<Status> call = mastodonApi.status(id);
|
mastodonApi.status(id)
|
||||||
call.enqueue(new Callback<Status>() {
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
@Override
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
.subscribe(
|
||||||
if (response.isSuccessful()) {
|
status -> {
|
||||||
int position = setStatus(response.body());
|
int position = setStatus(status);
|
||||||
recyclerView.scrollToPosition(position);
|
recyclerView.scrollToPosition(position);
|
||||||
} else {
|
},
|
||||||
onThreadRequestFailure(id);
|
throwable -> onThreadRequestFailure(id, throwable)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Status> call, @NonNull Throwable t) {
|
|
||||||
onThreadRequestFailure(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callList.add(call);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendThreadRequest(final String id) {
|
private void sendThreadRequest(final String id) {
|
||||||
Call<StatusContext> call = mastodonApi.statusContext(id);
|
mastodonApi.statusContext(id)
|
||||||
call.enqueue(new Callback<StatusContext>() {
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
@Override
|
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||||
public void onResponse(@NonNull Call<StatusContext> call, @NonNull Response<StatusContext> response) {
|
.subscribe(
|
||||||
StatusContext context = response.body();
|
context -> {
|
||||||
if (response.isSuccessful() && context != null) {
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
setContext(context.getAncestors(), context.getDescendants());
|
||||||
setContext(context.getAncestors(), context.getDescendants());
|
},
|
||||||
} else {
|
throwable -> onThreadRequestFailure(id, throwable)
|
||||||
onThreadRequestFailure(id);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<StatusContext> call, @NonNull Throwable t) {
|
|
||||||
onThreadRequestFailure(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callList.add(call);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onThreadRequestFailure(final String id) {
|
private void onThreadRequestFailure(final String id, final Throwable throwable) {
|
||||||
View view = getView();
|
View view = getView();
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
|
@ -516,7 +495,7 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Couldn't display thread fetch error message");
|
Log.e(TAG, "Network request failed", throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,14 +56,7 @@ interface MastodonApi {
|
||||||
@Query("max_id") maxId: String?,
|
@Query("max_id") maxId: String?,
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?
|
@Query("limit") limit: Int?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/timelines/home")
|
|
||||||
fun homeTimelineSingle(
|
|
||||||
@Query("max_id") maxId: String?,
|
|
||||||
@Query("since_id") sinceId: String?,
|
|
||||||
@Query("limit") limit: Int?
|
|
||||||
): Single<List<Status>>
|
|
||||||
|
|
||||||
@GET("api/v1/timelines/public")
|
@GET("api/v1/timelines/public")
|
||||||
fun publicTimeline(
|
fun publicTimeline(
|
||||||
|
@ -71,7 +64,7 @@ interface MastodonApi {
|
||||||
@Query("max_id") maxId: String?,
|
@Query("max_id") maxId: String?,
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?
|
@Query("limit") limit: Int?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/timelines/tag/{hashtag}")
|
@GET("api/v1/timelines/tag/{hashtag}")
|
||||||
fun hashtagTimeline(
|
fun hashtagTimeline(
|
||||||
|
@ -81,7 +74,7 @@ interface MastodonApi {
|
||||||
@Query("max_id") maxId: String?,
|
@Query("max_id") maxId: String?,
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?
|
@Query("limit") limit: Int?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/timelines/list/{listId}")
|
@GET("api/v1/timelines/list/{listId}")
|
||||||
fun listTimeline(
|
fun listTimeline(
|
||||||
|
@ -89,7 +82,7 @@ interface MastodonApi {
|
||||||
@Query("max_id") maxId: String?,
|
@Query("max_id") maxId: String?,
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?
|
@Query("limit") limit: Int?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/notifications")
|
@GET("api/v1/notifications")
|
||||||
fun notifications(
|
fun notifications(
|
||||||
|
@ -97,7 +90,7 @@ interface MastodonApi {
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?,
|
@Query("limit") limit: Int?,
|
||||||
@Query("exclude_types[]") excludes: Set<Notification.Type>?
|
@Query("exclude_types[]") excludes: Set<Notification.Type>?
|
||||||
): Call<List<Notification>>
|
): Single<Response<List<Notification>>>
|
||||||
|
|
||||||
@GET("api/v1/markers")
|
@GET("api/v1/markers")
|
||||||
fun markersWithAuth(
|
fun markersWithAuth(
|
||||||
|
@ -114,12 +107,7 @@ interface MastodonApi {
|
||||||
): Single<List<Notification>>
|
): Single<List<Notification>>
|
||||||
|
|
||||||
@POST("api/v1/notifications/clear")
|
@POST("api/v1/notifications/clear")
|
||||||
fun clearNotifications(): Call<ResponseBody>
|
fun clearNotifications(): Single<ResponseBody>
|
||||||
|
|
||||||
@GET("api/v1/notifications/{id}")
|
|
||||||
fun notification(
|
|
||||||
@Path("id") notificationId: String
|
|
||||||
): Call<Notification>
|
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@POST("api/v1/media")
|
@POST("api/v1/media")
|
||||||
|
@ -146,17 +134,12 @@ interface MastodonApi {
|
||||||
@GET("api/v1/statuses/{id}")
|
@GET("api/v1/statuses/{id}")
|
||||||
fun status(
|
fun status(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Call<Status>
|
|
||||||
|
|
||||||
@GET("api/v1/statuses/{id}")
|
|
||||||
fun statusSingle(
|
|
||||||
@Path("id") statusId: String
|
|
||||||
): Single<Status>
|
): Single<Status>
|
||||||
|
|
||||||
@GET("api/v1/statuses/{id}/context")
|
@GET("api/v1/statuses/{id}/context")
|
||||||
fun statusContext(
|
fun statusContext(
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): Call<StatusContext>
|
): Single<StatusContext>
|
||||||
|
|
||||||
@GET("api/v1/statuses/{id}/reblogged_by")
|
@GET("api/v1/statuses/{id}/reblogged_by")
|
||||||
fun statusRebloggedBy(
|
fun statusRebloggedBy(
|
||||||
|
@ -295,7 +278,7 @@ interface MastodonApi {
|
||||||
@Query("exclude_replies") excludeReplies: Boolean?,
|
@Query("exclude_replies") excludeReplies: Boolean?,
|
||||||
@Query("only_media") onlyMedia: Boolean?,
|
@Query("only_media") onlyMedia: Boolean?,
|
||||||
@Query("pinned") pinned: Boolean?
|
@Query("pinned") pinned: Boolean?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/accounts/{id}/followers")
|
@GET("api/v1/accounts/{id}/followers")
|
||||||
fun accountFollowers(
|
fun accountFollowers(
|
||||||
|
@ -398,14 +381,14 @@ interface MastodonApi {
|
||||||
@Query("max_id") maxId: String?,
|
@Query("max_id") maxId: String?,
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?
|
@Query("limit") limit: Int?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/bookmarks")
|
@GET("api/v1/bookmarks")
|
||||||
fun bookmarks(
|
fun bookmarks(
|
||||||
@Query("max_id") maxId: String?,
|
@Query("max_id") maxId: String?,
|
||||||
@Query("since_id") sinceId: String?,
|
@Query("since_id") sinceId: String?,
|
||||||
@Query("limit") limit: Int?
|
@Query("limit") limit: Int?
|
||||||
): Call<List<Status>>
|
): Single<Response<List<Status>>>
|
||||||
|
|
||||||
@GET("api/v1/follow_requests")
|
@GET("api/v1/follow_requests")
|
||||||
fun followRequests(
|
fun followRequests(
|
||||||
|
@ -415,20 +398,10 @@ interface MastodonApi {
|
||||||
@POST("api/v1/follow_requests/{id}/authorize")
|
@POST("api/v1/follow_requests/{id}/authorize")
|
||||||
fun authorizeFollowRequest(
|
fun authorizeFollowRequest(
|
||||||
@Path("id") accountId: String
|
@Path("id") accountId: String
|
||||||
): Call<Relationship>
|
|
||||||
|
|
||||||
@POST("api/v1/follow_requests/{id}/reject")
|
|
||||||
fun rejectFollowRequest(
|
|
||||||
@Path("id") accountId: String
|
|
||||||
): Call<Relationship>
|
|
||||||
|
|
||||||
@POST("api/v1/follow_requests/{id}/authorize")
|
|
||||||
fun authorizeFollowRequestObservable(
|
|
||||||
@Path("id") accountId: String
|
|
||||||
): Single<Relationship>
|
): Single<Relationship>
|
||||||
|
|
||||||
@POST("api/v1/follow_requests/{id}/reject")
|
@POST("api/v1/follow_requests/{id}/reject")
|
||||||
fun rejectFollowRequestObservable(
|
fun rejectFollowRequest(
|
||||||
@Path("id") accountId: String
|
@Path("id") accountId: String
|
||||||
): Single<Relationship>
|
): Single<Relationship>
|
||||||
|
|
||||||
|
|
|
@ -66,9 +66,9 @@ class TimelineRepositoryImpl(
|
||||||
sinceIdMinusOne: String?, limit: Int,
|
sinceIdMinusOne: String?, limit: Int,
|
||||||
accountId: Long, requestMode: TimelineRequestMode
|
accountId: Long, requestMode: TimelineRequestMode
|
||||||
): Single<out List<TimelineStatus>> {
|
): Single<out List<TimelineStatus>> {
|
||||||
return mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1)
|
return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)
|
||||||
.map { statuses ->
|
.map { response ->
|
||||||
this.saveStatusesToDb(accountId, statuses, maxId, sinceId)
|
this.saveStatusesToDb(accountId, response.body().orEmpty(), maxId, sinceId)
|
||||||
}
|
}
|
||||||
.flatMap { statuses ->
|
.flatMap { statuses ->
|
||||||
this.addFromDbIfNeeded(accountId, statuses, maxId, sinceId, limit, requestMode)
|
this.addFromDbIfNeeded(accountId, statuses, maxId, sinceId, limit, requestMode)
|
||||||
|
@ -85,7 +85,7 @@ class TimelineRepositoryImpl(
|
||||||
private fun addFromDbIfNeeded(accountId: Long, statuses: List<Either<Placeholder, Status>>,
|
private fun addFromDbIfNeeded(accountId: Long, statuses: List<Either<Placeholder, Status>>,
|
||||||
maxId: String?, sinceId: String?, limit: Int,
|
maxId: String?, sinceId: String?, limit: Int,
|
||||||
requestMode: TimelineRequestMode
|
requestMode: TimelineRequestMode
|
||||||
): Single<List<TimelineStatus>>? {
|
): Single<List<TimelineStatus>> {
|
||||||
return if (requestMode != NETWORK && statuses.size < 2) {
|
return if (requestMode != NETWORK && statuses.size < 2) {
|
||||||
val newMaxID = if (statuses.isEmpty()) {
|
val newMaxID = if (statuses.isEmpty()) {
|
||||||
maxId
|
maxId
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.mockito.ArgumentMatchers.*
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
import retrofit2.Response
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
@ -76,8 +77,8 @@ class TimelineRepositoryTest {
|
||||||
makeStatus("3"),
|
makeStatus("3"),
|
||||||
makeStatus("2")
|
makeStatus("2")
|
||||||
)
|
)
|
||||||
whenever(mastodonApi.homeTimelineSingle(isNull(), isNull(), anyInt()))
|
whenever(mastodonApi.homeTimeline(isNull(), isNull(), anyInt()))
|
||||||
.thenReturn(Single.just(statuses))
|
.thenReturn(Single.just(Response.success(statuses)))
|
||||||
val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.NETWORK)
|
val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.NETWORK)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
|
||||||
|
@ -107,8 +108,8 @@ class TimelineRepositoryTest {
|
||||||
)
|
)
|
||||||
val sinceId = "2"
|
val sinceId = "2"
|
||||||
val sinceIdMinusOne = "1"
|
val sinceIdMinusOne = "1"
|
||||||
whenever(mastodonApi.homeTimelineSingle(null, sinceIdMinusOne, limit + 1))
|
whenever(mastodonApi.homeTimeline(null, sinceIdMinusOne, limit + 1))
|
||||||
.thenReturn(Single.just(response))
|
.thenReturn(Single.just(Response.success(response)))
|
||||||
val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit,
|
val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit,
|
||||||
TimelineRequestMode.NETWORK)
|
TimelineRequestMode.NETWORK)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
@ -141,8 +142,8 @@ class TimelineRepositoryTest {
|
||||||
)
|
)
|
||||||
val sinceId = "2"
|
val sinceId = "2"
|
||||||
val sinceIdMinusOne = "1"
|
val sinceIdMinusOne = "1"
|
||||||
whenever(mastodonApi.homeTimelineSingle(null, sinceIdMinusOne, limit + 1))
|
whenever(mastodonApi.homeTimeline(null, sinceIdMinusOne, limit + 1))
|
||||||
.thenReturn(Single.just(response))
|
.thenReturn(Single.just(Response.success(response)))
|
||||||
val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit,
|
val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit,
|
||||||
TimelineRequestMode.NETWORK)
|
TimelineRequestMode.NETWORK)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
@ -181,8 +182,8 @@ class TimelineRepositoryTest {
|
||||||
val sinceId = "2"
|
val sinceId = "2"
|
||||||
val sinceIdMinusOne = "1"
|
val sinceIdMinusOne = "1"
|
||||||
val maxId = "3"
|
val maxId = "3"
|
||||||
whenever(mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1))
|
whenever(mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1))
|
||||||
.thenReturn(Single.just(response))
|
.thenReturn(Single.just(Response.success(response)))
|
||||||
val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit,
|
val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit,
|
||||||
TimelineRequestMode.NETWORK)
|
TimelineRequestMode.NETWORK)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
@ -224,8 +225,8 @@ class TimelineRepositoryTest {
|
||||||
val sinceId = "2"
|
val sinceId = "2"
|
||||||
val sinceIdMinusOne = "1"
|
val sinceIdMinusOne = "1"
|
||||||
val maxId = "4"
|
val maxId = "4"
|
||||||
whenever(mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1))
|
whenever(mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1))
|
||||||
.thenReturn(Single.just(response))
|
.thenReturn(Single.just(Response.success(response)))
|
||||||
val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit,
|
val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit,
|
||||||
TimelineRequestMode.NETWORK)
|
TimelineRequestMode.NETWORK)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
@ -263,8 +264,8 @@ class TimelineRepositoryTest {
|
||||||
dbResult.status = dbStatus.toEntity(account.id, gson)
|
dbResult.status = dbStatus.toEntity(account.id, gson)
|
||||||
dbResult.account = status.account.toEntity(account.id, gson)
|
dbResult.account = status.account.toEntity(account.id, gson)
|
||||||
|
|
||||||
whenever(mastodonApi.homeTimelineSingle(any(), any(), any()))
|
whenever(mastodonApi.homeTimeline(any(), any(), any()))
|
||||||
.thenReturn(Single.just(listOf(status)))
|
.thenReturn(Single.just(Response.success((listOf(status)))))
|
||||||
whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30))
|
whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30))
|
||||||
.thenReturn(Single.just(listOf(dbResult)))
|
.thenReturn(Single.just(listOf(dbResult)))
|
||||||
val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY)
|
val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY)
|
||||||
|
@ -281,8 +282,8 @@ class TimelineRepositoryTest {
|
||||||
val dbResult2 = TimelineStatusWithAccount()
|
val dbResult2 = TimelineStatusWithAccount()
|
||||||
dbResult2.status = Placeholder("1").toEntity(account.id)
|
dbResult2.status = Placeholder("1").toEntity(account.id)
|
||||||
|
|
||||||
whenever(mastodonApi.homeTimelineSingle(any(), any(), any()))
|
whenever(mastodonApi.homeTimeline(any(), any(), any()))
|
||||||
.thenReturn(Single.just(listOf(status)))
|
.thenReturn(Single.just(Response.success(listOf(status))))
|
||||||
whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30))
|
whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30))
|
||||||
.thenReturn(Single.just(listOf(dbResult, dbResult2)))
|
.thenReturn(Single.just(listOf(dbResult, dbResult2)))
|
||||||
val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY)
|
val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY)
|
||||||
|
|
Loading…
Add table
Reference in a new issue