Android featured post widget

This commit is contained in:
LittleSheep 2024-12-15 18:57:54 +08:00
parent abc21f858b
commit 00678c0ac8
6 changed files with 177 additions and 12 deletions

View File

@ -14,6 +14,8 @@ 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 'io.coil-kt.coil3:coil-compose:3.0.4'
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.4'
} }
android { android {

View File

@ -52,12 +52,22 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="image/*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" /> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as

View File

@ -0,0 +1,32 @@
package dev.solsynth.solian.data
import java.time.Instant
data class SolarPost(
val id: Int,
val body: SolarPostBody,
val publisher: SolarPublisher,
val publisherId: Int,
val createdAt: Instant,
val updatedAt: Instant,
val editedAt: Instant?,
val publishedAt: Instant?
)
data class SolarPostBody(
val content: String?,
val title: String?,
val description: String?,
val attachments: List<String>?
)
data class SolarPublisher(
val id: Int,
val name: String,
val nick: String,
val description: String?,
val avatar: String?,
val banner: String?,
val createdAt: Instant,
val updatedAt: Instant
)

View File

@ -11,7 +11,6 @@ import androidx.glance.appwidget.provideContent
import androidx.glance.background import androidx.glance.background
import androidx.glance.currentState import androidx.glance.currentState
import androidx.glance.layout.Alignment import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column import androidx.glance.layout.Column
import androidx.glance.layout.Row import androidx.glance.layout.Row
import androidx.glance.layout.Spacer import androidx.glance.layout.Spacer
@ -49,7 +48,7 @@ class CheckInWidget : GlanceAppWidget() {
.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 resultTierSymbols = listOf("大凶", "", "中平", "", "") val resultTierSymbols = listOf("大凶", "", "中平", "", "")
val prefs = currentState.preferences val prefs = currentState.preferences
val checkInRaw = prefs.getString("today_check_in", null) val checkInRaw = prefs.getString("today_check_in", null)

View File

@ -1,17 +1,42 @@
import android.content.Context import android.content.Context
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidget
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.currentState
import androidx.glance.layout.Box import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding import androidx.glance.layout.padding
import androidx.glance.layout.width
import androidx.glance.state.GlanceStateDefinition import androidx.glance.state.GlanceStateDefinition
import androidx.glance.text.FontFamily
import androidx.glance.text.FontWeight
import androidx.glance.text.Text import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import coil3.ImageLoader
import coil3.compose.AsyncImage
import coil3.compose.setSingletonImageLoaderFactory
import coil3.request.crossfade
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import dev.solsynth.solian.data.InstantAdapter
import dev.solsynth.solian.data.SolarPost
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
class FeaturedPostWidget : GlanceAppWidget() { class FeaturedPostWidget : GlanceAppWidget() {
override val stateDefinition: GlanceStateDefinition<*>? override val stateDefinition: GlanceStateDefinition<*>?
@ -23,15 +48,112 @@ class FeaturedPostWidget : GlanceAppWidget() {
} }
} }
private val serverUrl = "https://api.sn.solsynth.dev"
private fun getAttachmentUrl(identifier: String): String {
return if (identifier.startsWith("http")) {
identifier
} else {
"$serverUrl/cgi/uc/attachments/$identifier"
}
}
@Composable @Composable
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) { private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
setSingletonImageLoaderFactory { context ->
ImageLoader.Builder(context)
.crossfade(true)
.build()
}
val gson =
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Instant::class.java, InstantAdapter())
.create()
val prefs = currentState.preferences val prefs = currentState.preferences
val checkIn = prefs.getString("post_featured", null) val postFeaturedRaw = prefs.getString("post_featured", null)
Box(modifier = GlanceModifier.background(Color.White).padding(16.dp)) {
checkIn?.let { Column(
Text(it) modifier = GlanceModifier
} ?: run { .fillMaxWidth()
Text("No featured posts") .background(Color.White)
.padding(16.dp)
) {
if (postFeaturedRaw != null) {
val postFeaturedList: Array<SolarPost> =
gson.fromJson(postFeaturedRaw, Array<SolarPost>::class.java)
val postFeatured = postFeaturedList.firstOrNull();
Row {
Text(
text = postFeatured?.publisher?.nick ?: "Unknown",
style = TextStyle(fontSize = 15.sp)
)
Spacer(modifier = GlanceModifier.width(8.dp))
Text(
text = "@${postFeatured?.publisher?.name}",
style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace)
)
}
Spacer(modifier = GlanceModifier.height(8.dp))
if (postFeatured?.body?.title != null) {
Text(
text = postFeatured.body.title,
style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif)
)
}
if (postFeatured?.body?.description != null) {
Text(
text = postFeatured.body.description,
style = TextStyle(fontSize = 19.sp, fontFamily = FontFamily.Serif)
)
}
if (postFeatured?.body?.title != null || postFeatured?.body?.description != null) {
Spacer(modifier = GlanceModifier.height(8.dp))
}
Text(
text = postFeatured?.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())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
style = TextStyle(fontSize = 13.sp),
)
}
Text(
"Solar Network Featured Post",
style = TextStyle(fontSize = 11.sp, fontWeight = FontWeight.Bold),
)
return@Column;
}
Column(
modifier = GlanceModifier.fillMaxSize(),
verticalAlignment = Alignment.Vertical.CenterVertically,
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
) {
Text(
text = "No featured posts",
style = TextStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)
)
Text(
text = "Open the app to load recommendations",
style = TextStyle(fontSize = 15.sp)
)
} }
} }
} }

View File

@ -60,7 +60,7 @@ struct CheckInEntry: TimelineEntry {
struct CheckInWidgetEntryView : View { struct CheckInWidgetEntryView : View {
var entry: CheckInProvider.Entry var entry: CheckInProvider.Entry
private let resultTierSymbols: [String] = ["大凶", "", "中平", "", ""] private let resultTierSymbols: [String] = ["大凶", "", "中平", "", ""]
func checkIn() -> Void {} func checkIn() -> Void {}