mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Implement pagination for History and Logs
- Integrate Jetpack Paging 3 and Room Paging dependencies. - Update `LogDao` and `HistoryDao` to return `PagingSource` instead of `Flow<List>`. - Refactor `ActivitiesScreen`, `ActivityScreen`, `LogsScreen`, and `RelayLogScreen` to use `Pager` and `collectAsLazyPagingItems`. - Update `LogDatabase` to version 2, adding an index on the `time` column for query performance. - Extract `ActivityRow` composable for reuse. - Remove explicit `VACUUM` commands from database setup.
This commit is contained in:
@@ -205,6 +205,9 @@ dependencies {
|
||||
implementation libs.work.runtime.ktx
|
||||
implementation libs.material3.window
|
||||
implementation libs.adaptive.android
|
||||
implementation libs.paging.common
|
||||
implementation libs.paging.compose
|
||||
implementation libs.paging.runtime
|
||||
|
||||
testImplementation libs.junit
|
||||
androidTestImplementation libs.ext.junit
|
||||
@@ -234,6 +237,8 @@ dependencies {
|
||||
|
||||
ksp(libs.room.compiler)
|
||||
implementation(libs.room.ktx)
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.room.paging)
|
||||
|
||||
freeImplementation libs.okhttp
|
||||
freeImplementation libs.okhttpCoroutines
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "3d70a45c6ee30a3f8c9d79b6d3a55cf0",
|
||||
"identityHash": "22c4d1bdcd7396ef577313c1cf4627d7",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "amber_log",
|
||||
@@ -44,12 +44,25 @@
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "log_by_time_key",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"orders": [
|
||||
"DESC"
|
||||
],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `log_by_time_key` ON `${TABLE_NAME}` (`time` DESC)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3d70a45c6ee30a3f8c9d79b6d3a55cf0')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '22c4d1bdcd7396ef577313c1cf4627d7')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "22c4d1bdcd7396ef577313c1cf4627d7",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "amber_log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `type` TEXT NOT NULL, `message` TEXT NOT NULL, `time` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "log_by_time_key",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"time"
|
||||
],
|
||||
"orders": [
|
||||
"DESC"
|
||||
],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `log_by_time_key` ON `${TABLE_NAME}` (`time` DESC)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '22c4d1bdcd7396ef577313c1cf4627d7')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
.addMigrations(MIGRATION_14_15)
|
||||
.addMigrations(MIGRATION_15_16)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
|
||||
instance
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.greenart7c3.nostrsigner.database
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
@@ -11,21 +12,27 @@ import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.models.Permission
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface HistoryDao {
|
||||
@Query("SELECT * FROM history where pkKey = :pk ORDER BY time DESC")
|
||||
fun getAllHistory(pk: String): Flow<List<HistoryEntity>>
|
||||
fun getAllHistoryPaging(pk: String): PagingSource<Int, HistoryEntity>
|
||||
|
||||
@Query("SELECT * FROM history where (kind = :query OR LOWER(type) LIKE '%' || :query || '%' OR LOWER(translatedPermission) LIKE '%' || :query || '%') AND pkKey = :pk ORDER BY time DESC")
|
||||
fun searchAllHistory(pk: String, query: String): Flow<List<HistoryEntity>>
|
||||
fun searchAllHistoryPaging(pk: String, query: String): PagingSource<Int, HistoryEntity>
|
||||
|
||||
@Query("SELECT * FROM history ORDER BY time DESC")
|
||||
fun getAllHistory(): Flow<List<HistoryEntity>>
|
||||
fun getAllHistoryPaging(): PagingSource<Int, HistoryEntity>
|
||||
|
||||
@Query("SELECT * FROM history where (kind = :query OR LOWER(type) LIKE '%' || :query || '%' OR LOWER(translatedPermission) LIKE '%' || :query || '%') ORDER BY time DESC")
|
||||
fun searchAllHistory(query: String): Flow<List<HistoryEntity>>
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM history
|
||||
WHERE (kind = :query OR LOWER(type) LIKE '%' || :query || '%'
|
||||
OR LOWER(translatedPermission) LIKE '%' || :query || '%')
|
||||
ORDER BY time DESC
|
||||
""",
|
||||
)
|
||||
fun searchAllHistoryPaging(query: String): PagingSource<Int, HistoryEntity>
|
||||
|
||||
@Query("DELETE FROM history where pkKey = :pk")
|
||||
suspend fun deleteHistory(pk: String)
|
||||
|
||||
@@ -43,7 +43,6 @@ abstract class HistoryDatabase : RoomDatabase() {
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.addMigrations(migration_1_2)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
|
||||
instance
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.greenart7c3.nostrsigner.database
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
@@ -9,6 +10,9 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface LogDao {
|
||||
@Insert
|
||||
fun insertAll(items: List<LogEntity>)
|
||||
|
||||
@Insert
|
||||
@Transaction
|
||||
suspend fun insertLog(logEntity: LogEntity)
|
||||
@@ -16,9 +20,15 @@ interface LogDao {
|
||||
@Query("SELECT * FROM amber_log ORDER BY time DESC")
|
||||
fun getLogs(): Flow<List<LogEntity>>
|
||||
|
||||
@Query("SELECT * FROM amber_log ORDER BY time DESC")
|
||||
fun getLogsPaging(): PagingSource<Int, LogEntity>
|
||||
|
||||
@Query("SELECT * FROM amber_log where url = :url ORDER BY time DESC")
|
||||
fun getLogsByUrl(url: String): Flow<List<LogEntity>>
|
||||
|
||||
@Query("SELECT * FROM amber_log where url = :url ORDER BY time DESC")
|
||||
fun getLogsByUrlPaging(url: String): PagingSource<Int, LogEntity>
|
||||
|
||||
@Query("DELETE FROM amber_log")
|
||||
@Transaction
|
||||
suspend fun clearLogs()
|
||||
|
||||
@@ -5,19 +5,27 @@ import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
LogEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
version = 2,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class LogDatabase : RoomDatabase() {
|
||||
abstract fun dao(): LogDao
|
||||
|
||||
companion object {
|
||||
val migration_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS `log_by_time_key` ON `amber_log` (`time` DESC)")
|
||||
}
|
||||
}
|
||||
|
||||
fun getDatabase(
|
||||
context: Context,
|
||||
npub: String,
|
||||
@@ -33,8 +41,8 @@ abstract class LogDatabase : RoomDatabase() {
|
||||
)
|
||||
.setQueryExecutor(executor)
|
||||
.setTransactionExecutor(transactionExecutor)
|
||||
.addMigrations(migration_1_2)
|
||||
.build()
|
||||
instance.openHelper.writableDatabase.execSQL("VACUUM")
|
||||
|
||||
instance
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package com.greenart7c3.nostrsigner.database
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.Index.Order
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "amber_log",
|
||||
indices = [
|
||||
Index(
|
||||
value = ["time"],
|
||||
name = "log_by_time_key",
|
||||
orders = [Order.DESC],
|
||||
),
|
||||
],
|
||||
)
|
||||
data class LogEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -9,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
@@ -31,18 +31,20 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.database.AppDatabase
|
||||
import com.greenart7c3.nostrsigner.database.HistoryEntity
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.models.supportedKindNumbers
|
||||
@@ -63,10 +65,24 @@ fun ActivitiesScreen(
|
||||
val database = Amber.instance.getHistoryDatabase(account.npub)
|
||||
|
||||
val context = LocalContext.current
|
||||
// State for the search query
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
|
||||
val filteredActivities = if (searchQuery.isEmpty()) database.dao().getAllHistory().collectAsStateWithLifecycle(emptyList()) else database.dao().searchAllHistory(searchQuery.toLowerCase(Locale.current)).collectAsStateWithLifecycle(emptyList())
|
||||
val pager = remember(searchQuery) {
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = 20,
|
||||
enablePlaceholders = false,
|
||||
),
|
||||
) {
|
||||
if (searchQuery.isEmpty()) {
|
||||
database.dao().getAllHistoryPaging()
|
||||
} else {
|
||||
database.dao().searchAllHistoryPaging(searchQuery.lowercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
|
||||
|
||||
val textFieldState by remember { mutableStateOf(TextFieldState(initialText = searchQuery)) }
|
||||
|
||||
@@ -92,7 +108,7 @@ fun ActivitiesScreen(
|
||||
),
|
||||
) {
|
||||
item {
|
||||
if (filteredActivities.value.isEmpty()) {
|
||||
if (lazyPagingItems.itemCount == 0) {
|
||||
Text(
|
||||
stringResource(R.string.no_activities_found),
|
||||
Modifier
|
||||
@@ -105,58 +121,78 @@ fun ActivitiesScreen(
|
||||
}
|
||||
}
|
||||
|
||||
items(filteredActivities.value) { activity ->
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(0.9f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
ApplicationName(
|
||||
key = activity.pkKey,
|
||||
accepted = activity.accepted,
|
||||
database = Amber.instance.getDatabase(account.npub),
|
||||
account = account,
|
||||
)
|
||||
items(lazyPagingItems.itemCount) { index ->
|
||||
val activity = lazyPagingItems[index]
|
||||
if (activity != null) {
|
||||
ActivityRow(activity = activity, account = account)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = activity.translatedPermission,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (activity.accepted) Color.Unspecified else Color.Gray,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = TimeUtils.formatLongToCustomDateTimeWithSeconds(activity.time * 1000),
|
||||
fontSize = 16.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
if (activity.accepted) Icons.Default.Check else Icons.Default.Close,
|
||||
contentDescription = if (activity.accepted) stringResource(R.string.accepted) else stringResource(R.string.rejected),
|
||||
tint = if (activity.accepted) Color(0xFF1D8802) else Color(0xFFFF6B00),
|
||||
modifier = Modifier.padding(start = 10.dp, top = 4.dp, bottom = 16.dp),
|
||||
)
|
||||
lazyPagingItems.apply {
|
||||
when (loadState.refresh) {
|
||||
is LoadState.Loading -> item {
|
||||
Log.d("ActivitiesScreen", "Loading...")
|
||||
Text("Loading...", Modifier.padding(16.dp))
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
is LoadState.Error -> item {
|
||||
Log.d("ActivitiesScreen", "Error loading data")
|
||||
Text("Error loading data", Modifier.padding(16.dp))
|
||||
}
|
||||
is LoadState.NotLoading -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActivityRow(activity: HistoryEntity, account: Account) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(0.9f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
ApplicationName(
|
||||
key = activity.pkKey,
|
||||
accepted = activity.accepted,
|
||||
database = Amber.instance.getDatabase(account.npub),
|
||||
account = account,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = activity.translatedPermission,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (activity.accepted) Color.Unspecified else Color.Gray,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = TimeUtils.formatLongToCustomDateTimeWithSeconds(activity.time * 1000),
|
||||
fontSize = 16.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
if (activity.accepted) Icons.Default.Check else Icons.Default.Close,
|
||||
contentDescription = null,
|
||||
tint = if (activity.accepted) Color(0xFF1D8802) else Color(0xFFFF6B00),
|
||||
modifier = Modifier.padding(start = 10.dp, top = 4.dp, bottom = 16.dp),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ApplicationName(
|
||||
key: String,
|
||||
|
||||
@@ -21,7 +21,9 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
@@ -36,8 +38,17 @@ fun LogsScreen(
|
||||
account: Account,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val logsFlow = Amber.instance.getLogDatabase(account.npub).dao().getLogs()
|
||||
val logs = logsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val pager =
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = 20,
|
||||
enablePlaceholders = false,
|
||||
),
|
||||
) {
|
||||
Amber.instance.getLogDatabase(account.npub).dao().getLogsPaging()
|
||||
}
|
||||
|
||||
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
|
||||
val state = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
@@ -57,45 +68,48 @@ fun LogsScreen(
|
||||
text = stringResource(R.string.clear_logs),
|
||||
)
|
||||
}
|
||||
items(logs.value) { log ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
items(lazyPagingItems.itemCount) { index ->
|
||||
val log = lazyPagingItems[index]
|
||||
log?.let {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
text = TimeUtils.formatLongToCustomDateTimeWithSeconds(log.time),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = log.url,
|
||||
fontSize = 20.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = log.type,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = log.message,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
text = TimeUtils.formatLongToCustomDateTimeWithSeconds(log.time),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = log.url,
|
||||
fontSize = 20.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = log.type,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = log.message,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,6 +696,7 @@ fun MainScreen(
|
||||
val localUrl = Base64.getDecoder().decode(url).toString(Charsets.UTF_8)
|
||||
RelayLogScreen(
|
||||
url = localUrl,
|
||||
account = account,
|
||||
paddingValues = PaddingValues(
|
||||
top = padding.calculateTopPadding() + (verticalPadding * 1.5f),
|
||||
bottom = padding.calculateBottomPadding(),
|
||||
|
||||
@@ -1,49 +1,34 @@
|
||||
package com.greenart7c3.nostrsigner.ui.actions
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.TimeUtils
|
||||
import com.greenart7c3.nostrsigner.models.supportedKindNumbers
|
||||
import com.greenart7c3.nostrsigner.ui.ActivityRow
|
||||
import com.greenart7c3.nostrsigner.ui.components.SimpleSearchBar
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -55,9 +40,28 @@ fun ActivityScreen(
|
||||
account: Account,
|
||||
key: String,
|
||||
) {
|
||||
val database = Amber.instance.getHistoryDatabase(account.npub)
|
||||
|
||||
val context = LocalContext.current
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
val filteredActivities = if (searchQuery.isEmpty()) Amber.instance.getHistoryDatabase(account.npub).dao().getAllHistory(key).collectAsStateWithLifecycle(emptyList()) else Amber.instance.getHistoryDatabase(account.npub).dao().searchAllHistory(key, searchQuery.toLowerCase(Locale.current)).collectAsStateWithLifecycle(emptyList())
|
||||
|
||||
val pager = remember(searchQuery) {
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = 20,
|
||||
enablePlaceholders = false,
|
||||
),
|
||||
) {
|
||||
if (searchQuery.isEmpty()) {
|
||||
database.dao().getAllHistoryPaging()
|
||||
} else {
|
||||
database.dao().searchAllHistoryPaging(searchQuery.lowercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
|
||||
|
||||
val textFieldState by remember { mutableStateOf(TextFieldState(initialText = searchQuery)) }
|
||||
|
||||
Column(
|
||||
@@ -81,60 +85,24 @@ fun ActivityScreen(
|
||||
bottom = paddingValues.calculateBottomPadding(),
|
||||
),
|
||||
) {
|
||||
item {
|
||||
if (filteredActivities.value.isEmpty()) {
|
||||
Text(
|
||||
stringResource(R.string.no_activities_found),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 18.sp,
|
||||
)
|
||||
items(lazyPagingItems.itemCount) { index ->
|
||||
val activity = lazyPagingItems[index]
|
||||
activity?.let {
|
||||
ActivityRow(activity = activity, account = account)
|
||||
}
|
||||
}
|
||||
|
||||
items(filteredActivities.value) { activity ->
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(0.9f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
text = activity.translatedPermission,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (activity.accepted) Color.Unspecified else Color.Gray,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = TimeUtils.formatLongToCustomDateTimeWithSeconds(activity.time * 1000),
|
||||
fontSize = 16.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
if (activity.accepted) Icons.Default.Check else Icons.Default.Close,
|
||||
contentDescription = if (activity.accepted) stringResource(R.string.accepted) else stringResource(R.string.rejected),
|
||||
tint = if (activity.accepted) Color(0xFF1D8802) else Color(0xFFFF6B00),
|
||||
modifier = Modifier.padding(start = 10.dp, top = 4.dp, bottom = 16.dp),
|
||||
)
|
||||
lazyPagingItems.apply {
|
||||
when (loadState.refresh) {
|
||||
is LoadState.Loading -> item {
|
||||
Log.d("ActivitiesScreen", "Loading...")
|
||||
Text("Loading...", Modifier.padding(16.dp))
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
is LoadState.Error -> item {
|
||||
Log.d("ActivitiesScreen", "Error loading data")
|
||||
Text("Error loading data", Modifier.padding(16.dp))
|
||||
}
|
||||
is LoadState.NotLoading -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -43,6 +42,9 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.BuildConfig
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
@@ -76,7 +78,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
@@ -438,52 +439,59 @@ fun onAddRelay(
|
||||
@Composable
|
||||
fun RelayLogScreen(
|
||||
paddingValues: PaddingValues,
|
||||
account: Account,
|
||||
url: String,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val flows = LocalPreferences.allSavedAccounts(context).map {
|
||||
Amber.instance.getLogDatabase(it.npub).dao().getLogsByUrl(url)
|
||||
}.merge()
|
||||
|
||||
val logs = flows.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val pager =
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = 20,
|
||||
enablePlaceholders = false,
|
||||
),
|
||||
) {
|
||||
Amber.instance.getLogDatabase(account.npub).dao().getLogsByUrlPaging(url)
|
||||
}
|
||||
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
|
||||
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize(),
|
||||
contentPadding = paddingValues,
|
||||
) {
|
||||
itemsIndexed(logs.value) { _, log ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
items(lazyPagingItems.itemCount) { index ->
|
||||
val log = lazyPagingItems[index]
|
||||
log?.let {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
text = formatLongToCustomDateTimeWithSeconds(log.time),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = log.type,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = log.message,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
text = formatLongToCustomDateTimeWithSeconds(log.time),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = log.type,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
|
||||
text = log.message,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ jna = "5.18.1"
|
||||
adaptiveAndroid = "1.2.0"
|
||||
materialIconsExtended = "1.7.8"
|
||||
collections = "0.4.0"
|
||||
pagingCommon = "3.3.6"
|
||||
|
||||
[libraries]
|
||||
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||
@@ -57,6 +58,8 @@ richtext-ui = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui"
|
||||
richtext-ui-material3 = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui-material3", version.ref = "richtextUi" }
|
||||
room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" }
|
||||
room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" }
|
||||
room-paging = { module = "androidx.room:room-paging", version.ref = "roomKtx" }
|
||||
runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose_ui" }
|
||||
security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCryptoKtx" }
|
||||
security-crypto-ktx = { module = "androidx.security:security-crypto-ktx", version.ref = "securityCryptoKtx" }
|
||||
@@ -76,6 +79,9 @@ coil-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", versio
|
||||
storage = { module = "com.anggrayudi:storage", version.ref = "storage" }
|
||||
material3-window = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3" }
|
||||
adaptive-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-android", version.ref = "adaptiveAndroid" }
|
||||
paging-common = { group = "androidx.paging", name = "paging-common", version.ref = "pagingCommon" }
|
||||
paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCommon" }
|
||||
paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "pagingCommon" }
|
||||
|
||||
[plugins]
|
||||
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
||||
Reference in New Issue
Block a user