diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 42c34ee9..95cd578b 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -68,6 +68,7 @@ object Dependencies { const val accompanist = "dev.chrisbanes.accompanist:accompanist-coil:0.6.2" const val composeFlowLayout = "com.star-zero:compose-flowlayout:0.0.1" + const val kamel = "com.alialbaali.kamel:kamel-image:0.2.0" const val pullToRefresh = "com.puculek.pulltorefresh:pull-to-refresh-compose:1.0.0" object Moshi { @@ -100,7 +101,6 @@ object Dependencies { object AndroidX { private const val version = "1.3.1-alpha02" - const val runner = "androidx.test:runner:$version" object Compose { diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 45033f0a..8a9794e6 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -17,6 +17,8 @@ dependencies { implementation(compose.runtime) implementation(compose.material) implementation(Dependencies.Kotlin.Coroutines.jvmCore) + implementation(Dependencies.ThirdParty.kamel) + implementation(Dependencies.ThirdParty.Retrofit.moshi) } compose.desktop { diff --git a/desktop/src/main/kotlin/ApiRepository.kt b/desktop/src/main/kotlin/ApiRepository.kt new file mode 100644 index 00000000..a345110f --- /dev/null +++ b/desktop/src/main/kotlin/ApiRepository.kt @@ -0,0 +1,20 @@ +import com.squareup.moshi.Moshi +import dev.msfjarvis.lobsters.data.api.LobstersApi +import dev.msfjarvis.lobsters.model.LobstersPost +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.create + +class ApiRepository { + private val moshi = Moshi.Builder() + .build() + private val retrofit = Retrofit.Builder() + .baseUrl(LobstersApi.BASE_URL) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + private val api: LobstersApi = retrofit.create() + + suspend fun loadPosts(pageNumber: Int): List { + return api.getHottestPosts(pageNumber) + } +} diff --git a/desktop/src/main/kotlin/LobstersItem.kt b/desktop/src/main/kotlin/LobstersItem.kt index 9d3e7154..e607fe05 100644 --- a/desktop/src/main/kotlin/LobstersItem.kt +++ b/desktop/src/main/kotlin/LobstersItem.kt @@ -1,13 +1,24 @@ +import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.msfjarvis.lobsters.data.local.SavedPost -import java.awt.Desktop +import io.kamel.image.KamelImage +import io.kamel.image.lazyImageResource import java.net.URI @Composable @@ -17,15 +28,67 @@ fun LobstersItem( Surface( modifier = Modifier .fillMaxWidth() - .requiredHeight(48.dp) .clickable { - if (Desktop.isDesktopSupported() && Desktop.getDesktop() - .isSupported(Desktop.Action.BROWSE) - ) { - Desktop.getDesktop().browse(URI(post.url)) + UrlLauncher.launch(post.url) + } + .wrapContentHeight(), + ) { + Row( + modifier = Modifier + .padding(start = 12.dp, end = 24.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = post.title, + color = titleColor, + fontWeight = FontWeight.Bold, + modifier = Modifier + .padding(top = 4.dp), + ) + TagRow( + tags = post.tags, + 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), + ) + Text( + text = "Submitted by ${post.submitterName}", + modifier = Modifier + .padding(4.dp), + ) } } + } + } +} + +@Composable +fun TagRow( + tags: List, + modifier: Modifier = Modifier, +) { + Row( + modifier = Modifier.then(modifier), ) { - Text(post.title) + tags.forEach { tag -> + Text( + text = tag, + modifier = Modifier + .background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp)) + .padding(horizontal = 8.dp), + color = Color.DarkGray, + ) + } } } diff --git a/desktop/src/main/kotlin/Main.kt b/desktop/src/main/kotlin/Main.kt index 153bb276..55138045 100644 --- a/desktop/src/main/kotlin/Main.kt +++ b/desktop/src/main/kotlin/Main.kt @@ -7,23 +7,31 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope 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.model.LobstersPost +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext -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 = "/avatars/Vigdis-100.png", - tags = listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"), -) +val repository = ApiRepository() @OptIn(ExperimentalStdlibApi::class) fun main() = Window(title = "Claw for lobste.rs") { + val coroutineScope = rememberCoroutineScope() + var items by remember { mutableStateOf(emptyList()) } + coroutineScope.launch { + withContext(Dispatchers.IO) { + items = repository.loadPosts(0).map(::toDbModel) + } + } LobstersTheme { Box( modifier = Modifier.fillMaxSize(), @@ -34,9 +42,13 @@ fun main() = Window(title = "Claw for lobste.rs") { .fillMaxSize() .verticalScroll(stateVertical), ) { - Column { - repeat(50) { - LobstersItem(TEST_POST) + if (items.isEmpty()) { + Text("Loading...") + } else { + Column { + items.forEach { + LobstersItem(it) + } } } } @@ -47,3 +59,16 @@ fun main() = Window(title = "Claw for lobste.rs") { } } } + +fun toDbModel(post: LobstersPost): SavedPost { + return SavedPost( + shortId = post.shortId, + title = post.title, + url = post.url, + createdAt = post.createdAt, + commentsUrl = post.commentsUrl, + submitterName = post.submitterUser.username, + submitterAvatarUrl = post.submitterUser.avatarUrl, + tags = post.tags, + ) +} diff --git a/desktop/src/main/kotlin/UrlLauncher.kt b/desktop/src/main/kotlin/UrlLauncher.kt new file mode 100644 index 00000000..3bd92c5f --- /dev/null +++ b/desktop/src/main/kotlin/UrlLauncher.kt @@ -0,0 +1,10 @@ +import java.awt.Desktop +import java.net.URI + +object UrlLauncher { + fun launch(url: String) { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI(url)) + } + } +}