Compare commits
	
		
			5 Commits
		
	
	
		
			7182336a0d
			...
			b50191970e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b50191970e | |||
| 1b69e6dd42 | |||
| 39fb4d474f | |||
| 392aebcad7 | |||
| e9e3a4c474 | 
| @@ -33,22 +33,6 @@ | |||||||
|             </intent-filter> |             </intent-filter> | ||||||
|  |  | ||||||
|             <!-- Sharing Intents --> |             <!-- Sharing Intents --> | ||||||
|             <intent-filter> |  | ||||||
|                 <action android:name="android.intent.action.VIEW" /> |  | ||||||
|                 <category android:name="android.intent.category.DEFAULT" /> |  | ||||||
|                 <category android:name="android.intent.category.BROWSABLE" /> |  | ||||||
|                 <data |  | ||||||
|                     android:scheme="https" |  | ||||||
|                     android:host="sn.solsynth.dev" |  | ||||||
|                     android:pathPrefix="/invite"/> |  | ||||||
|             </intent-filter> |  | ||||||
|             <intent-filter> |  | ||||||
|                 <action android:name="android.intent.action.VIEW" /> |  | ||||||
|                 <category android:name="android.intent.category.DEFAULT" /> |  | ||||||
|                 <data |  | ||||||
|                     android:mimeType="*/*" |  | ||||||
|                     android:scheme="content" /> |  | ||||||
|             </intent-filter> |  | ||||||
|             <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" /> | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ 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.action.clickable | import androidx.glance.action.clickable | ||||||
| import androidx.glance.appwidget.GlanceAppWidget | import androidx.glance.appwidget.GlanceAppWidget | ||||||
| import androidx.glance.appwidget.provideContent | import androidx.glance.appwidget.provideContent | ||||||
| @@ -25,8 +26,10 @@ import androidx.glance.text.Text | |||||||
| import androidx.glance.text.TextStyle | import androidx.glance.text.TextStyle | ||||||
| import com.google.gson.FieldNamingPolicy | import com.google.gson.FieldNamingPolicy | ||||||
| import com.google.gson.GsonBuilder | import com.google.gson.GsonBuilder | ||||||
|  | import dev.solsynth.solian.MainActivity | ||||||
| import dev.solsynth.solian.data.InstantAdapter | import dev.solsynth.solian.data.InstantAdapter | ||||||
| import dev.solsynth.solian.data.SolarCheckInRecord | import dev.solsynth.solian.data.SolarCheckInRecord | ||||||
|  | import es.antonborri.home_widget.actionStartActivity | ||||||
| import java.time.Instant | import java.time.Instant | ||||||
| import java.time.LocalDate | import java.time.LocalDate | ||||||
| import java.time.OffsetDateTime | import java.time.OffsetDateTime | ||||||
| @@ -39,7 +42,9 @@ class CheckInWidget : GlanceAppWidget() { | |||||||
|  |  | ||||||
|     override suspend fun provideGlance(context: Context, id: GlanceId) { |     override suspend fun provideGlance(context: Context, id: GlanceId) { | ||||||
|         provideContent { |         provideContent { | ||||||
|             GlanceContent(context, currentState()) |             GlanceTheme { | ||||||
|  |                 GlanceContent(context, currentState()) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -53,18 +58,27 @@ class CheckInWidget : GlanceAppWidget() { | |||||||
|         val resultTierSymbols = listOf("大凶", "凶", "中平", "吉", "大吉") |         val resultTierSymbols = listOf("大凶", "凶", "中平", "吉", "大吉") | ||||||
|  |  | ||||||
|         val prefs = currentState.preferences |         val prefs = currentState.preferences | ||||||
|         val checkInRaw = prefs.getString("pas_check_in_record", null) |         val checkInRaw: String? = prefs.getString("pas_check_in_record", null) | ||||||
|  |  | ||||||
|  |         val checkIn: SolarCheckInRecord? = | ||||||
|  |             checkInRaw?.let { checkInRaw -> | ||||||
|  |                 gson.fromJson(checkInRaw, SolarCheckInRecord::class.java) | ||||||
|  |             } ?: null; | ||||||
|  |  | ||||||
|         Column( |         Column( | ||||||
|             modifier = GlanceModifier |             modifier = GlanceModifier | ||||||
|                 .fillMaxWidth() |                 .fillMaxWidth() | ||||||
|                 .fillMaxHeight() |                 .fillMaxHeight() | ||||||
|                 .background(Color.White) |                 .background(GlanceTheme.colors.widgetBackground) | ||||||
|                 .padding(16.dp) |                 .padding(16.dp) | ||||||
|  |                 .clickable( | ||||||
|  |                     onClick = actionStartActivity<MainActivity>( | ||||||
|  |                         context, | ||||||
|  |                         Uri.parse("https://sn.solsynth.dev") | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|         ) { |         ) { | ||||||
|             if (checkInRaw != null) { |             if (checkIn != null) { | ||||||
|                 val checkIn: SolarCheckInRecord = |  | ||||||
|                     gson.fromJson(checkInRaw, SolarCheckInRecord::class.java) |  | ||||||
|                 val dateFormatter = DateTimeFormatter.ofPattern("EEE, MM/dd") |                 val dateFormatter = DateTimeFormatter.ofPattern("EEE, MM/dd") | ||||||
|  |  | ||||||
|                 val checkDate = checkIn.createdAt.atZone(ZoneId.of("UTC")).toLocalDate() |                 val checkDate = checkIn.createdAt.atZone(ZoneId.of("UTC")).toLocalDate() | ||||||
| @@ -73,11 +87,11 @@ class CheckInWidget : GlanceAppWidget() { | |||||||
|                     Column { |                     Column { | ||||||
|                         Text( |                         Text( | ||||||
|                             text = resultTierSymbols[checkIn.resultTier], |                             text = resultTierSymbols[checkIn.resultTier], | ||||||
|                             style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif) |                             style = TextStyle(fontSize = 17.sp) | ||||||
|                         ) |                         ) | ||||||
|                         Text( |                         Text( | ||||||
|                             text = "+${checkIn.resultExperience} EXP", |                             text = "+${checkIn.resultExperience} EXP", | ||||||
|                             style = TextStyle(fontSize = 15.sp, fontFamily = FontFamily.Monospace) |                             style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace) | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|                     Spacer(modifier = GlanceModifier.height(8.dp)) |                     Spacer(modifier = GlanceModifier.height(8.dp)) | ||||||
| @@ -88,18 +102,18 @@ class CheckInWidget : GlanceAppWidget() { | |||||||
|                                 ZoneId.systemDefault() |                                 ZoneId.systemDefault() | ||||||
|                             ) |                             ) | ||||||
|                                 .format(dateFormatter), |                                 .format(dateFormatter), | ||||||
|                             style = TextStyle(fontSize = 13.sp) |                             style = TextStyle(fontSize = 11.sp) | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     return@Column; |                     return@Column; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Text( |             Text( | ||||||
|             text = "You haven't checked in today", |                 text = "You haven't checked in today", | ||||||
|             style = TextStyle(fontSize = 15.sp) |                 style = TextStyle(fontSize = 15.sp) | ||||||
|         ) |             ) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
|  | import HomeWidgetGlanceState | ||||||
|  | import HomeWidgetGlanceStateDefinition | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.graphics.Bitmap |  | ||||||
| import android.graphics.BitmapFactory |  | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.Color | ||||||
| @@ -9,17 +9,13 @@ 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.GlanceTheme | ||||||
| import androidx.glance.Image |  | ||||||
| import androidx.glance.ImageProvider |  | ||||||
| import androidx.glance.action.clickable | import androidx.glance.action.clickable | ||||||
| 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.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 | ||||||
| @@ -39,10 +35,6 @@ import dev.solsynth.solian.MainActivity | |||||||
| import dev.solsynth.solian.data.InstantAdapter | import dev.solsynth.solian.data.InstantAdapter | ||||||
| import dev.solsynth.solian.data.SolarPost | import dev.solsynth.solian.data.SolarPost | ||||||
| import es.antonborri.home_widget.actionStartActivity | import es.antonborri.home_widget.actionStartActivity | ||||||
| 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 | ||||||
| @@ -52,45 +44,18 @@ class RandomPostWidget : 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) { | ||||||
|         provideContent { |         provideContent { | ||||||
|             GlanceTheme { |             GlanceTheme { | ||||||
|                 GlanceContent(context, currentState(), null) |                 GlanceContent(context, currentState()) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     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 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Composable |     @Composable | ||||||
|     private fun GlanceContent( |     private fun GlanceContent( | ||||||
|         context: Context, |         context: Context, | ||||||
|         currentState: HomeWidgetGlanceState, |         currentState: HomeWidgetGlanceState, | ||||||
|         avatar: Bitmap? |  | ||||||
|     ) { |     ) { | ||||||
|         val prefs = currentState.preferences |         val prefs = currentState.preferences | ||||||
|         val postRaw = prefs.getString("int_random_post", null) |         val postRaw = prefs.getString("int_random_post", null) | ||||||
| @@ -109,7 +74,7 @@ class RandomPostWidget : GlanceAppWidget() { | |||||||
|             modifier = GlanceModifier |             modifier = GlanceModifier | ||||||
|                 .fillMaxWidth() |                 .fillMaxWidth() | ||||||
|                 .fillMaxHeight() |                 .fillMaxHeight() | ||||||
|                 .background(Color.White) |                 .background(GlanceTheme.colors.widgetBackground) | ||||||
|                 .padding(16.dp) |                 .padding(16.dp) | ||||||
|                 .clickable( |                 .clickable( | ||||||
|                     onClick = actionStartActivity<MainActivity>( |                     onClick = actionStartActivity<MainActivity>( | ||||||
| @@ -120,17 +85,6 @@ class RandomPostWidget : GlanceAppWidget() { | |||||||
|         ) { |         ) { | ||||||
|             if (data != null) { |             if (data != null) { | ||||||
|                 Row(verticalAlignment = Alignment.CenterVertically) { |                 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)) |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     Text( |                     Text( | ||||||
|                         text = data.publisher.nick, |                         text = data.publisher.nick, | ||||||
|                         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="120dp" |     android:minWidth="40dp" | ||||||
|     android:minHeight="40dp" |     android:minHeight="40dp" | ||||||
|     android:resizeMode="horizontal|vertical" |     android:resizeMode="horizontal|vertical" | ||||||
|     android:updatePeriodMillis="10000"> |     android:updatePeriodMillis="10000"> | ||||||
|   | |||||||
| @@ -370,6 +370,8 @@ | |||||||
|   "dailyCheckNegativeHint6": "Going out", |   "dailyCheckNegativeHint6": "Going out", | ||||||
|   "dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain", |   "dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain", | ||||||
|   "happyBirthday": "Happy birthday, {}!", |   "happyBirthday": "Happy birthday, {}!", | ||||||
|  |   "celebrateMerryXmas": "Merry christmas, {}!", | ||||||
|  |   "celebrateNewYear": "Happy new year, {}!", | ||||||
|   "friendNew": "Add Friend", |   "friendNew": "Add Friend", | ||||||
|   "friendRequests": "Friend Requests", |   "friendRequests": "Friend Requests", | ||||||
|   "friendRequestsDescription": { |   "friendRequestsDescription": { | ||||||
| @@ -455,5 +457,7 @@ | |||||||
|   "poweredBy": "Powered by {}", |   "poweredBy": "Powered by {}", | ||||||
|   "shareIntent": "Share", |   "shareIntent": "Share", | ||||||
|   "shareIntentDescription":  "What do you want to do with the content you are sharing?", |   "shareIntentDescription":  "What do you want to do with the content you are sharing?", | ||||||
|   "shareIntentPostStory": "Post a Story" |   "shareIntentPostStory": "Post a Story", | ||||||
|  |   "updateAvailable": "Update Available", | ||||||
|  |   "updateOngoing": "正在更新,请稍后..." | ||||||
| } | } | ||||||
|   | |||||||
| @@ -368,6 +368,8 @@ | |||||||
|   "dailyCheckNegativeHint6": "出门", |   "dailyCheckNegativeHint6": "出门", | ||||||
|   "dailyCheckNegativeHint6Description": "忘带伞遇上大雨", |   "dailyCheckNegativeHint6Description": "忘带伞遇上大雨", | ||||||
|   "happyBirthday": "生日快乐,{}!", |   "happyBirthday": "生日快乐,{}!", | ||||||
|  |   "celebrateMerryXmas": "圣诞快乐,{}!", | ||||||
|  |   "celebrateNewYear": "新年快乐,{}!", | ||||||
|   "friendNew": "添加好友", |   "friendNew": "添加好友", | ||||||
|   "friendRequests": "好友请求", |   "friendRequests": "好友请求", | ||||||
|   "friendRequestsDescription": { |   "friendRequestsDescription": { | ||||||
| @@ -453,5 +455,7 @@ | |||||||
|   "poweredBy": "由 {} 提供支持", |   "poweredBy": "由 {} 提供支持", | ||||||
|   "shareIntent": "分享", |   "shareIntent": "分享", | ||||||
|   "shareIntentDescription": "您想对您分享的内容做些什么?", |   "shareIntentDescription": "您想对您分享的内容做些什么?", | ||||||
|   "shareIntentPostStory": "发布动态" |   "shareIntentPostStory": "发布动态", | ||||||
|  |   "updateAvailable": "检测到更新可用", | ||||||
|  |   "updateOngoing": "正在更新,请稍后……" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import SwiftUI | |||||||
|  |  | ||||||
| struct CheckInProvider: TimelineProvider { | struct CheckInProvider: TimelineProvider { | ||||||
|     func placeholder(in context: Context) -> CheckInEntry { |     func placeholder(in context: Context) -> CheckInEntry { | ||||||
|         CheckInEntry(date: Date(), user: nil, checkIn: nil) |         CheckInEntry(date: Date(), checkIn: nil) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) { |     func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) { | ||||||
| @@ -23,24 +23,17 @@ struct CheckInProvider: TimelineProvider { | |||||||
|         jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter) |         jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter) | ||||||
|         jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase |         jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase | ||||||
|          |          | ||||||
|         let userRaw = prefs?.string(forKey: "user") |  | ||||||
|         var user: SolarUser? |  | ||||||
|         if let userRaw = userRaw { |  | ||||||
|             user = try! jsonDecoder.decode(SolarUser.self, from: userRaw.data(using: .utf8)!) |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         let checkInRaw = prefs?.string(forKey: "pas_check_in_record") |         let checkInRaw = prefs?.string(forKey: "pas_check_in_record") | ||||||
|         var checkIn: SolarCheckInRecord? |         var checkIn: SolarCheckInRecord? | ||||||
|         if let checkInRaw = checkInRaw { |         if let checkInRaw = checkInRaw { | ||||||
|             checkIn = try! jsonDecoder.decode(SolarCheckInRecord.self, from: checkInRaw.data(using: .utf8)!) |             checkIn = try! jsonDecoder.decode(SolarCheckInRecord.self, from: checkInRaw.data(using: .utf8)!) | ||||||
|             if checkIn != nil && Calendar.current.isDate(checkIn!.createdAt, inSameDayAs: Date()) { |             if checkIn != nil && !Calendar.current.isDate(checkIn!.createdAt, inSameDayAs: Date()) { | ||||||
|                 checkIn = nil |                 checkIn = nil | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         let entry = CheckInEntry( |         let entry = CheckInEntry( | ||||||
|             date: Date(), |             date: Date(), | ||||||
|             user: user, |  | ||||||
|             checkIn: checkIn |             checkIn: checkIn | ||||||
|         ) |         ) | ||||||
|         completion(entry) |         completion(entry) | ||||||
| @@ -56,7 +49,6 @@ struct CheckInProvider: TimelineProvider { | |||||||
|  |  | ||||||
| struct CheckInEntry: TimelineEntry { | struct CheckInEntry: TimelineEntry { | ||||||
|     let date: Date |     let date: Date | ||||||
|     let user: SolarUser? |  | ||||||
|     let checkIn: SolarCheckInRecord? |     let checkIn: SolarCheckInRecord? | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -135,10 +127,9 @@ struct CheckInWidget: Widget { | |||||||
| #Preview(as: .systemSmall) { | #Preview(as: .systemSmall) { | ||||||
|     CheckInWidget() |     CheckInWidget() | ||||||
| } timeline: { | } timeline: { | ||||||
|     CheckInEntry(date: .now, user: nil, checkIn: nil) |     CheckInEntry(date: .now, checkIn: nil) | ||||||
|     CheckInEntry( |     CheckInEntry( | ||||||
|         date: .now, |         date: .now, | ||||||
|         user: SolarUser(id: 1, name: "demo", nick: "Deemo"), |  | ||||||
|         checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now) |         checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ struct RandomPostProvider: TimelineProvider { | |||||||
|     func placeholder(in context: Context) -> RandomPostEntry { |     func placeholder(in context: Context) -> RandomPostEntry { | ||||||
|         RandomPostEntry(date: Date(), user: nil, randomPost: nil, family: .systemMedium) |         RandomPostEntry(date: Date(), user: nil, randomPost: nil, family: .systemMedium) | ||||||
|     } |     } | ||||||
|  |      | ||||||
|     func getSnapshot(in context: Context, completion: @escaping (RandomPostEntry) -> ()) { |     func getSnapshot(in context: Context, completion: @escaping (RandomPostEntry) -> ()) { | ||||||
|         let prefs = UserDefaults(suiteName: "group.solsynth.solian") |         let prefs = UserDefaults(suiteName: "group.solsynth.solian") | ||||||
|          |          | ||||||
| @@ -45,7 +45,7 @@ struct RandomPostProvider: TimelineProvider { | |||||||
|         ) |         ) | ||||||
|         completion(entry) |         completion(entry) | ||||||
|     } |     } | ||||||
|  |      | ||||||
|     func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { |     func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { | ||||||
|         getSnapshot(in: context) { (entry) in |         getSnapshot(in: context) { (entry) in | ||||||
|             let timeline = Timeline(entries: [entry], policy: .atEnd) |             let timeline = Timeline(entries: [entry], policy: .atEnd) | ||||||
| @@ -64,7 +64,7 @@ struct RandomPostEntry: TimelineEntry { | |||||||
|  |  | ||||||
| struct RandomPostWidgetEntryView : View { | struct RandomPostWidgetEntryView : View { | ||||||
|     var entry: RandomPostProvider.Entry |     var entry: RandomPostProvider.Entry | ||||||
|  |      | ||||||
|     var body: some View { |     var body: some View { | ||||||
|         VStack(alignment: .leading, spacing: 0) { |         VStack(alignment: .leading, spacing: 0) { | ||||||
|             if let randomPost = entry.randomPost { |             if let randomPost = entry.randomPost { | ||||||
| @@ -73,15 +73,20 @@ struct RandomPostWidgetEntryView : View { | |||||||
|                         if let avatar = randomPost.publisher.avatar { |                         if let avatar = randomPost.publisher.avatar { | ||||||
|                             let avatarUrl = getAttachmentUrl(for: avatar) |                             let avatarUrl = getAttachmentUrl(for: avatar) | ||||||
|                             let size: CGFloat = 28 |                             let size: CGFloat = 28 | ||||||
|                             let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: size, height: size), mode: .aspectFit) |                             let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: size, height: size), mode: .aspectFill) | ||||||
|                              |                              | ||||||
|                             KFImage.url(URL(string: avatarUrl)) |                             KFImage.url(URL(string: avatarUrl)) | ||||||
|                                 .resizable() |                                 .resizable() | ||||||
|                                 .setProcessor(scaleProcessor) |                                 .setProcessor(scaleProcessor) | ||||||
|                                 .fade(duration: 0.25) |                                 .fade(duration: 0.25) | ||||||
|                                 .aspectRatio(contentMode: .fit) |                                 .placeholder{ | ||||||
|  |                                     ProgressView() | ||||||
|  |                                         .progressViewStyle(CircularProgressViewStyle()) | ||||||
|  |                                 } | ||||||
|  |                                 .aspectRatio(contentMode: .fill) | ||||||
|                                 .frame(width: size, height: size) |                                 .frame(width: size, height: size) | ||||||
|                                 .cornerRadius(size / 2) |                                 .cornerRadius(size / 2) | ||||||
|  |                              | ||||||
|                                 .frame(width: size, height: size, alignment: .center) |                                 .frame(width: size, height: size, alignment: .center) | ||||||
|                         } |                         } | ||||||
|                          |                          | ||||||
| @@ -158,7 +163,7 @@ struct RandomPostWidgetEntryView : View { | |||||||
|  |  | ||||||
| struct RandomPostWidget: Widget { | struct RandomPostWidget: Widget { | ||||||
|     let kind: String = "SolarRandomPostWidget" |     let kind: String = "SolarRandomPostWidget" | ||||||
|  |      | ||||||
|     var body: some WidgetConfiguration { |     var body: some WidgetConfiguration { | ||||||
|         StaticConfiguration(kind: kind, provider: RandomPostProvider()) { entry in |         StaticConfiguration(kind: kind, provider: RandomPostProvider()) { entry in | ||||||
|             if #available(iOS 17.0, *) { |             if #available(iOS 17.0, *) { | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import 'dart:io'; | |||||||
|  |  | ||||||
| import 'package:bitsdojo_window/bitsdojo_window.dart'; | import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||||
| import 'package:croppy/croppy.dart'; | import 'package:croppy/croppy.dart'; | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:easy_localization_loader/easy_localization_loader.dart'; | import 'package:easy_localization_loader/easy_localization_loader.dart'; | ||||||
| import 'package:firebase_core/firebase_core.dart'; | import 'package:firebase_core/firebase_core.dart'; | ||||||
| @@ -12,9 +13,11 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; | import 'package:hive_flutter/hive_flutter.dart'; | ||||||
|  | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:relative_time/relative_time.dart'; | import 'package:relative_time/relative_time.dart'; | ||||||
| import 'package:responsive_framework/responsive_framework.dart'; | import 'package:responsive_framework/responsive_framework.dart'; | ||||||
|  | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:surface/firebase_options.dart'; | import 'package:surface/firebase_options.dart'; | ||||||
| import 'package:surface/providers/channel.dart'; | import 'package:surface/providers/channel.dart'; | ||||||
| @@ -38,7 +41,9 @@ import 'package:surface/types/realm.dart'; | |||||||
| import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy; | import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy; | ||||||
| import 'package:surface/widgets/dialog.dart'; | import 'package:surface/widgets/dialog.dart'; | ||||||
| import 'package:surface/widgets/version_label.dart'; | import 'package:surface/widgets/version_label.dart'; | ||||||
|  | import 'package:version/version.dart'; | ||||||
| import 'package:workmanager/workmanager.dart'; | import 'package:workmanager/workmanager.dart'; | ||||||
|  | import 'package:in_app_review/in_app_review.dart'; | ||||||
|  |  | ||||||
| @pragma('vm:entry-point') | @pragma('vm:entry-point') | ||||||
| void appBackgroundDispatcher() { | void appBackgroundDispatcher() { | ||||||
| @@ -125,7 +130,7 @@ class SolianApp extends StatelessWidget { | |||||||
|             Provider(create: (ctx) => HomeWidgetProvider(ctx)), |             Provider(create: (ctx) => HomeWidgetProvider(ctx)), | ||||||
|  |  | ||||||
|             // Preferences layer |             // Preferences layer | ||||||
|             Provider(create: (ctx) => ConfigProvider(ctx)), |             ChangeNotifierProvider(create: (ctx) => ConfigProvider(ctx)), | ||||||
|  |  | ||||||
|             // Display layer |             // Display layer | ||||||
|             ChangeNotifierProvider(create: (_) => ThemeProvider()), |             ChangeNotifierProvider(create: (_) => ThemeProvider()), | ||||||
| @@ -201,6 +206,55 @@ class _AppSplashScreen extends StatefulWidget { | |||||||
| class _AppSplashScreenState extends State<_AppSplashScreen> { | class _AppSplashScreenState extends State<_AppSplashScreen> { | ||||||
|   bool _isReady = false; |   bool _isReady = false; | ||||||
|  |  | ||||||
|  |   void _tryRequestRating() async { | ||||||
|  |     final prefs = await SharedPreferences.getInstance(); | ||||||
|  |     if (prefs.containsKey('first_boot_time')) { | ||||||
|  |       final rawTime = prefs.getString('first_boot_time'); | ||||||
|  |       final time = DateTime.tryParse(rawTime ?? ''); | ||||||
|  |       if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) { | ||||||
|  |         final inAppReview = InAppReview.instance; | ||||||
|  |         if (prefs.getBool('rating_requested') == true) return; | ||||||
|  |         if (await inAppReview.isAvailable()) { | ||||||
|  |           await inAppReview.requestReview(); | ||||||
|  |           prefs.setBool('rating_requested', true); | ||||||
|  |         } else { | ||||||
|  |           log('Unable request app review, unavailable'); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       prefs.setString('first_boot_time', DateTime.now().toIso8601String()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _checkForUpdate() async { | ||||||
|  |     if (kIsWeb) return; | ||||||
|  |     try { | ||||||
|  |       final info = await PackageInfo.fromPlatform(); | ||||||
|  |       final localVersionString = '${info.version}+${info.buildNumber}'; | ||||||
|  |       final resp = await Dio( | ||||||
|  |         BaseOptions( | ||||||
|  |           sendTimeout: const Duration(seconds: 60), | ||||||
|  |           receiveTimeout: const Duration(seconds: 60), | ||||||
|  |         ), | ||||||
|  |       ).get( | ||||||
|  |         'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1', | ||||||
|  |       ); | ||||||
|  |       final remoteVersionString = (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0'; | ||||||
|  |       final remoteVersion = Version.parse(remoteVersionString.split('+').first); | ||||||
|  |       final localVersion = Version.parse(localVersionString.split('+').first); | ||||||
|  |       final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0; | ||||||
|  |       final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0; | ||||||
|  |       log("[Update] Local: $localVersionString, Remote: $remoteVersionString"); | ||||||
|  |       if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) { | ||||||
|  |         final config = context.read<ConfigProvider>(); | ||||||
|  |         config.setUpdate(remoteVersionString); | ||||||
|  |         log("[Update] Update available: $remoteVersionString"); | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       if (mounted) context.showErrorDialog('Unable to check update: $e'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Future<void> _initialize() async { |   Future<void> _initialize() async { | ||||||
|     try { |     try { | ||||||
|       final home = context.read<HomeWidgetProvider>(); |       final home = context.read<HomeWidgetProvider>(); | ||||||
| @@ -235,7 +289,11 @@ class _AppSplashScreenState extends State<_AppSplashScreen> { | |||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     _initialize().then((_) => _postInitialization()); |     _initialize().then((_) { | ||||||
|  |       _postInitialization(); | ||||||
|  |       _tryRequestRating(); | ||||||
|  |       _checkForUpdate(); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ const Map<String, FilterQuality> kImageQualityLevel = { | |||||||
|   'settingsImageQualityHigh': FilterQuality.high, |   'settingsImageQualityHigh': FilterQuality.high, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ConfigProvider { | class ConfigProvider extends ChangeNotifier { | ||||||
|   late final SharedPreferences prefs; |   late final SharedPreferences prefs; | ||||||
|  |  | ||||||
|   late final HomeWidgetProvider _home; |   late final HomeWidgetProvider _home; | ||||||
| @@ -36,8 +36,16 @@ class ConfigProvider { | |||||||
|   String get serverUrl { |   String get serverUrl { | ||||||
|     return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; |     return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set serverUrl(String url) { |   set serverUrl(String url) { | ||||||
|     prefs.setString(kNetworkServerStoreKey, url); |     prefs.setString(kNetworkServerStoreKey, url); | ||||||
|     _home.saveWidgetData("nex_server_url", url); |     _home.saveWidgetData("nex_server_url", url); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   String? updatableVersion; | ||||||
|  |  | ||||||
|  |   void setUpdate(String newVersion) { | ||||||
|  |     updatableVersion = newVersion; | ||||||
|  |     notifyListeners(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,10 @@ | |||||||
|  | import 'dart:io'; | ||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:flutter_app_update/flutter_app_update.dart'; | ||||||
| import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| @@ -10,6 +13,7 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:surface/providers/config.dart'; | ||||||
| import 'package:surface/providers/post.dart'; | import 'package:surface/providers/post.dart'; | ||||||
| import 'package:surface/providers/sn_network.dart'; | import 'package:surface/providers/sn_network.dart'; | ||||||
| import 'package:surface/providers/userinfo.dart'; | import 'package:surface/providers/userinfo.dart'; | ||||||
| @@ -69,18 +73,15 @@ class _HomeScreenState extends State<HomeScreen> { | |||||||
|       body: LayoutBuilder( |       body: LayoutBuilder( | ||||||
|         builder: (context, constraints) { |         builder: (context, constraints) { | ||||||
|           return Align( |           return Align( | ||||||
|             alignment: constraints.maxWidth > 640 |             alignment: constraints.maxWidth > 640 ? Alignment.center : Alignment.topCenter, | ||||||
|                 ? Alignment.center |  | ||||||
|                 : Alignment.topCenter, |  | ||||||
|             child: Container( |             child: Container( | ||||||
|               constraints: const BoxConstraints(maxWidth: 640), |               constraints: const BoxConstraints(maxWidth: 640), | ||||||
|               child: SingleChildScrollView( |               child: SingleChildScrollView( | ||||||
|                 child: Column( |                 child: Column( | ||||||
|                   mainAxisAlignment: constraints.maxWidth > 640 |                   mainAxisAlignment: constraints.maxWidth > 640 ? MainAxisAlignment.center : MainAxisAlignment.start, | ||||||
|                       ? MainAxisAlignment.center |  | ||||||
|                       : MainAxisAlignment.start, |  | ||||||
|                   children: [ |                   children: [ | ||||||
|                     _HomeDashSpecialDayWidget().padding(top: 8, horizontal: 8), |                     _HomeDashSpecialDayWidget().padding(bottom: 8, horizontal: 8), | ||||||
|  |                     _HomeDashUpdateWidget(padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8)), | ||||||
|                     StaggeredGrid.extent( |                     StaggeredGrid.extent( | ||||||
|                       maxCrossAxisExtent: 280, |                       maxCrossAxisExtent: 280, | ||||||
|                       mainAxisSpacing: 8, |                       mainAxisSpacing: 8, | ||||||
| @@ -104,6 +105,52 @@ class _HomeScreenState extends State<HomeScreen> { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class _HomeDashUpdateWidget extends StatelessWidget { | ||||||
|  |   final EdgeInsets? padding; | ||||||
|  |  | ||||||
|  |   const _HomeDashUpdateWidget({super.key, this.padding}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final config = context.watch<ConfigProvider>(); | ||||||
|  |  | ||||||
|  |     return ListenableBuilder( | ||||||
|  |       listenable: config, | ||||||
|  |       builder: (context, _) { | ||||||
|  |         if (config.updatableVersion != null) { | ||||||
|  |           return Container( | ||||||
|  |             padding: padding, | ||||||
|  |             child: Card( | ||||||
|  |               child: ListTile( | ||||||
|  |                 leading: Icon(Symbols.update), | ||||||
|  |                 title: Text('updateAvailable').tr(), | ||||||
|  |                 subtitle: Text(config.updatableVersion!), | ||||||
|  |                 trailing: (kIsWeb || Platform.isWindows || Platform.isLinux) | ||||||
|  |                     ? null | ||||||
|  |                     : IconButton( | ||||||
|  |                         icon: const Icon(Symbols.arrow_right_alt), | ||||||
|  |                         onPressed: () { | ||||||
|  |                           final model = UpdateModel( | ||||||
|  |                             'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk', | ||||||
|  |                             'solian-app-release-${config.updatableVersion!}.apk', | ||||||
|  |                             'ic_notification', | ||||||
|  |                             'https://apps.apple.com/us/app/solian/id6499032345', | ||||||
|  |                           ); | ||||||
|  |                           AzhonAppUpdate.update(model); | ||||||
|  |                           context.showSnackbar('updateOngoing'.tr()); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return SizedBox.shrink(); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class _HomeDashSpecialDayWidget extends StatelessWidget { | class _HomeDashSpecialDayWidget extends StatelessWidget { | ||||||
|   const _HomeDashSpecialDayWidget({super.key}); |   const _HomeDashSpecialDayWidget({super.key}); | ||||||
|  |  | ||||||
| @@ -112,10 +159,10 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | |||||||
|     final ua = context.watch<UserProvider>(); |     final ua = context.watch<UserProvider>(); | ||||||
|     final today = DateTime.now(); |     final today = DateTime.now(); | ||||||
|     final birthday = ua.user?.profile?.birthday?.toLocal(); |     final birthday = ua.user?.profile?.birthday?.toLocal(); | ||||||
|     final isBirthday = birthday != null && |     final isBirthday = birthday != null && birthday.day == today.day && birthday.month == today.month; | ||||||
|         birthday.day == today.day && |  | ||||||
|         birthday.month == today.month; |  | ||||||
|     return Column( |     return Column( | ||||||
|  |       spacing: 8, | ||||||
|       children: [ |       children: [ | ||||||
|         if (isBirthday) |         if (isBirthday) | ||||||
|           Card( |           Card( | ||||||
| @@ -124,6 +171,20 @@ class _HomeDashSpecialDayWidget extends StatelessWidget { | |||||||
|               title: Text('happyBirthday').tr(args: [ua.user?.nick ?? 'user']), |               title: Text('happyBirthday').tr(args: [ua.user?.nick ?? 'user']), | ||||||
|             ), |             ), | ||||||
|           ).padding(bottom: 8), |           ).padding(bottom: 8), | ||||||
|  |         if (today.month == 12 && today.day == 25) | ||||||
|  |           Card( | ||||||
|  |             child: ListTile( | ||||||
|  |               leading: Text('🎄').fontSize(24), | ||||||
|  |               title: Text('celebrateMerryXmas').tr(args: [ua.user?.nick ?? 'user']), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         if (today.month == 1 && today.day == 1) | ||||||
|  |           Card( | ||||||
|  |             child: ListTile( | ||||||
|  |               leading: Text('🎉').fontSize(24), | ||||||
|  |               title: Text('celebrateNewYear').tr(args: [ua.user?.nick ?? 'user']), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -174,20 +235,15 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget _buildDetailChunk(int index, bool positive) { |   Widget _buildDetailChunk(int index, bool positive) { | ||||||
|     final prefix = |     final prefix = positive ? 'dailyCheckPositiveHint' : 'dailyCheckNegativeHint'; | ||||||
|         positive ? 'dailyCheckPositiveHint' : 'dailyCheckNegativeHint'; |     final mod = positive ? kSuggestionPositiveHintCount : kSuggestionNegativeHintCount; | ||||||
|     final mod = |  | ||||||
|         positive ? kSuggestionPositiveHintCount : kSuggestionNegativeHintCount; |  | ||||||
|     final pos = math.max(1, _todayRecord!.resultModifiers[index] % mod); |     final pos = math.max(1, _todayRecord!.resultModifiers[index] % mod); | ||||||
|     return Column( |     return Column( | ||||||
|       crossAxisAlignment: CrossAxisAlignment.start, |       crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|       children: [ |       children: [ | ||||||
|         Text( |         Text( | ||||||
|           prefix.tr(args: ['$prefix$pos'.tr()]), |           prefix.tr(args: ['$prefix$pos'.tr()]), | ||||||
|           style: Theme.of(context) |           style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), | ||||||
|               .textTheme |  | ||||||
|               .titleMedium! |  | ||||||
|               .copyWith(fontWeight: FontWeight.bold), |  | ||||||
|         ), |         ), | ||||||
|         Text( |         Text( | ||||||
|           '$prefix${pos}Description', |           '$prefix${pos}Description', | ||||||
| @@ -222,10 +278,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> { | |||||||
|               else |               else | ||||||
|                 Text( |                 Text( | ||||||
|                   'dailyCheckEverythingIsNegative', |                   'dailyCheckEverythingIsNegative', | ||||||
|                   style: Theme.of(context) |                   style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), | ||||||
|                       .textTheme |  | ||||||
|                       .titleMedium! |  | ||||||
|                       .copyWith(fontWeight: FontWeight.bold), |  | ||||||
|                 ).tr(), |                 ).tr(), | ||||||
|               const Gap(8), |               const Gap(8), | ||||||
|               if (_todayRecord?.resultTier != 4) |               if (_todayRecord?.resultTier != 4) | ||||||
| @@ -241,10 +294,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> { | |||||||
|               else |               else | ||||||
|                 Text( |                 Text( | ||||||
|                   'dailyCheckEverythingIsPositive', |                   'dailyCheckEverythingIsPositive', | ||||||
|                   style: Theme.of(context) |                   style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), | ||||||
|                       .textTheme |  | ||||||
|                       .titleMedium! |  | ||||||
|                       .copyWith(fontWeight: FontWeight.bold), |  | ||||||
|                 ).tr(), |                 ).tr(), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
| @@ -362,12 +412,10 @@ class _HomeDashNotificationWidget extends StatefulWidget { | |||||||
|   const _HomeDashNotificationWidget({super.key}); |   const _HomeDashNotificationWidget({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<_HomeDashNotificationWidget> createState() => |   State<_HomeDashNotificationWidget> createState() => _HomeDashNotificationWidgetState(); | ||||||
|       _HomeDashNotificationWidgetState(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| class _HomeDashNotificationWidgetState | class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget> { | ||||||
|     extends State<_HomeDashNotificationWidget> { |  | ||||||
|   int? _count; |   int? _count; | ||||||
|  |  | ||||||
|   Future<void> _fetchNotificationCount() async { |   Future<void> _fetchNotificationCount() async { | ||||||
| @@ -404,9 +452,7 @@ class _HomeDashNotificationWidgetState | |||||||
|                   style: Theme.of(context).textTheme.titleLarge, |                   style: Theme.of(context).textTheme.titleLarge, | ||||||
|                 ).tr(), |                 ).tr(), | ||||||
|                 Text( |                 Text( | ||||||
|                   _count == null |                   _count == null ? 'loading'.tr() : 'notificationUnreadCount'.plural(_count ?? 0), | ||||||
|                       ? 'loading'.tr() |  | ||||||
|                       : 'notificationUnreadCount'.plural(_count ?? 0), |  | ||||||
|                   style: Theme.of(context).textTheme.bodyLarge, |                   style: Theme.of(context).textTheme.bodyLarge, | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
| @@ -437,12 +483,10 @@ class _HomeDashRecommendationPostWidget extends StatefulWidget { | |||||||
|   const _HomeDashRecommendationPostWidget({super.key}); |   const _HomeDashRecommendationPostWidget({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<_HomeDashRecommendationPostWidget> createState() => |   State<_HomeDashRecommendationPostWidget> createState() => _HomeDashRecommendationPostWidgetState(); | ||||||
|       _HomeDashRecommendationPostWidgetState(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| class _HomeDashRecommendationPostWidgetState | class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendationPostWidget> { | ||||||
|     extends State<_HomeDashRecommendationPostWidget> { |  | ||||||
|   bool _isBusy = false; |   bool _isBusy = false; | ||||||
|   List<SnPost>? _posts; |   List<SnPost>? _posts; | ||||||
|  |  | ||||||
| @@ -491,8 +535,7 @@ class _HomeDashRecommendationPostWidgetState | |||||||
|           ).padding(horizontal: 18, top: 12, bottom: 8), |           ).padding(horizontal: 18, top: 12, bottom: 8), | ||||||
|           Expanded( |           Expanded( | ||||||
|             child: PageView.builder( |             child: PageView.builder( | ||||||
|               scrollBehavior: |               scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: { | ||||||
|                   ScrollConfiguration.of(context).copyWith(dragDevices: { |  | ||||||
|                 PointerDeviceKind.mouse, |                 PointerDeviceKind.mouse, | ||||||
|                 PointerDeviceKind.touch, |                 PointerDeviceKind.touch, | ||||||
|               }), |               }), | ||||||
| @@ -505,8 +548,7 @@ class _HomeDashRecommendationPostWidgetState | |||||||
|                       showMenu: false, |                       showMenu: false, | ||||||
|                     ).padding(bottom: 8), |                     ).padding(bottom: 8), | ||||||
|                     onTap: () { |                     onTap: () { | ||||||
|                       GoRouter.of(context) |                       GoRouter.of(context).pushNamed('postDetail', pathParameters: { | ||||||
|                           .pushNamed('postDetail', pathParameters: { |  | ||||||
|                         'slug': _posts![index].id.toString(), |                         'slug': _posts![index].id.toString(), | ||||||
|                       }); |                       }); | ||||||
|                     }, |                     }, | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import firebase_messaging | |||||||
| import flutter_udid | import flutter_udid | ||||||
| import flutter_webrtc | import flutter_webrtc | ||||||
| import gal | import gal | ||||||
|  | import in_app_review | ||||||
| import livekit_client | import livekit_client | ||||||
| import media_kit_libs_macos_video | import media_kit_libs_macos_video | ||||||
| import media_kit_video | import media_kit_video | ||||||
| @@ -41,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | |||||||
|   FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin")) |   FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin")) | ||||||
|   FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) |   FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) | ||||||
|   GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) |   GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) | ||||||
|  |   InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) | ||||||
|   LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) |   LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) | ||||||
|   MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) |   MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) | ||||||
|   MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) |   MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -627,6 +627,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.5.2" |     version: "4.5.2" | ||||||
|  |   flutter_app_update: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: flutter_app_update | ||||||
|  |       sha256: "09290240949c4651581cd6fc535e52d019e189e694d6019c56b5a56c2e69ba65" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "3.2.2" | ||||||
|   flutter_cache_manager: |   flutter_cache_manager: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -729,10 +737,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_udid |       name: flutter_udid | ||||||
|       sha256: "63384bd96203aaefccfd7137fab642edda18afede12b0e9e1a2c96fe2589fd07" |       sha256: be464dc5b1fb7ee894f6a32d65c086ca5e177fdcf9375ac08d77495b98150f84 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.0.0" |     version: "3.0.1" | ||||||
|   flutter_web_plugins: |   flutter_web_plugins: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -962,6 +970,22 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.2.1+1" |     version: "0.2.1+1" | ||||||
|  |   in_app_review: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: in_app_review | ||||||
|  |       sha256: "36a06771b88fb0e79985b15e7f2ac0f1142e903fe72517f3c055d78bc3bc1819" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.0.10" | ||||||
|  |   in_app_review_platform_interface: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: in_app_review_platform_interface | ||||||
|  |       sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "2.0.5" | ||||||
|   intl: |   intl: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1975,6 +1999,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.4" |     version: "2.1.4" | ||||||
|  |   version: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: version | ||||||
|  |       sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "3.0.2" | ||||||
|   very_good_infinite_list: |   very_good_infinite_list: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -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+36 | version: 2.1.1+35 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ^3.5.4 |   sdk: ^3.5.4 | ||||||
| @@ -108,6 +108,9 @@ dependencies: | |||||||
|   home_widget: ^0.7.0 |   home_widget: ^0.7.0 | ||||||
|   receive_sharing_intent: ^1.8.1 |   receive_sharing_intent: ^1.8.1 | ||||||
|   workmanager: ^0.5.2 |   workmanager: ^0.5.2 | ||||||
|  |   flutter_app_update: ^3.2.2 | ||||||
|  |   in_app_review: ^2.0.10 | ||||||
|  |   version: ^3.0.2 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								web/.well-known/apple-app-site-association
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								web/.well-known/apple-app-site-association
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | { | ||||||
|  |   "applinks": { | ||||||
|  |     "apps": [], | ||||||
|  |     "details": [ | ||||||
|  |       { | ||||||
|  |         "appIDs": [ | ||||||
|  |           "W7HPZ53V6B.dev.solsynth.solian" | ||||||
|  |         ], | ||||||
|  |         "paths": [ | ||||||
|  |           "*" | ||||||
|  |         ], | ||||||
|  |         "components": [ | ||||||
|  |           { | ||||||
|  |             "/": "/*" | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "webcredentials": { | ||||||
|  |     "apps": [ | ||||||
|  |       "W7HPZ53V6B.dev.solsynth.solian" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user