mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +01:00
Load shared items while scrolling
Signed-off-by: Tim Krüger <t@timkrueger.me>
This commit is contained in:
parent
d92f5546e9
commit
a322a2ad73
@ -8,6 +8,7 @@ import androidx.core.content.res.ResourcesCompat
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.adapters.SharedItemsAdapter
|
import com.nextcloud.talk.adapters.SharedItemsAdapter
|
||||||
@ -60,7 +61,7 @@ class SharedItemsActivity : AppCompatActivity() {
|
|||||||
SharedItemsViewModel.Factory(userEntity, roomToken, currentTab)
|
SharedItemsViewModel.Factory(userEntity, roomToken, currentTab)
|
||||||
).get(SharedItemsViewModel::class.java)
|
).get(SharedItemsViewModel::class.java)
|
||||||
|
|
||||||
viewModel.media.observe(this) {
|
viewModel.sharedItems.observe(this) {
|
||||||
Log.d(TAG, "Items received: $it")
|
Log.d(TAG, "Items received: $it")
|
||||||
|
|
||||||
if (currentTab == TAB_MEDIA) {
|
if (currentTab == TAB_MEDIA) {
|
||||||
@ -71,8 +72,6 @@ class SharedItemsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val layoutManager = GridLayoutManager(this, 4)
|
val layoutManager = GridLayoutManager(this, 4)
|
||||||
binding.imageRecycler.layoutManager = layoutManager
|
binding.imageRecycler.layoutManager = layoutManager
|
||||||
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
} else {
|
} else {
|
||||||
val adapter = SharedItemsListAdapter()
|
val adapter = SharedItemsListAdapter()
|
||||||
adapter.items = it.items
|
adapter.items = it.items
|
||||||
@ -82,15 +81,22 @@ class SharedItemsActivity : AppCompatActivity() {
|
|||||||
val layoutManager = LinearLayoutManager(this)
|
val layoutManager = LinearLayoutManager(this)
|
||||||
layoutManager.orientation = LinearLayoutManager.VERTICAL
|
layoutManager.orientation = LinearLayoutManager.VERTICAL
|
||||||
binding.imageRecycler.layoutManager = layoutManager
|
binding.imageRecycler.layoutManager = layoutManager
|
||||||
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.imageRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState)
|
||||||
|
if (!recyclerView.canScrollVertically(1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||||
|
viewModel.loadNextItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateItems(type: String) {
|
fun updateItems(type: String) {
|
||||||
currentTab = type
|
currentTab = type
|
||||||
viewModel.loadMediaItems(type)
|
viewModel.loadItems(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initTabs() {
|
private fun initTabs() {
|
||||||
@ -134,13 +140,9 @@ class SharedItemsActivity : AppCompatActivity() {
|
|||||||
updateItems(tab.tag as String)
|
updateItems(tab.tag as String)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab) {
|
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
|
||||||
// unused atm
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTabReselected(tab: TabLayout.Tab) {
|
override fun onTabReselected(tab: TabLayout.Tab) = Unit
|
||||||
// unused atm
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,12 +29,16 @@ class SharedItemsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun media(type: String): Observable<Response<ChatShareOverall>>? {
|
fun media(type: String): Observable<Response<ChatShareOverall>>? {
|
||||||
|
return media(type, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun media(type: String, lastKnownMessageId: Int?): Observable<Response<ChatShareOverall>>? {
|
||||||
val credentials = ApiUtils.getCredentials(parameters!!.userName, parameters!!.userToken)
|
val credentials = ApiUtils.getCredentials(parameters!!.userName, parameters!!.userToken)
|
||||||
|
|
||||||
return ncApi.getSharedItems(
|
return ncApi.getSharedItems(
|
||||||
credentials,
|
credentials,
|
||||||
ApiUtils.getUrlForChatSharedItems(1, parameters!!.baseUrl, parameters!!.roomToken),
|
ApiUtils.getUrlForChatSharedItems(1, parameters!!.baseUrl, parameters!!.roomToken),
|
||||||
type, null, null
|
type, lastKnownMessageId, 28
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package com.nextcloud.talk.repositories
|
package com.nextcloud.talk.repositories
|
||||||
|
|
||||||
class SharedMediaItems(
|
class SharedMediaItems(
|
||||||
val items: List<SharedItem>,
|
val type: String,
|
||||||
val lastSeenId: String,
|
val items: MutableList<SharedItem>,
|
||||||
|
var lastSeenId: Int?,
|
||||||
|
var moreItemsExisting: Boolean,
|
||||||
val authHeader: Map<String, String>
|
val authHeader: Map<String, String>
|
||||||
)
|
)
|
||||||
|
@ -16,76 +16,109 @@ import io.reactivex.disposables.Disposable
|
|||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
class SharedItemsViewModel(private val repository: SharedItemsRepository, private val initialType: String) : ViewModel() {
|
class SharedItemsViewModel(private val repository: SharedItemsRepository, private val initialType: String) :
|
||||||
|
ViewModel() {
|
||||||
|
|
||||||
private val _media: MutableLiveData<SharedMediaItems> by lazy {
|
private val _sharedItems: MutableLiveData<SharedMediaItems> by lazy {
|
||||||
MutableLiveData<SharedMediaItems>().also {
|
MutableLiveData<SharedMediaItems>().also {
|
||||||
loadMediaItems(initialType)
|
loadItems(initialType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val media: LiveData<SharedMediaItems>
|
val sharedItems: LiveData<SharedMediaItems>
|
||||||
get() = _media
|
get() = _sharedItems
|
||||||
|
|
||||||
fun loadMediaItems(type: String) {
|
fun loadNextItems() {
|
||||||
|
val currentSharedItems = sharedItems.value!!
|
||||||
|
|
||||||
|
if (currentSharedItems.moreItemsExisting) {
|
||||||
|
repository.media(currentSharedItems.type, currentSharedItems.lastSeenId)?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(observer(currentSharedItems.type, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadItems(type: String) {
|
||||||
repository.media(type)?.subscribeOn(Schedulers.io())
|
repository.media(type)?.subscribeOn(Schedulers.io())
|
||||||
?.observeOn(AndroidSchedulers.mainThread())
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
?.subscribe(object : Observer<Response<ChatShareOverall>> {
|
?.subscribe(observer(type, true))
|
||||||
|
}
|
||||||
|
|
||||||
var chatLastGiven: String = ""
|
private fun observer(type: String, initModel: Boolean): Observer<Response<ChatShareOverall>> {
|
||||||
val items = mutableMapOf<String, SharedItem>()
|
return object : Observer<Response<ChatShareOverall>> {
|
||||||
|
|
||||||
override fun onSubscribe(d: Disposable) = Unit
|
var chatLastGiven: Int? = null
|
||||||
|
val items = mutableMapOf<String, SharedItem>()
|
||||||
|
|
||||||
override fun onNext(response: Response<ChatShareOverall>) {
|
override fun onSubscribe(d: Disposable) = Unit
|
||||||
|
|
||||||
if (response.headers()["x-chat-last-given"] != null) {
|
override fun onNext(response: Response<ChatShareOverall>) {
|
||||||
chatLastGiven = response.headers()["x-chat-last-given"]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val mediaItems = response.body()!!.ocs!!.data
|
if (response.headers()["x-chat-last-given"] != null) {
|
||||||
mediaItems?.forEach {
|
chatLastGiven = response.headers()["x-chat-last-given"]!!.toInt()
|
||||||
if (it.value.messageParameters.containsKey("file")) {
|
|
||||||
val fileParameters = it.value.messageParameters["file"]!!
|
|
||||||
|
|
||||||
val previewAvailable = "yes".equals(fileParameters["preview-available"]!!, ignoreCase = true)
|
|
||||||
|
|
||||||
items[it.value.id] = SharedItem(
|
|
||||||
fileParameters["id"]!!,
|
|
||||||
fileParameters["name"]!!,
|
|
||||||
fileParameters["size"]!!.toInt(),
|
|
||||||
fileParameters["path"]!!,
|
|
||||||
fileParameters["link"]!!,
|
|
||||||
fileParameters["mimetype"]!!,
|
|
||||||
previewAvailable,
|
|
||||||
repository.previewLink(fileParameters["id"]),
|
|
||||||
repository.parameters!!.userEntity
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "location and deckcard are not yet supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(e: Throwable) {
|
val mediaItems = response.body()!!.ocs!!.data
|
||||||
Log.d(TAG, "An error occurred: $e")
|
mediaItems?.forEach {
|
||||||
}
|
if (it.value.messageParameters.containsKey("file")) {
|
||||||
|
val fileParameters = it.value.messageParameters["file"]!!
|
||||||
|
|
||||||
override fun onComplete() {
|
val previewAvailable = "yes".equals(fileParameters["preview-available"]!!, ignoreCase = true)
|
||||||
this@SharedItemsViewModel._media.value =
|
|
||||||
|
items[it.value.id] = SharedItem(
|
||||||
|
fileParameters["id"]!!,
|
||||||
|
fileParameters["name"]!!,
|
||||||
|
fileParameters["size"]!!.toInt(),
|
||||||
|
fileParameters["path"]!!,
|
||||||
|
fileParameters["link"]!!,
|
||||||
|
fileParameters["mimetype"]!!,
|
||||||
|
previewAvailable,
|
||||||
|
repository.previewLink(fileParameters["id"]),
|
||||||
|
repository.parameters!!.userEntity
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "location and deckcard are not yet supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
Log.d(TAG, "An error occurred: $e")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
|
||||||
|
val sortedMutableItems = items.toSortedMap().values.toList().reversed().toMutableList()
|
||||||
|
val moreItemsExisting = items.count() == 28
|
||||||
|
|
||||||
|
if (initModel) {
|
||||||
|
this@SharedItemsViewModel._sharedItems.value =
|
||||||
SharedMediaItems(
|
SharedMediaItems(
|
||||||
items.toSortedMap().values.toList().reversed(),
|
type,
|
||||||
|
sortedMutableItems,
|
||||||
chatLastGiven,
|
chatLastGiven,
|
||||||
|
moreItemsExisting,
|
||||||
|
repository.authHeader()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val oldItems = this@SharedItemsViewModel._sharedItems.value!!.items
|
||||||
|
this@SharedItemsViewModel._sharedItems.value =
|
||||||
|
SharedMediaItems(
|
||||||
|
type,
|
||||||
|
(oldItems.toMutableList() + sortedMutableItems) as MutableList<SharedItem>,
|
||||||
|
chatLastGiven,
|
||||||
|
moreItemsExisting,
|
||||||
repository.authHeader()
|
repository.authHeader()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(val userEntity: UserEntity, val roomToken: String, private val initialType: String) : ViewModelProvider
|
class Factory(val userEntity: UserEntity, val roomToken: String, private val initialType: String) :
|
||||||
.Factory {
|
ViewModelProvider
|
||||||
|
.Factory {
|
||||||
|
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
if (modelClass.isAssignableFrom(SharedItemsViewModel::class.java)) {
|
if (modelClass.isAssignableFrom(SharedItemsViewModel::class.java)) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
~ Nextcloud Talk application
|
~ Nextcloud Talk application
|
||||||
~
|
~
|
||||||
~ @author Tim Krüger
|
~ @author Tim Krüger
|
||||||
@ -34,12 +33,12 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:background="@color/appbar"
|
android:background="@color/appbar"
|
||||||
android:theme="?attr/actionBarPopupTheme"
|
android:theme="?attr/actionBarPopupTheme"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_scrollFlags="enterAlwaysCollapsed|noScroll"
|
app:layout_scrollFlags="enterAlwaysCollapsed|noScroll"
|
||||||
app:navigationIconTint="@color/fontAppbar"
|
app:navigationIconTint="@color/fontAppbar"
|
||||||
app:popupTheme="@style/appActionBarPopupMenu"
|
app:popupTheme="@style/appActionBarPopupMenu"
|
||||||
app:titleTextColor="@color/fontAppbar"
|
app:titleTextColor="@color/fontAppbar"
|
||||||
tools:title="@string/nc_app_product_name"
|
tools:title="@string/nc_app_product_name" />
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/shared_items_tabs"
|
android:id="@+id/shared_items_tabs"
|
||||||
@ -54,18 +53,15 @@
|
|||||||
app:tabMode="scrollable"
|
app:tabMode="scrollable"
|
||||||
app:tabTextAppearance="@style/TextAppearanceTab" />
|
app:tabTextAppearance="@style/TextAppearanceTab" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/nestedScrollView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/shared_items_tabs">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/image_recycler"
|
android:id="@+id/image_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/attachment_item" />
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
</androidx.core.widget.NestedScrollView>
|
app:layout_constraintTop_toBottomOf="@+id/shared_items_tabs"
|
||||||
|
tools:listitem="@layout/attachment_item" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -20,40 +20,45 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:fresco="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/preview_container"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:layout_marginStart="2dp"
|
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
app:layout_alignSelf="flex_start"
|
|
||||||
app:layout_flexGrow="1"
|
|
||||||
app:layout_wrapBefore="true">
|
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<FrameLayout
|
||||||
xmlns:fresco="http://schemas.android.com/apk/res-auto"
|
android:id="@+id/preview_container"
|
||||||
android:id="@+id/image"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="100dp"
|
android:layout_height="0dp"
|
||||||
android:padding="4dp"
|
android:layout_marginStart="2dp"
|
||||||
android:src="@drawable/ic_mimetype_file"
|
android:layout_marginEnd="2dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
app:layout_alignSelf="flex_start"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:placeholderImageScaleType="fitCenter"
|
app:layout_flexGrow="1"
|
||||||
fresco:actualImageScaleType="centerCrop"
|
app:layout_wrapBefore="true">
|
||||||
fresco:failureImage="@drawable/ic_mimetype_file"
|
|
||||||
fresco:placeholderImage="@drawable/ic_mimetype_file"
|
|
||||||
fresco:roundedCornerRadius="4dp"
|
|
||||||
tools:src="@drawable/ic_call_black_24dp"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/progress_bar"
|
android:id="@+id/image"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:padding="4dp"
|
||||||
android:visibility="gone" />
|
android:src="@drawable/ic_mimetype_file"
|
||||||
</FrameLayout>
|
app:placeholderImageScaleType="fitCenter"
|
||||||
|
fresco:actualImageScaleType="centerCrop"
|
||||||
|
fresco:failureImage="@drawable/ic_mimetype_file"
|
||||||
|
fresco:placeholderImage="@drawable/ic_mimetype_file"
|
||||||
|
fresco:roundedCornerRadius="4dp" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue
Block a user