Show random post instead of featured post

This commit is contained in:
LittleSheep 2024-12-21 04:12:52 +08:00
parent 65a8f1e6c3
commit 8db6513eef
5 changed files with 127 additions and 31 deletions

View File

@ -14,6 +14,7 @@ dependencies {
implementation "androidx.glance:glance-appwidget:1.1.1"
implementation 'androidx.compose.foundation:foundation-layout-android:1.7.6'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'io.coil-kt.coil3:coil-compose:3.0.4'
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.4'
}
@ -50,8 +51,7 @@ android {
buildTypes {
debug {
minifyEnabled true
shrinkResources true
debuggable true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

View File

@ -0,0 +1,6 @@
package dev.solsynth.solian.data
import androidx.annotation.Keep
@Keep
data class SolarPagination<T>(val count: Int, val data: List<T>)

View File

@ -1,16 +1,22 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.currentState
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.ContentScale
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxHeight
@ -24,11 +30,27 @@ import androidx.glance.text.FontFamily
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import coil3.Image
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import coil3.imageLoader
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.crossfade
import coil3.toBitmap
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import dev.solsynth.solian.data.InstantAdapter
import dev.solsynth.solian.data.SolarPagination
import dev.solsynth.solian.data.SolarPost
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.IOException
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
@ -38,23 +60,83 @@ class FeaturedPostWidget : GlanceAppWidget() {
override val stateDefinition: GlanceStateDefinition<*>?
get() = HomeWidgetGlanceStateDefinition()
private val defaultUrl = "https://api.sn.solsynth.dev"
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceContent(context, currentState())
// TODO: Fix this
// val state = currentState<HomeWidgetGlanceState>()
// val prefs = state.preferences
// var baseUrl = prefs.getString("nex_server_url", null) ?: defaultUrl
// if (baseUrl.startsWith("\"") && baseUrl.endsWith("\"")) {
// baseUrl = baseUrl.substring(1, baseUrl.length - 1)
// }
val postData = withContext(Dispatchers.IO) { fetchPostRandomly(defaultUrl) }
val avatarImage = withContext(Dispatchers.IO) {
postData?.publisher?.avatar?.let {
loadImageFromUrl(it)
}
}
@Composable
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
provideContent {
GlanceTheme {
GlanceContent(context, postData, avatarImage)
}
}
}
private val client = OkHttpClient()
private fun resizeBitmap(bitmap: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap {
val aspectRatio = bitmap.width.toFloat() / bitmap.height.toFloat()
val newWidth = if (bitmap.width > maxWidth) maxWidth else bitmap.width
val newHeight = (newWidth / aspectRatio).toInt()
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
return resizedBitmap
}
private fun loadImageFromUrl(url: String): Bitmap? {
val request = Request.Builder().url(url).build()
return try {
val response: Response = client.newCall(request).execute()
val inputStream = response.body?.byteStream()
val bitmap = BitmapFactory.decodeStream(inputStream)
resizeBitmap(bitmap, 120, 120)
} catch (e: IOException) {
e.printStackTrace()
null
}
}
private fun fetchPostRandomly(baseUrl: String): SolarPost? {
val gson =
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Instant::class.java, InstantAdapter())
.create()
val type = object : TypeToken<SolarPagination<SolarPost>>() {}.type
val prefs = currentState.preferences
val postFeaturedRaw = prefs.getString("post_featured", null)
val request = Request.Builder()
.url("$baseUrl/cgi/co/recommendations/shuffle?take=1")
.build()
return try {
val response: Response = client.newCall(request).execute()
if (response.isSuccessful) {
val body = response.body?.string()
val resp = gson.fromJson<SolarPagination<SolarPost>>(body, type)
resp.data.firstOrNull()
} else {
null
}
} catch (e: IOException) {
null
}
}
@Composable
private fun GlanceContent(context: Context, data: SolarPost?, avatar: Bitmap?) {
Column(
modifier = GlanceModifier
.fillMaxWidth()
@ -62,55 +144,63 @@ class FeaturedPostWidget : GlanceAppWidget() {
.background(Color.White)
.padding(16.dp)
) {
if (postFeaturedRaw != null) {
val postFeatured = gson.fromJson(postFeaturedRaw, SolarPost::class.java)
if (data != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (avatar != null) {
Image(
provider = ImageProvider(bitmap = avatar),
contentDescription = null,
modifier = GlanceModifier.width(36.dp).height(36.dp)
.cornerRadius(18.dp),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = GlanceModifier.width(8.dp))
Row {
Text(
text = postFeatured?.publisher?.nick ?: "Unknown",
text = data.publisher.nick,
style = TextStyle(fontSize = 15.sp)
)
Spacer(modifier = GlanceModifier.width(8.dp))
Text(
text = "@${postFeatured?.publisher?.name}",
text = "@${data.publisher.name}",
style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace)
)
}
Spacer(modifier = GlanceModifier.height(8.dp))
if (postFeatured?.body?.title != null) {
if (data.body.title != null) {
Text(
text = postFeatured.body.title,
text = data.body.title,
style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif)
)
}
if (postFeatured?.body?.description != null) {
if (data.body.description != null) {
Text(
text = postFeatured.body.description,
text = data.body.description,
style = TextStyle(fontSize = 19.sp, fontFamily = FontFamily.Serif)
)
}
if (postFeatured?.body?.title != null || postFeatured?.body?.description != null) {
if (data.body.title != null || data.body.description != null) {
Spacer(modifier = GlanceModifier.height(8.dp))
}
Text(
text = postFeatured?.body?.content ?: "No content",
text = data.body.content ?: "No content",
style = TextStyle(fontSize = 15.sp),
)
Spacer(modifier = GlanceModifier.height(8.dp))
if (postFeatured?.createdAt != null) {
Text(
LocalDateTime.ofInstant(postFeatured.createdAt, ZoneId.systemDefault())
LocalDateTime.ofInstant(data.createdAt, ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
style = TextStyle(fontSize = 13.sp),
)
}
Text(
"Solar Network Featured Post",
@ -126,11 +216,11 @@ class FeaturedPostWidget : GlanceAppWidget() {
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
) {
Text(
text = "No featured posts",
text = "Unable to fetch post",
style = TextStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)
)
Text(
text = "Open the app to load recommendations",
text = "Check your internet connection",
style = TextStyle(fontSize = 15.sp)
)
}

View File

@ -1,6 +1,6 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="320dp"
android:minWidth="280dp"
android:minHeight="40dp"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="10000">

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.1.1+33
version: 2.1.1+34
environment:
sdk: ^3.5.4