all: reformat with ktfmt google style

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2021-04-02 13:05:08 +05:30
parent 8448910628
commit db07a12be5
54 changed files with 496 additions and 656 deletions

View file

@ -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"))

View file

@ -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 = {},
)
}
}

View file

@ -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()
}

View file

@ -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())

View file

@ -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,
)
}

View file

@ -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
},
)
}
}

View file

@ -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
)
})
)
}
}

View file

@ -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()

View file

@ -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() }
}
}

View file

@ -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>() {

View file

@ -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>() {

View file

@ -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)
}
}
}
}

View file

@ -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(

View file

@ -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

View file

@ -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),

View file

@ -13,7 +13,6 @@ object MoshiModule {
@Provides
@Reusable
fun provideMoshi(): Moshi {
return Moshi.Builder()
.build()
return Moshi.Builder().build()
}
}

View file

@ -62,8 +62,7 @@ fun LobstersApp() {
newestPostsListState.animateScrollToItem(index)
}
}
else -> {
}
else -> {}
}
}

View file

@ -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() }
},
)
}
}

View file

@ -18,9 +18,7 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalUrlLauncher provides urlLauncher) {
LobstersTheme {
LobstersApp()
}
LobstersTheme { LobstersApp() }
}
}
}

View file

@ -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,

View file

@ -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) }
}

View file

@ -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,
)
}

View file

@ -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(

View file

@ -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,

View file

@ -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() } }
}

View file

@ -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) {

View file

@ -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> {

View file

@ -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,