Data loading improvements

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-16 16:24:07 +02:00
parent 8a3008ef25
commit f99dac5f45
12 changed files with 172 additions and 80 deletions

View File

@ -143,6 +143,7 @@ ext {
work_version = "1.0.1" work_version = "1.0.1"
koin_version = "2.1.0-alpha-1" koin_version = "2.1.0-alpha-1"
lifecycle_version = "2.1.0" lifecycle_version = "2.1.0"
coil_version = "0.7.0"
} }
@ -269,6 +270,10 @@ dependencies {
implementation 'com.github.mario.fresco:animated-gif:111' implementation 'com.github.mario.fresco:animated-gif:111'
implementation 'com.github.mario.fresco:imagepipeline-okhttp3:111' implementation 'com.github.mario.fresco:imagepipeline-okhttp3:111'
implementation "io.coil-kt:coil:${coil_version}"
implementation "io.coil-kt:coil-gif:${coil_version}"
implementation "io.coil-kt:coil-svg:${coil_version}"
implementation 'com.github.natario1:Autocomplete:v1.1.0' implementation 'com.github.natario1:Autocomplete:v1.1.0'
implementation 'com.github.cotechde.hwsecurity:hwsecurity-fido:2.4.5' implementation 'com.github.cotechde.hwsecurity:hwsecurity-fido:2.4.5'

View File

@ -151,7 +151,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
Security.insertProviderAt(Conscrypt.newProvider(), 1) Security.insertProviderAt(Conscrypt.newProvider(), 1)
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync() ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
DeviceUtils.ignoreSpecialBatteryFeatures()
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build() val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build() val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()

View File

@ -48,6 +48,7 @@ import com.nextcloud.talk.jobs.CapabilitiesWorker;
import com.nextcloud.talk.jobs.PushRegistrationWorker; import com.nextcloud.talk.jobs.PushRegistrationWorker;
import com.nextcloud.talk.jobs.SignalingSettingsWorker; import com.nextcloud.talk.jobs.SignalingSettingsWorker;
import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListView;
import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.ClosedInterfaceImpl; import com.nextcloud.talk.utils.ClosedInterfaceImpl;
import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.bundle.BundleKeys;
@ -407,7 +408,7 @@ public class AccountVerificationController extends BaseController {
getActivity().runOnUiThread(() -> { getActivity().runOnUiThread(() -> {
if (userUtils.getUsers().size() == 1) { if (userUtils.getUsers().size() == 1) {
getRouter().setRoot(RouterTransaction.with(new getRouter().setRoot(RouterTransaction.with(new
ConversationsListController()) ConversationsListView())
.pushChangeHandler(new HorizontalChangeHandler()) .pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler())); .popChangeHandler(new HorizontalChangeHandler()));
} else { } else {
@ -474,7 +475,7 @@ public class AccountVerificationController extends BaseController {
} else { } else {
if (userUtils.anyUserExists()) { if (userUtils.anyUserExists()) {
getRouter().setRoot(RouterTransaction.with(new ConversationsListController()) getRouter().setRoot(RouterTransaction.with(new ConversationsListView())
.pushChangeHandler(new HorizontalChangeHandler()) .pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler())); .popChangeHandler(new HorizontalChangeHandler()));
} else { } else {

View File

@ -142,9 +142,6 @@ public class ConversationsListController extends BaseController implements Searc
@BindView(R.id.progressBar) @BindView(R.id.progressBar)
ProgressBar progressBarView; ProgressBar progressBarView;
@BindView(R.id.emptyLayout)
RelativeLayout emptyLayoutView;
@BindView(R.id.fast_scroller) @BindView(R.id.fast_scroller)
FastScroller fastScroller; FastScroller fastScroller;
@ -319,7 +316,7 @@ public class ConversationsListController extends BaseController implements Searc
progressBarView.setVisibility(View.GONE); progressBarView.setVisibility(View.GONE);
} }
if (roomsOverall.getOcs().getData().size() > 0) { /*if (roomsOverall.getOcs().getData().size() > 0) {
if (emptyLayoutView.getVisibility() != View.GONE) { if (emptyLayoutView.getVisibility() != View.GONE) {
emptyLayoutView.setVisibility(View.GONE); emptyLayoutView.setVisibility(View.GONE);
} }
@ -335,7 +332,7 @@ public class ConversationsListController extends BaseController implements Searc
if (swipeRefreshLayout.getVisibility() != View.GONE) { if (swipeRefreshLayout.getVisibility() != View.GONE) {
swipeRefreshLayout.setVisibility(View.GONE); swipeRefreshLayout.setVisibility(View.GONE);
} }
} }*/
Conversation conversation; Conversation conversation;
for (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) { for (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) {
@ -386,14 +383,14 @@ public class ConversationsListController extends BaseController implements Searc
HttpException exception = (HttpException) throwable; HttpException exception = (HttpException) throwable;
switch (exception.code()) { switch (exception.code()) {
case 401: case 401:
if (getParentController() != null && /*if (getParentController() != null &&
getParentController().getRouter() != null) { getParentController().getRouter() != null) {
getParentController().getRouter().pushController((RouterTransaction.with getParentController().getRouter().pushController((RouterTransaction.with
(new WebViewLoginController(currentUser.getBaseUrl(), (new WebViewLoginController(currentUser.getBaseUrl(),
true)) true))
.pushChangeHandler(new VerticalChangeHandler()) .pushChangeHandler(new VerticalChangeHandler())
.popChangeHandler(new VerticalChangeHandler()))); .popChangeHandler(new VerticalChangeHandler())));
} }*/
break; break;
default: default:
break; break;
@ -432,7 +429,7 @@ public class ConversationsListController extends BaseController implements Searc
swipeRefreshLayout.setOnRefreshListener(() -> fetchData(false)); swipeRefreshLayout.setOnRefreshListener(() -> fetchData(false));
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary); swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen()); //emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen());
floatingActionButton.setOnClickListener(v -> { floatingActionButton.setOnClickListener(v -> {
showNewConversationsScreen(); showNewConversationsScreen();
}); });

View File

@ -251,3 +251,9 @@ fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkReposito
return NextcloudTalkRepositoryImpl(apiService) return NextcloudTalkRepositoryImpl(apiService)
} }
fun createGetConversationsUseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): GetConversationsUseCase {
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
}

View File

@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.SearchManager import android.app.SearchManager
import android.content.Context import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -60,6 +61,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -72,12 +74,14 @@ import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import kotlinx.android.synthetic.main.controller_conversations_rv.view.emptyLayout import kotlinx.android.synthetic.main.controller_conversations_rv.view.dataStateView
import kotlinx.android.synthetic.main.controller_conversations_rv.view.floatingActionButton import kotlinx.android.synthetic.main.controller_conversations_rv.view.floatingActionButton
import kotlinx.android.synthetic.main.controller_conversations_rv.view.progressBar
import kotlinx.android.synthetic.main.controller_conversations_rv.view.recyclerView import kotlinx.android.synthetic.main.controller_conversations_rv.view.recyclerView
import kotlinx.android.synthetic.main.controller_conversations_rv.view.swipeRefreshLayoutView
import kotlinx.android.synthetic.main.fast_scroller.view.fast_scroller import kotlinx.android.synthetic.main.fast_scroller.view.fast_scroller
import kotlinx.android.synthetic.main.view_states.view.errorStateImageView
import kotlinx.android.synthetic.main.view_states.view.errorStateTextView
import kotlinx.android.synthetic.main.view_states.view.loadingStateView
import kotlinx.android.synthetic.main.view_states.view.stateWithMessageView
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.parceler.Parcels import org.parceler.Parcels
import java.util.ArrayList import java.util.ArrayList
@ -178,18 +182,36 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
viewState.observe(this@ConversationsListView, Observer { value -> viewState.observe(this@ConversationsListView, Observer { value ->
when (value) { when (value) {
LOADING -> { LOADING -> {
view?.recyclerView?.visibility = View.GONE view?.loadingStateView?.visibility = View.VISIBLE
view?.emptyLayout?.visibility = View.GONE view?.stateWithMessageView?.visibility = View.GONE
view?.swipeRefreshLayoutView?.visibility = View.GONE view?.dataStateView?.visibility = View.GONE
view?.progressBar?.visibility = View.VISIBLE
view?.floatingActionButton?.visibility = View.GONE view?.floatingActionButton?.visibility = View.GONE
searchItem?.setVisible(false) searchItem?.isVisible = false
} }
LOADED, FAILED -> { LOADED -> {
view?.recyclerView?.visibility = View.VISIBLE view?.loadingStateView?.visibility = View.GONE
// The rest is handled in an actual network call view?.stateWithMessageView?.visibility = View.GONE
view?.progressBar?.visibility = View.GONE view!!.dataStateView.visibility = View.VISIBLE
view?.floatingActionButton?.visibility = View.VISIBLE view?.floatingActionButton?.visibility = View.GONE
searchItem?.isVisible = true
}
LOADED_EMPTY, FAILED -> {
view?.loadingStateView?.visibility = View.GONE
view?.dataStateView?.visibility = View.VISIBLE
view?.floatingActionButton?.visibility = View.GONE
searchItem?.isVisible = false
if (value.equals(FAILED)) {
view?.stateWithMessageView?.errorStateTextView?.text = messageData
view?.stateWithMessageView?.errorStateImageView?.setImageResource(
R.drawable.ic_announcement_white_24dp
)
} else {
view?.stateWithMessageView?.errorStateTextView?.text = resources?.getText(R.string.nc_conversations_empty)
view?.stateWithMessageView?.errorStateImageView?.setImageResource(R.drawable.ic_logo)
}
view?.stateWithMessageView?.visibility = View.VISIBLE
} }
else -> { else -> {
// We should not be here // We should not be here
@ -208,16 +230,6 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
} }
recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?) recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?)
if (it.isNotEmpty()) {
view?.emptyLayout?.visibility = View.GONE
view?.swipeRefreshLayoutView?.visibility = View.VISIBLE
searchItem?.setVisible(true)
} else {
view?.emptyLayout?.visibility = View.VISIBLE
view?.swipeRefreshLayoutView?.visibility = View.GONE
searchItem?.setVisible(false)
}
}) })
}) })
@ -243,7 +255,8 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
dataSource.subscribe(object : BaseBitmapDataSubscriber() { dataSource.subscribe(object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) { override fun onNewResultImpl(bitmap: Bitmap?) {
if (bitmap != null && resources != null) { if (bitmap != null && resources != null) {
val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources!!, bitmap) val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources as Resources,
bitmap)
roundedBitmapDrawable.isCircular = true roundedBitmapDrawable.isCircular = true
roundedBitmapDrawable.setAntiAlias(true) roundedBitmapDrawable.setAntiAlias(true)
menuItem.icon = roundedBitmapDrawable menuItem.icon = roundedBitmapDrawable
@ -261,8 +274,19 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
return R.layout.controller_conversations_rv return R.layout.controller_conversations_rv
} }
@OnClick(R.id.floatingActionButton, R.id.emptyLayout) @OnClick(R.id.floatingActionButton)
fun onFloatingActionButtonClick() { fun onFloatingActionButtonClick() {
openNewConversationScreen()
}
@OnClick(R.id.stateWithMessageView)
fun onStateWithMessageViewClick() {
if (viewModel.viewState.equals(LOADED_EMPTY)) {
openNewConversationScreen()
}
}
private fun openNewConversationScreen() {
val bundle = Bundle() val bundle = Bundle()
bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true) bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true)
router.pushController( router.pushController(
@ -273,7 +297,7 @@ class ConversationsListView() : BaseView(), OnQueryTextListener,
} }
override fun getTitle(): String? { override fun getTitle(): String? {
return resources!!.getString(R.string.nc_app_name) return resources?.getString(R.string.nc_app_name)
} }
override fun onAttach(view: View) { override fun onAttach(view: View) {

View File

@ -30,6 +30,7 @@ import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.mvvm.ViewState import com.nextcloud.talk.newarch.mvvm.ViewState
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.database.user.UserUtils
import org.apache.commons.lang3.builder.CompareToBuilder import org.apache.commons.lang3.builder.CompareToBuilder
@ -41,7 +42,7 @@ class ConversationsListViewModel constructor(
val conversationsListData = MutableLiveData<List<Conversation>>() val conversationsListData = MutableLiveData<List<Conversation>>()
val viewState = MutableLiveData<ViewState>(LOADING) val viewState = MutableLiveData<ViewState>(LOADING)
val messageData = MutableLiveData<String>() var messageData : String? = null
val searchQuery = MutableLiveData<String>() val searchQuery = MutableLiveData<String>()
lateinit var currentUser: UserEntity lateinit var currentUser: UserEntity
@ -66,11 +67,12 @@ class ConversationsListViewModel constructor(
}) })
conversationsListData.value = newConversations conversationsListData.value = newConversations
viewState.value = LOADED viewState.value = if (newConversations.isNotEmpty()) LOADED else LOADED_EMPTY
} }
override fun onError(errorModel: ErrorModel?) { override fun onError(errorModel: ErrorModel?) {
messageData.value = errorModel?.message messageData = errorModel?.message
viewState.value = FAILED viewState.value = FAILED
} }

View File

@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.mvvm
enum class ViewState { enum class ViewState {
LOADING, LOADING,
LOADED_EMPTY,
LOADED, LOADED,
FAILED FAILED
} }

View File

@ -0,0 +1,25 @@
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,11h-2L11,5h2v6zM13,15h-2v-2h2v2z"/>
</vector>

View File

@ -25,54 +25,20 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ProgressBar <include layout="@layout/view_states"/>
android:id="@+id/progressBar"
android:layout_width="@dimen/item_height"
android:layout_height="@dimen/item_height"
android:layout_gravity="center"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimary"
android:indeterminateTintMode="src_in" />
<RelativeLayout
android:id="@+id/emptyLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<ImageView
android:id="@+id/noConversationsImageView"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_logo"
android:tint="@color/colorPrimary" />
<TextView
android:id="@+id/sendHiTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/noConversationsImageView"
android:layout_margin="8dp"
android:text="@string/nc_conversations_empty"
android:textAlignment="center"
android:textSize="20sp" />
</RelativeLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayoutView" android:id="@+id/swipeRefreshLayoutView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone" android:visibility="visible"
app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior"> app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior">
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:id="@+id/dataStateView"
android:visibility="invisible">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ProgressBar
android:id="@+id/loadingStateView"
android:layout_width="@dimen/item_height"
android:layout_height="@dimen/item_height"
android:layout_gravity="center"
android:layout_margin="@dimen/activity_horizontal_margin"
android:indeterminate="true"
android:layout_centerInParent="true"
android:indeterminateTint="@color/colorPrimary"
android:indeterminateTintMode="src_in"
android:visibility="gone"
/>
<RelativeLayout
android:id="@+id/stateWithMessageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/errorStateImageView"
android:layout_width="@dimen/item_height"
android:layout_height="@dimen/item_height"
android:layout_above="@id/errorStateTextView"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_announcement_white_24dp"
android:tint="@color/colorPrimary"
/>
<TextView
android:id="@+id/errorStateTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="8dp"
android:textAlignment="center"
android:textSize="20sp"
/>
</RelativeLayout>
</RelativeLayout>