Compare commits

...

5 Commits

Author SHA1 Message Date
b50191970e 🚀 Launch 2.1.1+35 2024-12-21 23:30:59 +08:00
1b69e6dd42 📝 Remove todo 2024-12-21 23:26:58 +08:00
39fb4d474f App updates & web deeplink 2024-12-21 23:26:42 +08:00
392aebcad7 🐛 Fix android widgets 2024-12-21 22:55:35 +08:00
e9e3a4c474 🐛 Bug fixes on iOS widget 2024-12-21 22:38:22 +08:00
15 changed files with 274 additions and 148 deletions

View File

@ -33,22 +33,6 @@
</intent-filter>
<!-- 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>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />

View File

@ -6,6 +6,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
@ -25,8 +26,10 @@ import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import dev.solsynth.solian.MainActivity
import dev.solsynth.solian.data.InstantAdapter
import dev.solsynth.solian.data.SolarCheckInRecord
import es.antonborri.home_widget.actionStartActivity
import java.time.Instant
import java.time.LocalDate
import java.time.OffsetDateTime
@ -39,7 +42,9 @@ class CheckInWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceContent(context, currentState())
GlanceTheme {
GlanceContent(context, currentState())
}
}
}
@ -53,18 +58,27 @@ class CheckInWidget : GlanceAppWidget() {
val resultTierSymbols = listOf("大凶", "", "中平", "", "大吉")
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(
modifier = GlanceModifier
.fillMaxWidth()
.fillMaxHeight()
.background(Color.White)
.background(GlanceTheme.colors.widgetBackground)
.padding(16.dp)
.clickable(
onClick = actionStartActivity<MainActivity>(
context,
Uri.parse("https://sn.solsynth.dev")
)
)
) {
if (checkInRaw != null) {
val checkIn: SolarCheckInRecord =
gson.fromJson(checkInRaw, SolarCheckInRecord::class.java)
if (checkIn != null) {
val dateFormatter = DateTimeFormatter.ofPattern("EEE, MM/dd")
val checkDate = checkIn.createdAt.atZone(ZoneId.of("UTC")).toLocalDate()
@ -73,11 +87,11 @@ class CheckInWidget : GlanceAppWidget() {
Column {
Text(
text = resultTierSymbols[checkIn.resultTier],
style = TextStyle(fontSize = 25.sp, fontFamily = FontFamily.Serif)
style = TextStyle(fontSize = 17.sp)
)
Text(
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))
@ -88,18 +102,18 @@ class CheckInWidget : GlanceAppWidget() {
ZoneId.systemDefault()
)
.format(dateFormatter),
style = TextStyle(fontSize = 13.sp)
style = TextStyle(fontSize = 11.sp)
)
}
return@Column;
}
}
}
Text(
text = "You haven't checked in today",
style = TextStyle(fontSize = 15.sp)
)
Text(
text = "You haven't checked in today",
style = TextStyle(fontSize = 15.sp)
)
}
}
}

View File

@ -1,6 +1,6 @@
import HomeWidgetGlanceState
import HomeWidgetGlanceStateDefinition
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
@ -9,17 +9,13 @@ import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.currentState
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.ContentScale
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxHeight
@ -39,10 +35,6 @@ import dev.solsynth.solian.MainActivity
import dev.solsynth.solian.data.InstantAdapter
import dev.solsynth.solian.data.SolarPost
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.LocalDateTime
import java.time.ZoneId
@ -52,45 +44,18 @@ class RandomPostWidget : GlanceAppWidget() {
override val stateDefinition: GlanceStateDefinition<*>?
get() = HomeWidgetGlanceStateDefinition()
private val defaultUrl = "https://api.sn.solsynth.dev"
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
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
private fun GlanceContent(
context: Context,
currentState: HomeWidgetGlanceState,
avatar: Bitmap?
) {
val prefs = currentState.preferences
val postRaw = prefs.getString("int_random_post", null)
@ -109,7 +74,7 @@ class RandomPostWidget : GlanceAppWidget() {
modifier = GlanceModifier
.fillMaxWidth()
.fillMaxHeight()
.background(Color.White)
.background(GlanceTheme.colors.widgetBackground)
.padding(16.dp)
.clickable(
onClick = actionStartActivity<MainActivity>(
@ -120,17 +85,6 @@ class RandomPostWidget : GlanceAppWidget() {
) {
if (data != null) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (avatar != null) {
Image(
provider = ImageProvider(bitmap = avatar),
contentDescription = null,
modifier = GlanceModifier.width(36.dp).height(36.dp)
.cornerRadius(18.dp),
contentScale = ContentScale.Crop
)
Spacer(modifier = GlanceModifier.width(8.dp))
}
Text(
text = data.publisher.nick,
style = TextStyle(fontSize = 15.sp)

View File

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

View File

@ -370,6 +370,8 @@
"dailyCheckNegativeHint6": "Going out",
"dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain",
"happyBirthday": "Happy birthday, {}!",
"celebrateMerryXmas": "Merry christmas, {}",
"celebrateNewYear": "Happy new year, {}",
"friendNew": "Add Friend",
"friendRequests": "Friend Requests",
"friendRequestsDescription": {
@ -455,5 +457,7 @@
"poweredBy": "Powered by {}",
"shareIntent": "Share",
"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": "正在更新,请稍后..."
}

View File

@ -368,6 +368,8 @@
"dailyCheckNegativeHint6": "出门",
"dailyCheckNegativeHint6Description": "忘带伞遇上大雨",
"happyBirthday": "生日快乐,{}",
"celebrateMerryXmas": "圣诞快乐,{}",
"celebrateNewYear": "新年快乐,{}",
"friendNew": "添加好友",
"friendRequests": "好友请求",
"friendRequestsDescription": {
@ -453,5 +455,7 @@
"poweredBy": "由 {} 提供支持",
"shareIntent": "分享",
"shareIntentDescription": "您想对您分享的内容做些什么?",
"shareIntentPostStory": "发布动态"
"shareIntentPostStory": "发布动态",
"updateAvailable": "检测到更新可用",
"updateOngoing": "正在更新,请稍后……"
}

View File

@ -10,7 +10,7 @@ import SwiftUI
struct CheckInProvider: TimelineProvider {
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) -> ()) {
@ -23,24 +23,17 @@ struct CheckInProvider: TimelineProvider {
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
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")
var checkIn: SolarCheckInRecord?
if let checkInRaw = checkInRaw {
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
}
}
let entry = CheckInEntry(
date: Date(),
user: user,
checkIn: checkIn
)
completion(entry)
@ -56,7 +49,6 @@ struct CheckInProvider: TimelineProvider {
struct CheckInEntry: TimelineEntry {
let date: Date
let user: SolarUser?
let checkIn: SolarCheckInRecord?
}
@ -135,10 +127,9 @@ struct CheckInWidget: Widget {
#Preview(as: .systemSmall) {
CheckInWidget()
} timeline: {
CheckInEntry(date: .now, user: nil, checkIn: nil)
CheckInEntry(date: .now, checkIn: nil)
CheckInEntry(
date: .now,
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now)
)
}

View File

@ -13,7 +13,7 @@ struct RandomPostProvider: TimelineProvider {
func placeholder(in context: Context) -> RandomPostEntry {
RandomPostEntry(date: Date(), user: nil, randomPost: nil, family: .systemMedium)
}
func getSnapshot(in context: Context, completion: @escaping (RandomPostEntry) -> ()) {
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
@ -45,7 +45,7 @@ struct RandomPostProvider: TimelineProvider {
)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
getSnapshot(in: context) { (entry) in
let timeline = Timeline(entries: [entry], policy: .atEnd)
@ -64,7 +64,7 @@ struct RandomPostEntry: TimelineEntry {
struct RandomPostWidgetEntryView : View {
var entry: RandomPostProvider.Entry
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let randomPost = entry.randomPost {
@ -73,15 +73,20 @@ struct RandomPostWidgetEntryView : View {
if let avatar = randomPost.publisher.avatar {
let avatarUrl = getAttachmentUrl(for: avatar)
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))
.resizable()
.setProcessor(scaleProcessor)
.fade(duration: 0.25)
.aspectRatio(contentMode: .fit)
.placeholder{
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
.aspectRatio(contentMode: .fill)
.frame(width: size, height: size)
.cornerRadius(size / 2)
.frame(width: size, height: size, alignment: .center)
}
@ -158,7 +163,7 @@ struct RandomPostWidgetEntryView : View {
struct RandomPostWidget: Widget {
let kind: String = "SolarRandomPostWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: RandomPostProvider()) { entry in
if #available(iOS 17.0, *) {

View File

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:croppy/croppy.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization_loader/easy_localization_loader.dart';
import 'package:firebase_core/firebase_core.dart';
@ -12,9 +13,11 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:relative_time/relative_time.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/firebase_options.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:surface/widgets/dialog.dart';
import 'package:surface/widgets/version_label.dart';
import 'package:version/version.dart';
import 'package:workmanager/workmanager.dart';
import 'package:in_app_review/in_app_review.dart';
@pragma('vm:entry-point')
void appBackgroundDispatcher() {
@ -125,7 +130,7 @@ class SolianApp extends StatelessWidget {
Provider(create: (ctx) => HomeWidgetProvider(ctx)),
// Preferences layer
Provider(create: (ctx) => ConfigProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => ConfigProvider(ctx)),
// Display layer
ChangeNotifierProvider(create: (_) => ThemeProvider()),
@ -201,6 +206,55 @@ class _AppSplashScreen extends StatefulWidget {
class _AppSplashScreenState extends State<_AppSplashScreen> {
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 {
try {
final home = context.read<HomeWidgetProvider>();
@ -235,7 +289,11 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
@override
void initState() {
super.initState();
_initialize().then((_) => _postInitialization());
_initialize().then((_) {
_postInitialization();
_tryRequestRating();
_checkForUpdate();
});
}
@override

View File

@ -16,7 +16,7 @@ const Map<String, FilterQuality> kImageQualityLevel = {
'settingsImageQualityHigh': FilterQuality.high,
};
class ConfigProvider {
class ConfigProvider extends ChangeNotifier {
late final SharedPreferences prefs;
late final HomeWidgetProvider _home;
@ -36,8 +36,16 @@ class ConfigProvider {
String get serverUrl {
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
}
set serverUrl(String url) {
prefs.setString(kNetworkServerStoreKey, url);
_home.saveWidgetData("nex_server_url", url);
}
String? updatableVersion;
void setUpdate(String newVersion) {
updatableVersion = newVersion;
notifyListeners();
}
}

View File

@ -1,7 +1,10 @@
import 'dart:io';
import 'dart:math' as math;
import 'dart:ui';
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:gap/gap.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:styled_widget/styled_widget.dart';
import 'package:flutter/material.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
@ -69,18 +73,15 @@ class _HomeScreenState extends State<HomeScreen> {
body: LayoutBuilder(
builder: (context, constraints) {
return Align(
alignment: constraints.maxWidth > 640
? Alignment.center
: Alignment.topCenter,
alignment: constraints.maxWidth > 640 ? Alignment.center : Alignment.topCenter,
child: Container(
constraints: const BoxConstraints(maxWidth: 640),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: constraints.maxWidth > 640
? MainAxisAlignment.center
: MainAxisAlignment.start,
mainAxisAlignment: constraints.maxWidth > 640 ? MainAxisAlignment.center : MainAxisAlignment.start,
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(
maxCrossAxisExtent: 280,
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 {
const _HomeDashSpecialDayWidget({super.key});
@ -112,10 +159,10 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
final ua = context.watch<UserProvider>();
final today = DateTime.now();
final birthday = ua.user?.profile?.birthday?.toLocal();
final isBirthday = birthday != null &&
birthday.day == today.day &&
birthday.month == today.month;
final isBirthday = birthday != null && birthday.day == today.day && birthday.month == today.month;
return Column(
spacing: 8,
children: [
if (isBirthday)
Card(
@ -124,6 +171,20 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
title: Text('happyBirthday').tr(args: [ua.user?.nick ?? 'user']),
),
).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) {
final prefix =
positive ? 'dailyCheckPositiveHint' : 'dailyCheckNegativeHint';
final mod =
positive ? kSuggestionPositiveHintCount : kSuggestionNegativeHintCount;
final prefix = positive ? 'dailyCheckPositiveHint' : 'dailyCheckNegativeHint';
final mod = positive ? kSuggestionPositiveHintCount : kSuggestionNegativeHintCount;
final pos = math.max(1, _todayRecord!.resultModifiers[index] % mod);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
prefix.tr(args: ['$prefix$pos'.tr()]),
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold),
),
Text(
'$prefix${pos}Description',
@ -222,10 +278,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
else
Text(
'dailyCheckEverythingIsNegative',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold),
).tr(),
const Gap(8),
if (_todayRecord?.resultTier != 4)
@ -241,10 +294,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
else
Text(
'dailyCheckEverythingIsPositive',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold),
).tr(),
],
),
@ -362,12 +412,10 @@ class _HomeDashNotificationWidget extends StatefulWidget {
const _HomeDashNotificationWidget({super.key});
@override
State<_HomeDashNotificationWidget> createState() =>
_HomeDashNotificationWidgetState();
State<_HomeDashNotificationWidget> createState() => _HomeDashNotificationWidgetState();
}
class _HomeDashNotificationWidgetState
extends State<_HomeDashNotificationWidget> {
class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget> {
int? _count;
Future<void> _fetchNotificationCount() async {
@ -404,9 +452,7 @@ class _HomeDashNotificationWidgetState
style: Theme.of(context).textTheme.titleLarge,
).tr(),
Text(
_count == null
? 'loading'.tr()
: 'notificationUnreadCount'.plural(_count ?? 0),
_count == null ? 'loading'.tr() : 'notificationUnreadCount'.plural(_count ?? 0),
style: Theme.of(context).textTheme.bodyLarge,
),
],
@ -437,12 +483,10 @@ class _HomeDashRecommendationPostWidget extends StatefulWidget {
const _HomeDashRecommendationPostWidget({super.key});
@override
State<_HomeDashRecommendationPostWidget> createState() =>
_HomeDashRecommendationPostWidgetState();
State<_HomeDashRecommendationPostWidget> createState() => _HomeDashRecommendationPostWidgetState();
}
class _HomeDashRecommendationPostWidgetState
extends State<_HomeDashRecommendationPostWidget> {
class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendationPostWidget> {
bool _isBusy = false;
List<SnPost>? _posts;
@ -491,8 +535,7 @@ class _HomeDashRecommendationPostWidgetState
).padding(horizontal: 18, top: 12, bottom: 8),
Expanded(
child: PageView.builder(
scrollBehavior:
ScrollConfiguration.of(context).copyWith(dragDevices: {
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
}),
@ -505,8 +548,7 @@ class _HomeDashRecommendationPostWidgetState
showMenu: false,
).padding(bottom: 8),
onTap: () {
GoRouter.of(context)
.pushNamed('postDetail', pathParameters: {
GoRouter.of(context).pushNamed('postDetail', pathParameters: {
'slug': _posts![index].id.toString(),
});
},

View File

@ -16,6 +16,7 @@ import firebase_messaging
import flutter_udid
import flutter_webrtc
import gal
import in_app_review
import livekit_client
import media_kit_libs_macos_video
import media_kit_video
@ -41,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))

View File

@ -627,6 +627,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -729,10 +737,10 @@ packages:
dependency: "direct main"
description:
name: flutter_udid
sha256: "63384bd96203aaefccfd7137fab642edda18afede12b0e9e1a2c96fe2589fd07"
sha256: be464dc5b1fb7ee894f6a32d65c086ca5e177fdcf9375ac08d77495b98150f84
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.1"
flutter_web_plugins:
dependency: "direct main"
description: flutter
@ -962,6 +970,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -1975,6 +1999,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.1.1+36
version: 2.1.1+35
environment:
sdk: ^3.5.4
@ -108,6 +108,9 @@ dependencies:
home_widget: ^0.7.0
receive_sharing_intent: ^1.8.1
workmanager: ^0.5.2
flutter_app_update: ^3.2.2
in_app_review: ^2.0.10
version: ^3.0.2
dev_dependencies:
flutter_test:

View File

@ -0,0 +1,25 @@
{
"applinks": {
"apps": [],
"details": [
{
"appIDs": [
"W7HPZ53V6B.dev.solsynth.solian"
],
"paths": [
"*"
],
"components": [
{
"/": "/*"
}
]
}
]
},
"webcredentials": {
"apps": [
"W7HPZ53V6B.dev.solsynth.solian"
]
}
}