mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-17 23:47:02 +05:30
all: reformat with ktfmt google style
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
8448910628
commit
db07a12be5
54 changed files with 496 additions and 656 deletions
|
@ -10,9 +10,7 @@ plugins {
|
|||
`core-library-desugaring`
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||
}
|
||||
repositories { maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
|
@ -22,7 +20,6 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
kapt(Dependencies.AndroidX.Hilt.daggerCompiler)
|
||||
implementation(project(":api"))
|
||||
implementation(project(":common"))
|
||||
|
|
|
@ -15,8 +15,7 @@ import org.junit.Test
|
|||
@Ignore("Shot is broken yet again")
|
||||
class LobstersTopBarTest : ScreenshotTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
@get:Rule val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun showsRefreshIconWhenOnHottestPostsScreen_DarkTheme() {
|
||||
|
@ -24,7 +23,7 @@ class LobstersTopBarTest : ScreenshotTest {
|
|||
DarkTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Hottest,
|
||||
toggleSortingOrder = { },
|
||||
toggleSortingOrder = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +37,7 @@ class LobstersTopBarTest : ScreenshotTest {
|
|||
LightTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Hottest,
|
||||
toggleSortingOrder = { },
|
||||
toggleSortingOrder = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +51,7 @@ class LobstersTopBarTest : ScreenshotTest {
|
|||
DarkTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Saved,
|
||||
toggleSortingOrder = { },
|
||||
toggleSortingOrder = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +65,7 @@ class LobstersTopBarTest : ScreenshotTest {
|
|||
LightTestTheme {
|
||||
LobstersTopAppBar(
|
||||
currentDestination = Destination.Saved,
|
||||
toggleSortingOrder = { },
|
||||
toggleSortingOrder = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import androidx.compose.ui.test.performClick
|
|||
import com.karumi.shot.ScreenshotTest
|
||||
import dev.msfjarvis.lobsters.ui.DarkTestTheme
|
||||
import dev.msfjarvis.lobsters.ui.main.LobstersBottomNav
|
||||
import dev.msfjarvis.lobsters.ui.theme.LobstersTheme
|
||||
import kotlin.test.Test
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
|
@ -22,8 +21,7 @@ import org.junit.Rule
|
|||
@Ignore("Shot is broken yet again")
|
||||
class LobstersBottomNavTest : ScreenshotTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
@get:Rule val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun bottomNavIsRenderedCorrectlyOnScreen() {
|
||||
|
@ -31,7 +29,7 @@ class LobstersBottomNavTest : ScreenshotTest {
|
|||
DarkTestTheme {
|
||||
LobstersBottomNav(
|
||||
currentDestination = Destination.startDestination,
|
||||
navigateToDestination = { /*TODO*/ },
|
||||
navigateToDestination = { /*TODO*/},
|
||||
jumpToIndex = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
@ -59,8 +57,6 @@ class LobstersBottomNavTest : ScreenshotTest {
|
|||
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||
}
|
||||
|
||||
private fun selectNode(testTag: String) = composeTestRule
|
||||
.onNodeWithTag(testTag)
|
||||
.assertHasClickAction()
|
||||
.performClick()
|
||||
private fun selectNode(testTag: String) =
|
||||
composeTestRule.onNodeWithTag(testTag).assertHasClickAction().performClick()
|
||||
}
|
||||
|
|
|
@ -19,21 +19,16 @@ import org.junit.Rule
|
|||
|
||||
@Ignore("Shot is broken yet again")
|
||||
class HeaderTest : ScreenshotTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
@get:Rule val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun headerDoesNotHaveATransparentBackground() {
|
||||
composeTestRule.setContent {
|
||||
DarkTestTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = Color(0xffffff))
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
) {
|
||||
MonthHeader(month = Month.AUGUST)
|
||||
}
|
||||
modifier =
|
||||
Modifier.background(color = Color(0xffffff)).fillMaxWidth().wrapContentHeight(),
|
||||
) { MonthHeader(month = Month.AUGUST) }
|
||||
}
|
||||
}
|
||||
compareScreenshot(composeTestRule.onRoot().captureToImage().asAndroidBitmap())
|
||||
|
|
|
@ -15,8 +15,7 @@ import org.junit.Rule
|
|||
@Ignore("Shot is broken yet again")
|
||||
class LobstersItemTest : ScreenshotTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
@get:Rule val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun singlePost() {
|
||||
|
@ -24,9 +23,9 @@ class LobstersItemTest : ScreenshotTest {
|
|||
DarkTestTheme {
|
||||
LobstersItem(
|
||||
post = TEST_POST,
|
||||
viewPost = { /*TODO*/ },
|
||||
viewComments = { /*TODO*/ },
|
||||
toggleSave = { /*TODO*/ },
|
||||
viewPost = {},
|
||||
viewComments = {},
|
||||
toggleSave = {},
|
||||
isSaved = true,
|
||||
)
|
||||
}
|
||||
|
@ -42,9 +41,9 @@ class LobstersItemTest : ScreenshotTest {
|
|||
items(10) {
|
||||
LobstersItem(
|
||||
post = TEST_POST,
|
||||
viewPost = { /*TODO*/ },
|
||||
viewComments = { /*TODO*/ },
|
||||
toggleSave = { /*TODO*/ },
|
||||
viewPost = {},
|
||||
viewComments = {},
|
||||
toggleSave = {},
|
||||
isSaved = true,
|
||||
)
|
||||
}
|
||||
|
@ -62,9 +61,9 @@ class LobstersItemTest : ScreenshotTest {
|
|||
items(10) {
|
||||
LobstersItem(
|
||||
post = TEST_POST.copy(tags = listOf("openbsd", "linux")),
|
||||
viewPost = { /*TODO*/ },
|
||||
viewComments = { /*TODO*/ },
|
||||
toggleSave = { /*TODO*/ },
|
||||
viewPost = {},
|
||||
viewComments = {},
|
||||
toggleSave = {},
|
||||
isSaved = true,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -25,13 +25,11 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
|
||||
private const val MAX_OFFSET = 400f
|
||||
private const val MIN_REFRESH_OFFSET = 250f
|
||||
private const val PERCENT_INDICATOR_PROGRESS_ON_DRAG = 0.85f
|
||||
private const val BASE_OFFSET = -48
|
||||
|
||||
|
||||
/**
|
||||
* A layout composable with [content].
|
||||
*
|
||||
|
@ -78,26 +76,30 @@ fun PullToRefresh(
|
|||
finishedListener = {
|
||||
indicatorOffset = 0f
|
||||
isFinishingRefresh = false
|
||||
})
|
||||
}
|
||||
)
|
||||
val offsetAnimation by animateFloatAsState(
|
||||
targetValue = if (isRefreshing || isFinishingRefresh) {
|
||||
indicatorOffset - minRefreshOffset
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
targetValue =
|
||||
if (isRefreshing || isFinishingRefresh) {
|
||||
indicatorOffset - minRefreshOffset
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
)
|
||||
val resettingScrollOffsetAnimation by animateFloatAsState(
|
||||
targetValue = if (isResettingScroll) {
|
||||
scrollToReset
|
||||
} else {
|
||||
0f
|
||||
},
|
||||
targetValue =
|
||||
if (isResettingScroll) {
|
||||
scrollToReset
|
||||
} else {
|
||||
0f
|
||||
},
|
||||
finishedListener = {
|
||||
if (isResettingScroll) {
|
||||
indicatorOffset = 0f
|
||||
isResettingScroll = false
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
if (isResettingScroll) {
|
||||
indicatorOffset -= resettingScrollOffsetAnimation
|
||||
|
@ -110,100 +112,102 @@ fun PullToRefresh(
|
|||
isRefreshingInternal = true
|
||||
}
|
||||
|
||||
val nestedScrollConnection = object : NestedScrollConnection {
|
||||
val nestedScrollConnection =
|
||||
object : NestedScrollConnection {
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource
|
||||
): Offset {
|
||||
if (!isRefreshing && source == NestedScrollSource.Drag) {
|
||||
val diff = if (indicatorOffset + available.y > maxOffset) {
|
||||
available.y - (indicatorOffset + available.y - maxOffset)
|
||||
} else if (indicatorOffset + available.y < 0) {
|
||||
0f
|
||||
} else {
|
||||
available.y
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource
|
||||
): Offset {
|
||||
if (!isRefreshing && source == NestedScrollSource.Drag) {
|
||||
val diff =
|
||||
if (indicatorOffset + available.y > maxOffset) {
|
||||
available.y - (indicatorOffset + available.y - maxOffset)
|
||||
} else if (indicatorOffset + available.y < 0) {
|
||||
0f
|
||||
} else {
|
||||
available.y
|
||||
}
|
||||
indicatorOffset += diff
|
||||
return Offset(0f, diff)
|
||||
}
|
||||
indicatorOffset += diff
|
||||
return Offset(0f, diff)
|
||||
return super.onPostScroll(consumed, available, source)
|
||||
}
|
||||
return super.onPostScroll(consumed, available, source)
|
||||
}
|
||||
|
||||
override fun onPreScroll(
|
||||
available: Offset,
|
||||
source: NestedScrollSource
|
||||
): Offset {
|
||||
if (!isRefreshing && source == NestedScrollSource.Drag) {
|
||||
if (available.y < 0 && indicatorOffset > 0) {
|
||||
val diff = if (indicatorOffset + available.y < 0) {
|
||||
indicatorOffset = 0f
|
||||
indicatorOffset
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
if (!isRefreshing && source == NestedScrollSource.Drag) {
|
||||
if (available.y < 0 && indicatorOffset > 0) {
|
||||
val diff =
|
||||
if (indicatorOffset + available.y < 0) {
|
||||
indicatorOffset = 0f
|
||||
indicatorOffset
|
||||
} else {
|
||||
indicatorOffset += available.y
|
||||
available.y
|
||||
}
|
||||
isFinishingRefresh = false
|
||||
return Offset.Zero.copy(y = diff)
|
||||
}
|
||||
}
|
||||
return super.onPreScroll(available, source)
|
||||
}
|
||||
|
||||
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||
if (!isRefreshing) {
|
||||
if (indicatorOffset > minRefreshOffset) {
|
||||
onRefresh()
|
||||
isRefreshingInternal = true
|
||||
} else {
|
||||
indicatorOffset += available.y
|
||||
available.y
|
||||
isResettingScroll = true
|
||||
scrollToReset = indicatorOffset
|
||||
}
|
||||
isFinishingRefresh = false
|
||||
return Offset.Zero.copy(y = diff)
|
||||
}
|
||||
return super.onPostFling(consumed, available)
|
||||
}
|
||||
return super.onPreScroll(available, source)
|
||||
}
|
||||
|
||||
override suspend fun onPostFling(
|
||||
consumed: Velocity,
|
||||
available: Velocity
|
||||
): Velocity {
|
||||
if (!isRefreshing) {
|
||||
if (indicatorOffset > minRefreshOffset) {
|
||||
onRefresh()
|
||||
isRefreshingInternal = true
|
||||
} else {
|
||||
isResettingScroll = true
|
||||
scrollToReset = indicatorOffset
|
||||
}
|
||||
}
|
||||
return super.onPostFling(consumed, available)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = CombinedModifier(
|
||||
inner = Modifier
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
.clip(RectangleShape),
|
||||
outer = modifier
|
||||
)
|
||||
modifier =
|
||||
CombinedModifier(
|
||||
inner = Modifier.nestedScroll(nestedScrollConnection).clip(RectangleShape),
|
||||
outer = modifier
|
||||
)
|
||||
) {
|
||||
content()
|
||||
|
||||
val offsetPx = if (isRefreshing || isFinishingRefresh) {
|
||||
offsetAnimation
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val absoluteOffset = BASE_OFFSET.dp + with(LocalDensity.current) {
|
||||
val diffedOffset = indicatorOffset - offsetPx
|
||||
val calculated = calculateAbsoluteOffset(diffedOffset, MAX_OFFSET)
|
||||
calculated.toDp()
|
||||
}
|
||||
val progressFromOffset = with(LocalDensity.current) {
|
||||
val coeff = MAX_OFFSET / (MAX_OFFSET - BASE_OFFSET)
|
||||
(indicatorOffset - BASE_OFFSET) / maxOffset * coeff
|
||||
}
|
||||
val offsetPx =
|
||||
if (isRefreshing || isFinishingRefresh) {
|
||||
offsetAnimation
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val absoluteOffset =
|
||||
BASE_OFFSET.dp +
|
||||
with(LocalDensity.current) {
|
||||
val diffedOffset = indicatorOffset - offsetPx
|
||||
val calculated = calculateAbsoluteOffset(diffedOffset, MAX_OFFSET)
|
||||
calculated.toDp()
|
||||
}
|
||||
val progressFromOffset =
|
||||
with(LocalDensity.current) {
|
||||
val coeff = MAX_OFFSET / (MAX_OFFSET - BASE_OFFSET)
|
||||
(indicatorOffset - BASE_OFFSET) / maxOffset * coeff
|
||||
}
|
||||
PullToRefreshProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.absoluteOffset(y = absoluteOffset)
|
||||
.scale(scaleAnimation)
|
||||
.rotate(indicatorOffset / MAX_OFFSET * 180 + 110),
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.absoluteOffset(y = absoluteOffset)
|
||||
.scale(scaleAnimation)
|
||||
.rotate(indicatorOffset / MAX_OFFSET * 180 + 110),
|
||||
progressColor = progressColor,
|
||||
backgroundColor = backgroundColor,
|
||||
progress = when {
|
||||
!isRefreshing && !isFinishingRefresh -> progressFromOffset * PERCENT_INDICATOR_PROGRESS_ON_DRAG
|
||||
else -> null
|
||||
},
|
||||
progress =
|
||||
when {
|
||||
!isRefreshing && !isFinishingRefresh ->
|
||||
progressFromOffset * PERCENT_INDICATOR_PROGRESS_ON_DRAG
|
||||
else -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,9 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
|
||||
private val CircularIndicatorDiameter = 40.dp
|
||||
private const val strokeWidthPx = 2.5f
|
||||
|
||||
|
||||
@Composable
|
||||
internal fun PullToRefreshProgressIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -39,22 +37,15 @@ internal fun PullToRefreshProgressIndicator(
|
|||
backgroundColor: Color,
|
||||
progress: Float? = null
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(modifier = modifier, horizontalArrangement = Arrangement.Center) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(CircularIndicatorDiameter)
|
||||
.height(CircularIndicatorDiameter),
|
||||
modifier = Modifier.width(CircularIndicatorDiameter).height(CircularIndicatorDiameter),
|
||||
shape = CircleShape,
|
||||
elevation = 6.dp,
|
||||
backgroundColor = backgroundColor,
|
||||
) {
|
||||
val padding = Modifier.padding(8.dp)
|
||||
val strokeWidth = with(LocalDensity.current) {
|
||||
(strokeWidthPx * this.density).toDp()
|
||||
}
|
||||
val strokeWidth = with(LocalDensity.current) { (strokeWidthPx * this.density).toDp() }
|
||||
|
||||
if (progress == null) {
|
||||
CircularProgressIndicator(
|
||||
|
@ -74,7 +65,6 @@ internal fun PullToRefreshProgressIndicator(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun ProgressIndicatorWithArrow(
|
||||
progress: Float,
|
||||
|
@ -83,47 +73,41 @@ fun ProgressIndicatorWithArrow(
|
|||
strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth,
|
||||
) {
|
||||
|
||||
val strokeWidthPx = with(LocalDensity.current) {
|
||||
strokeWidth.toPx()
|
||||
}
|
||||
val strokeWidthPx = with(LocalDensity.current) { strokeWidth.toPx() }
|
||||
val arrowWidth = 2.5f * strokeWidthPx * (0.5f + progress * 0.5f)
|
||||
val stroke = Stroke(width = strokeWidthPx, cap = StrokeCap.Butt)
|
||||
val diameterOffset = stroke.width / 2
|
||||
|
||||
val arrowPath = Path().apply {
|
||||
moveTo(0f, -arrowWidth)
|
||||
lineTo(arrowWidth, 0f)
|
||||
lineTo(0f, arrowWidth)
|
||||
close()
|
||||
}
|
||||
val arrowPath =
|
||||
Path().apply {
|
||||
moveTo(0f, -arrowWidth)
|
||||
lineTo(arrowWidth, 0f)
|
||||
lineTo(0f, arrowWidth)
|
||||
close()
|
||||
}
|
||||
Box(modifier = modifier) {
|
||||
Canvas(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(), onDraw = {
|
||||
val arcDimen = size.width - 2 * diameterOffset
|
||||
withTransform({
|
||||
translate(top = strokeWidthPx / 2, left = size.width / 2)
|
||||
rotate(
|
||||
degrees = progress * 360,
|
||||
pivot = Offset(x = 0f, y = size.height / 2 - diameterOffset)
|
||||
)
|
||||
}) {
|
||||
drawPath(
|
||||
path = arrowPath,
|
||||
color = color
|
||||
Canvas(
|
||||
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
|
||||
onDraw = {
|
||||
val arcDimen = size.width - 2 * diameterOffset
|
||||
withTransform({
|
||||
translate(top = strokeWidthPx / 2, left = size.width / 2)
|
||||
rotate(
|
||||
degrees = progress * 360,
|
||||
pivot = Offset(x = 0f, y = size.height / 2 - diameterOffset)
|
||||
)
|
||||
}) { drawPath(path = arrowPath, color = color) }
|
||||
|
||||
drawArc(
|
||||
color = color,
|
||||
startAngle = -90f,
|
||||
sweepAngle = 360 * progress,
|
||||
useCenter = false,
|
||||
topLeft = Offset(diameterOffset, diameterOffset),
|
||||
size = Size(arcDimen, arcDimen),
|
||||
style = stroke
|
||||
)
|
||||
}
|
||||
|
||||
drawArc(
|
||||
color = color,
|
||||
startAngle = -90f,
|
||||
sweepAngle = 360 * progress,
|
||||
useCenter = false,
|
||||
topLeft = Offset(diameterOffset, diameterOffset),
|
||||
size = Size(arcDimen, arcDimen),
|
||||
style = stroke
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,5 +3,4 @@ package dev.msfjarvis.lobsters
|
|||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class ClawApplication : Application()
|
||||
@HiltAndroidApp class ClawApplication : Application()
|
||||
|
|
|
@ -8,7 +8,9 @@ import javax.inject.Inject
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ClawPreferences @Inject constructor(
|
||||
class ClawPreferences
|
||||
@Inject
|
||||
constructor(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) {
|
||||
private val sortKey = booleanPreferencesKey("post_sorting_order")
|
||||
|
@ -17,8 +19,6 @@ class ClawPreferences @Inject constructor(
|
|||
get() = dataStore.data.map { preferences -> preferences[sortKey] ?: false }
|
||||
|
||||
suspend fun toggleSortingOrder() {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[sortKey] = (preferences[sortKey] ?: false).not()
|
||||
}
|
||||
dataStore.edit { preferences -> preferences[sortKey] = (preferences[sortKey] ?: false).not() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import androidx.paging.PagingState
|
|||
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
|
||||
class HottestPostsPagingSource constructor(
|
||||
class HottestPostsPagingSource
|
||||
constructor(
|
||||
private val lobstersRepository: LobstersRepository,
|
||||
) : PagingSource<Int, LobstersPost>() {
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ import androidx.paging.PagingState
|
|||
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
|
||||
class NewestPostsPagingSource constructor(
|
||||
class NewestPostsPagingSource
|
||||
constructor(
|
||||
private val lobstersRepository: LobstersRepository,
|
||||
) : PagingSource<Int, LobstersPost>() {
|
||||
|
||||
|
|
|
@ -2,14 +2,15 @@ package dev.msfjarvis.lobsters.data.repo
|
|||
|
||||
import dev.msfjarvis.lobsters.data.api.LobstersApi
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
import dev.msfjarvis.lobsters.database.LobstersDatabase
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class LobstersRepository constructor(
|
||||
class LobstersRepository
|
||||
constructor(
|
||||
private val lobstersApi: LobstersApi,
|
||||
private val lobstersDatabase: LobstersDatabase,
|
||||
) {
|
||||
|
@ -26,13 +27,15 @@ class LobstersRepository constructor(
|
|||
return savedPostsCache.values.toList()
|
||||
}
|
||||
|
||||
suspend fun fetchHottestPosts(page: Int): List<LobstersPost> = withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersApi.getHottestPosts(page)
|
||||
}
|
||||
suspend fun fetchHottestPosts(page: Int): List<LobstersPost> =
|
||||
withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersApi.getHottestPosts(page)
|
||||
}
|
||||
|
||||
suspend fun fetchNewestPosts(page: Int): List<LobstersPost> = withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersApi.getNewestPosts(page)
|
||||
}
|
||||
suspend fun fetchNewestPosts(page: Int): List<LobstersPost> =
|
||||
withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersApi.getNewestPosts(page)
|
||||
}
|
||||
|
||||
// https://issuetracker.google.com/issues/181221325
|
||||
@Suppress("NewApi")
|
||||
|
@ -40,27 +43,28 @@ class LobstersRepository constructor(
|
|||
if (_isCacheReady.value) return
|
||||
val posts = getSavedPosts()
|
||||
|
||||
posts.forEach {
|
||||
savedPostsCache[it.shortId] = it
|
||||
}
|
||||
posts.forEach { savedPostsCache[it.shortId] = it }
|
||||
_isCacheReady.value = true
|
||||
}
|
||||
|
||||
private suspend fun getSavedPosts(): List<SavedPost> = withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersDatabase.savedPostQueries.selectAllPosts().executeAsList()
|
||||
}
|
||||
private suspend fun getSavedPosts(): List<SavedPost> =
|
||||
withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersDatabase.savedPostQueries.selectAllPosts().executeAsList()
|
||||
}
|
||||
|
||||
suspend fun addPost(post: SavedPost) = withContext(Dispatchers.IO) {
|
||||
if (!savedPostsCache.containsKey(post.shortId)) {
|
||||
savedPostsCache.putIfAbsent(post.shortId, post)
|
||||
lobstersDatabase.savedPostQueries.insertOrReplacePost(post)
|
||||
suspend fun addPost(post: SavedPost) =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (!savedPostsCache.containsKey(post.shortId)) {
|
||||
savedPostsCache.putIfAbsent(post.shortId, post)
|
||||
lobstersDatabase.savedPostQueries.insertOrReplacePost(post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removePost(post: SavedPost) = withContext(Dispatchers.IO) {
|
||||
if (savedPostsCache.containsKey(post.shortId)) {
|
||||
savedPostsCache.remove(post.shortId)
|
||||
lobstersDatabase.savedPostQueries.deletePost(post.shortId)
|
||||
suspend fun removePost(post: SavedPost) =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (savedPostsCache.containsKey(post.shortId)) {
|
||||
savedPostsCache.remove(post.shortId)
|
||||
lobstersDatabase.savedPostQueries.deletePost(post.shortId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,12 @@ object ApiModule {
|
|||
|
||||
@Provides
|
||||
fun provideClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.build()
|
||||
return OkHttpClient.Builder().build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Using [Lazy] here is a trick I picked up from Zac Sweers, which he explained in more
|
||||
* detail here: https://www.zacsweers.dev/dagger-party-tricks-deferred-okhttp-init/
|
||||
* Using [Lazy] here is a trick I picked up from Zac Sweers, which he explained in more detail
|
||||
* here: https://www.zacsweers.dev/dagger-party-tricks-deferred-okhttp-init/
|
||||
*/
|
||||
@Provides
|
||||
fun provideRetrofit(
|
||||
|
|
|
@ -6,6 +6,4 @@ import javax.inject.Qualifier
|
|||
* Qualifier for a string value that needs to be provided to the [ApiModule.provideRetrofit] method
|
||||
* as the base URL of our API.
|
||||
*/
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class BaseUrlQualifier
|
||||
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class BaseUrlQualifier
|
||||
|
|
|
@ -32,10 +32,7 @@ object DatabaseModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesLobstersDatabase(
|
||||
sqlDriver: SqlDriver,
|
||||
tagsAdapter: TagsAdapter
|
||||
): LobstersDatabase {
|
||||
fun providesLobstersDatabase(sqlDriver: SqlDriver, tagsAdapter: TagsAdapter): LobstersDatabase {
|
||||
return LobstersDatabase(
|
||||
sqlDriver,
|
||||
SavedPost.Adapter(tagsAdapter),
|
||||
|
|
|
@ -13,7 +13,6 @@ object MoshiModule {
|
|||
@Provides
|
||||
@Reusable
|
||||
fun provideMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.build()
|
||||
return Moshi.Builder().build()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,8 +62,7 @@ fun LobstersApp() {
|
|||
newestPostsListState.animateScrollToItem(index)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,10 @@ fun LobstersTopAppBar(
|
|||
IconResource(
|
||||
resourceId = R.drawable.ic_sort_24px,
|
||||
contentDescription = Strings.ChangeSortingOrder.get(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
.clickable { scope.launch { toggleSortingOrder() } },
|
||||
modifier =
|
||||
Modifier.padding(horizontal = 8.dp, vertical = 8.dp).clickable {
|
||||
scope.launch { toggleSortingOrder() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,7 @@ class MainActivity : AppCompatActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
CompositionLocalProvider(LocalUrlLauncher provides urlLauncher) {
|
||||
LobstersTheme {
|
||||
LobstersApp()
|
||||
}
|
||||
LobstersTheme { LobstersApp() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,7 @@ import androidx.annotation.DrawableRes
|
|||
import dev.msfjarvis.lobsters.R
|
||||
import dev.msfjarvis.lobsters.utils.Strings
|
||||
|
||||
/**
|
||||
* Destinations for navigation within the app.
|
||||
*/
|
||||
/** Destinations for navigation within the app. */
|
||||
enum class Destination(
|
||||
val route: String,
|
||||
val labelRes: Strings,
|
||||
|
|
|
@ -14,24 +14,24 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import dev.msfjarvis.lobsters.ui.theme.LobstersTheme
|
||||
import java.time.Month
|
||||
import java.util.Locale
|
||||
import java.time.format.TextStyle as JTextStyle
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun MonthHeader(month: Month) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
Modifier.fillMaxWidth()
|
||||
.background(MaterialTheme.colors.secondary)
|
||||
.wrapContentHeight()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = month.getDisplayName(JTextStyle.FULL, Locale.getDefault()),
|
||||
style = MaterialTheme.typography.h5.copy(
|
||||
color = MaterialTheme.colors.onSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
style =
|
||||
MaterialTheme.typography.h5.copy(
|
||||
color = MaterialTheme.colors.onSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
)
|
||||
}
|
||||
|
@ -40,7 +40,5 @@ fun MonthHeader(month: Month) {
|
|||
@Preview
|
||||
@Composable
|
||||
fun MonthHeaderPreview() {
|
||||
LobstersTheme {
|
||||
MonthHeader(month = Month.JULY)
|
||||
}
|
||||
LobstersTheme { MonthHeader(month = Month.JULY) }
|
||||
}
|
||||
|
|
|
@ -39,16 +39,17 @@ import dev.msfjarvis.lobsters.util.IconResource
|
|||
import dev.msfjarvis.lobsters.utils.Strings
|
||||
import dev.msfjarvis.lobsters.utils.get
|
||||
|
||||
val TEST_POST = SavedPost(
|
||||
shortId = "zqyydb",
|
||||
title = "k2k20 hackathon report: Bob Beck on LibreSSL progress",
|
||||
url = "https://undeadly.org/cgi?action=article;sid=20200921105847",
|
||||
createdAt = "2020-09-21T07:11:14.000-05:00",
|
||||
commentsUrl = "https://lobste.rs/s/zqyydb/k2k20_hackathon_report_bob_beck_on",
|
||||
submitterName = "Vigdis",
|
||||
submitterAvatarUrl = "/404.html",
|
||||
tags = listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"),
|
||||
)
|
||||
val TEST_POST =
|
||||
SavedPost(
|
||||
shortId = "zqyydb",
|
||||
title = "k2k20 hackathon report: Bob Beck on LibreSSL progress",
|
||||
url = "https://undeadly.org/cgi?action=article;sid=20200921105847",
|
||||
createdAt = "2020-09-21T07:11:14.000-05:00",
|
||||
commentsUrl = "https://lobste.rs/s/zqyydb/k2k20_hackathon_report_bob_beck_on",
|
||||
submitterName = "Vigdis",
|
||||
submitterAvatarUrl = "/404.html",
|
||||
tags = listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
@ -61,22 +62,17 @@ fun LobstersItem(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.clickable { viewPost.invoke() }
|
||||
.then(modifier),
|
||||
modifier = Modifier.clickable { viewPost.invoke() }.then(modifier),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
) {
|
||||
PostTitle(
|
||||
title = post.title,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 4.dp),
|
||||
modifier = Modifier.padding(bottom = 4.dp),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TagRow(
|
||||
|
@ -144,11 +140,8 @@ fun SubmitterAvatar(
|
|||
data = "${LobstersApi.BASE_URL}/$avatarUrl",
|
||||
contentDescription = Strings.AvatarContentDescription.get(name),
|
||||
fadeIn = true,
|
||||
requestBuilder = {
|
||||
transformations(CircleCropTransformation())
|
||||
},
|
||||
modifier = Modifier
|
||||
.requiredSize(24.dp),
|
||||
requestBuilder = { transformations(CircleCropTransformation()) },
|
||||
modifier = Modifier.requiredSize(24.dp),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -158,8 +151,7 @@ fun SubmitterNameText(
|
|||
) {
|
||||
Text(
|
||||
text = Strings.SubmittedBy.get(name),
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp),
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -172,15 +164,14 @@ fun SaveButton(
|
|||
IconToggleButton(
|
||||
checked = isSaved,
|
||||
onCheckedChange = { onClick.invoke() },
|
||||
modifier = Modifier
|
||||
.requiredSize(32.dp)
|
||||
.then(modifier),
|
||||
modifier = Modifier.requiredSize(32.dp).then(modifier),
|
||||
) {
|
||||
Crossfade(targetState = isSaved) { saved ->
|
||||
IconResource(
|
||||
resourceId = if (saved) R.drawable.ic_favorite_24px else R.drawable.ic_favorite_border_24px,
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
contentDescription = if (saved) Strings.RemoveFromSavedPosts.get() else Strings.AddToSavedPosts.get(),
|
||||
contentDescription =
|
||||
if (saved) Strings.RemoveFromSavedPosts.get() else Strings.AddToSavedPosts.get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -193,9 +184,7 @@ fun CommentsButton(
|
|||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.requiredSize(32.dp)
|
||||
.then(modifier),
|
||||
modifier = Modifier.requiredSize(32.dp).then(modifier),
|
||||
) {
|
||||
IconResource(
|
||||
resourceId = R.drawable.ic_insert_comment_24px,
|
||||
|
@ -220,9 +209,9 @@ fun TagRow(
|
|||
tags.forEach { tag ->
|
||||
Text(
|
||||
text = tag,
|
||||
modifier = Modifier
|
||||
.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
|
||||
.padding(vertical = 2.dp, horizontal = 6.dp),
|
||||
modifier =
|
||||
Modifier.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
|
||||
.padding(vertical = 2.dp, horizontal = 6.dp),
|
||||
color = Color.DarkGray,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ import dev.msfjarvis.lobsters.model.LobstersPost
|
|||
import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher
|
||||
import dev.msfjarvis.lobsters.util.toDbModel
|
||||
|
||||
/**
|
||||
* Composable for rendering a list of [LobstersPost] fetched from the network.
|
||||
*/
|
||||
/** Composable for rendering a list of [LobstersPost] fetched from the network. */
|
||||
@Composable
|
||||
fun NetworkPosts(
|
||||
posts: LazyPagingItems<LobstersPost>,
|
||||
|
@ -42,11 +40,7 @@ fun NetworkPosts(
|
|||
},
|
||||
) {
|
||||
if (posts.loadState.refresh == LoadState.Loading) {
|
||||
LazyColumn {
|
||||
items(15) {
|
||||
LoadingLobstersItem()
|
||||
}
|
||||
}
|
||||
LazyColumn { items(15) { LoadingLobstersItem() } }
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
|
@ -54,8 +48,7 @@ fun NetworkPosts(
|
|||
) {
|
||||
items(posts) { item ->
|
||||
if (item != null) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val item = item.toDbModel()
|
||||
@Suppress("NAME_SHADOWING") val item = item.toDbModel()
|
||||
var isSaved by remember(item.shortId) { mutableStateOf(isPostSaved(item.shortId)) }
|
||||
|
||||
LobstersItem(
|
||||
|
|
|
@ -58,11 +58,8 @@ fun SavedPosts(
|
|||
) {
|
||||
val grouped = posts.groupBy { it.createdAt.asZonedDateTime().month }
|
||||
grouped.forEach { (month, posts) ->
|
||||
stickyHeader {
|
||||
MonthHeader(month = month)
|
||||
}
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val posts = if (sortOrder) posts.reversed() else posts
|
||||
stickyHeader { MonthHeader(month = month) }
|
||||
@Suppress("NAME_SHADOWING") val posts = if (sortOrder) posts.reversed() else posts
|
||||
items(posts) { item ->
|
||||
LobstersItem(
|
||||
post = item,
|
||||
|
|
|
@ -34,13 +34,15 @@ fun LoadingLobstersItem() {
|
|||
val alpha by infiniteTransition.animateFloat(
|
||||
initialValue = 0.2f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = 1000
|
||||
0.7f at 500
|
||||
},
|
||||
repeatMode = RepeatMode.Reverse
|
||||
)
|
||||
animationSpec =
|
||||
infiniteRepeatable(
|
||||
animation =
|
||||
keyframes {
|
||||
durationMillis = 1000
|
||||
0.7f at 500
|
||||
},
|
||||
repeatMode = RepeatMode.Reverse
|
||||
)
|
||||
)
|
||||
val color = Color.LightGray.copy(alpha = alpha)
|
||||
Surface(
|
||||
|
@ -54,29 +56,22 @@ fun LoadingLobstersItem() {
|
|||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.requiredHeight(12.dp)
|
||||
.background(color)
|
||||
.padding(8.dp),
|
||||
modifier = Modifier.fillMaxWidth().requiredHeight(12.dp).background(color).padding(8.dp),
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier
|
||||
.absoluteOffset(y = 12.dp),
|
||||
modifier = Modifier.absoluteOffset(y = 12.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(30.dp)
|
||||
.background(color = color, shape = CircleShape),
|
||||
modifier = Modifier.requiredSize(30.dp).background(color = color, shape = CircleShape),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredHeight(12.dp)
|
||||
.requiredWidth(40.dp)
|
||||
.absoluteOffset(x = 12.dp)
|
||||
.background(color),
|
||||
modifier =
|
||||
Modifier.requiredHeight(12.dp)
|
||||
.requiredWidth(40.dp)
|
||||
.absoluteOffset(x = 12.dp)
|
||||
.background(color),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -87,9 +82,5 @@ fun LoadingLobstersItem() {
|
|||
@Preview
|
||||
@Composable
|
||||
fun ShimmerListPreview() {
|
||||
LazyColumn {
|
||||
items(10) {
|
||||
LoadingLobstersItem()
|
||||
}
|
||||
}
|
||||
LazyColumn { items(10) { LoadingLobstersItem() } }
|
||||
}
|
||||
|
|
|
@ -9,27 +9,29 @@ import androidx.compose.ui.graphics.Color
|
|||
|
||||
val titleColor = Color(0xFF7395D9)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Color.White,
|
||||
secondary = Color(0xFF6C0000),
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
onPrimary = Color.DarkGray,
|
||||
onSecondary = Color.White,
|
||||
onBackground = Color.White,
|
||||
onSurface = Color.White,
|
||||
)
|
||||
val lightColors =
|
||||
lightColors(
|
||||
primary = Color.White,
|
||||
secondary = Color(0xFF6C0000),
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
onPrimary = Color.DarkGray,
|
||||
onSecondary = Color.White,
|
||||
onBackground = Color.White,
|
||||
onSurface = Color.White,
|
||||
)
|
||||
|
||||
val darkColors = darkColors(
|
||||
primary = Color.White,
|
||||
secondary = Color(0xFFD2362D),
|
||||
background = Color.Black,
|
||||
surface = Color.Black,
|
||||
onPrimary = Color.Black,
|
||||
onSecondary = Color.White,
|
||||
onBackground = Color.White,
|
||||
onSurface = Color.White,
|
||||
)
|
||||
val darkColors =
|
||||
darkColors(
|
||||
primary = Color.White,
|
||||
secondary = Color(0xFFD2362D),
|
||||
background = Color.Black,
|
||||
surface = Color.Black,
|
||||
onPrimary = Color.Black,
|
||||
onSecondary = Color.White,
|
||||
onBackground = Color.White,
|
||||
onSurface = Color.White,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LobstersTheme(children: @Composable () -> Unit) {
|
||||
|
|
|
@ -20,27 +20,38 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
class LobstersViewModel @Inject constructor(
|
||||
class LobstersViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val lobstersRepository: LobstersRepository,
|
||||
private val clawPreferences: ClawPreferences,
|
||||
) : ViewModel() {
|
||||
private val _savedPosts = MutableStateFlow<List<SavedPost>>(emptyList())
|
||||
val savedPosts = _savedPosts.asStateFlow()
|
||||
val hottestPosts = Pager(PagingConfig(25)) {
|
||||
HottestPostsPagingSource(lobstersRepository).also { hottestPostsPagingSource = it }
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
val newestPosts = Pager(PagingConfig(25)) {
|
||||
NewestPostsPagingSource(lobstersRepository).also { newestPostsPagingSource = it }
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
val hottestPosts =
|
||||
Pager(PagingConfig(25)) {
|
||||
HottestPostsPagingSource(lobstersRepository).also { hottestPostsPagingSource = it }
|
||||
}
|
||||
.flow
|
||||
.cachedIn(viewModelScope)
|
||||
val newestPosts =
|
||||
Pager(PagingConfig(25)) {
|
||||
NewestPostsPagingSource(lobstersRepository).also { newestPostsPagingSource = it }
|
||||
}
|
||||
.flow
|
||||
.cachedIn(viewModelScope)
|
||||
private var hottestPostsPagingSource: HottestPostsPagingSource? = null
|
||||
private var newestPostsPagingSource: NewestPostsPagingSource? = null
|
||||
|
||||
init {
|
||||
lobstersRepository.isCacheReady.onEach { ready ->
|
||||
if (ready) {
|
||||
_savedPosts.value = lobstersRepository.getAllPostsFromCache()
|
||||
lobstersRepository
|
||||
.isCacheReady
|
||||
.onEach { ready ->
|
||||
if (ready) {
|
||||
_savedPosts.value = lobstersRepository.getAllPostsFromCache()
|
||||
}
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun getSortOrder(): Flow<Boolean> {
|
||||
|
|
|
@ -3,9 +3,7 @@ package dev.msfjarvis.lobsters.util
|
|||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
|
||||
/**
|
||||
* Convert a [LobstersPost] object returned by the API into a [SavedPost] for persistence.
|
||||
*/
|
||||
/** Convert a [LobstersPost] object returned by the API into a [SavedPost] for persistence. */
|
||||
fun LobstersPost.toDbModel(): SavedPost {
|
||||
return SavedPost(
|
||||
shortId = shortId,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue