desktop: integrate with API

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2021-03-14 19:57:10 +05:30
parent be6e0c9498
commit 704e59afdf
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
6 changed files with 142 additions and 22 deletions

View file

@ -68,6 +68,7 @@ object Dependencies {
const val accompanist = "dev.chrisbanes.accompanist:accompanist-coil:0.6.2" const val accompanist = "dev.chrisbanes.accompanist:accompanist-coil:0.6.2"
const val composeFlowLayout = "com.star-zero:compose-flowlayout:0.0.1" 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" const val pullToRefresh = "com.puculek.pulltorefresh:pull-to-refresh-compose:1.0.0"
object Moshi { object Moshi {
@ -100,7 +101,6 @@ object Dependencies {
object AndroidX { object AndroidX {
private const val version = "1.3.1-alpha02" private const val version = "1.3.1-alpha02"
const val runner = "androidx.test:runner:$version"
object Compose { object Compose {

View file

@ -17,6 +17,8 @@ dependencies {
implementation(compose.runtime) implementation(compose.runtime)
implementation(compose.material) implementation(compose.material)
implementation(Dependencies.Kotlin.Coroutines.jvmCore) implementation(Dependencies.Kotlin.Coroutines.jvmCore)
implementation(Dependencies.ThirdParty.kamel)
implementation(Dependencies.ThirdParty.Retrofit.moshi)
} }
compose.desktop { compose.desktop {

View file

@ -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<LobstersPost> {
return api.getHottestPosts(pageNumber)
}
}

View file

@ -1,13 +1,24 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable 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.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.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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 androidx.compose.ui.unit.dp
import dev.msfjarvis.lobsters.data.local.SavedPost 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 import java.net.URI
@Composable @Composable
@ -17,15 +28,67 @@ fun LobstersItem(
Surface( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.requiredHeight(48.dp)
.clickable { .clickable {
if (Desktop.isDesktopSupported() && Desktop.getDesktop() UrlLauncher.launch(post.url)
.isSupported(Desktop.Action.BROWSE) }
.wrapContentHeight(),
) {
Row(
modifier = Modifier
.padding(start = 12.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
Desktop.getDesktop().browse(URI(post.url)) 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<String>,
modifier: Modifier = Modifier,
) { ) {
Text(post.title) Row(
modifier = Modifier.then(modifier),
) {
tags.forEach { tag ->
Text(
text = tag,
modifier = Modifier
.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
.padding(horizontal = 8.dp),
color = Color.DarkGray,
)
}
} }
} }

View file

@ -7,23 +7,31 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.verticalScroll 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import dev.msfjarvis.lobsters.data.local.SavedPost 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( val repository = ApiRepository()
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"),
)
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
fun main() = Window(title = "Claw for lobste.rs") { fun main() = Window(title = "Claw for lobste.rs") {
val coroutineScope = rememberCoroutineScope()
var items by remember { mutableStateOf(emptyList<SavedPost>()) }
coroutineScope.launch {
withContext(Dispatchers.IO) {
items = repository.loadPosts(0).map(::toDbModel)
}
}
LobstersTheme { LobstersTheme {
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -34,9 +42,13 @@ fun main() = Window(title = "Claw for lobste.rs") {
.fillMaxSize() .fillMaxSize()
.verticalScroll(stateVertical), .verticalScroll(stateVertical),
) { ) {
if (items.isEmpty()) {
Text("Loading...")
} else {
Column { Column {
repeat(50) { items.forEach {
LobstersItem(TEST_POST) 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,
)
}

View file

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