✨ Android check in widget
This commit is contained in:
parent
d67e33a41d
commit
abc21f858b
@ -13,6 +13,7 @@ dependencies {
|
||||
implementation "androidx.glance:glance:1.1.1"
|
||||
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'
|
||||
}
|
||||
|
||||
android {
|
||||
@ -25,8 +26,12 @@ android {
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.8"
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
@ -35,7 +40,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "dev.solsynth.solian"
|
||||
minSdk = flutter.minSdkVersion
|
||||
minSdk = 26
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
@ -80,14 +80,23 @@
|
||||
android:value="2" />
|
||||
|
||||
<!-- Widgets -->
|
||||
<receiver android:name=".glance.HomeWidgetReceiver"
|
||||
<receiver android:name=".widgets.CheckInWidgetReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/home_widget" />
|
||||
android:resource="@xml/check_in_widget" />
|
||||
</receiver>
|
||||
<receiver android:name=".widgets.FeaturedPostWidgetReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/featured_post_widget" />
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
package dev.solsynth.solian.data
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
||||
class InstantAdapter : JsonSerializer<Instant?>,
|
||||
JsonDeserializer<Instant?> {
|
||||
override fun serialize(
|
||||
src: Instant?,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonPrimitive(formatter.format(src))
|
||||
}
|
||||
|
||||
@Throws(JsonParseException::class)
|
||||
override fun deserialize(
|
||||
json: JsonElement,
|
||||
typeOfT: Type?,
|
||||
context: JsonDeserializationContext?
|
||||
): Instant {
|
||||
return Instant.parse(json.asString)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package dev.solsynth.solian.data
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class SolarUser(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val nick: String
|
||||
)
|
||||
|
||||
data class SolarCheckInRecord(
|
||||
val id: Int,
|
||||
val resultTier: Int,
|
||||
val resultExperience: Int,
|
||||
val createdAt: Instant
|
||||
)
|
@ -0,0 +1,98 @@
|
||||
import android.content.Context
|
||||
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.Button
|
||||
import androidx.glance.GlanceId
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.provideContent
|
||||
import androidx.glance.background
|
||||
import androidx.glance.currentState
|
||||
import androidx.glance.layout.Alignment
|
||||
import androidx.glance.layout.Box
|
||||
import androidx.glance.layout.Column
|
||||
import androidx.glance.layout.Row
|
||||
import androidx.glance.layout.Spacer
|
||||
import androidx.glance.layout.fillMaxWidth
|
||||
import androidx.glance.layout.height
|
||||
import androidx.glance.layout.padding
|
||||
import androidx.glance.state.GlanceStateDefinition
|
||||
import androidx.glance.text.FontFamily
|
||||
import androidx.glance.text.FontWeight
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import com.google.gson.FieldNamingPolicy
|
||||
import com.google.gson.GsonBuilder
|
||||
import dev.solsynth.solian.data.InstantAdapter
|
||||
import dev.solsynth.solian.data.SolarCheckInRecord
|
||||
import java.time.Instant
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class CheckInWidget : GlanceAppWidget() {
|
||||
override val stateDefinition: GlanceStateDefinition<*>?
|
||||
get() = HomeWidgetGlanceStateDefinition()
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
provideContent {
|
||||
GlanceContent(context, currentState())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
||||
val gson =
|
||||
GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||
.create()
|
||||
val resultTierSymbols = listOf("大凶", "凶", "中平", "大吉", "吉")
|
||||
|
||||
val prefs = currentState.preferences
|
||||
val checkInRaw = prefs.getString("today_check_in", null)
|
||||
|
||||
Column(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.White)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
if (checkInRaw != null) {
|
||||
val checkIn = gson.fromJson(checkInRaw, SolarCheckInRecord::class.java)
|
||||
val dateFormatter = DateTimeFormatter.ofPattern("EEE, MM/dd")
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = resultTierSymbols[checkIn.resultTier],
|
||||
style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif)
|
||||
)
|
||||
Text(
|
||||
text = "+${checkIn.resultExperience} EXP",
|
||||
style = TextStyle(fontSize = 15.sp, fontFamily = FontFamily.Monospace)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
Row(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = OffsetDateTime.ofInstant(checkIn.createdAt, ZoneId.systemDefault())
|
||||
.format(dateFormatter),
|
||||
style = TextStyle(fontSize = 13.sp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = "You haven't checked in today",
|
||||
style = TextStyle(fontSize = 15.sp)
|
||||
)
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
Button(
|
||||
text = "Check In",
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package dev.solsynth.solian.widgets
|
||||
|
||||
import CheckInWidget
|
||||
import HomeWidgetGlanceWidgetReceiver
|
||||
|
||||
class CheckInWidgetReceiver : HomeWidgetGlanceWidgetReceiver<CheckInWidget>() {
|
||||
override val glanceAppWidget = CheckInWidget()
|
||||
}
|
@ -13,7 +13,7 @@ import androidx.glance.layout.padding
|
||||
import androidx.glance.state.GlanceStateDefinition
|
||||
import androidx.glance.text.Text
|
||||
|
||||
class AppWidget : GlanceAppWidget() {
|
||||
class FeaturedPostWidget : GlanceAppWidget() {
|
||||
override val stateDefinition: GlanceStateDefinition<*>?
|
||||
get() = HomeWidgetGlanceStateDefinition()
|
||||
|
||||
@ -26,11 +26,13 @@ class AppWidget : GlanceAppWidget() {
|
||||
@Composable
|
||||
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
||||
val prefs = currentState.preferences
|
||||
val counter = prefs.getInt("counter", 0)
|
||||
val checkIn = prefs.getString("post_featured", null)
|
||||
Box(modifier = GlanceModifier.background(Color.White).padding(16.dp)) {
|
||||
Text(
|
||||
counter.toString()
|
||||
)
|
||||
checkIn?.let {
|
||||
Text(it)
|
||||
} ?: run {
|
||||
Text("No featured posts")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package dev.solsynth.solian.widgets
|
||||
|
||||
import FeaturedPostWidget
|
||||
import HomeWidgetGlanceWidgetReceiver
|
||||
|
||||
class FeaturedPostWidgetReceiver : HomeWidgetGlanceWidgetReceiver<FeaturedPostWidget>() {
|
||||
override val glanceAppWidget = FeaturedPostWidget()
|
||||
}
|
7
android/app/src/main/res/xml/featured_post_widget.xml
Normal file
7
android/app/src/main/res/xml/featured_post_widget.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/glance_default_loading_layout"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="10000">
|
||||
</appwidget-provider>
|
@ -1,3 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
kotlin.suppressKotlinVersionCompatibilityCheck=true
|
||||
|
@ -18,7 +18,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.7.2' apply false
|
||||
id "com.android.application" version '8.7.3' apply false
|
||||
// START: FlutterFire Configuration
|
||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||
id "com.google.firebase.crashlytics" version "2.8.1" apply false
|
||||
|
@ -15,7 +15,8 @@ class HomeWidgetProvider {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveWidgetData(String id, dynamic data, {bool update = true}) async {
|
||||
Future<void> saveWidgetData(String id, dynamic data,
|
||||
{bool update = true}) async {
|
||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||
await HomeWidget.saveWidgetData(id, jsonEncode(data));
|
||||
if (update) await updateWidget();
|
||||
@ -29,8 +30,14 @@ class HomeWidgetProvider {
|
||||
await HomeWidget.updateWidget(
|
||||
name: widget,
|
||||
iOSName: widget,
|
||||
androidName: "com.solsynth.solian.$widget",
|
||||
qualifiedAndroidName: "group.solsynth.solian.$widget",
|
||||
);
|
||||
}
|
||||
} else if (Platform.isAndroid) {
|
||||
const widgets = ["FeaturedPostWidget", "CheckInWidget"];
|
||||
for (final widget in widgets) {
|
||||
await HomeWidget.updateWidget(
|
||||
androidName: "${widget}Receiver",
|
||||
qualifiedAndroidName: "dev.solsynth.solian.widgets.${widget}Receiver",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user