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

@ -4,16 +4,12 @@ import dev.msfjarvis.lobsters.model.LobstersPost
import retrofit2.http.GET
import retrofit2.http.Query
/**
* Simple interface defining an API for lobste.rs
*/
/** Simple interface defining an API for lobste.rs */
interface LobstersApi {
@GET("hottest.json")
suspend fun getHottestPosts(@Query("page") page: Int): List<LobstersPost>
@GET("hottest.json") suspend fun getHottestPosts(@Query("page") page: Int): List<LobstersPost>
@GET("newest.json")
suspend fun getNewestPosts(@Query("page") page: Int): List<LobstersPost>
@GET("newest.json") suspend fun getNewestPosts(@Query("page") page: Int): List<LobstersPost>
companion object {
const val BASE_URL = "https://lobste.rs"

View file

@ -5,8 +5,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class KeybaseSignature(
@Json(name = "kb_username")
val kbUsername: String,
@Json(name = "sig_hash")
val sigHash: String,
@Json(name = "kb_username") val kbUsername: String,
@Json(name = "sig_hash") val sigHash: String,
)

View file

@ -5,22 +5,16 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class LobstersPost(
@Json(name = "short_id")
val shortId: String,
@Json(name = "short_id_url")
val shortIdUrl: String,
@Json(name = "created_at")
val createdAt: String,
@Json(name = "short_id") val shortId: String,
@Json(name = "short_id_url") val shortIdUrl: String,
@Json(name = "created_at") val createdAt: String,
val title: String,
val url: String,
val score: Long,
val flags: Long,
@Json(name = "comment_count")
val commentCount: Long,
@Json(name = "comment_count") val commentCount: Long,
val description: String,
@Json(name = "comments_url")
val commentsUrl: String,
@Json(name = "submitter_user")
val submitterUser: Submitter,
@Json(name = "comments_url") val commentsUrl: String,
@Json(name = "submitter_user") val submitterUser: Submitter,
val tags: List<String>,
)

View file

@ -6,22 +6,14 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Submitter(
val username: String,
@Json(name = "created_at")
val createdAt: String,
@Json(name = "is_admin")
val isAdmin: Boolean,
@Json(name = "created_at") val createdAt: String,
@Json(name = "is_admin") val isAdmin: Boolean,
val about: String,
@Json(name = "is_moderator")
val isModerator: Boolean,
@Json(name = "is_moderator") val isModerator: Boolean,
val karma: Long = 0,
@Json(name = "avatar_url")
val avatarUrl: String,
@Json(name = "invited_by_user")
val invitedByUser: String,
@Json(name = "github_username")
val githubUsername: String? = null,
@Json(name = "twitter_username")
val twitterUsername: String? = null,
@Json(name = "keybase_signatures")
val keybaseSignatures: List<KeybaseSignature> = emptyList(),
@Json(name = "avatar_url") val avatarUrl: String,
@Json(name = "invited_by_user") val invitedByUser: String,
@Json(name = "github_username") val githubUsername: String? = null,
@Json(name = "twitter_username") val twitterUsername: String? = null,
@Json(name = "keybase_signatures") val keybaseSignatures: List<KeybaseSignature> = emptyList(),
)

View file

@ -22,11 +22,10 @@ class LobstersApiTest {
companion object {
private val webServer = MockWebServer()
private val apiData = TestUtils.getJson("hottest.json")
private val moshi = Moshi.Builder()
.build()
private val okHttp = OkHttpClient.Builder()
.build()
private val retrofit = Retrofit.Builder()
private val moshi = Moshi.Builder().build()
private val okHttp = OkHttpClient.Builder().build()
private val retrofit =
Retrofit.Builder()
.client(okHttp)
.baseUrl("http://localhost:8080/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
@ -37,7 +36,8 @@ class LobstersApiTest {
@BeforeClass
fun setUp() {
webServer.start(8080)
webServer.dispatcher = object : Dispatcher() {
webServer.dispatcher =
object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
return MockResponse().setBody(apiData).setResponseCode(200)
}
@ -60,18 +60,14 @@ class LobstersApiTest {
@Test
fun `no moderator posts in test data`() = runBlocking {
val posts = apiClient.getHottestPosts(1)
val moderatorPosts = posts.asSequence()
.filter { it.submitterUser.isModerator }
.toSet()
val moderatorPosts = posts.asSequence().filter { it.submitterUser.isModerator }.toSet()
assertTrue(moderatorPosts.isEmpty())
}
@Test
fun `posts with no urls`() = runBlocking {
val posts = apiClient.getHottestPosts(1)
val commentsOnlyPosts = posts.asSequence()
.filter { it.url.isEmpty() }
.toSet()
val commentsOnlyPosts = posts.asSequence().filter { it.url.isEmpty() }.toSet()
assertEquals(2, commentsOnlyPosts.size)
}
}

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

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() {
@ -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,16 +76,19 @@ fun PullToRefresh(
finishedListener = {
indicatorOffset = 0f
isFinishingRefresh = false
})
}
)
val offsetAnimation by animateFloatAsState(
targetValue = if (isRefreshing || isFinishingRefresh) {
targetValue =
if (isRefreshing || isFinishingRefresh) {
indicatorOffset - minRefreshOffset
} else {
0f
}
)
val resettingScrollOffsetAnimation by animateFloatAsState(
targetValue = if (isResettingScroll) {
targetValue =
if (isResettingScroll) {
scrollToReset
} else {
0f
@ -97,7 +98,8 @@ fun PullToRefresh(
indicatorOffset = 0f
isResettingScroll = false
}
})
}
)
if (isResettingScroll) {
indicatorOffset -= resettingScrollOffsetAnimation
@ -110,7 +112,8 @@ fun PullToRefresh(
isRefreshingInternal = true
}
val nestedScrollConnection = object : NestedScrollConnection {
val nestedScrollConnection =
object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
@ -118,7 +121,8 @@ fun PullToRefresh(
source: NestedScrollSource
): Offset {
if (!isRefreshing && source == NestedScrollSource.Drag) {
val diff = if (indicatorOffset + available.y > maxOffset) {
val diff =
if (indicatorOffset + available.y > maxOffset) {
available.y - (indicatorOffset + available.y - maxOffset)
} else if (indicatorOffset + available.y < 0) {
0f
@ -131,13 +135,11 @@ fun PullToRefresh(
return super.onPostScroll(consumed, available, source)
}
override fun onPreScroll(
available: Offset,
source: NestedScrollSource
): Offset {
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) {
val diff =
if (indicatorOffset + available.y < 0) {
indicatorOffset = 0f
indicatorOffset
} else {
@ -151,10 +153,7 @@ fun PullToRefresh(
return super.onPreScroll(available, source)
}
override suspend fun onPostFling(
consumed: Velocity,
available: Velocity
): Velocity {
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
if (!isRefreshing) {
if (indicatorOffset > minRefreshOffset) {
onRefresh()
@ -169,39 +168,44 @@ fun PullToRefresh(
}
Box(
modifier = CombinedModifier(
inner = Modifier
.nestedScroll(nestedScrollConnection)
.clip(RectangleShape),
modifier =
CombinedModifier(
inner = Modifier.nestedScroll(nestedScrollConnection).clip(RectangleShape),
outer = modifier
)
) {
content()
val offsetPx = if (isRefreshing || isFinishingRefresh) {
val offsetPx =
if (isRefreshing || isFinishingRefresh) {
offsetAnimation
} else {
0f
}
val absoluteOffset = BASE_OFFSET.dp + with(LocalDensity.current) {
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 progressFromOffset =
with(LocalDensity.current) {
val coeff = MAX_OFFSET / (MAX_OFFSET - BASE_OFFSET)
(indicatorOffset - BASE_OFFSET) / maxOffset * coeff
}
PullToRefreshProgressIndicator(
modifier = Modifier
.fillMaxWidth()
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
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,23 +73,22 @@ 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 {
val arrowPath =
Path().apply {
moveTo(0f, -arrowWidth)
lineTo(arrowWidth, 0f)
lineTo(0f, arrowWidth)
close()
}
Box(modifier = modifier) {
Canvas(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(), onDraw = {
Canvas(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
onDraw = {
val arcDimen = size.width - 2 * diameterOffset
withTransform({
translate(top = strokeWidthPx / 2, left = size.width / 2)
@ -107,12 +96,7 @@ fun ProgressIndicatorWithArrow(
degrees = progress * 360,
pivot = Offset(x = 0f, y = size.height / 2 - diameterOffset)
)
}) {
drawPath(
path = arrowPath,
color = color
)
}
}) { drawPath(path = arrowPath, color = color) }
drawArc(
color = color,
@ -123,7 +107,7 @@ fun ProgressIndicatorWithArrow(
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,11 +27,13 @@ class LobstersRepository constructor(
return savedPostsCache.values.toList()
}
suspend fun fetchHottestPosts(page: Int): List<LobstersPost> = withContext(Dispatchers.IO) {
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) {
suspend fun fetchNewestPosts(page: Int): List<LobstersPost> =
withContext(Dispatchers.IO) {
return@withContext lobstersApi.getNewestPosts(page)
}
@ -40,24 +43,25 @@ 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) {
private suspend fun getSavedPosts(): List<SavedPost> =
withContext(Dispatchers.IO) {
return@withContext lobstersDatabase.savedPostQueries.selectAllPosts().executeAsList()
}
suspend fun addPost(post: SavedPost) = withContext(Dispatchers.IO) {
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) {
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,21 +14,21 @@ 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(
style =
MaterialTheme.typography.h5.copy(
color = MaterialTheme.colors.onSecondary,
textAlign = TextAlign.Center,
),
@ -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,7 +39,8 @@ import dev.msfjarvis.lobsters.util.IconResource
import dev.msfjarvis.lobsters.utils.Strings
import dev.msfjarvis.lobsters.utils.get
val TEST_POST = SavedPost(
val TEST_POST =
SavedPost(
shortId = "zqyydb",
title = "k2k20 hackathon report: Bob Beck on LibreSSL progress",
url = "https://undeadly.org/cgi?action=article;sid=20200921105847",
@ -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,8 +209,8 @@ fun TagRow(
tags.forEach { tag ->
Text(
text = tag,
modifier = Modifier
.background(Color(0xFFFFFCD7), RoundedCornerShape(8.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,8 +34,10 @@ fun LoadingLobstersItem() {
val alpha by infiniteTransition.animateFloat(
initialValue = 0.2f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = keyframes {
animationSpec =
infiniteRepeatable(
animation =
keyframes {
durationMillis = 1000
0.7f at 500
},
@ -54,26 +56,19 @@ 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)
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,7 +9,8 @@ import androidx.compose.ui.graphics.Color
val titleColor = Color(0xFF7395D9)
val lightColors = lightColors(
val lightColors =
lightColors(
primary = Color.White,
secondary = Color(0xFF6C0000),
background = Color.White,
@ -20,7 +21,8 @@ val lightColors = lightColors(
onSurface = Color.White,
)
val darkColors = darkColors(
val darkColors =
darkColors(
primary = Color.White,
secondary = Color(0xFFD2362D),
background = Color.Black,

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)) {
val hottestPosts =
Pager(PagingConfig(25)) {
HottestPostsPagingSource(lobstersRepository).also { hottestPostsPagingSource = it }
}.flow.cachedIn(viewModelScope)
val newestPosts = Pager(PagingConfig(25)) {
}
.flow
.cachedIn(viewModelScope)
val newestPosts =
Pager(PagingConfig(25)) {
NewestPostsPagingSource(lobstersRepository).also { newestPostsPagingSource = it }
}.flow.cachedIn(viewModelScope)
}
.flow
.cachedIn(viewModelScope)
private var hottestPostsPagingSource: HottestPostsPagingSource? = null
private var newestPostsPagingSource: NewestPostsPagingSource? = null
init {
lobstersRepository.isCacheReady.onEach { ready ->
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,

View file

@ -1,6 +1,4 @@
plugins {
`lobsters-plugin`
}
plugins { `lobsters-plugin` }
subprojects {
configurations.configureEach {

View file

@ -1,15 +1,11 @@
plugins {
`kotlin-dsl`
}
plugins { `kotlin-dsl` }
repositories {
google()
gradlePluginPortal()
}
kotlinDslPluginOptions {
experimentalWarning.set(false)
}
kotlinDslPluginOptions { experimentalWarning.set(false) }
gradlePlugin {
plugins {

View file

@ -1,6 +1,4 @@
plugins {
`kotlin-dsl`
}
plugins { `kotlin-dsl` }
repositories {
mavenCentral()
@ -8,9 +6,7 @@ repositories {
gradlePluginPortal()
}
kotlinDslPluginOptions {
experimentalWarning.set(false)
}
kotlinDslPluginOptions { experimentalWarning.set(false) }
// force compilation of Dependencies.kt so it can be referenced in buildSrc/build.gradle.kts
sourceSets.main {

View file

@ -18,14 +18,12 @@ import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/**
* Configure root project.
* Note that classpath dependencies still need to be defined in the `buildscript` block in the top-level build.gradle.kts file.
* Configure root project. Note that classpath dependencies still need to be defined in the
* `buildscript` block in the top-level build.gradle.kts file.
*/
internal fun Project.configureForRootProject() {
// register task for cleaning the build directory in the root project
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}
tasks.register<Delete>("clean") { delete(rootProject.buildDir) }
tasks.withType<Wrapper> {
gradleVersion = "7.0-rc-1"
distributionType = Wrapper.DistributionType.ALL
@ -33,18 +31,12 @@ internal fun Project.configureForRootProject() {
}
}
/**
* Configure all projects including the root project
*/
/** Configure all projects including the root project */
internal fun Project.configureForAllProjects() {
repositories {
google()
mavenCentral()
jcenter {
content {
includeGroup("org.jetbrains.compose.*")
}
}
jcenter { content { includeGroup("org.jetbrains.compose.*") } }
maven("https://dl.bintray.com/kotlin/kotlinx") {
name = "KotlinX Bintray"
content {
@ -62,22 +54,20 @@ internal fun Project.configureForAllProjects() {
}
tasks.withType<Test> {
maxParallelForks = Runtime.getRuntime().availableProcessors() * 2
testLogging {
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
}
}
/**
* Apply configuration options for Android Application projects.
*/
/** Apply configuration options for Android Application projects. */
@Suppress("UnstableApiUsage")
internal fun BaseAppModuleExtension.configureAndroidApplicationOptions(project: Project) {
val minifySwitch =
project.providers.environmentVariable("DISABLE_MINIFY").forUseAtConfigurationTime()
project.tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
freeCompilerArgs =
freeCompilerArgs +
listOf(
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=androidx.compose.material.ExperimentalMaterialApi"
)
@ -97,9 +87,7 @@ internal fun BaseAppModuleExtension.configureAndroidApplicationOptions(project:
}
}
/**
* Apply baseline configurations for all Android projects (Application and Library).
*/
/** Apply baseline configurations for all Android projects (Application and Library). */
@Suppress("UnstableApiUsage")
internal fun TestedExtension.configureCommonAndroidOptions() {
compileSdkVersion(30)

View file

@ -9,7 +9,8 @@ import org.gradle.kotlin.dsl.withType
/**
* A plugin that enables Java 8 desugaring for consuming new Java language APIs.
*
* Apply this plugin to the build.gradle.kts file in Android Application or Android Library projects:
* Apply this plugin to the build.gradle.kts file in Android Application or Android Library
* projects:
* ```
* plugins {
* `core-library-desugaring`

View file

@ -2,9 +2,7 @@ import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
/**
* Apply default kapt configs to the [Project].
*/
/** Apply default kapt configs to the [Project]. */
internal fun Project.configureKapt() {
extensions.configure<KaptExtension> {
javacOptions {
@ -17,15 +15,14 @@ internal fun Project.configureKapt() {
}
}
// disable kapt tasks for unit tests
tasks.matching {
it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin")
}.configureEach { enabled = false }
tasks
.matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") }
.configureEach { enabled = false }
}
}
private val Project.hasDaggerCompilerDependency: Boolean
get() = configurations.any {
it.dependencies.any { dependency ->
dependency.name == "dagger-compiler"
}
get() =
configurations.any {
it.dependencies.any { dependency -> dependency.name == "dagger-compiler" }
}

View file

@ -3,7 +3,5 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
internal val additionalCompilerArgs = listOf(
"-Xopt-in=kotlin.RequiresOptIn",
"-Xskip-prerelease-check"
)
internal val additionalCompilerArgs =
listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xskip-prerelease-check")

View file

@ -29,8 +29,7 @@ class LobstersPlugin : Plugin<Project> {
project.plugins.all {
when (this) {
is JavaPlugin,
is JavaLibraryPlugin -> {
is JavaPlugin, is JavaLibraryPlugin -> {
project.tasks.withType<JavaCompile> {
options.compilerArgs.add("-Xlint:unchecked")
options.isDeprecation = true
@ -42,7 +41,9 @@ class LobstersPlugin : Plugin<Project> {
is AppPlugin -> {
project.extensions.getByType<TestedExtension>().configureCommonAndroidOptions()
project.extensions.getByType<BaseAppModuleExtension>().configureBuildSigning(project)
project.extensions.getByType<BaseAppModuleExtension>()
project
.extensions
.getByType<BaseAppModuleExtension>()
.configureAndroidApplicationOptions(project)
}
is Kapt3GradleSubplugin -> {
@ -56,4 +57,5 @@ class LobstersPlugin : Plugin<Project> {
}
}
private val Project.isRoot get() = this == this.rootProject
private val Project.isRoot
get() = this == this.rootProject

View file

@ -9,9 +9,7 @@ import org.gradle.api.Project
private const val KEYSTORE_CONFIG_PATH = "keystore.properties"
/**
* Configure signing for all build types.
*/
/** Configure signing for all build types. */
@Suppress("UnstableApiUsage")
internal fun BaseAppModuleExtension.configureBuildSigning(project: Project) {
with(project) {

View file

@ -3,7 +3,6 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
import com.android.build.gradle.internal.plugins.AppPlugin
import com.github.zafarkhaja.semver.Version
import java.io.OutputStream
@ -14,34 +13,26 @@ import org.gradle.api.Project
private const val VERSIONING_PROP_FILE = "version.properties"
private const val VERSIONING_PROP_VERSION_NAME = "versioning-plugin.versionName"
private const val VERSIONING_PROP_VERSION_CODE = "versioning-plugin.versionCode"
private const val VERSIONING_PROP_COMMENT = """
private const val VERSIONING_PROP_COMMENT =
"""
This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
"""
/**
* A Gradle [Plugin] that takes a [Project] with the [AppPlugin] applied and dynamically sets the
* versionCode and versionName properties based on values read from a [VERSIONING_PROP_FILE] file in
* the [Project.getBuildDir] directory. It also adds Gradle tasks to bump the major, minor, and patch
* versions along with one to prepare the next snapshot.
* the [Project.getBuildDir] directory. It also adds Gradle tasks to bump the major, minor, and
* patch versions along with one to prepare the next snapshot.
*/
@Suppress(
"UnstableApiUsage",
"NAME_SHADOWING"
)
@Suppress("UnstableApiUsage", "NAME_SHADOWING")
class VersioningPlugin : Plugin<Project> {
/**
* Generate the Android 'versionCode' property
*/
/** Generate the Android 'versionCode' property */
private fun Version.androidCode(): Int {
return majorVersion * 1_00_00 +
minorVersion * 1_00 +
patchVersion
return majorVersion * 1_00_00 + minorVersion * 1_00 + patchVersion
}
/**
* Write an Android-specific variant of [this] to [stream]
*/
/** Write an Android-specific variant of [this] to [stream] */
private fun Version.writeForAndroid(stream: OutputStream) {
val newVersionCode = androidCode()
val props = Properties()
@ -50,16 +41,15 @@ class VersioningPlugin : Plugin<Project> {
props.store(stream, VERSIONING_PROP_COMMENT)
}
/**
* Returns the same [Version], but with build metadata stripped.
*/
/** Returns the same [Version], but with build metadata stripped. */
private fun Version.clearPreRelease(): Version {
return Version.forIntegers(majorVersion, minorVersion, patchVersion)
}
override fun apply(project: Project) {
with(project) {
val appPlugin = requireNotNull(plugins.findPlugin(AppPlugin::class.java)) {
val appPlugin =
requireNotNull(plugins.findPlugin(AppPlugin::class.java)) {
"Plugin 'com.android.application' must be applied to use this plugin"
}
val propFile = layout.projectDirectory.file(VERSIONING_PROP_FILE)
@ -68,7 +58,8 @@ class VersioningPlugin : Plugin<Project> {
}
val contents = providers.fileContents(propFile).asText.forUseAtConfigurationTime()
val versionProps = Properties().also { it.load(contents.get().byteInputStream()) }
val versionName = requireNotNull(versionProps.getProperty(VERSIONING_PROP_VERSION_NAME)) {
val versionName =
requireNotNull(versionProps.getProperty(VERSIONING_PROP_VERSION_NAME)) {
"version.properties must contain a '$VERSIONING_PROP_VERSION_NAME' property"
}
val versionCode =
@ -80,32 +71,21 @@ class VersioningPlugin : Plugin<Project> {
afterEvaluate {
val version = Version.valueOf(versionName)
tasks.register("clearPreRelease") {
doLast {
version.clearPreRelease()
.writeForAndroid(propFile.asFile.outputStream())
}
doLast { version.clearPreRelease().writeForAndroid(propFile.asFile.outputStream()) }
}
tasks.register("bumpMajor") {
doLast {
version.incrementMajorVersion()
.writeForAndroid(propFile.asFile.outputStream())
}
doLast { version.incrementMajorVersion().writeForAndroid(propFile.asFile.outputStream()) }
}
tasks.register("bumpMinor") {
doLast {
version.incrementMinorVersion()
.writeForAndroid(propFile.asFile.outputStream())
}
doLast { version.incrementMinorVersion().writeForAndroid(propFile.asFile.outputStream()) }
}
tasks.register("bumpPatch") {
doLast {
version.incrementPatchVersion()
.writeForAndroid(propFile.asFile.outputStream())
}
doLast { version.incrementPatchVersion().writeForAndroid(propFile.asFile.outputStream()) }
}
tasks.register("bumpSnapshot") {
doLast {
version.incrementMinorVersion()
version
.incrementMinorVersion()
.setPreReleaseVersion("SNAPSHOT")
.writeForAndroid(propFile.asFile.outputStream())
}

View file

@ -5,9 +5,7 @@ plugins {
`lobsters-plugin`
}
repositories {
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
repositories { maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
// workaround for https://youtrack.jetbrains.com/issue/KT-43944
android {
@ -40,23 +38,11 @@ kotlin {
}
}
val jvmMain by getting {
dependencies {
implementation(compose.runtime)
}
}
val jvmMain by getting { dependencies { implementation(compose.runtime) } }
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}
val androidTest by getting { dependencies { implementation(kotlin("test-junit")) } }
val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}
val jvmTest by getting { dependencies { implementation(kotlin("test-junit")) } }
val commonTest by getting {
dependencies {
@ -68,9 +54,7 @@ kotlin {
}
android {
buildFeatures {
androidResources = true
}
buildFeatures { androidResources = true }
sourceSets {
named("main") {

View file

@ -6,7 +6,8 @@ import androidx.browser.customtabs.CustomTabsIntent
actual class UrlLauncher(private val context: Context) {
actual fun launch(url: String) {
val customTabsIntent = CustomTabsIntent.Builder()
val customTabsIntent =
CustomTabsIntent.Builder()
.setShareState(CustomTabsIntent.SHARE_STATE_ON)
.setShowTitle(true)
.setColorScheme(CustomTabsIntent.COLOR_SCHEME_DARK)

View file

@ -2,8 +2,6 @@ package dev.msfjarvis.lobsters.utils
import androidx.compose.runtime.Composable
@Composable
expect fun Strings.get(): String
@Composable expect fun Strings.get(): String
@Composable
expect fun Strings.get(fmt: Any): String
@Composable expect fun Strings.get(fmt: Any): String

View file

@ -13,5 +13,4 @@ enum class Strings {
SavedPosts,
SubmittedBy,
NewestPosts,
;
}

View file

@ -17,7 +17,8 @@ class SqlDelightQueriesTest {
fun setUp() {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
LobstersDatabase.Schema.create(driver)
val database = LobstersDatabase(
val database =
LobstersDatabase(
driver,
SavedPost.Adapter(TagsAdapter()),
)
@ -128,12 +129,12 @@ class SqlDelightQueriesTest {
assertEquals(0, postsCount)
}
private fun createTestData(count: Int): ArrayList<SavedPost> {
val posts = arrayListOf<SavedPost>()
for (i in 1..count) {
val post = SavedPost(
val post =
SavedPost(
shortId = "test_id_$i",
createdAt = "0",
title = "test",

View file

@ -6,9 +6,7 @@ plugins {
`lobsters-plugin`
}
repositories {
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
repositories { maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
dependencies {
implementation(project(":api"))
@ -22,8 +20,4 @@ dependencies {
implementation(Dependencies.ThirdParty.Retrofit.moshi)
}
compose.desktop {
application {
mainClass = "dev.msfjarvis.lobsters.ui.Main"
}
}
compose.desktop { application { mainClass = "dev.msfjarvis.lobsters.ui.Main" } }

View file

@ -8,9 +8,9 @@ import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.create
class ApiRepository {
private val moshi = Moshi.Builder()
.build()
private val retrofit = Retrofit.Builder()
private val moshi = Moshi.Builder().build()
private val retrofit =
Retrofit.Builder()
.baseUrl(LobstersApi.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()

View file

@ -29,16 +29,11 @@ fun LobstersItem(
post: SavedPost,
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.clickable {
UrlLauncher.launch(post.url)
}
.wrapContentHeight(),
modifier =
Modifier.fillMaxWidth().clickable { UrlLauncher.launch(post.url) }.wrapContentHeight(),
) {
Row(
modifier = Modifier
.padding(start = 12.dp, end = 24.dp),
modifier = Modifier.padding(start = 12.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
@ -49,26 +44,21 @@ fun LobstersItem(
text = post.title,
color = titleColor,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(top = 4.dp),
modifier = Modifier.padding(top = 4.dp),
)
TagRow(
tags = post.tags,
modifier = Modifier
.padding(top = 8.dp, bottom = 8.dp, end = 16.dp),
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, end = 16.dp),
)
Row {
KamelImage(
resource = lazyImageResource(data = URI(post.submitterAvatarUrl)),
contentDescription = "${post.submitterName}'s avatar",
modifier = Modifier
.requiredWidth(30.dp)
.padding(4.dp),
modifier = Modifier.requiredWidth(30.dp).padding(4.dp),
)
Text(
text = "Submitted by ${post.submitterName}",
modifier = Modifier
.padding(4.dp),
modifier = Modifier.padding(4.dp),
)
}
}
@ -87,8 +77,8 @@ fun TagRow(
tags.forEach { tag ->
Text(
text = tag,
modifier = Modifier
.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
modifier =
Modifier.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp),
color = Color.DarkGray,
)

View file

@ -1,4 +1,5 @@
@file:JvmName("Main")
package dev.msfjarvis.lobsters.ui
import androidx.compose.desktop.Window
@ -12,15 +13,15 @@ import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import dev.msfjarvis.lobsters.data.local.SavedPost
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.msfjarvis.lobsters.data.ApiRepository
import dev.msfjarvis.lobsters.data.local.SavedPost
import dev.msfjarvis.lobsters.model.LobstersPost
import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher
import dev.msfjarvis.lobsters.ui.urllauncher.UrlLauncher
@ -31,14 +32,13 @@ import kotlinx.coroutines.withContext
val repository = ApiRepository()
@OptIn(ExperimentalStdlibApi::class)
fun main() = Window(title = "Claw for lobste.rs") {
fun main() =
Window(title = "Claw for lobste.rs") {
val urlLauncher = UrlLauncher()
val coroutineScope = rememberCoroutineScope()
var items by remember { mutableStateOf(emptyList<SavedPost>()) }
coroutineScope.launch {
withContext(Dispatchers.IO) {
items = repository.loadPosts(0).map(::toDbModel)
}
withContext(Dispatchers.IO) { items = repository.loadPosts(0).map(::toDbModel) }
}
LobstersTheme {
Box(
@ -46,19 +46,13 @@ fun main() = Window(title = "Claw for lobste.rs") {
) {
val stateVertical = rememberScrollState(0)
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical),
modifier = Modifier.fillMaxSize().verticalScroll(stateVertical),
) {
if (items.isEmpty()) {
Text("Loading...")
} else {
CompositionLocalProvider(LocalUrlLauncher provides urlLauncher) {
Column {
items.forEach {
LobstersItem(it)
}
}
Column { items.forEach { LobstersItem(it) } }
}
}
}

View file

@ -1,4 +1,5 @@
@file:Suppress("UNUSED")
package dev.msfjarvis.lobsters.ui
import androidx.compose.material.MaterialTheme
@ -9,7 +10,8 @@ import androidx.compose.ui.graphics.Color
val titleColor = Color(0xFF7395D9)
val lightColors = lightColors(
val lightColors =
lightColors(
primary = Color.White,
secondary = Color(0xFF6C0000),
background = Color.White,
@ -20,7 +22,8 @@ val lightColors = lightColors(
onSurface = Color.White,
)
val darkColors = darkColors(
val darkColors =
darkColors(
primary = Color.White,
secondary = Color(0xFF6C0000),
background = Color.Black,
@ -32,10 +35,7 @@ val darkColors = darkColors(
)
@Composable
fun LobstersTheme(
useLightColors: Boolean = true,
children: @Composable () -> Unit
) {
fun LobstersTheme(useLightColors: Boolean = true, children: @Composable () -> Unit) {
MaterialTheme(
colors = if (useLightColors) lightColors else darkColors,
content = children,

View file

@ -1,5 +1,7 @@
rootProject.name = "Claw"
include(":app", ":api", ":common", ":database", ":desktop")
pluginManagement {
repositories {
google()