mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-17 20:17:02 +05:30
Merge #170
170: Add ability to change saved posts sorting order r=msfjarvis a=msfjarvis Fixes #167 bors r+ Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
commit
4a40192647
16 changed files with 238 additions and 0 deletions
|
@ -33,6 +33,7 @@ dependencies {
|
|||
implementation(compose.ui)
|
||||
implementation(Dependencies.AndroidX.appCompat)
|
||||
implementation(Dependencies.AndroidX.browser)
|
||||
implementation(Dependencies.AndroidX.datastore)
|
||||
implementation(Dependencies.AndroidX.Compose.activity)
|
||||
implementation(Dependencies.AndroidX.Compose.lifecycleViewModel)
|
||||
implementation(Dependencies.AndroidX.Compose.navigation)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 6 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
|
@ -0,0 +1,74 @@
|
|||
package dev.msfjarvis.lobsters.ui.main
|
||||
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.test.captureToImage
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import com.karumi.shot.ScreenshotTest
|
||||
import dev.msfjarvis.lobsters.ui.DarkTestTheme
|
||||
import dev.msfjarvis.lobsters.ui.LightTestTheme
|
||||
import dev.msfjarvis.lobsters.ui.navigation.Destination
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class LobstersTopBarTest : ScreenshotTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun showsRefreshIconWhenOnHottestPostsScreen_DarkTheme() {
|
||||
composeTestRule.setContent {
|
||||
DarkTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Hottest,
|
||||
toggleSortingOrder = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showsRefreshIconWhenOnHottestPostsScreen_LightTheme() {
|
||||
composeTestRule.setContent {
|
||||
LightTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Hottest,
|
||||
toggleSortingOrder = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doesNotShowRefreshIconWhenOnSavedPostsScreen_DarkTheme() {
|
||||
composeTestRule.setContent {
|
||||
DarkTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Saved,
|
||||
toggleSortingOrder = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doesNotShowRefreshIconWhenOnSavedPostsScreen_LightTheme() {
|
||||
composeTestRule.setContent {
|
||||
LightTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Saved,
|
||||
toggleSortingOrder = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package dev.msfjarvis.lobsters.data.preferences
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ClawPreferences @Inject constructor(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) {
|
||||
private val sortKey = booleanPreferencesKey("post_sorting_order")
|
||||
|
||||
val sortingOrder: Flow<Boolean>
|
||||
get() = dataStore.data.map { preferences -> preferences[sortKey] ?: false }
|
||||
|
||||
suspend fun toggleSortingOrder() {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[sortKey] = (preferences[sortKey] ?: false).not()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package dev.msfjarvis.lobsters.injection
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class PreferenceStoreFileNameQualifier
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataStoreModule {
|
||||
|
||||
@Provides
|
||||
fun provideDataStore(
|
||||
@ApplicationContext context: Context,
|
||||
@PreferenceStoreFileNameQualifier fileName: String,
|
||||
): DataStore<Preferences> {
|
||||
return PreferenceDataStoreFactory.create { context.preferencesDataStoreFile(fileName) }
|
||||
}
|
||||
|
||||
@Provides
|
||||
@PreferenceStoreFileNameQualifier
|
||||
fun provideDataStoreFilename(): String {
|
||||
return "Claw_preferences"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package dev.msfjarvis.lobsters.injection
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.msfjarvis.lobsters.data.preferences.ClawPreferences
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object PreferenceModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideClawPreferences(
|
||||
dataStore: DataStore<Preferences>,
|
||||
): ClawPreferences {
|
||||
return ClawPreferences(dataStore)
|
||||
}
|
||||
}
|
|
@ -54,6 +54,12 @@ fun LobstersApp() {
|
|||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
LobstersTopAppBar(
|
||||
currentDestination,
|
||||
viewModel::toggleSortOrder,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
LobstersBottomNav(
|
||||
currentDestination,
|
||||
|
@ -78,6 +84,7 @@ fun LobstersApp() {
|
|||
posts = savedPosts,
|
||||
saveAction = viewModel::toggleSave,
|
||||
modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding()),
|
||||
sortReversed = viewModel.getSortOrder(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package dev.msfjarvis.lobsters.ui.main
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.msfjarvis.lobsters.R
|
||||
import dev.msfjarvis.lobsters.ui.navigation.Destination
|
||||
import dev.msfjarvis.lobsters.util.IconResource
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun LobstersTopAppBar(
|
||||
currentDestination: Destination,
|
||||
toggleSortingOrder: suspend () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.app_name),
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
if (currentDestination == Destination.Saved) {
|
||||
IconResource(
|
||||
resourceId = R.drawable.ic_sort_24px,
|
||||
contentDescription = stringResource(id = R.string.change_sorting_order),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
.clickable { scope.launch { toggleSortingOrder() } },
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -10,6 +10,8 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
@ -20,16 +22,19 @@ import dev.msfjarvis.lobsters.data.local.SavedPost
|
|||
import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher
|
||||
import dev.msfjarvis.lobsters.util.IconResource
|
||||
import dev.msfjarvis.lobsters.util.asZonedDateTime
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SavedPosts(
|
||||
posts: List<SavedPost>,
|
||||
sortReversed: Flow<Boolean>,
|
||||
modifier: Modifier = Modifier,
|
||||
saveAction: (SavedPost) -> Unit,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val urlLauncher = LocalUrlLauncher.current
|
||||
val sortOrder by sortReversed.collectAsState(false)
|
||||
|
||||
if (posts.isEmpty()) {
|
||||
Column(
|
||||
|
@ -55,6 +60,8 @@ fun SavedPosts(
|
|||
stickyHeader {
|
||||
MonthHeader(month = month)
|
||||
}
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val posts = if (sortOrder) posts.reversed() else posts
|
||||
items(posts) { item ->
|
||||
LobstersItem(
|
||||
post = item,
|
||||
|
|
|
@ -7,9 +7,11 @@ import androidx.paging.PagingConfig
|
|||
import androidx.paging.cachedIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.data.preferences.ClawPreferences
|
||||
import dev.msfjarvis.lobsters.data.remote.LobstersPagingSource
|
||||
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -19,6 +21,7 @@ import kotlinx.coroutines.launch
|
|||
@HiltViewModel
|
||||
class LobstersViewModel @Inject constructor(
|
||||
private val lobstersRepository: LobstersRepository,
|
||||
private val clawPreferences: ClawPreferences,
|
||||
) : ViewModel() {
|
||||
private val _savedPosts = MutableStateFlow<List<SavedPost>>(emptyList())
|
||||
val savedPosts = _savedPosts.asStateFlow()
|
||||
|
@ -35,6 +38,14 @@ class LobstersViewModel @Inject constructor(
|
|||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun getSortOrder(): Flow<Boolean> {
|
||||
return clawPreferences.sortingOrder
|
||||
}
|
||||
|
||||
suspend fun toggleSortOrder() {
|
||||
clawPreferences.toggleSortingOrder()
|
||||
}
|
||||
|
||||
fun reloadPosts() {
|
||||
pagingSource?.invalidate()
|
||||
}
|
||||
|
|
9
app/src/main/res/drawable/ic_sort_24px.xml
Normal file
9
app/src/main/res/drawable/ic_sort_24px.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,18h4c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,16c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM3,7c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,6c-0.55,0 -1,0.45 -1,1zM4,13h10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"/>
|
||||
</vector>
|
|
@ -10,4 +10,5 @@
|
|||
<string name="remove_from_saved_posts">Remove from saved posts</string>
|
||||
<string name="refresh_posts_content_description">Refresh posts</string>
|
||||
<string name="open_comments">Open comments</string>
|
||||
<string name="change_sorting_order">Change sort order</string>
|
||||
</resources>
|
||||
|
|
|
@ -34,6 +34,7 @@ object Dependencies {
|
|||
const val appCompat = "androidx.appcompat:appcompat:1.3.0-beta01"
|
||||
const val browser = "androidx.browser:browser:1.3.0"
|
||||
const val coreLibraryDesugaring = "com.android.tools:desugar_jdk_libs:1.0.10"
|
||||
const val datastore = "androidx.datastore:datastore-preferences:1.0.0-alpha08"
|
||||
|
||||
object Compose {
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue