✨ Show random post instead of featured post
This commit is contained in:
parent
65a8f1e6c3
commit
8db6513eef
@ -14,6 +14,7 @@ dependencies {
|
|||||||
implementation "androidx.glance:glance-appwidget:1.1.1"
|
implementation "androidx.glance:glance-appwidget:1.1.1"
|
||||||
implementation 'androidx.compose.foundation:foundation-layout-android:1.7.6'
|
implementation 'androidx.compose.foundation:foundation-layout-android:1.7.6'
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
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-compose:3.0.4'
|
||||||
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.4'
|
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.4'
|
||||||
}
|
}
|
||||||
@ -50,8 +51,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled true
|
debuggable true
|
||||||
shrinkResources true
|
|
||||||
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
@ -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>)
|
@ -1,16 +1,22 @@
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.glance.GlanceId
|
import androidx.glance.GlanceId
|
||||||
import androidx.glance.GlanceModifier
|
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.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
import androidx.glance.appwidget.provideContent
|
import androidx.glance.appwidget.provideContent
|
||||||
import androidx.glance.background
|
import androidx.glance.background
|
||||||
import androidx.glance.currentState
|
|
||||||
import androidx.glance.layout.Alignment
|
import androidx.glance.layout.Alignment
|
||||||
import androidx.glance.layout.Column
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.ContentScale
|
||||||
import androidx.glance.layout.Row
|
import androidx.glance.layout.Row
|
||||||
import androidx.glance.layout.Spacer
|
import androidx.glance.layout.Spacer
|
||||||
import androidx.glance.layout.fillMaxHeight
|
import androidx.glance.layout.fillMaxHeight
|
||||||
@ -24,11 +30,27 @@ import androidx.glance.text.FontFamily
|
|||||||
import androidx.glance.text.FontWeight
|
import androidx.glance.text.FontWeight
|
||||||
import androidx.glance.text.Text
|
import androidx.glance.text.Text
|
||||||
import androidx.glance.text.TextStyle
|
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.FieldNamingPolicy
|
||||||
import com.google.gson.GsonBuilder
|
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.InstantAdapter
|
||||||
|
import dev.solsynth.solian.data.SolarPagination
|
||||||
import dev.solsynth.solian.data.SolarPost
|
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.Instant
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
@ -38,23 +60,83 @@ class FeaturedPostWidget : GlanceAppWidget() {
|
|||||||
override val stateDefinition: GlanceStateDefinition<*>?
|
override val stateDefinition: GlanceStateDefinition<*>?
|
||||||
get() = HomeWidgetGlanceStateDefinition()
|
get() = HomeWidgetGlanceStateDefinition()
|
||||||
|
|
||||||
|
private val defaultUrl = "https://api.sn.solsynth.dev"
|
||||||
|
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
provideContent {
|
provideContent {
|
||||||
GlanceContent(context, currentState())
|
GlanceTheme {
|
||||||
|
GlanceContent(context, postData, avatarImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
private val client = OkHttpClient()
|
||||||
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
|
||||||
|
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 =
|
val gson =
|
||||||
GsonBuilder()
|
GsonBuilder()
|
||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||||
.create()
|
.create()
|
||||||
|
val type = object : TypeToken<SolarPagination<SolarPost>>() {}.type
|
||||||
|
|
||||||
val prefs = currentState.preferences
|
val request = Request.Builder()
|
||||||
val postFeaturedRaw = prefs.getString("post_featured", null)
|
.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(
|
Column(
|
||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -62,55 +144,63 @@ class FeaturedPostWidget : GlanceAppWidget() {
|
|||||||
.background(Color.White)
|
.background(Color.White)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
if (postFeaturedRaw != null) {
|
if (data != null) {
|
||||||
val postFeatured = gson.fromJson(postFeaturedRaw, SolarPost::class.java)
|
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(
|
||||||
text = postFeatured?.publisher?.nick ?: "Unknown",
|
text = data.publisher.nick,
|
||||||
style = TextStyle(fontSize = 15.sp)
|
style = TextStyle(fontSize = 15.sp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "@${postFeatured?.publisher?.name}",
|
text = "@${data.publisher.name}",
|
||||||
style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace)
|
style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
|
|
||||||
if (postFeatured?.body?.title != null) {
|
if (data.body.title != null) {
|
||||||
Text(
|
Text(
|
||||||
text = postFeatured.body.title,
|
text = data.body.title,
|
||||||
style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif)
|
style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (postFeatured?.body?.description != null) {
|
if (data.body.description != null) {
|
||||||
Text(
|
Text(
|
||||||
text = postFeatured.body.description,
|
text = data.body.description,
|
||||||
style = TextStyle(fontSize = 19.sp, fontFamily = FontFamily.Serif)
|
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))
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = postFeatured?.body?.content ?: "No content",
|
text = data.body.content ?: "No content",
|
||||||
style = TextStyle(fontSize = 15.sp),
|
style = TextStyle(fontSize = 15.sp),
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
|
|
||||||
|
|
||||||
if (postFeatured?.createdAt != null) {
|
Text(
|
||||||
Text(
|
LocalDateTime.ofInstant(data.createdAt, ZoneId.systemDefault())
|
||||||
LocalDateTime.ofInstant(postFeatured.createdAt, ZoneId.systemDefault())
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
|
||||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
|
style = TextStyle(fontSize = 13.sp),
|
||||||
style = TextStyle(fontSize = 13.sp),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"Solar Network Featured Post",
|
"Solar Network Featured Post",
|
||||||
@ -126,11 +216,11 @@ class FeaturedPostWidget : GlanceAppWidget() {
|
|||||||
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
|
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "No featured posts",
|
text = "Unable to fetch post",
|
||||||
style = TextStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)
|
style = TextStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Open the app to load recommendations",
|
text = "Check your internet connection",
|
||||||
style = TextStyle(fontSize = 15.sp)
|
style = TextStyle(fontSize = 15.sp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:initialLayout="@layout/glance_default_loading_layout"
|
android:initialLayout="@layout/glance_default_loading_layout"
|
||||||
android:minWidth="320dp"
|
android:minWidth="280dp"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:resizeMode="horizontal|vertical"
|
android:resizeMode="horizontal|vertical"
|
||||||
android:updatePeriodMillis="10000">
|
android:updatePeriodMillis="10000">
|
||||||
|
@ -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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Loading…
Reference in New Issue
Block a user