Compare commits

...

7 Commits

Author SHA1 Message Date
efcfd3f57d 🚀 Launch 2.3.2+73 2025-02-23 21:37:33 +08:00
84759715a4 💄 Not showing notification when in the channel 2025-02-23 21:19:34 +08:00
fda09382dd 💄 Hide unread count auto after entering channel 2025-02-23 21:10:32 +08:00
2c5dd0563a 🐛 Fix checking for update db issue 2025-02-23 21:10:18 +08:00
5bdd8e94fa 🐛 Bug hotfix launch 2.3.2+72 2025-02-23 18:48:26 +08:00
2a53031c9a 🚀 Relaunch 2.3.2+71 2025-02-23 15:41:18 +08:00
e8bc7261f3 Updater 2025-02-23 15:41:03 +08:00
18 changed files with 312 additions and 86 deletions

View File

@ -718,5 +718,8 @@
"stickersNew": "New Sticker", "stickersNew": "New Sticker",
"stickersNewDescription": "Create a new sticker belongs to this pack.", "stickersNewDescription": "Create a new sticker belongs to this pack.",
"stickersPackNew": "New Sticker Pack", "stickersPackNew": "New Sticker Pack",
"trayMenuShow": "Show" "trayMenuShow": "Show",
"update": "Update",
"forceUpdate": "Force Update",
"forceUpdateDescription": "Force to show the application update popup, even the new version is not available."
} }

View File

@ -716,5 +716,8 @@
"stickersNew": "新建贴图", "stickersNew": "新建贴图",
"stickersNewDescription": "创建一个新的贴图。", "stickersNewDescription": "创建一个新的贴图。",
"stickersPackNew": "新建贴图包", "stickersPackNew": "新建贴图包",
"trayMenuShow": "显示" "trayMenuShow": "显示",
"update": "更新",
"forceUpdate": "强制更新",
"forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。"
} }

View File

@ -715,5 +715,9 @@
"fieldStickerAttachment": "附件", "fieldStickerAttachment": "附件",
"stickersNew": "新建貼圖", "stickersNew": "新建貼圖",
"stickersNewDescription": "創建一個新的貼圖。", "stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包" "stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示",
"update": "更新",
"forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"
} }

View File

@ -715,5 +715,9 @@
"fieldStickerAttachment": "附件", "fieldStickerAttachment": "附件",
"stickersNew": "新建貼圖", "stickersNew": "新建貼圖",
"stickersNewDescription": "創建一個新的貼圖。", "stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包" "stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示",
"update": "更新",
"forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"
} }

View File

@ -179,7 +179,7 @@ PODS:
- in_app_review (2.0.0): - in_app_review (2.0.0):
- Flutter - Flutter
- Kingfisher (8.2.0) - Kingfisher (8.2.0)
- livekit_client (2.3.6): - livekit_client (2.4.0):
- Flutter - Flutter
- flutter_webrtc - flutter_webrtc
- WebRTC-SDK (= 125.6422.06) - WebRTC-SDK (= 125.6422.06)
@ -426,7 +426,7 @@ SPEC CHECKSUMS:
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011 in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
Kingfisher: 323e5c4ec7983aaace12af655a7b51a7f88a599d Kingfisher: 323e5c4ec7983aaace12af655a7b51a7f88a599d
livekit_client: 148b2cf67a09aaf475ba8e5bf1667fe10dc35f81 livekit_client: 9819ebc8be8ef00ed0fae7d806bf8938ec689573
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e

View File

@ -322,6 +322,7 @@ class ChatMessageController extends ChangeNotifier {
notifyListeners(); notifyListeners();
final mostRecentMessage = await (_dt.db.snLocalChatMessage.select() final mostRecentMessage = await (_dt.db.snLocalChatMessage.select()
..where((e) => e.channelId.equals(channel!.id))
..limit(1) ..limit(1)
..orderBy([ ..orderBy([
(e) => (e) =>

View File

@ -254,10 +254,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
receiveTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60),
), ),
).get( ).get(
'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1', 'https://api.github.com/repos/Solsynth/HyperNet.Surface/releases/latest',
); );
final remoteVersionString = final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0';
(resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
final remoteVersion = Version.parse(remoteVersionString.split('+').first); final remoteVersion = Version.parse(remoteVersionString.split('+').first);
final localVersion = Version.parse(localVersionString.split('+').first); final localVersion = Version.parse(localVersionString.split('+').first);
final remoteBuildNumber = final remoteBuildNumber =
@ -269,10 +268,12 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
remoteBuildNumber > localBuildNumber) && remoteBuildNumber > localBuildNumber) &&
mounted) { mounted) {
final config = context.read<ConfigProvider>(); final config = context.read<ConfigProvider>();
config.setUpdate(remoteVersionString); config.setUpdate(
remoteVersionString, resp.data?['body'] ?? 'No changelog');
log("[Update] Update available: $remoteVersionString"); log("[Update] Update available: $remoteVersionString");
} }
} catch (e) { } catch (e) {
log('[Error] Unable to check update: $e');
if (mounted) context.showErrorDialog('Unable to check update: $e'); if (mounted) context.showErrorDialog('Unable to check update: $e');
} }
} }

View File

@ -65,7 +65,11 @@ class ChatChannelProvider extends ChangeNotifier {
.getSingleOrNull(); .getSingleOrNull();
if (local != null) { if (local != null) {
final out = local.content; final out = local.content;
return out.copyWith(realm: await _rels.getRealm(out.realmId!)); if (out.realmId != null) {
return out.copyWith(realm: await _rels.getRealm(out.realmId!));
} else {
return out;
}
} }
var resp = var resp =

View File

@ -58,7 +58,8 @@ class ConfigProvider extends ChangeNotifier {
: false; : false;
} }
if (newDrawerIsExpanded != drawerIsExpanded || newDrawerIsCollapsed != drawerIsCollapsed) { if (newDrawerIsExpanded != drawerIsExpanded ||
newDrawerIsCollapsed != drawerIsCollapsed) {
drawerIsExpanded = newDrawerIsExpanded; drawerIsExpanded = newDrawerIsExpanded;
drawerIsCollapsed = newDrawerIsCollapsed; drawerIsCollapsed = newDrawerIsCollapsed;
notifyListeners(); notifyListeners();
@ -66,7 +67,9 @@ class ConfigProvider extends ChangeNotifier {
} }
FilterQuality get imageQuality { FilterQuality get imageQuality {
return kImageQualityLevel.values.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ?? FilterQuality.high; return kImageQualityLevel.values
.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ??
FilterQuality.high;
} }
String get serverUrl { String get serverUrl {
@ -76,6 +79,7 @@ class ConfigProvider extends ChangeNotifier {
bool get realmCompactView { bool get realmCompactView {
return prefs.getBool(kAppRealmCompactView) ?? false; return prefs.getBool(kAppRealmCompactView) ?? false;
} }
set realmCompactView(bool value) { set realmCompactView(bool value) {
prefs.setBool(kAppRealmCompactView, value); prefs.setBool(kAppRealmCompactView, value);
} }
@ -86,9 +90,11 @@ class ConfigProvider extends ChangeNotifier {
} }
String? updatableVersion; String? updatableVersion;
String? updatableChangelog;
void setUpdate(String newVersion) { void setUpdate(String newVersion, String newChangelog) {
updatableVersion = newVersion; updatableVersion = newVersion;
updatableChangelog = newChangelog;
notifyListeners(); notifyListeners();
} }
} }

View File

@ -78,10 +78,23 @@ class NotificationProvider extends ChangeNotifier {
int showingTrayCount = 0; int showingTrayCount = 0;
List<SnNotification> notifications = List.empty(growable: true); List<SnNotification> notifications = List.empty(growable: true);
int? skippableNotifyChannel;
void listen() { void listen() {
_ws.pk.stream.listen((event) { _ws.pk.stream.listen((event) {
if (event.method == 'notifications.new') { if (event.method == 'notifications.new') {
final notification = SnNotification.fromJson(event.payload!); final notification = SnNotification.fromJson(event.payload!);
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact();
if (notification.topic == 'messaging.message') {
if (notification.metadata['channel_id'] != null &&
notification.metadata['channel_id'] == skippableNotifyChannel) {
return;
}
}
if (showingCount < 0) showingCount = 0; if (showingCount < 0) showingCount = 0;
showingCount++; showingCount++;
showingTrayCount++; showingTrayCount++;
@ -92,8 +105,6 @@ class NotificationProvider extends ChangeNotifier {
}); });
notifyListeners(); notifyListeners();
updateTray(); updateTray();
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact();
if (!kIsWeb) { if (!kIsWeb) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {

View File

@ -258,7 +258,8 @@ class _ChatScreenState extends State<ChatScreen> {
channel.name), channel.name),
), ),
const Gap(8), const Gap(8),
if (_unreadCounts?[channel.id] != null) if (_unreadCounts?[channel.id] != null &&
_unreadCounts![channel.id]! > 0)
Badge( Badge(
label: Text('${_unreadCounts![channel.id]}'), label: Text('${_unreadCounts![channel.id]}'),
), ),
@ -294,7 +295,11 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias, 'alias': channel.alias,
}, },
).then((value) { ).then((value) {
if (mounted) _refreshChannels(noRemote: true); if (mounted) {
_unreadCounts?[channel.id] = 0;
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
}); });
}, },
); );
@ -305,7 +310,8 @@ class _ChatScreenState extends State<ChatScreen> {
children: [ children: [
Expanded(child: Text(channel.name)), Expanded(child: Text(channel.name)),
const Gap(8), const Gap(8),
if (_unreadCounts?[channel.id] != null) if (_unreadCounts?[channel.id] != null &&
_unreadCounts![channel.id]! > 0)
Badge( Badge(
label: Text('${_unreadCounts![channel.id]}'), label: Text('${_unreadCounts![channel.id]}'),
), ),
@ -330,6 +336,7 @@ class _ChatScreenState extends State<ChatScreen> {
), ),
onTap: () { onTap: () {
if (doExpand) { if (doExpand) {
_unreadCounts?[channel.id] = 0;
setState(() => _focusChannel = channel); setState(() => _focusChannel = channel);
return; return;
} }
@ -340,7 +347,10 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias, 'alias': channel.alias,
}, },
).then((value) { ).then((value) {
if (value == true) _refreshChannels(noRemote: true); if (mounted) {
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
}); });
}, },
); );

View File

@ -58,7 +58,8 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
try { try {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/channels/${_channel!.keyPath}/members/me'); final resp =
await sn.client.get('/cgi/im/channels/${_channel!.keyPath}/me');
_profile = SnChannelMember.fromJson(resp.data); _profile = SnChannelMember.fromJson(resp.data);
_notifyLevel = _profile!.notify; _notifyLevel = _profile!.notify;
if (!mounted) return; if (!mounted) return;
@ -245,7 +246,11 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('channelDetailPersonalRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('channelDetailPersonalRegion')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
ListTile( ListTile(
leading: const Icon(Symbols.notifications), leading: const Icon(Symbols.notifications),
trailing: DropdownButtonHideUnderline( trailing: DropdownButtonHideUnderline(
@ -284,7 +289,8 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
), ),
ListTile( ListTile(
leading: AccountImage( leading: AccountImage(
content: ud.getAccountFromCache(_profile!.accountId)?.avatar, content:
ud.getAccountFromCache(_profile!.accountId)?.avatar,
radius: 18, radius: 18,
), ),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
@ -303,7 +309,8 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
title: Text('channelActionLeave').tr(), title: Text('channelActionLeave').tr(),
subtitle: Text('channelActionLeaveDescription').tr(), subtitle: Text('channelActionLeaveDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding:
const EdgeInsets.symmetric(horizontal: 24),
onTap: _leaveChannel, onTap: _leaveChannel,
), ),
], ],
@ -311,7 +318,11 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('channelDetailMemberRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('channelDetailMemberRegion')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
ListTile( ListTile(
leading: const Icon(Symbols.group), leading: const Icon(Symbols.group),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
@ -333,7 +344,11 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('channelDetailAdminRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), Text('channelDetailAdminRegion')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
ListTile( ListTile(
leading: const Icon(Symbols.edit), leading: const Icon(Symbols.edit),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
@ -379,10 +394,12 @@ class _ChannelProfileDetailDialog extends StatefulWidget {
}); });
@override @override
State<_ChannelProfileDetailDialog> createState() => _ChannelProfileDetailDialogState(); State<_ChannelProfileDetailDialog> createState() =>
_ChannelProfileDetailDialogState();
} }
class _ChannelProfileDetailDialogState extends State<_ChannelProfileDetailDialog> { class _ChannelProfileDetailDialogState
extends State<_ChannelProfileDetailDialog> {
bool _isBusy = false; bool _isBusy = false;
final TextEditingController _nickController = TextEditingController(); final TextEditingController _nickController = TextEditingController();
@ -457,7 +474,8 @@ class _ChannelMemberListWidget extends StatefulWidget {
const _ChannelMemberListWidget({required this.channel}); const _ChannelMemberListWidget({required this.channel});
@override @override
State<_ChannelMemberListWidget> createState() => _ChannelMemberListWidgetState(); State<_ChannelMemberListWidget> createState() =>
_ChannelMemberListWidgetState();
} }
class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
@ -472,10 +490,12 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
try { try {
final ud = context.read<UserDirectoryProvider>(); final ud = context.read<UserDirectoryProvider>();
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: { final resp = await sn.client.get(
'take': 10, '/cgi/im/channels/${widget.channel.keyPath}/members',
'offset': _members.length, queryParameters: {
}); 'take': 10,
'offset': _members.length,
});
final out = List<SnChannelMember>.from( final out = List<SnChannelMember>.from(
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [], resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
); );
@ -533,7 +553,9 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
children: [ children: [
const Icon(Symbols.group, size: 24), const Icon(Symbols.group, size: 24),
const Gap(16), const Gap(16),
Text('channelMemberManage').tr().textStyle(Theme.of(context).textTheme.titleLarge!), Text('channelMemberManage')
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
], ],
).padding(horizontal: 20, top: 16, bottom: 12), ).padding(horizontal: 20, top: 16, bottom: 12),
Expanded( Expanded(
@ -544,7 +566,8 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
}, },
child: InfiniteList( child: InfiniteList(
itemCount: _members.length, itemCount: _members.length,
hasReachedMax: _totalCount != null && _members.length >= _totalCount!, hasReachedMax:
_totalCount != null && _members.length >= _totalCount!,
isLoading: _isBusy, isLoading: _isBusy,
onFetchData: _fetchMembers, onFetchData: _fetchMembers,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -555,7 +578,8 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
content: ud.getAccountFromCache(member.accountId)?.avatar, content: ud.getAccountFromCache(member.accountId)?.avatar,
), ),
title: Text( title: Text(
ud.getAccountFromCache(member.accountId)?.name ?? 'unknown'.tr(), ud.getAccountFromCache(member.accountId)?.name ??
'unknown'.tr(),
), ),
subtitle: Text(member.nick ?? 'unknown'.tr()), subtitle: Text(member.nick ?? 'unknown'.tr()),
trailing: SizedBox( trailing: SizedBox(
@ -565,7 +589,8 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
IconButton( IconButton(
onPressed: _isUpdating ? null : () => _deleteMember(member), onPressed:
_isUpdating ? null : () => _deleteMember(member),
icon: const Icon(Symbols.person_remove), icon: const Icon(Symbols.person_remove),
), ),
], ],

View File

@ -1,10 +1,8 @@
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/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';
@ -29,6 +27,7 @@ import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart'; import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/updater.dart';
class HomeScreenDashEntry { class HomeScreenDashEntry {
final String name; final String name;
@ -83,14 +82,20 @@ class _HomeScreenState extends State<HomeScreen> {
body: LayoutBuilder( body: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return Align( return Align(
alignment: constraints.maxWidth > 640 ? Alignment.center : Alignment.topCenter, alignment: constraints.maxWidth > 640
? 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.center : MainAxisAlignment.start, mainAxisAlignment: constraints.maxWidth > 640
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: [ children: [
_HomeDashUpdateWidget(padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8)), _HomeDashUpdateWidget(
padding: const EdgeInsets.only(
bottom: 8, left: 8, right: 8)),
_HomeDashSpecialDayWidget().padding(horizontal: 8), _HomeDashSpecialDayWidget().padding(horizontal: 8),
StaggeredGrid.extent( StaggeredGrid.extent(
maxCrossAxisExtent: 280, maxCrossAxisExtent: 280,
@ -136,21 +141,15 @@ class _HomeDashUpdateWidget extends StatelessWidget {
leading: Icon(Symbols.update), leading: Icon(Symbols.update),
title: Text('updateAvailable').tr(), title: Text('updateAvailable').tr(),
subtitle: Text(config.updatableVersion!), subtitle: Text(config.updatableVersion!),
trailing: (kIsWeb || Platform.isWindows || Platform.isLinux) trailing: IconButton(
? null icon: const Icon(Symbols.arrow_right_alt),
: IconButton( onPressed: () {
icon: const Icon(Symbols.arrow_right_alt), showModalBottomSheet(
onPressed: () { context: context,
final model = UpdateModel( builder: (context) => VersionUpdatePopup(),
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk', );
'solian-app-release-${config.updatableVersion!}.apk', },
'ic_launcher', ),
'https://apps.apple.com/us/app/solian/id6499032345',
);
AzhonAppUpdate.update(model);
context.showSnackbar('updateOngoing'.tr());
},
),
), ),
), ),
); );
@ -166,7 +165,8 @@ class _HomeDashSpecialDayWidget extends StatefulWidget {
const _HomeDashSpecialDayWidget(); const _HomeDashSpecialDayWidget();
@override @override
State<_HomeDashSpecialDayWidget> createState() => _HomeDashSpecialDayWidgetState(); State<_HomeDashSpecialDayWidget> createState() =>
_HomeDashSpecialDayWidgetState();
} }
class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> { class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
@ -208,7 +208,9 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: ListTile( child: ListTile(
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24), leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]), title: Text('pending$name').tr(args: [
RelativeTime(context).format(date).replaceFirst('in', '').trim()
]),
subtitle: Row( subtitle: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -297,12 +299,19 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
children: [ children: [
Text( Text(
_article!.title, _article!.title,
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 18), style: Theme.of(context)
maxLines: MediaQuery.of(context).size.width >= 640 ? 2 : 1, .textTheme
.titleMedium!
.copyWith(fontSize: 18),
maxLines:
MediaQuery.of(context).size.width >= 640 ? 2 : 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Text( Text(
parse(_article!.description).children.map((e) => e.text.trim()).join(), parse(_article!.description)
.children
.map((e) => e.text.trim())
.join(),
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
@ -313,9 +322,13 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
spacing: 2, spacing: 2,
children: [ children: [
Text(DateFormat().format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), Text(DateFormat().format(date)).textStyle(
Text(' · ').textStyle(Theme.of(context).textTheme.bodySmall!).bold(), Theme.of(context).textTheme.bodySmall!),
Text(RelativeTime(context).format(date)).textStyle(Theme.of(context).textTheme.bodySmall!), Text(' · ')
.textStyle(Theme.of(context).textTheme.bodySmall!)
.bold(),
Text(RelativeTime(context).format(date)).textStyle(
Theme.of(context).textTheme.bodySmall!),
], ],
).opacity(0.75); ).opacity(0.75);
}), }),
@ -386,15 +399,20 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
} }
Widget _buildDetailChunk(int index, bool positive) { Widget _buildDetailChunk(int index, bool positive) {
final prefix = positive ? 'dailyCheckPositiveHint' : 'dailyCheckNegativeHint'; final prefix =
final mod = positive ? kSuggestionPositiveHintCount : kSuggestionNegativeHintCount; positive ? 'dailyCheckPositiveHint' : 'dailyCheckNegativeHint';
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).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
), ),
Text( Text(
'$prefix${pos}Description', '$prefix${pos}Description',
@ -429,7 +447,10 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
else else
Text( Text(
'dailyCheckEverythingIsNegative', 'dailyCheckEverythingIsNegative',
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
).tr(), ).tr(),
const Gap(8), const Gap(8),
if (_todayRecord?.resultTier != 4) if (_todayRecord?.resultTier != 4)
@ -445,7 +466,10 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
else else
Text( Text(
'dailyCheckEverythingIsPositive', 'dailyCheckEverythingIsPositive',
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
).tr(), ).tr(),
], ],
), ),
@ -571,10 +595,12 @@ class _HomeDashNotificationWidget extends StatefulWidget {
const _HomeDashNotificationWidget(); const _HomeDashNotificationWidget();
@override @override
State<_HomeDashNotificationWidget> createState() => _HomeDashNotificationWidgetState(); State<_HomeDashNotificationWidget> createState() =>
_HomeDashNotificationWidgetState();
} }
class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget> { class _HomeDashNotificationWidgetState
extends State<_HomeDashNotificationWidget> {
int? _count; int? _count;
Future<void> _fetchNotificationCount() async { Future<void> _fetchNotificationCount() async {
@ -612,7 +638,9 @@ class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
).tr(), ).tr(),
Text( Text(
_count == null ? 'loading'.tr() : 'notificationUnreadCount'.plural(_count ?? 0), _count == null
? 'loading'.tr()
: 'notificationUnreadCount'.plural(_count ?? 0),
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
], ],
@ -643,10 +671,12 @@ class _HomeDashRecommendationPostWidget extends StatefulWidget {
const _HomeDashRecommendationPostWidget(); const _HomeDashRecommendationPostWidget();
@override @override
State<_HomeDashRecommendationPostWidget> createState() => _HomeDashRecommendationPostWidgetState(); State<_HomeDashRecommendationPostWidget> createState() =>
_HomeDashRecommendationPostWidgetState();
} }
class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendationPostWidget> { class _HomeDashRecommendationPostWidgetState
extends State<_HomeDashRecommendationPostWidget> {
bool _isBusy = false; bool _isBusy = false;
List<SnPost>? _posts; List<SnPost>? _posts;
@ -710,13 +740,15 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
).tr(), ).tr(),
], ],
), ),
Text('${_currentPage + 1}/${_posts?.length ?? 0}', style: GoogleFonts.robotoMono()) Text('${_currentPage + 1}/${_posts?.length ?? 0}',
style: GoogleFonts.robotoMono())
], ],
).padding(horizontal: 18, top: 12, bottom: 8), ).padding(horizontal: 18, top: 12, bottom: 8),
Expanded( Expanded(
child: PageView.builder( child: PageView.builder(
controller: _pageController, controller: _pageController,
scrollBehavior: ScrollConfiguration.of(context).copyWith(dragDevices: { scrollBehavior:
ScrollConfiguration.of(context).copyWith(dragDevices: {
PointerDeviceKind.mouse, PointerDeviceKind.mouse,
PointerDeviceKind.touch, PointerDeviceKind.touch,
}), }),
@ -729,7 +761,8 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
showMenu: false, showMenu: false,
).padding(bottom: 8), ).padding(bottom: 8),
onTap: () { onTap: () {
GoRouter.of(context).pushNamed('postDetail', pathParameters: { GoRouter.of(context)
.pushNamed('postDetail', pathParameters: {
'slug': _posts![index].id.toString(), 'slug': _posts![index].id.toString(),
}); });
}, },

View File

@ -24,6 +24,7 @@ import 'package:surface/providers/theme.dart';
import 'package:surface/theme.dart'; import 'package:surface/theme.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/updater.dart';
const Map<String, Color> kColorSchemes = { const Map<String, Color> kColorSchemes = {
'colorSchemeIndigo': Colors.indigo, 'colorSchemeIndigo': Colors.indigo,
@ -604,6 +605,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
}, },
), ),
ListTile(
title: Text('forceUpdate').tr(),
subtitle: Text('forceUpdateDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.update),
trailing: const Icon(Symbols.chevron_right),
onTap: () async {
showModalBottomSheet(
context: context,
builder: (context) => VersionUpdatePopup(),
);
},
),
ListTile( ListTile(
title: Text('settingsMiscAbout').tr(), title: Text('settingsMiscAbout').tr(),
subtitle: Text('settingsMiscAboutDescription').tr(), subtitle: Text('settingsMiscAboutDescription').tr(),

96
lib/widgets/updater.dart Normal file
View File

@ -0,0 +1,96 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_update/azhon_app_update.dart';
import 'package:flutter_app_update/update_model.dart';
import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/markdown_content.dart';
import 'package:url_launcher/url_launcher_string.dart';
class VersionUpdatePopup extends StatelessWidget {
const VersionUpdatePopup({super.key});
void _update(BuildContext context) async {
if (kIsWeb) return;
final config = context.read<ConfigProvider>();
if (Platform.isAndroid) {
final model = UpdateModel(
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk',
'solian-app-release-${config.updatableVersion!}.apk',
'ic_launcher',
'https://apps.apple.com/us/app/solian/id6499032345',
);
AzhonAppUpdate.update(model);
context.showSnackbar('updateOngoing'.tr());
return;
}
final resp = await Dio(
BaseOptions(
sendTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60),
),
).get(
'https://api.github.com/repos/Solsynth/HyperNet.Surface/releases/latest',
);
launchUrlString(resp.data?['html_url']);
}
@override
Widget build(BuildContext context) {
final config = context.watch<ConfigProvider>();
return Column(
children: [
Row(
children: [
const Icon(Icons.update),
const Gap(16),
Text('update')
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Row(
children: [
Expanded(
child: Text(
config.updatableVersion ?? 'unknown'.tr(),
style: GoogleFonts.robotoMono(),
),
),
ElevatedButton(
style: ButtonStyle(
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -3,
),
),
onPressed: () => _update(context),
child: Text('update').tr(),
),
],
).padding(horizontal: 20),
const Divider(height: 1).padding(vertical: 8),
Expanded(
child: SingleChildScrollView(
child: MarkdownTextContent(
content: config.updatableChangelog ?? 'No changelog',
).padding(horizontal: 20),
),
)
],
);
}
}

View File

@ -142,10 +142,12 @@ PODS:
- HotKey - HotKey
- in_app_review (2.0.0): - in_app_review (2.0.0):
- FlutterMacOS - FlutterMacOS
- livekit_client (2.3.6): - livekit_client (2.4.0):
- flutter_webrtc - flutter_webrtc
- FlutterMacOS - FlutterMacOS
- WebRTC-SDK (= 125.6422.06) - WebRTC-SDK (= 125.6422.06)
- local_notifier (0.1.0):
- FlutterMacOS
- media_kit_libs_macos_video (1.0.4): - media_kit_libs_macos_video (1.0.4):
- FlutterMacOS - FlutterMacOS
- media_kit_native_event_loop (1.0.0): - media_kit_native_event_loop (1.0.0):
@ -225,6 +227,7 @@ DEPENDENCIES:
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`) - hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`) - livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
- local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`)
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`) - media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
@ -297,6 +300,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos :path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
livekit_client: livekit_client:
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos :path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
local_notifier:
:path: Flutter/ephemeral/.symlinks/plugins/local_notifier/macos
media_kit_libs_macos_video: media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_native_event_loop: media_kit_native_event_loop:
@ -356,7 +361,8 @@ SPEC CHECKSUMS:
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160 hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93 in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
livekit_client: 0ad107154753a5a76802d2222c040223ad049499 livekit_client: 2e766be2c3ee6274a8e2633b356b98b5eb842987
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5

View File

@ -2309,10 +2309,11 @@ packages:
workmanager: workmanager:
dependency: "direct main" dependency: "direct main"
description: description:
name: workmanager path: workmanager
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00 ref: main
url: "https://pub.dev" resolved-ref: "4ce065135dc1b91fee918f81596b42a56850391d"
source: hosted url: "https://github.com/fluttercommunity/flutter_workmanager.git"
source: git
version: "0.5.2" version: "0.5.2"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive

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 # 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.3.2+71 version: 2.3.2+73
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@ -103,7 +103,11 @@ dependencies:
flutter_svg: ^2.0.16 flutter_svg: ^2.0.16
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:
git:
url: https://github.com/fluttercommunity/flutter_workmanager.git
path: workmanager
ref: main
flutter_app_update: ^3.2.2 flutter_app_update: ^3.2.2
in_app_review: ^2.0.10 in_app_review: ^2.0.10
version: ^3.0.2 version: ^3.0.2