Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add user history #629

Merged
merged 2 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions app/src/main/java/dev/dimension/flare/ui/common/PagingStateExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package dev.dimension.flare.ui.common

import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.runtime.Composable
import dev.dimension.flare.common.PagingState
import dev.dimension.flare.common.onEmpty
Expand Down Expand Up @@ -52,3 +54,47 @@ fun <T : Any> LazyListScope.items(
item(content = emptyContent)
}
}

fun <T : Any> LazyStaggeredGridScope.items(
state: PagingState<T>,
emptyContent: @Composable LazyStaggeredGridItemScope.() -> Unit = {},
errorContent: @Composable LazyStaggeredGridItemScope.(Throwable) -> Unit = {},
loadingContent: @Composable LazyStaggeredGridItemScope.() -> Unit = {},
loadingCount: Int = 10,
key: (PagingState.Success<T>.(index: Int) -> Any)? = null,
contentType: PagingState.Success<T>.(index: Int) -> Any? = { null },
itemContent: @Composable LazyStaggeredGridItemScope.(T) -> Unit,
) {
state
.onSuccess {
items(
count = itemCount,
key =
key?.let {
{
it(this, it)
}
},
contentType = {
contentType(this, it)
},
) { index ->
val item = get(index)
if (item != null) {
itemContent(item)
} else {
loadingContent()
}
}
}.onLoading {
items(loadingCount) {
loadingContent()
}
}.onError {
item {
errorContent(it)
}
}.onEmpty {
item(content = emptyContent)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ internal fun LazyStaggeredGridScope.status(
}

@Composable
private fun AdaptiveCard(
internal fun AdaptiveCard(
content: @Composable () -> Unit,
modifier: Modifier = Modifier,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ package dev.dimension.flare.ui.screen.settings
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
Expand All @@ -20,20 +26,27 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import compose.icons.FontAwesomeIcons
import compose.icons.fontawesomeicons.Solid
import compose.icons.fontawesomeicons.solid.Xmark
import dev.dimension.flare.R
import dev.dimension.flare.ui.common.items
import dev.dimension.flare.ui.component.BackButton
import dev.dimension.flare.ui.component.FAIcon
import dev.dimension.flare.ui.component.FlareScaffold
import dev.dimension.flare.ui.component.ThemeWrapper
import dev.dimension.flare.ui.component.status.AdaptiveCard
import dev.dimension.flare.ui.component.status.LazyStatusVerticalStaggeredGrid
import dev.dimension.flare.ui.component.status.status
import dev.dimension.flare.ui.model.ClickContext
import dev.dimension.flare.ui.model.UiState
import dev.dimension.flare.ui.presenter.invoke
import dev.dimension.flare.ui.presenter.settings.LocalCacheSearchPresenter
import dev.dimension.flare.ui.theme.screenHorizontalPadding
import kotlinx.collections.immutable.toImmutableList
import moe.tlaster.precompose.molecule.producePresenter

@Destination<RootGraph>(
Expand All @@ -59,6 +72,7 @@ private fun LocalCacheSearchScreen(onBack: () -> Unit) {
val keyboardController = LocalSoftwareKeyboardController.current
var text by rememberSaveable { mutableStateOf("") }
val lazyListState = rememberLazyStaggeredGridState()
val uriHandler = LocalUriHandler.current
FlareScaffold(
topBar = {
Box(
Expand Down Expand Up @@ -122,10 +136,59 @@ private fun LocalCacheSearchScreen(onBack: () -> Unit) {
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize(),
) {
if (text.isNullOrEmpty()) {
status(state.history)
} else {
status(state.data)
item(
span = StaggeredGridItemSpan.FullLine,
) {
SingleChoiceSegmentedButtonRow(
modifier =
Modifier
.padding(horizontal = screenHorizontalPadding, vertical = 8.dp)
.widthIn(max = 300.dp),
) {
state.allSearchTypes.forEachIndexed { index, searchType ->
SegmentedButton(
selected = state.selectedSearchType == searchType,
onClick = {
state.setSearchType(searchType)
},
shape =
SegmentedButtonDefaults.itemShape(
index = index,
count = state.allSearchTypes.size,
),
) {
Text(text = stringResource(id = searchType.title))
}
}
}
}
when (state.selectedSearchType) {
SearchType.Status ->
status(
if (text.isEmpty()) {
state.history
} else {
state.data
},
)
SearchType.User ->
items(
if (text.isEmpty()) {
state.userHistory
} else {
state.searchUser
},
) { user ->
AdaptiveCard(
content = {
AccountItem(
userState = UiState.Success(user),
onClick = { user.onClicked.invoke(ClickContext(uriHandler::openUri)) },
toLogin = {},
)
},
)
}
}
}
}
Expand All @@ -136,11 +199,26 @@ private fun presenter() =
run {
val state = remember { LocalCacheSearchPresenter() }.invoke()
var searchBarExpanded by remember { mutableStateOf(false) }
var selectedSearchType by remember { mutableStateOf(SearchType.Status) }
object : LocalCacheSearchPresenter.State by state {
val searchBarExpanded = searchBarExpanded

fun setSearchBarExpanded(value: Boolean) {
searchBarExpanded = value
}

val selectedSearchType = selectedSearchType
val allSearchTypes = SearchType.entries.toImmutableList()

fun setSearchType(value: SearchType) {
selectedSearchType = value
}
}
}

private enum class SearchType(
val title: Int,
) {
Status(R.string.local_history_search_status_title),
User(R.string.local_history_search_user_title),
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ internal fun SettingsRoute(
scaffoldNavigator.navigateBack()
},
uriHandler = uriHandler,
rootNavigator = navigator,
),
)
dependency(navigationState)
Expand Down Expand Up @@ -164,6 +165,7 @@ internal class ProxyDestinationsNavigator(
private val navigator: DestinationsNavigator,
private val navigateBack: () -> Unit,
val uriHandler: UriHandler,
val rootNavigator: DestinationsNavigator,
) : DestinationsNavigator by navigator {
override fun navigateUp(): Boolean =
if (navigator.navigateUp()) {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -691,5 +691,6 @@
<string name="fans_title">Follower</string>

<string name="local_history_search_placeholder">Search local cache</string>

<string name="local_history_search_status_title">Status</string>
<string name="local_history_search_user_title">User</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.room.RoomDatabase
import androidx.room.RoomDatabaseConstructor
import androidx.room.TypeConverters

internal const val CACHE_DATABASE_VERSION = 14
internal const val CACHE_DATABASE_VERSION = 15

@Database(
entities = [
Expand All @@ -19,6 +19,7 @@ internal const val CACHE_DATABASE_VERSION = 14
dev.dimension.flare.data.database.cache.model.DbMessageItem::class,
dev.dimension.flare.data.database.cache.model.DbDirectMessageTimeline::class,
dev.dimension.flare.data.database.cache.model.DbMessageRoomReference::class,
dev.dimension.flare.data.database.cache.model.DbUserHistory::class,
],
version = CACHE_DATABASE_VERSION,
exportSchema = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package dev.dimension.flare.data.database.cache.dao

import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import dev.dimension.flare.data.database.cache.model.DbUser
import dev.dimension.flare.data.database.cache.model.DbUserHistory
import dev.dimension.flare.data.database.cache.model.DbUserHistoryWithUser
import dev.dimension.flare.data.database.cache.model.UserContent
import dev.dimension.flare.model.MicroBlogKey
import dev.dimension.flare.model.PlatformType
Expand Down Expand Up @@ -42,4 +46,18 @@ internal interface UserDao {

@Query("DELETE FROM DbUser")
suspend fun clear()

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertHistory(data: DbUserHistory)

@Transaction
@Query("SELECT * FROM DbUserHistory ORDER BY lastVisit DESC")
fun getUserHistory(): PagingSource<Int, DbUserHistoryWithUser>

@Transaction
@Query(
"SELECT * FROM DbUser " +
"WHERE DbUser.name like :query OR DbUser.handle like :query",
)
fun searchUser(query: String): PagingSource<Int, DbUser>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.dimension.flare.data.database.cache.model

import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.Relation
import dev.dimension.flare.model.MicroBlogKey

@Entity(
indices = [
Index(value = ["userKey", "accountKey"], unique = true),
],
)
internal data class DbUserHistory(
val userKey: MicroBlogKey,
val accountKey: MicroBlogKey,
val lastVisit: Long,
@PrimaryKey
val _id: String = "$accountKey-$userKey",
)

internal data class DbUserHistoryWithUser(
@Embedded
val data: DbUserHistory,
@Relation(parentColumn = "userKey", entityColumn = "userKey")
val user: DbUser,
)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import dev.dimension.flare.ui.presenter.PresenterBase
import dev.dimension.flare.ui.presenter.home.UserState
import dev.dimension.flare.ui.presenter.settings.ImmutableListWrapper
import dev.dimension.flare.ui.presenter.settings.toImmutableListWrapper
import dev.dimension.flare.ui.presenter.status.LogUserHistoryPresenter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -69,6 +70,12 @@ public class ProfilePresenter(
}.collectAsState()
}
}
accountServiceState.onSuccess {
val userKey = userKey ?: if (it is AuthenticatedMicroblogDataSource) it.accountKey else null
if (userKey != null) {
remember { LogUserHistoryPresenter(accountType, userKey) }.body()
}
}

val mediaState =
remember {
Expand Down
Loading
Loading