yayan 1 年之前
父节点
当前提交
a26757baef

+ 1 - 0
app/build.gradle

@@ -91,6 +91,7 @@ dependencies {
     implementation "androidx.compose.runtime:runtime-livedata"
 
     implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
+    implementation "androidx.paging:paging-compose:3.2.1"
 
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0'

+ 7 - 10
app/src/main/java/io/nexilis/alpha/ui/main/Chat.kt

@@ -4,19 +4,19 @@ import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.KeyboardDoubleArrowDown
 import androidx.compose.material3.*
 import androidx.compose.runtime.*
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.constraintlayout.compose.ConstraintLayout
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.navigation.NavHostController
+import androidx.paging.compose.collectAsLazyPagingItems
+import androidx.paging.compose.itemKey
 import io.nexilis.alpha.ui.components.ContentChat
 import io.nexilis.alpha.ui.components.InputChat
 import io.nexilis.alpha.ui.components.LeftBubbleChat
@@ -34,7 +34,8 @@ fun Chat(
     me: Buddy
 ) {
     val messageModel: MessageViewModel = hiltViewModel()
-    val list by messageModel.getOpposite(pin).observeAsState()
+    val flow  = remember { messageModel.getOpposite(pin) }
+    val lazyPagingItems = flow.collectAsLazyPagingItems()
     val listState = rememberLazyListState()
     val scope = rememberCoroutineScope()
     ConstraintLayout(
@@ -49,17 +50,13 @@ fun Chat(
             modifier = Modifier
                 .fillMaxSize()
                 .constrainAs(lazyColumn) {
-                    start.linkTo(parent.start)
-                    top.linkTo(parent.top)
-                    end.linkTo(parent.end)
                     bottom.linkTo(input.top)
-                }
-                .padding(bottom = 32.dp),
+                },
             state = listState,
             reverseLayout = true
         ) {
-            list?.let { l ->
-                items(items = l) { message ->
+            items(count = lazyPagingItems.itemCount, key = lazyPagingItems.itemKey { it.message_id }) { index ->
+                lazyPagingItems[index]?.let { message ->
                     ListItem(modifier = Modifier.fillMaxWidth(), headlineContent = {
                         Row(
                             modifier = Modifier.fillMaxWidth(),

+ 3 - 0
cpaas-lite/build.gradle

@@ -47,6 +47,7 @@ dependencies {
     annotationProcessor("androidx.room:room-compiler:2.6.1")
     ksp "androidx.room:room-compiler:2.6.1"
     implementation "androidx.room:room-ktx:2.6.1"
+    implementation "androidx.room:room-paging:2.6.1"
 
     implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
     implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
@@ -65,6 +66,8 @@ dependencies {
 
     implementation 'io.coil-kt:coil:2.6.0'
 
+    implementation "androidx.paging:paging-common-ktx:3.2.1"
+
 }
 
 kapt {

+ 2 - 2
cpaas-lite/src/main/java/io/nexilis/service/core/ApiModule.kt

@@ -98,8 +98,8 @@ object ApiModule {
     }
 
     @Provides
-    fun provideServiceBankDao(database: ApiRoomDatabase): ServiceBankDao {
-        return database.serviceBankDao()
+    fun provideRemoteKeyDao(database: ApiRoomDatabase): RemoteKeyDao {
+        return database.remoteKeyDao()
     }
 
     @Provides

+ 3 - 2
cpaas-lite/src/main/java/io/nexilis/service/data/daos/MessageDao.kt

@@ -1,6 +1,7 @@
 package io.nexilis.service.data.daos
 
 import androidx.lifecycle.LiveData
+import androidx.paging.PagingSource
 import androidx.room.Dao
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy
@@ -19,8 +20,8 @@ abstract class MessageDao {
     @Query("select * from Message where message_id = :id")
     abstract fun getSync(id: String): Message?
 
-    @Query("select * from Message where opposite_pin = :pin order by server_date desc")
-    abstract fun getOpposite(pin: String): LiveData<List<Message>>
+    @Query("select * from Message where opposite_pin = :pin order by server_date desc limit :limit offset :offset")
+    abstract fun getOpposite(pin: String, offset: Int = -1, limit: Int = -1): PagingSource<Int, Message>
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     abstract suspend fun _insert(entity: Message)

+ 21 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/daos/RemoteKeyDao.kt

@@ -0,0 +1,21 @@
+package io.nexilis.service.data.daos
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import io.nexilis.service.data.entities.*
+
+@Dao
+interface RemoteKeyDao {
+
+    @Query("select * from RemoteKey where `id` = :id")
+    fun get(id: String): RemoteKey?
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(entity: RemoteKey)
+
+    @Query("delete from RemoteKey where `id` = :id")
+    suspend fun delete(id: String)
+
+}

+ 0 - 25
cpaas-lite/src/main/java/io/nexilis/service/data/daos/ServiceBankDao.kt

@@ -1,25 +0,0 @@
-package io.nexilis.service.data.daos
-
-import androidx.lifecycle.LiveData
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
-import io.nexilis.service.data.entities.*
-
-@Dao
-interface ServiceBankDao {
-
-    @Query("select * from ServiceBank")
-    fun getAll(): LiveData<List<ServiceBank>>
-
-    @Query("select * from ServiceBank where service_id = :id")
-    fun get(id: String): LiveData<List<ServiceBank>>
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    suspend fun insert(entity: ServiceBank)
-
-    @Query("delete from ServiceBank")
-    suspend fun deleteAll()
-
-}

+ 11 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/entities/RemoteKey.kt

@@ -0,0 +1,11 @@
+package io.nexilis.service.data.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class RemoteKey(
+    @PrimaryKey
+    val id: String,
+    val nextOffset: Int,
+)

+ 0 - 12
cpaas-lite/src/main/java/io/nexilis/service/data/entities/ServiceBank.kt

@@ -1,12 +0,0 @@
-package io.nexilis.service.data.entities
-
-import androidx.room.Entity
-
-@Entity(primaryKeys = ["service_id"])
-data class ServiceBank(
-    val service_id: String,
-    val service_name: String,
-    val description: String,
-    val parent: String,
-    val is_tablet: String
-) : MainEntity

+ 46 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/paging/MessageRemoteMediator.kt

@@ -0,0 +1,46 @@
+package io.nexilis.service.data.paging
+
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.LoadType
+import androidx.paging.PagingState
+import androidx.paging.RemoteMediator
+import io.nexilis.service.data.daos.MessageDao
+import io.nexilis.service.data.daos.RemoteKeyDao
+import io.nexilis.service.data.entities.Message
+import io.nexilis.service.data.entities.RemoteKey
+
+@OptIn(ExperimentalPagingApi::class)
+class MessageRemoteMediator (private val pin: String, private val remoteKeyDao: RemoteKeyDao, private val messageDao: MessageDao): RemoteMediator<Int, Message>() {
+
+    private val REMOTE_KEY_ID = "message"
+    override suspend fun load(
+        loadType: LoadType,
+        state: PagingState<Int, Message>
+    ): MediatorResult {
+        return try {
+            val loadKey = when (loadType) {
+                LoadType.REFRESH -> 0
+                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
+                LoadType.APPEND -> {
+                    state.lastItemOrNull()
+                        ?: return MediatorResult.Success(endOfPaginationReached = true)
+
+                    val remoteKey = remoteKeyDao.get(REMOTE_KEY_ID)
+                    if (remoteKey == null || remoteKey.nextOffset == 0)
+                        return MediatorResult.Success(endOfPaginationReached = true)
+                    remoteKey.nextOffset
+                }
+            }
+            remoteKeyDao.insert(
+                RemoteKey(
+                    id = REMOTE_KEY_ID,
+                    nextOffset = loadKey + state.config.pageSize,
+                )
+            )
+            MediatorResult.Success(endOfPaginationReached = 1 < state.config.pageSize)
+        } catch (e: Exception) {
+            MediatorResult.Error(e)
+        }
+    }
+
+}

+ 4 - 4
cpaas-lite/src/main/java/io/nexilis/service/data/repositories/MessageRepository.kt

@@ -4,6 +4,7 @@ import android.content.Context
 import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LiveData
+import androidx.paging.PagingSource
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.OutOfQuotaPolicy
 import androidx.work.WorkManager
@@ -28,7 +29,7 @@ import javax.inject.Inject
 class MessageRepository @Inject constructor(
     private val dao: MessageDao,
     private val messageStatusDao: MessageStatusDao,
-    private val groupMemberDao: GroupMemberDao
+    private val groupMemberDao: GroupMemberDao,
 ) : Repository {
 
     val all: LiveData<List<Message>> = dao.getAll()
@@ -36,9 +37,8 @@ class MessageRepository @Inject constructor(
     fun get(id: String): LiveData<Message> {
         return dao.get(id)
     }
-
-    fun getOpposite(pin: String): LiveData<List<Message>> {
-        return dao.getOpposite(pin)
+    fun getOpposite(pin: String, offset: Int = -1, limit: Int = -1): PagingSource<Int, Message> {
+        return dao.getOpposite(pin, offset, limit)
     }
 
     override suspend fun insert(entity: MainEntity) {

+ 2 - 2
cpaas-lite/src/main/java/io/nexilis/service/data/rooms/ApiRoomDatabase.kt

@@ -28,7 +28,7 @@ import io.nexilis.service.data.entities.*
         MessageSummary::class,
         Prefs::class,
         Pull::class,
-        ServiceBank::class,
+        RemoteKey::class,
         WorkingArea::class
     ], version = 1, exportSchema = false
 )
@@ -50,7 +50,7 @@ abstract class ApiRoomDatabase : RoomDatabase() {
     abstract fun messageSummaryDao(): MessageSummaryDao
     abstract fun prefsDao(): PrefsDao
     abstract fun pullDao(): PullDao
-    abstract fun serviceBankDao(): ServiceBankDao
+    abstract fun remoteKeyDao(): RemoteKeyDao
     abstract fun workingAreaDao(): WorkingAreaDao
 
     class ApiDatabaseCallback : Callback() {

+ 15 - 2
cpaas-lite/src/main/java/io/nexilis/service/data/viewmodels/MessageViewModel.kt

@@ -5,6 +5,11 @@ import android.net.Uri
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import androidx.paging.Pager
+import androidx.paging.PagingConfig
+import androidx.paging.PagingData
+import androidx.paging.cachedIn
+import androidx.paging.map
 import dagger.hilt.android.lifecycle.HiltViewModel
 import io.nexilis.service.core.getFileName
 import io.nexilis.service.core.getMimeType
@@ -12,6 +17,8 @@ import io.nexilis.service.core.toFile
 import io.nexilis.service.data.entities.Message
 import io.nexilis.service.data.repositories.MessageRepository
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import javax.inject.Inject
 
@@ -27,8 +34,14 @@ class MessageViewModel @Inject constructor(
         return repository.get(id)
     }
 
-    fun getOpposite(pin: String): LiveData<List<Message>> {
-        return repository.getOpposite(pin)
+    fun getOpposite(pin: String, offset: Int = -1, limit: Int = -1): Flow<PagingData<Message>> {
+        val flow = Pager(
+            config = PagingConfig(pageSize = 20),
+            pagingSourceFactory = {
+                repository.getOpposite(pin = pin, offset = offset, limit = limit)
+            }
+        ).flow.cachedIn(viewModelScope)
+        return flow.map { pagingData -> pagingData.map { it } }
     }
 
     fun insert(entity: Message) = viewModelScope.launch(Dispatchers.IO) {