Compare commits

...

34 Commits

Author SHA1 Message Date
19076f8136 🚀 Launch 2.2.2+54 2025-01-20 17:35:13 +08:00
dc77a936ce ⬆️ Upgrade deps 2025-01-20 16:53:38 +08:00
7f58710c6f Notification indicator 2025-01-20 16:52:53 +08:00
068ddcdcdc 💄 Optimized connection indicator 2025-01-20 14:40:26 +08:00
f4e9252ca0 💄 Optimized post list 2025-01-20 14:21:41 +08:00
3b1e918117 🐛 Fix side nav cause render error 2025-01-20 01:43:11 +08:00
ed7981fdaf 🧪 Post max width 2025-01-19 17:20:24 +08:00
9698ca53e4 Swipe up to view attachment details 2025-01-19 11:44:14 +08:00
ddc1dc7daf 💄 Optimize attachment zoom page 2025-01-19 01:00:00 +08:00
1625a957f8 👔 Use material design 3 by default 2025-01-19 00:39:47 +08:00
2dc50d627e 🧱 Fix roadsign config 2025-01-16 21:51:28 +08:00
2ffde9a3dd 🚀 Launch 2.2.2+53 2025-01-15 16:00:59 +08:00
5967a91ae1 Chat user popover 2025-01-15 15:52:52 +08:00
32c1effcb5 💄 Post editor content max width 2025-01-15 15:19:46 +08:00
9d0e19c56f 🐛 Fix chat messages 2025-01-15 15:14:13 +08:00
acf4e634fe 🐛 Fix websocket will put message in wrong channel 2025-01-14 23:32:02 +08:00
25942c2338 💄 Optimize chat max width 2025-01-14 23:30:35 +08:00
a4f81f6ba1 🐛 Post auto warp 2025-01-14 23:24:11 +08:00
c1b9090e51 💄 Optimized attachment list 2025-01-14 23:17:34 +08:00
f494f70003 🚀 Launch 2.2.2+52 2025-01-08 18:07:51 +08:00
fb2a55a909 🐛 Fix editing message did not load the attachment 2025-01-08 17:48:46 +08:00
4edfa7fd50 🐛 Optimizing styling of chat 2025-01-08 17:37:16 +08:00
d699cac9b1 🚀 Launch 2.2.2+51 2025-01-07 18:10:20 +08:00
c0428e12c1 🐛 Fixed the drawer styling issue 2025-01-07 13:11:45 +08:00
55f434ff05 🚀 Launch 2.2.2+40 2025-01-06 23:39:49 +08:00
f2b3bdda2d 🐛 Add ability check to text selection chat message action 2025-01-06 23:17:24 +08:00
1f6bf33b0e Chat message action on system text selection area 2025-01-06 23:15:18 +08:00
e2027b1a32 Able to prefer sidebar to collapse 2025-01-06 22:57:44 +08:00
2b3a58b55e Optimize temporary save post scenario 2025-01-06 22:12:10 +08:00
6ac536412a 🚀 Launch 2.2.2+49 2025-01-06 22:05:20 +08:00
52f8ffe4e4 💄 Update the app bar color when in transparent mode 2025-01-06 21:57:50 +08:00
aca81431aa 🐛 Fix desktop share post as image do not include file extension name 2025-01-06 21:53:35 +08:00
1fadd850b7 💄 Optimize some styling 2025-01-06 21:46:21 +08:00
ed2a9a21b6 🐛 Fix chat username height difference 2025-01-06 19:18:23 +08:00
40 changed files with 1268 additions and 635 deletions

View File

@ -1,12 +1,12 @@
{
"sync": {
"region": "solian-next",
"region": "solian",
"configPath": "roadsign.toml"
},
"deployments": [
{
"region": "solian-next",
"site": "solian-next-web",
"region": "solian",
"site": "solian-web",
"path": "build/web"
}
]

View File

@ -17,6 +17,7 @@
android:label="Solian"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"

View File

@ -181,6 +181,8 @@
"settingsAppearance": "Appearance",
"settingsAppBarTransparent": "Transparent App Bar",
"settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.",
"settingsDrawerPreferCollapse": "Prefer Drawer Collapse",
"settingsDrawerPreferCollapseDescription": "Make the drawer to collapse even when the screen is wide enough.",
"settingsBackgroundImage": "Background Image",
"settingsBackgroundImageDescription": "Set the background image that will be applied globally.",
"settingsBackgroundImageClear": "Clear Existing Background Image",
@ -213,8 +215,9 @@
"sensitiveContentCollapsed": "Sensitive content has been collapsed.",
"sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.",
"sensitiveContentReveal": "Reveal",
"serverConnecting": "Connecting to server...",
"serverDisconnected": "Lost connection from server",
"serverConnecting": "Connecting...",
"serverDisconnected": "Connection Lost",
"serverConnected": "Connected",
"fieldChatAlias": "Channel Alias",
"fieldChatAliasHint": "The unique channel alias within the site, used to represent the channel in URL, leave blank to auto generate. Should be URL-Safe.",
"fieldChatName": "Name",
@ -292,6 +295,7 @@
"addAttachmentFromCameraPhoto": "Take photo",
"addAttachmentFromCameraVideo": "Take video",
"addAttachmentFromRandomId": "Link via RID",
"attachmentDetailInfo": "Attachment details",
"attachmentPastedImage": "Pasted Image",
"attachmentInsertLink": "Insert Link",
"attachmentSetAsPostThumbnail": "Set as post thumbnail",

View File

@ -185,6 +185,8 @@
"settingsThemeMaterial3Description": "将应用主题设置为 Material 3 设计范式的主题。",
"settingsAppBarTransparent": "透明顶栏",
"settingsAppBarTransparentDescription": "为顶栏启用透明效果。",
"settingsDrawerPreferCollapse": "侧边栏偏好折叠",
"settingsDrawerPreferCollapseDescription": "将侧边栏优先折叠,即使屏幕宽度足够大去放下整个侧边栏。",
"settingsColorScheme": "主题色",
"settingsColorSchemeDescription": "设置应用主题色。",
"settingsColorSeed": "预设色彩主题",
@ -211,8 +213,9 @@
"sensitiveContentCollapsed": "敏感内容已折叠。",
"sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。",
"sensitiveContentReveal": "显示内容",
"serverConnecting": "正在连接服务器…",
"serverDisconnected": "已与服务器断开连接",
"serverConnecting": "正在连接…",
"serverDisconnected": "已断开连接",
"serverConnected": "已连接",
"fieldChatAlias": "频道别名",
"fieldChatAliasHint": "全站范围内唯一的频道别名,用于在 URL 中表示该频道,留空则自动生成。应遵循 URL-Safe 的原则。",
"fieldChatName": "名称",
@ -290,6 +293,7 @@
"addAttachmentFromCameraPhoto": "拍摄照片",
"addAttachmentFromCameraVideo": "拍摄视频",
"addAttachmentFromRandomId": "通过访问 ID 链接",
"attachmentDetailInfo": "附件详细信息",
"attachmentPastedImage": "粘贴的图片",
"attachmentInsertLink": "插入连接",
"attachmentSetAsPostThumbnail": "设置为帖子缩略图",

View File

@ -185,6 +185,8 @@
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
"settingsAppBarTransparent": "透明頂欄",
"settingsAppBarTransparentDescription": "為頂欄啓用透明效果。",
"settingsDrawerPreferCollapse": "側邊欄偏好摺疊",
"settingsDrawerPreferCollapseDescription": "將側邊欄優先摺疊,即使屏幕寬度足夠大去放下整個側邊欄。",
"settingsColorScheme": "主題色",
"settingsColorSchemeDescription": "設置應用主題色。",
"settingsColorSeed": "預設色彩主題",

View File

@ -185,6 +185,8 @@
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
"settingsAppBarTransparent": "透明頂欄",
"settingsAppBarTransparentDescription": "為頂欄啟用透明效果。",
"settingsDrawerPreferCollapse": "側邊欄偏好摺疊",
"settingsDrawerPreferCollapseDescription": "將側邊欄優先摺疊,即使屏幕寬度足夠大去放下整個側邊欄。",
"settingsColorScheme": "主題色",
"settingsColorSchemeDescription": "設置應用主題色。",
"settingsColorSeed": "預設色彩主題",

View File

@ -43,58 +43,58 @@ PODS:
- Flutter
- file_saver (0.0.1):
- Flutter
- Firebase/Analytics (11.4.0):
- Firebase/Analytics (11.6.0):
- Firebase/Core
- Firebase/Core (11.4.0):
- Firebase/Core (11.6.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 11.4.0)
- Firebase/CoreOnly (11.4.0):
- FirebaseCore (= 11.4.0)
- Firebase/Messaging (11.4.0):
- FirebaseAnalytics (~> 11.6.0)
- Firebase/CoreOnly (11.6.0):
- FirebaseCore (~> 11.6.0)
- Firebase/Messaging (11.6.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.4.0)
- firebase_analytics (11.3.6):
- Firebase/Analytics (= 11.4.0)
- FirebaseMessaging (~> 11.6.0)
- firebase_analytics (11.4.0):
- Firebase/Analytics (= 11.6.0)
- firebase_core
- Flutter
- firebase_core (3.9.0):
- Firebase/CoreOnly (= 11.4.0)
- firebase_core (3.10.0):
- Firebase/CoreOnly (= 11.6.0)
- Flutter
- firebase_messaging (15.1.6):
- Firebase/Messaging (= 11.4.0)
- firebase_messaging (15.2.0):
- Firebase/Messaging (= 11.6.0)
- firebase_core
- Flutter
- FirebaseAnalytics (11.4.0):
- FirebaseAnalytics/AdIdSupport (= 11.4.0)
- FirebaseCore (~> 11.0)
- FirebaseAnalytics (11.6.0):
- FirebaseAnalytics/AdIdSupport (= 11.6.0)
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseAnalytics/AdIdSupport (11.6.0):
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.4.0)
- GoogleAppMeasurement (= 11.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.4.0):
- FirebaseCoreInternal (~> 11.0)
- FirebaseCore (11.6.0):
- FirebaseCoreInternal (~> 11.6.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.6.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (11.6.0):
- FirebaseCore (~> 11.6.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseMessaging (11.6.0):
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@ -110,27 +110,27 @@ PODS:
- flutter_udid (0.0.1):
- Flutter
- SAMKeychain
- flutter_webrtc (0.12.2):
- flutter_webrtc (0.12.6):
- Flutter
- WebRTC-SDK (= 125.6422.06)
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (11.4.0):
- GoogleAppMeasurement/AdIdSupport (= 11.4.0)
- GoogleAppMeasurement (11.6.0):
- GoogleAppMeasurement/AdIdSupport (= 11.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.4.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.4.0)
- GoogleAppMeasurement/AdIdSupport (11.6.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.4.0):
- GoogleAppMeasurement/WithoutAdIdSupport (11.6.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
@ -173,7 +173,7 @@ PODS:
- in_app_review (2.0.0):
- Flutter
- Kingfisher (8.1.3)
- livekit_client (2.3.4):
- livekit_client (2.3.5):
- Flutter
- flutter_webrtc
- WebRTC-SDK (= 125.6422.06)
@ -369,29 +369,29 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
firebase_analytics: 2815af29d49c1a994652abd37a5b001a88bc7b75
firebase_core: b62a5080210edad3f2934314a8b2c6f5124e8e10
firebase_messaging: 98619a0572d82cfb3668e78859ba9f1110e268c9
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
firebase_analytics: 07bd7cfbac54bfcdccf2bb2530f9a65486f7ef3f
firebase_core: feb37e79f775c2bd08dd35e02d83678291317e10
firebase_messaging: e2f0ba891b1509668c07f5099761518a5af8fe3c
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
flutter_webrtc: 1a53bd24f97bcfeff512f13699e721897f261563
flutter_webrtc: 90260f83024b1b96d239a575ea4e3708e79344d1
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef
livekit_client: 4eaa7a2968fc7e7c57888f43d90394547cc8d9e9
livekit_client: dcc5fd47ba69c98fc6baeb12e862c9d43807d976
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e

View File

@ -74,6 +74,7 @@ class ChatMessageController extends ChangeNotifier {
_wsSubscription = _ws.stream.stream.listen((event) {
switch (event.method) {
case 'events.new':
if (event.payload?['channel_id'] != channel?.id) break;
final payload = SnChatMessage.fromJson(event.payload!);
_addMessage(payload);
break;

View File

@ -154,7 +154,10 @@ class PostWriteController extends ChangeNotifier {
final TextEditingController descriptionController = TextEditingController();
final TextEditingController aliasController = TextEditingController();
PostWriteController() {
bool _temporarySaveActive = false;
PostWriteController({bool doLoadFromTemporary = true}) {
_temporarySaveActive = doLoadFromTemporary;
titleController.addListener(() {
_temporaryPlanSave();
notifyListeners();
@ -166,7 +169,7 @@ class PostWriteController extends ChangeNotifier {
contentController.addListener(() {
_temporaryPlanSave();
});
_temporaryLoad();
if (doLoadFromTemporary) _temporaryLoad();
}
String mode = kTitleMap.keys.first;
@ -317,6 +320,7 @@ class PostWriteController extends ChangeNotifier {
Timer? _temporarySaveTimer;
void _temporaryPlanSave() {
if (!_temporarySaveActive) return;
_temporarySaveTimer?.cancel();
_temporarySaveTimer = Timer(const Duration(seconds: 1), () {
_temporarySave();

View File

@ -258,6 +258,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
Future<void> _initialize() async {
try {
final cfg = context.read<ConfigProvider>();
WidgetsBinding.instance.addPostFrameCallback((_) {
cfg.calcDrawerSize(context);
});
final home = context.read<HomeWidgetProvider>();
await home.initialize();
if (!mounted) return;
@ -275,6 +279,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
await ws.tryConnect();
if (!mounted) return;
final notify = context.read<NotificationProvider>();
notify.listen();
await notify.registerPushNotifications();
} catch (err) {
if (!mounted) return;
@ -298,6 +303,17 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
@override
Widget build(BuildContext context) {
return widget.child;
final cfg = context.read<ConfigProvider>();
return NotificationListener<SizeChangedLayoutNotification>(
onNotification: (notification) {
WidgetsBinding.instance.addPostFrameCallback((_) {
cfg.calcDrawerSize(context);
});
return false;
},
child: SizeChangedLayoutNotifier(
child: widget.child,
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/providers/widget.dart';
@ -12,6 +13,7 @@ const kNetworkServerStoreKey = 'app_server_url';
const kAppbarTransparentStoreKey = 'app_bar_transparent';
const kAppBackgroundStoreKey = 'app_has_background';
const kAppColorSchemeStoreKey = 'app_color_scheme';
const kAppDrawerPreferCollapse = 'app_drawer_prefer_collapse';
const Map<String, FilterQuality> kImageQualityLevel = {
'settingsImageQualityLowest': FilterQuality.none,
@ -33,6 +35,24 @@ class ConfigProvider extends ChangeNotifier {
prefs = await SharedPreferences.getInstance();
}
bool drawerIsCollapsed = false;
bool drawerIsExpanded = false;
void calcDrawerSize(BuildContext context) {
final rpb = ResponsiveBreakpoints.of(context);
final newDrawerIsCollapsed = rpb.smallerOrEqualTo(MOBILE);
final newDrawerIsExpanded = rpb.largerThan(TABLET)
? (prefs.getBool(kAppDrawerPreferCollapse) ?? false)
? false
: true
: false;
if (newDrawerIsExpanded != drawerIsExpanded || newDrawerIsCollapsed != drawerIsCollapsed) {
drawerIsExpanded = newDrawerIsExpanded;
drawerIsCollapsed = newDrawerIsCollapsed;
notifyListeners();
}
}
FilterQuality get imageQuality {
return kImageQualityLevel.values.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ?? FilterQuality.high;
}

View File

@ -8,14 +8,18 @@ import 'package:flutter_udid/flutter_udid.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/providers/websocket.dart';
import 'package:surface/types/notification.dart';
class NotificationProvider extends ChangeNotifier {
late final SnNetworkProvider _sn;
late final UserProvider _ua;
late final WebSocketProvider _ws;
NotificationProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_ua = context.read<UserProvider>();
_ws = context.read<WebSocketProvider>();
}
Future<void> registerPushNotifications() async {
@ -62,4 +66,21 @@ class NotificationProvider extends ChangeNotifier {
},
);
}
List<SnNotification> notifications = List.empty(growable: true);
void listen() {
_ws.stream.stream.listen((event) {
if (event.method == 'notifications.new') {
final notification = SnNotification.fromJson(event.payload!);
notifications.add(notification);
notifyListeners();
}
});
}
void clear() {
notifications.clear();
notifyListeners();
}
}

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:surface/screens/abuse_report.dart';
import 'package:surface/screens/account.dart';
import 'package:surface/screens/account/pfp.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/screens/account/profile_edit.dart';
import 'package:surface/screens/account/publishers/publisher_edit.dart';
import 'package:surface/screens/account/publishers/publisher_new.dart';

View File

@ -236,7 +236,7 @@ class _ChatScreenState extends State<ChatScreen> {
'alias': channel.alias,
},
).then((value) {
if (value == true) _refreshChannels();
if (mounted) _refreshChannels();
});
},
);

View File

@ -1,3 +1,4 @@
import 'package:animations/animations.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
@ -8,6 +9,7 @@ import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/post/post_detail.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart';
@ -210,6 +212,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
),
),
),
const SliverGap(8),
SliverInfiniteList(
itemCount: _posts.length,
isLoading: _isBusy,
@ -217,27 +220,37 @@ class _ExploreScreenState extends State<ExploreScreen> {
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
return GestureDetector(
child: PostItem(
data: _posts[idx],
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
_refreshPosts();
},
return Center(
child: OpenContainer(
closedBuilder: (_, __) => Container(
constraints: const BoxConstraints(maxWidth: 640),
child: PostItem(
data: _posts[idx],
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
_refreshPosts();
},
),
),
openBuilder: (_, close) => PostDetailScreen(
slug: _posts[idx].id.toString(),
preload: _posts[idx],
onBack: close,
),
openColor: Colors.transparent,
openElevation: 0,
closedColor: Theme.of(context).colorScheme.surface,
transitionType: ContainerTransitionType.fade,
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
onTap: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {'slug': _posts[idx].id.toString()},
extra: _posts[idx],
);
},
);
},
separatorBuilder: (context, index) => const Divider(height: 1),
separatorBuilder: (_, __) => const Gap(8),
),
],
),

View File

@ -206,10 +206,11 @@ class _NotificationScreenState extends State<NotificationScreen> {
style: Theme.of(context).textTheme.titleSmall,
),
if (nty.subtitle != null) const Gap(4),
MarkdownTextContent(
content: nty.body,
isAutoWarp: true,
isSelectable: true,
SelectionArea(
child: MarkdownTextContent(
content: nty.body,
isAutoWarp: true,
),
),
if ([
'interactive.feedback',

View File

@ -13,6 +13,7 @@ import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_background.dart';
import 'package:surface/widgets/post/post_comment_list.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/post/post_mini_editor.dart';
@ -20,12 +21,9 @@ import 'package:surface/widgets/post/post_mini_editor.dart';
class PostDetailScreen extends StatefulWidget {
final String slug;
final SnPost? preload;
final Function? onBack;
const PostDetailScreen({
super.key,
required this.slug,
this.preload,
});
const PostDetailScreen({super.key, required this.slug, this.preload, this.onBack});
@override
State<PostDetailScreen> createState() => _PostDetailScreenState();
@ -67,123 +65,129 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
final ua = context.watch<UserProvider>();
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (GoRouter.of(context).canPop()) {
GoRouter.of(context).pop(context);
return;
}
GoRouter.of(context).replaceNamed('explore');
},
),
title: _data?.body['title'] != null
? RichText(
textAlign: TextAlign.center,
text: TextSpan(children: [
TextSpan(
text: _data?.body['title'] ?? 'postNoun'.tr(),
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
const TextSpan(text: '\n'),
TextSpan(
text: 'postDetail'.tr(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
]),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
: Text('postDetail').tr(),
),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: LoadingIndicator(isActive: _isBusy),
return AppBackground(
isRoot: widget.onBack != null,
child: Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (widget.onBack != null) {
widget.onBack!.call();
}
if (GoRouter.of(context).canPop()) {
GoRouter.of(context).pop(context);
return;
}
GoRouter.of(context).replaceNamed('explore');
},
),
if (_data != null)
SliverToBoxAdapter(
child: PostItem(
data: _data!,
maxWidth: 640,
showComments: false,
showFullPost: true,
onChanged: (data) {
setState(() => _data = data);
},
onDeleted: () {
Navigator.pop(context);
},
),
),
const SliverToBoxAdapter(child: Divider(height: 1)),
if (_data != null)
SliverToBoxAdapter(
child: Container(
constraints: const BoxConstraints(maxWidth: 640),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.comment, size: 24),
const Gap(16),
Text('postCommentsDetailed')
.plural(_data!.metric.replyCount)
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, vertical: 12).center(),
),
),
if (_data != null && ua.isAuthorized)
SliverToBoxAdapter(
child: Container(
height: 240,
constraints: const BoxConstraints(maxWidth: 640),
margin:
ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.all(8) : EdgeInsets.zero,
decoration: BoxDecoration(
borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
? const BorderRadius.all(Radius.circular(8))
: BorderRadius.zero,
border: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
? Border.all(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
)
: Border.symmetric(
horizontal: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
title: _data?.body['title'] != null
? RichText(
textAlign: TextAlign.center,
text: TextSpan(children: [
TextSpan(
text: _data?.body['title'] ?? 'postNoun'.tr(),
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
child: PostMiniEditor(
postReplyId: _data!.id,
onPost: () {
setState(() {
_data = _data!.copyWith(
metric: _data!.metric.copyWith(
replyCount: _data!.metric.replyCount + 1,
),
);
});
_childListKey.currentState!.refresh();
),
const TextSpan(text: '\n'),
TextSpan(
text: 'postDetail'.tr(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
]),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
: Text('postDetail').tr(),
),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: LoadingIndicator(isActive: _isBusy),
),
if (_data != null)
SliverToBoxAdapter(
child: PostItem(
data: _data!,
maxWidth: 640,
showComments: false,
showFullPost: true,
onChanged: (data) {
setState(() => _data = data);
},
onDeleted: () {
Navigator.pop(context);
},
),
).center(),
),
if (_data != null)
PostCommentSliverList(
key: _childListKey,
parentPostId: _data!.id,
maxWidth: 640,
),
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
],
),
const SliverToBoxAdapter(child: Divider(height: 1)),
if (_data != null)
SliverToBoxAdapter(
child: Container(
constraints: const BoxConstraints(maxWidth: 640),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.comment, size: 24),
const Gap(16),
Text('postCommentsDetailed')
.plural(_data!.metric.replyCount)
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, vertical: 12).center(),
),
),
if (_data != null && ua.isAuthorized)
SliverToBoxAdapter(
child: Container(
height: 240,
constraints: const BoxConstraints(maxWidth: 640),
margin:
ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.all(8) : EdgeInsets.zero,
decoration: BoxDecoration(
borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
? const BorderRadius.all(Radius.circular(8))
: BorderRadius.zero,
border: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
? Border.all(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
)
: Border.symmetric(
horizontal: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
),
),
),
child: PostMiniEditor(
postReplyId: _data!.id,
onPost: () {
setState(() {
_data = _data!.copyWith(
metric: _data!.metric.copyWith(
replyCount: _data!.metric.replyCount + 1,
),
);
});
_childListKey.currentState!.refresh();
},
),
).center(),
),
if (_data != null)
PostCommentSliverList(
key: _childListKey,
parentPostId: _data!.id,
maxWidth: 640,
),
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
],
),
),
);
}

View File

@ -54,7 +54,9 @@ class PostEditorScreen extends StatefulWidget {
}
class _PostEditorScreenState extends State<PostEditorScreen> {
final PostWriteController _writeController = PostWriteController();
late final PostWriteController _writeController = PostWriteController(
doLoadFromTemporary: widget.postEditId == null,
);
bool _isFetching = false;
@ -301,19 +303,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
],
),
// Content Input Area
TextField(
controller: _writeController.contentController,
maxLines: null,
decoration: InputDecoration(
hintText: 'fieldPostContent'.tr(),
hintStyle: TextStyle(fontSize: 14),
isCollapsed: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
Container(
constraints: const BoxConstraints(maxWidth: 640),
child: TextField(
controller: _writeController.contentController,
maxLines: null,
decoration: InputDecoration(
hintText: 'fieldPostContent'.tr(),
hintStyle: TextStyle(fontSize: 14),
isCollapsed: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
),
border: InputBorder.none,
),
border: InputBorder.none,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
]
.expandIndexed(
@ -364,6 +369,15 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LoadingIndicator(isActive: _isLoading),
if (_writeController.isBusy && _writeController.progress != null)
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _writeController.progress),
duration: Duration(milliseconds: 300),
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
)
else if (_writeController.isBusy)
const LinearProgressIndicator(value: null, minHeight: 2),
Container(
child: _writeController.temporaryRestored
? Container(
@ -394,15 +408,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
)
.height(_writeController.temporaryRestored ? 32 : 0, animate: true)
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
LoadingIndicator(isActive: _isLoading),
if (_writeController.isBusy && _writeController.progress != null)
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _writeController.progress),
duration: Duration(milliseconds: 300),
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
)
else if (_writeController.isBusy)
const LinearProgressIndicator(value: null, minHeight: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@ -120,7 +120,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
subtitle: Text('settingsThemeMaterial3Description').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
secondary: const Icon(Symbols.new_releases),
value: _prefs.getBool(kMaterialYouToggleStoreKey) ?? false,
value: _prefs.getBool(kMaterialYouToggleStoreKey) ?? true,
onChanged: (value) {
setState(() {
_prefs.setBool(
@ -240,6 +240,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() {});
},
),
CheckboxListTile(
secondary: const Icon(Symbols.left_panel_close),
title: Text('settingsDrawerPreferCollapse').tr(),
subtitle: Text('settingsDrawerPreferCollapseDescription').tr(),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
value: _prefs.getBool(kAppDrawerPreferCollapse) ?? false,
onChanged: (value) {
_prefs.setBool(kAppDrawerPreferCollapse, value ?? false);
final cfg = context.read<ConfigProvider>();
cfg.calcDrawerSize(context);
setState(() {});
},
),
],
),
Column(

View File

@ -36,7 +36,7 @@ Future<ThemeData> createAppTheme(
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
return ThemeData(
useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? false),
useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true),
colorScheme: colorScheme,
brightness: brightness,
iconTheme: IconThemeData(
@ -48,9 +48,19 @@ Future<ThemeData> createAppTheme(
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: hasAppBarBlurry ? 0 : null,
backgroundColor: hasAppBarBlurry ? colorScheme.surfaceContainer.withAlpha(200) : colorScheme.primary,
backgroundColor: hasAppBarBlurry ? colorScheme.primary.withOpacity(0.3) : colorScheme.primary,
foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary,
),
scaffoldBackgroundColor: Colors.transparent,
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
},
),
);
}

View File

@ -0,0 +1,164 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:relative_time/relative_time.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/experience.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/account.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/universal_image.dart';
class AccountPopoverCard extends StatelessWidget {
final SnAccount data;
const AccountPopoverCard({super.key, required this.data});
@override
Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (data.banner.isNotEmpty)
Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: AspectRatio(
aspectRatio: 16 / 7,
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(data.banner),
fit: BoxFit.cover,
),
),
),
// Top padding
Gap(16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountImage(
content: data.avatar,
radius: 20,
),
Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(data.nick).bold(),
Text('@${data.name}').fontSize(13).opacity(0.75),
],
),
),
IconButton(
onPressed: () {
Navigator.pop(context);
GoRouter.of(context).pushNamed(
'accountProfilePage',
pathParameters: {'name': data.name},
);
},
icon: const Icon(Symbols.chevron_right),
padding: EdgeInsets.zero,
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
),
const Gap(8)
],
).padding(horizontal: 16),
const Gap(16),
Wrap(
children: data.badges
.map(
(ele) => Tooltip(
richMessage: TextSpan(
children: [
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
)
.toList(),
).padding(horizontal: 24),
const Gap(8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.star),
const Gap(8),
Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'),
const Gap(8),
Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0)).fontSize(11).opacity(0.5),
const Gap(8),
Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 160),
child: LinearProgressIndicator(
value: calcLevelUpProgress(data.profile?.experience ?? 0),
borderRadius: BorderRadius.circular(8),
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
).alignment(Alignment.centerLeft),
),
],
).padding(horizontal: 24),
FutureBuilder(
future: sn.client.get('/cgi/id/users/${data.name}/status'),
builder: (context, snapshot) {
final SnAccountStatusInfo? status =
snapshot.hasData ? SnAccountStatusInfo.fromJson(snapshot.data!.data) : null;
return Row(
children: [
Icon(
Symbols.circle,
fill: 1,
size: 16,
color: (status?.isOnline ?? false) ? Colors.green : Colors.grey,
).padding(all: 4),
const Gap(8),
Text(
status != null
? status.isOnline
? 'accountStatusOnline'.tr()
: 'accountStatusOffline'.tr()
: 'loading'.tr(),
),
if (status != null && !status.isOnline && status.lastSeenAt != null)
Text(
'accountStatusLastSeen'.tr(args: [
status.lastSeenAt != null
? RelativeTime(context).format(
status.lastSeenAt!.toLocal(),
)
: 'unknown',
]),
).padding(left: 6).opacity(0.75),
],
).padding(horizontal: 24);
},
),
// Bottom padding
const Gap(16),
],
);
}
}

View File

@ -106,6 +106,44 @@ class _AttachmentListState extends State<AttachmentList> {
}
if (widget.gridded) {
final fullOfImage =
widget.data.where((ele) => ele?.mediaType == SnMediaType.image).length == widget.data.length;
if(!fullOfImage) {
return Container(
margin: widget.padding ?? EdgeInsets.zero,
decoration: BoxDecoration(
color: backgroundColor,
border: Border(
top: borderSide,
bottom: borderSide,
),
borderRadius: AttachmentList.kDefaultRadius,
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: Column(
spacing: 4,
children: widget.data
.mapIndexed(
(idx, ele) => GestureDetector(
child: AspectRatio(
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
child: Container(
constraints: constraints,
child: AttachmentItem(
data: ele,
heroTag: heroTags[idx],
fit: BoxFit.cover,
),
),
),
),
)
.toList(),
),
),
);
}
return Container(
margin: widget.padding ?? EdgeInsets.zero,
decoration: BoxDecoration(

View File

@ -129,6 +129,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
bool _showDetail = false;
@override
Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>();
@ -144,218 +146,348 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
onDismissed: () {
Navigator.of(context).pop();
},
direction: DismissiblePageDismissDirection.down,
direction: DismissiblePageDismissDirection.none,
backgroundColor: Colors.transparent,
isFullScreen: true,
child: Scaffold(
body: Stack(
children: [
Builder(builder: (context) {
if (widget.data.length == 1) {
final heroTag = widget.heroTags?.first ?? uuid.v4();
return Hero(
tag: 'attachment-${widget.data.first.rid}-$heroTag',
child: PhotoView(
key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'),
backgroundDecoration: BoxDecoration(color: Colors.transparent),
imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.first.rid),
),
),
);
}
return PhotoViewGallery.builder(
pageController: _pageController,
scrollPhysics: const BouncingScrollPhysics(),
builder: (context, idx) {
final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
return PhotoViewGalleryPageOptions(
imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.elementAt(idx).rid),
),
heroAttributes: PhotoViewHeroAttributes(
tag: 'attachment-${widget.data.first.rid}-$heroTag',
child: GestureDetector(
behavior: HitTestBehavior.translucent,
child: Scaffold(
body: Stack(
children: [
Builder(builder: (context) {
if (widget.data.length == 1) {
final heroTag = widget.heroTags?.first ?? uuid.v4();
return Hero(
tag: 'attachment-${widget.data.first.rid}-$heroTag',
child: PhotoView(
key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'),
backgroundDecoration: BoxDecoration(color: Colors.transparent),
imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.first.rid),
),
),
);
},
itemCount: widget.data.length,
loadingBuilder: (context, event) => Center(
child: SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(
value: event == null ? 0 : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1),
}
return PhotoViewGallery.builder(
pageController: _pageController,
scrollPhysics: const BouncingScrollPhysics(),
builder: (context, idx) {
final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
return PhotoViewGalleryPageOptions(
imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.elementAt(idx).rid),
),
heroAttributes: PhotoViewHeroAttributes(
tag: 'attachment-${widget.data.first.rid}-$heroTag',
),
);
},
itemCount: widget.data.length,
loadingBuilder: (context, event) => Center(
child: SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(
value: event == null ? 0 : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1),
),
),
),
),
backgroundDecoration: BoxDecoration(color: Colors.transparent),
);
}),
Align(
alignment: Alignment.bottomCenter,
child: IgnorePointer(
child: Container(
height: 300,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surface,
Colors.transparent,
],
backgroundDecoration: BoxDecoration(color: Colors.transparent),
);
}),
Align(
alignment: Alignment.bottomCenter,
child: IgnorePointer(
child: Container(
height: 300,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surface,
Colors.transparent,
],
),
),
),
),
),
),
Positioned(
left: 16,
right: 16,
bottom: 16 + MediaQuery.of(context).padding.bottom,
child: Material(
color: Colors.transparent,
child: Builder(builder: (context) {
final ud = context.read<UserDirectoryProvider>();
final item = widget.data.elementAt(
widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0,
);
final account = ud.getAccountFromCache(item.accountId);
Positioned(
left: 16,
right: 16,
bottom: 16 + MediaQuery.of(context).padding.bottom,
child: Material(
color: Colors.transparent,
child: Builder(builder: (context) {
final ud = context.read<UserDirectoryProvider>();
final item = widget.data.elementAt(
widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0,
);
final account = ud.getAccountFromCache(item.accountId);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.accountId > 0)
Row(
children: [
IgnorePointer(
child: AccountImage(
content: account?.avatar,
radius: 19,
),
),
const Gap(8),
Expanded(
child: IgnorePointer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'attachmentUploadBy'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
Text(
account?.nick ?? 'unknown'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
],
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.accountId > 0)
Row(
children: [
IgnorePointer(
child: AccountImage(
content: account?.avatar,
radius: 19,
),
),
),
if (widget.data.length > 1)
IgnorePointer(
child: Text(
'${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}',
style: GoogleFonts.robotoMono(fontSize: 13),
).padding(right: 8),
),
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16)),
onTap: _isDownloading
? null
: () => _saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
child: Container(
padding: const EdgeInsets.all(6),
child: !_isDownloading
? !_isCompletedDownload
? const Icon(Symbols.save_alt)
: const Icon(Symbols.download_done)
: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
value: _progressOfDownload,
strokeWidth: 3,
),
const Gap(8),
Expanded(
child: IgnorePointer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'attachmentUploadBy'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
Text(
account?.nick ?? 'unknown'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
if (widget.data.length > 1)
IgnorePointer(
child: Text(
'${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}',
style: GoogleFonts.robotoMono(fontSize: 13),
).padding(right: 8),
),
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16)),
onTap: _isDownloading
? null
: () =>
_saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
child: Container(
padding: const EdgeInsets.all(6),
child: !_isDownloading
? !_isCompletedDownload
? const Icon(Symbols.save_alt)
: const Icon(Symbols.download_done)
: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
value: _progressOfDownload,
strokeWidth: 3,
),
),
),
),
],
),
const Gap(4),
IgnorePointer(
child: Text(
item.alt,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
),
],
),
const Gap(4),
IgnorePointer(
child: Text(
item.alt,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
),
const Gap(2),
IgnorePointer(
child: Wrap(
spacing: 6,
children: [
if (item.metadata['exif'] == null)
const Gap(2),
IgnorePointer(
child: Wrap(
spacing: 6,
children: [
if (item.metadata['exif'] == null)
Text(
'#${item.rid}',
style: metaTextStyle,
),
if (item.metadata['exif']?['Model'] != null)
Text(
'attachmentShotOn'.tr(args: [
item.metadata['exif']?['Model'],
]),
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['ISO'] != null)
Text(
'ISO${item.metadata['exif']?['ISO']}',
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['Aperture'] != null)
Text(
'f/${item.metadata['exif']?['Aperture']}',
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['Megapixels'] != null &&
item.metadata['exif']?['Model'] != null)
Text(
'${item.metadata['exif']?['Megapixels']}MP',
style: metaTextStyle,
)
else
Text(
item.size.formatBytes(),
style: metaTextStyle,
),
if (item.metadata['width'] != null && item.metadata['height'] != null)
Text(
'${item.metadata['width']}x${item.metadata['height']}',
style: metaTextStyle,
),
if (item.metadata['ratio'] != null)
Text(
(item.metadata['ratio'] as num).toStringAsFixed(2),
style: metaTextStyle,
),
Text(
'#${item.rid}',
item.mimetype,
style: metaTextStyle,
),
if (item.metadata['exif']?['Model'] != null)
Text(
'attachmentShotOn'.tr(args: [
item.metadata['exif']?['Model'],
]),
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['ISO'] != null)
Text(
'ISO${item.metadata['exif']?['ISO']}',
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['Aperture'] != null)
Text(
'f/${item.metadata['exif']?['Aperture']}',
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['Megapixels'] != null && item.metadata['exif']?['Model'] != null)
Text(
'${item.metadata['exif']?['Megapixels']}MP',
style: metaTextStyle,
)
else
Text(
'${item.size} Bytes',
style: metaTextStyle,
),
if (item.metadata['width'] != null && item.metadata['height'] != null)
Text(
'${item.metadata['width']}x${item.metadata['height']}',
style: metaTextStyle,
),
if (item.metadata['ratio'] != null)
Text(
(item.metadata['ratio'] as num).toStringAsFixed(2),
style: metaTextStyle,
),
Text(
item.mimetype,
style: metaTextStyle,
),
],
],
),
),
),
],
);
}),
],
);
}),
),
),
),
],
],
),
),
onVerticalDragUpdate: (details) {
if (_showDetail) return;
if (details.delta.dy < 0) {
_showDetail = true;
showModalBottomSheet(
context: context,
builder: (context) => _AttachmentZoomDetailPopup(
data: widget.data.elementAt(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
),
).then((_) {
_showDetail = false;
});
}
},
onTap: () {
Navigator.of(context).pop();
},
),
);
}
}
class _AttachmentZoomDetailPopup extends StatelessWidget {
final SnAttachment data;
const _AttachmentZoomDetailPopup({required this.data});
@override
Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>();
final account = ud.getAccountFromCache(data.accountId);
const tableGap = TableRow(
children: [
TableCell(child: SizedBox(height: 16)),
TableCell(child: SizedBox(height: 16)),
],
);
return SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.info, size: 24),
const Gap(16),
Text('attachmentDetailInfo').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Expanded(
child: Table(
columnWidths: {
0: IntrinsicColumnWidth(),
1: FlexColumnWidth(),
},
children: [
TableRow(
children: [
TableCell(
child: Text('attachmentUploadBy').tr().padding(right: 16),
),
TableCell(
child: Row(
children: [
if (data.accountId > 0)
AccountImage(
content: account?.avatar,
radius: 8,
),
const Gap(8),
Text(data.accountId > 0 ? account?.nick ?? 'unknown'.tr() : 'unknown'.tr()),
const Gap(8),
Text('#${data.accountId}', style: GoogleFonts.robotoMono()).opacity(0.75),
],
),
),
],
),
tableGap,
TableRow(
children: [
TableCell(child: Text('Mimetype').padding(right: 16)),
TableCell(child: Text(data.mimetype)),
],
),
TableRow(
children: [
TableCell(child: Text('Size').padding(right: 16)),
TableCell(
child: Row(
children: [
Text(data.size.formatBytes()),
const Gap(12),
Text('${data.size} Bytes', style: GoogleFonts.robotoMono()).opacity(0.75),
],
)),
],
),
TableRow(
children: [
TableCell(child: Text('Name').padding(right: 16)),
TableCell(child: Text(data.name)),
],
),
if (data.hash.isNotEmpty)
TableRow(
children: [
TableCell(child: Text('Hash').padding(right: 16)),
TableCell(child: Text(data.hash, style: GoogleFonts.robotoMono(fontSize: 11)).opacity(0.9)),
],
),
tableGap,
...(data.metadata['exif']?.keys.map((k) => TableRow(
children: [
TableCell(child: Text(k).padding(right: 16)),
TableCell(child: Text(data.metadata['exif'][k].toString())),
],
)) ??
[]),
],
).padding(horizontal: 20, vertical: 8),
),
],
),
);
}

View File

@ -1,14 +1,18 @@
import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_context_menu/flutter_context_menu.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:popover/popover.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_popover.dart';
import 'package:surface/widgets/attachment/attachment_list.dart';
import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/link_preview.dart';
@ -95,8 +99,28 @@ class ChatMessage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isMerged && !isCompact)
AccountImage(
content: user?.avatar,
GestureDetector(
child: AccountImage(
content: user?.avatar,
),
onTap: () {
if (user == null) return;
showPopover(
backgroundColor: Theme.of(context).colorScheme.surface,
context: context,
transition: PopoverTransition.other,
bodyBuilder: (context) => SizedBox(
width: math.min(400, MediaQuery.of(context).size.width - 10),
child: AccountPopoverCard(
data: user,
),
),
direction: PopoverDirection.bottom,
arrowHeight: 5,
arrowWidth: 15,
arrowDxOffset: -190,
);
},
)
else if (isMerged)
const Gap(40),
@ -124,10 +148,13 @@ class ChatMessage extends StatelessWidget {
dateFormatter.format(data.createdAt.toLocal()),
).fontSize(13),
],
),
).height(21),
if (isCompact) const Gap(8),
if (data.preload?.quoteEvent != null)
StyledWidget(Container(
constraints: BoxConstraints(
maxWidth: 480,
),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: Border.all(
@ -150,7 +177,12 @@ class ChatMessage extends StatelessWidget {
),
)).padding(bottom: 4, top: 4),
switch (data.type) {
'messages.new' => _ChatMessageText(data: data),
'messages.new' => _ChatMessageText(
data: data,
onReply: onReply,
onEdit: onEdit,
onDelete: onDelete,
),
_ => _ChatMessageSystemNotify(data: data),
},
],
@ -181,19 +213,75 @@ class ChatMessage extends StatelessWidget {
class _ChatMessageText extends StatelessWidget {
final SnChatMessage data;
final Function(SnChatMessage)? onReply;
final Function(SnChatMessage)? onEdit;
final Function(SnChatMessage)? onDelete;
const _ChatMessageText({required this.data});
const _ChatMessageText({required this.data, this.onReply, this.onEdit, this.onDelete});
@override
Widget build(BuildContext context) {
final ua = context.read<UserProvider>();
final isOwner = ua.isAuthorized && data.sender.accountId == ua.user?.id;
if (data.body['text'] != null && data.body['text'].isNotEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownTextContent(
content: data.body['text'],
isSelectable: true,
isAutoWarp: true,
SelectionArea(
contextMenuBuilder: (context, editableTextState) {
final List<ContextMenuButtonItem> items = editableTextState.contextMenuButtonItems;
if (onReply != null) {
items.insert(
0,
ContextMenuButtonItem(
label: 'reply'.tr(),
onPressed: () {
ContextMenuController.removeAny();
onReply?.call(data);
},
),
);
}
if (isOwner && onEdit != null) {
items.insert(
1,
ContextMenuButtonItem(
label: 'edit'.tr(),
onPressed: () {
ContextMenuController.removeAny();
onEdit?.call(data);
},
),
);
}
if (isOwner && onDelete != null) {
items.insert(
2,
ContextMenuButtonItem(
label: 'delete'.tr(),
onPressed: () {
ContextMenuController.removeAny();
onDelete?.call(data);
},
),
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: items,
);
},
child: Container(
constraints: const BoxConstraints(maxWidth: 480),
child: MarkdownTextContent(
content: data.body['text'],
isAutoWarp: true,
),
),
),
if (data.updatedAt != data.createdAt)
Text(

View File

@ -48,6 +48,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
void setEdit(SnChatMessage? value) {
_contentController.text = value?.body['text'] ?? '';
_attachments.clear();
_attachments.addAll(value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
setState(() => _editingMessage = value);
}
@ -101,7 +103,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
},
);
_attachments[i] = PostWriteMedia(item);
setState(() {
_attachments[i] = PostWriteMedia(item);
});
}
} catch (err) {
if (!mounted) return;
@ -113,7 +117,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
// Send the message
// NOTICE This future should not be awaited, so that the message can be sent in the background and the user can continue to type
widget.controller.sendMessage(
'messages.new',
_editingMessage != null ? 'messages.edit' : 'messages.new',
_contentController.text,
attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(),
relatedId: _editingMessage?.id,
@ -197,6 +201,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
InkWell(
child: Text('cancel'.tr()),
onTap: () {
_attachments.clear();
setState(() => _replyingMessage = null);
},
),
@ -236,6 +241,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
InkWell(
child: Text('cancel'.tr()),
onTap: () {
_attachments.clear();
_contentController.clear();
setState(() => _editingMessage = null);
},

View File

@ -1,5 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/userinfo.dart';
@ -18,13 +20,9 @@ class ConnectionIndicator extends StatelessWidget {
final ua = context.read<UserProvider>();
return GestureDetector(
child: Container(
padding: EdgeInsets.only(
bottom: 8,
top: MediaQuery.of(context).padding.top + 8,
left: 24,
right: 24,
),
child: Material(
elevation: 2,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
color: Theme.of(context).colorScheme.secondaryContainer,
child: ua.isAuthorized
? Row(
@ -32,21 +30,25 @@ class ConnectionIndicator extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (ws.isBusy)
Text('serverConnecting').tr().textColor(
Theme.of(context).colorScheme.onSecondaryContainer)
Text('serverConnecting').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
else if (!ws.isConnected)
Text('serverDisconnected').tr().textColor(
Theme.of(context).colorScheme.onSecondaryContainer),
Text('serverDisconnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
else
Text('serverConnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer),
const Gap(8),
if (ws.isBusy)
const CircularProgressIndicator(strokeWidth: 2.5)
.width(12)
.height(12)
.padding(horizontal: 4, right: 4)
else if (!ws.isConnected)
const Icon(Symbols.power_off, size: 18)
else
const Icon(Symbols.power, size: 18),
],
)
).padding(horizontal: 8, vertical: 4)
: const SizedBox.shrink(),
)
.height(
(ws.isBusy || !ws.isConnected) && ua.isAuthorized
? MediaQuery.of(context).padding.top + 36
: 0,
animate: true)
.animate(
).opacity((ws.isBusy || !ws.isConnected) && ua.isAuthorized ? 1 : 0, animate: true).animate(
const Duration(milliseconds: 300),
Curves.easeInOut,
),

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_context_menu/flutter_context_menu.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/config.dart';
class ContextMenuArea extends StatelessWidget {
final ContextMenu contextMenu;
@ -22,11 +23,10 @@ class ContextMenuArea extends StatelessWidget {
return Listener(
onPointerDown: (event) {
mousePosition = event.position;
final isCollapseDrawer = ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE);
if (!isCollapseDrawer) {
final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET);
final cfg = context.read<ConfigProvider>();
if (!cfg.drawerIsCollapsed) {
// Leave padding for side navigation
mousePosition = isExpandDrawer
mousePosition = cfg.drawerIsExpanded
? mousePosition.copyWith(dx: mousePosition.dx - 304 * 2)
: mousePosition.copyWith(dx: mousePosition.dx - 72 * 2);
}

View File

@ -17,7 +17,6 @@ import 'attachment/attachment_zoom.dart';
class MarkdownTextContent extends StatelessWidget {
final String content;
final bool isSelectable;
final bool isAutoWarp;
final bool isEnlargeSticker;
final TextScaler? textScaler;
@ -26,14 +25,14 @@ class MarkdownTextContent extends StatelessWidget {
const MarkdownTextContent({
super.key,
required this.content,
this.isSelectable = false,
this.isAutoWarp = false,
this.isEnlargeSticker = false,
this.textScaler,
this.attachments,
});
Widget _buildContent(BuildContext context) {
@override
Widget build(BuildContext context) {
return Markdown(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
@ -68,8 +67,7 @@ class MarkdownTextContent extends StatelessWidget {
),
code: GoogleFonts.robotoMono(height: 1),
),
builders: {
},
builders: {},
softLineBreak: true,
extensionSet: markdown.ExtensionSet(
<markdown.BlockSyntax>[
@ -144,7 +142,7 @@ class MarkdownTextContent extends StatelessWidget {
);
case 'attachments':
final attachment = attachments?.firstWhere(
(ele) => ele?.rid == segments[1],
(ele) => ele?.rid == segments[1],
orElse: () => null,
);
if (attachment != null) {
@ -202,14 +200,6 @@ class MarkdownTextContent extends StatelessWidget {
},
);
}
@override
Widget build(BuildContext context) {
if (isSelectable) {
return SelectionArea(child: _buildContent(context));
}
return _buildContent(context);
}
}
class _UserNameCardInlineSyntax extends markdown.InlineSyntax {

View File

@ -1,9 +1,13 @@
import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/navigation.dart';
import 'package:surface/widgets/version_label.dart';
@ -28,8 +32,9 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
@override
Widget build(BuildContext context) {
final nav = context.watch<NavigationProvider>();
final cfg = context.watch<ConfigProvider>();
final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(TABLET) ? Colors.transparent : null;
final backgroundColor = cfg.drawerIsExpanded ? Colors.transparent : null;
return ListenableBuilder(
listenable: nav,
@ -44,6 +49,18 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
backgroundColor: backgroundColor,
selectedIndex: nav.currentIndex,
children: [
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && !cfg.drawerIsExpanded)
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / MediaQuery.of(context).devicePixelRatio,
),
),
),
child: WindowTitleBarBox(),
),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -18,9 +18,7 @@ class _AppRailNavigationState extends State<AppRailNavigation> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context
.read<NavigationProvider>()
.autoDetectIndex(GoRouter.maybeOf(context));
context.read<NavigationProvider>().autoDetectIndex(GoRouter.maybeOf(context));
});
}
@ -31,11 +29,11 @@ class _AppRailNavigationState extends State<AppRailNavigation> {
return ListenableBuilder(
listenable: nav,
builder: (context, _) {
final destinations =
nav.destinations.where((ele) => ele.isPinned).toList();
final destinations = nav.destinations.where((ele) => ele.isPinned).toList();
return NavigationRail(
selectedIndex: nav.currentIndex,
selectedIndex:
nav.currentIndex != null && nav.currentIndex! < nav.pinnedDestinationCount ? nav.currentIndex : null,
destinations: [
...destinations.where((ele) => ele.isPinned).map((ele) {
return NavigationRailDestination(

View File

@ -6,8 +6,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/navigation.dart';
import 'package:surface/widgets/connection_indicator.dart';
import 'package:surface/widgets/dialog.dart';
@ -15,6 +17,7 @@ import 'package:surface/widgets/navigation/app_background.dart';
import 'package:surface/widgets/navigation/app_bottom_navigation.dart';
import 'package:surface/widgets/navigation/app_drawer_navigation.dart';
import 'package:surface/widgets/navigation/app_rail_navigation.dart';
import 'package:surface/widgets/notify_indicator.dart';
final globalRootScaffoldKey = GlobalKey<ScaffoldState>();
@ -57,10 +60,11 @@ class AppRootScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cfg = context.watch<ConfigProvider>();
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final isCollapseDrawer = ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE);
final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET);
final isCollapseDrawer = cfg.drawerIsCollapsed;
final isExpandedDrawer = cfg.drawerIsExpanded;
final routeName = GoRouter.of(context).routerDelegate.currentConfiguration.last.route.name;
final isShowBottomNavigation = NavigationProvider.kShowBottomNavScreen.contains(routeName)
@ -81,7 +85,7 @@ class AppRootScaffold extends StatelessWidget {
),
),
),
child: isExpandDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(),
child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(),
),
Expanded(child: body),
],
@ -95,59 +99,66 @@ class AppRootScaffold extends StatelessWidget {
iconMouseDown: Theme.of(context).colorScheme.primary,
);
final safeTop = MediaQuery.of(context).padding.top;
return AppBackground(
isRoot: true,
child: Scaffold(
key: globalRootScaffoldKey,
body: Column(
body: Stack(
children: [
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
children: [
WindowTitleBarBox(
child: MoveWindow(
child: Text(
'Solar Network',
style: GoogleFonts.spaceGrotesk(),
).padding(horizontal: 12, vertical: 5),
),
),
if (!Platform.isMacOS)
Expanded(
child: WindowTitleBarBox(
child: Row(
children: [
Expanded(child: MoveWindow()),
Row(
children: [
MinimizeWindowButton(colors: windowButtonColor),
MaximizeWindowButton(colors: windowButtonColor),
CloseWindowButton(colors: windowButtonColor),
],
),
],
),
Column(
children: [
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 1 / devicePixelRatio,
),
),
],
),
),
ConnectionIndicator(),
Expanded(child: innerWidget),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
children: [
WindowTitleBarBox(
child: MoveWindow(
child: Text(
'Solar Network',
style: GoogleFonts.spaceGrotesk(),
).padding(horizontal: 12, vertical: 5),
),
),
if (!Platform.isMacOS)
Expanded(
child: WindowTitleBarBox(
child: Row(
children: [
Expanded(child: MoveWindow()),
Row(
children: [
MinimizeWindowButton(colors: windowButtonColor),
MaximizeWindowButton(colors: windowButtonColor),
CloseWindowButton(colors: windowButtonColor),
],
),
],
),
),
),
],
),
),
Expanded(child: innerWidget),
],
),
Positioned(top: safeTop > 0 ? safeTop : 16, right: 8, child: NotifyIndicator()),
Positioned(top: safeTop > 0 ? safeTop : 16, left: 8, child: ConnectionIndicator()),
],
),
drawer: !isExpandDrawer ? AppNavigationDrawer() : null,
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
drawerEdgeDragWidth: isPopable ? 0 : null,
bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null,
),

View File

@ -0,0 +1,58 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/notification.dart';
import 'package:surface/providers/userinfo.dart';
class NotifyIndicator extends StatelessWidget {
const NotifyIndicator({super.key});
@override
Widget build(BuildContext context) {
final ua = context.read<UserProvider>();
final nty = context.watch<NotificationProvider>();
return ListenableBuilder(
listenable: nty,
builder: (context, _) {
return GestureDetector(
child: Material(
elevation: 2,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
color: Theme.of(context).colorScheme.secondaryContainer,
child: ua.isAuthorized
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
nty.notifications.lastOrNull?.title ??
'notificationUnreadCount'.plural(nty.notifications.length),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (nty.notifications.lastOrNull?.body != null)
Text(
nty.notifications.lastOrNull!.body,
maxLines: 1,
overflow: TextOverflow.ellipsis,
).padding(left: 4),
const Gap(8),
const Icon(Symbols.notifications_unread, size: 18),
],
).padding(horizontal: 8, vertical: 4)
: const SizedBox.shrink(),
).opacity(nty.notifications.isNotEmpty && ua.isAuthorized ? 1 : 0, animate: true).animate(
const Duration(milliseconds: 300),
Curves.easeInOut,
),
onTap: () {
nty.clear();
},
);
});
}
}

View File

@ -113,7 +113,7 @@ class PostItem extends StatelessWidget {
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
} else {
await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}', file: imageFile);
await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile);
}
await imageFile.delete();
@ -879,13 +879,18 @@ class _PostContentBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (data.body['content'] == null) return const SizedBox.shrink();
return MarkdownTextContent(
isSelectable: isSelectable,
final content = MarkdownTextContent(
isAutoWarp: data.type == 'story',
isEnlargeSticker: true,
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
content: data.body['content'],
attachments: data.preload?.attachments,
);
if (isSelectable) {
return SelectionArea(child: content);
}
return content;
}
}

View File

@ -25,7 +25,7 @@ class PostMiniEditor extends StatefulWidget {
}
class _PostMiniEditorState extends State<PostMiniEditor> {
final PostWriteController _writeController = PostWriteController();
final PostWriteController _writeController = PostWriteController(doLoadFromTemporary: false);
bool _isFetching = false;

View File

@ -55,17 +55,20 @@ class UniversalImage extends StatelessWidget {
? null
: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: TweenAnimationBuilder(
tween: Tween(
begin: 0,
end: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: 0,
),
duration: const Duration(milliseconds: 300),
builder: (context, value, _) => CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null,
return Container(
constraints: BoxConstraints(maxHeight: 80),
child: Center(
child: TweenAnimationBuilder(
tween: Tween(
begin: 0,
end: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: 0,
),
duration: const Duration(milliseconds: 300),
builder: (context, value, _) => CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null,
),
),
),
);

View File

@ -12,59 +12,59 @@ PODS:
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- Firebase/Analytics (11.4.0):
- Firebase/Analytics (11.6.0):
- Firebase/Core
- Firebase/Core (11.4.0):
- Firebase/Core (11.6.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 11.4.0)
- Firebase/CoreOnly (11.4.0):
- FirebaseCore (= 11.4.0)
- Firebase/Messaging (11.4.0):
- FirebaseAnalytics (~> 11.6.0)
- Firebase/CoreOnly (11.6.0):
- FirebaseCore (~> 11.6.0)
- Firebase/Messaging (11.6.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.4.0)
- firebase_analytics (11.3.6):
- Firebase/Analytics (= 11.4.0)
- FirebaseMessaging (~> 11.6.0)
- firebase_analytics (11.4.0):
- Firebase/Analytics (= 11.6.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.9.0):
- Firebase/CoreOnly (~> 11.4.0)
- firebase_core (3.10.0):
- Firebase/CoreOnly (~> 11.6.0)
- FlutterMacOS
- firebase_messaging (15.1.6):
- Firebase/CoreOnly (~> 11.4.0)
- Firebase/Messaging (~> 11.4.0)
- firebase_messaging (15.2.0):
- Firebase/CoreOnly (~> 11.6.0)
- Firebase/Messaging (~> 11.6.0)
- firebase_core
- FlutterMacOS
- FirebaseAnalytics (11.4.0):
- FirebaseAnalytics/AdIdSupport (= 11.4.0)
- FirebaseCore (~> 11.0)
- FirebaseAnalytics (11.6.0):
- FirebaseAnalytics/AdIdSupport (= 11.6.0)
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseAnalytics/AdIdSupport (11.6.0):
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.4.0)
- GoogleAppMeasurement (= 11.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.4.0):
- FirebaseCoreInternal (~> 11.0)
- FirebaseCore (11.6.0):
- FirebaseCoreInternal (~> 11.6.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.6.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (11.6.0):
- FirebaseCore (~> 11.6.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseMessaging (11.6.0):
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@ -75,28 +75,28 @@ PODS:
- flutter_udid (0.0.1):
- FlutterMacOS
- SAMKeychain
- flutter_webrtc (0.12.2):
- flutter_webrtc (0.12.6):
- FlutterMacOS
- WebRTC-SDK (= 125.6422.06)
- FlutterMacOS (1.0.0)
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (11.4.0):
- GoogleAppMeasurement/AdIdSupport (= 11.4.0)
- GoogleAppMeasurement (11.6.0):
- GoogleAppMeasurement/AdIdSupport (= 11.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.4.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.4.0)
- GoogleAppMeasurement/AdIdSupport (11.6.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.6.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.4.0):
- GoogleAppMeasurement/WithoutAdIdSupport (11.6.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
@ -134,7 +134,7 @@ PODS:
- GoogleUtilities/Privacy
- in_app_review (2.0.0):
- FlutterMacOS
- livekit_client (2.3.4):
- livekit_client (2.3.5):
- flutter_webrtc
- FlutterMacOS
- WebRTC-SDK (= 125.6422.06)
@ -287,24 +287,24 @@ SPEC CHECKSUMS:
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
firebase_analytics: a80b3d6645f2f12d626fde928b61dae12e5ea2ef
firebase_core: 1dfe1f4d02ad78be0277e320aa3d8384cf46231f
firebase_messaging: 61f678060b69a7ae1013e3a939ec8e1c56ef6fcf
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
firebase_analytics: 5249f87da6fed852901581aab2602e0280ec2fdb
firebase_core: 6d9bb8b0ea817e8fe0d928177d42275b45fdba6f
firebase_messaging: ae8e88b586e4d50abc7cac5bacf74d21967fd226
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
flutter_webrtc: 53c9e1285ab32dfb58afb1e1471416a877e23d7a
flutter_webrtc: d55fd3f5c75b42940b6b4b2cf376a5797398d1f8
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
livekit_client: b7ab91e79e657d7d40da16cb2f90d517cb72d406
livekit_client: 91c68237edede55f8891a166a28c1daec8a1e4b1
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5

View File

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: daa1d780fdecf8af925680c06c86563cdd445deea995d5c9176f1302a2b10bbe
sha256: "27899c95f9e7ec06c8310e6e0eac967707714b9f1450c4a58fa00ca011a4a8ae"
url: "https://pub.dev"
source: hosted
version: "1.3.48"
version: "1.3.49"
_macros:
dependency: transitive
description: dart
@ -266,10 +266,10 @@ packages:
dependency: transitive
description:
name: connectivity_plus
sha256: e0817759ec6d2d8e57eb234e6e57d2173931367a865850c7acea40d4b4f9c27d
sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412"
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.2"
connectivity_plus_platform_interface:
dependency: transitive
description:
@ -290,10 +290,10 @@ packages:
dependency: "direct main"
description:
name: croppy
sha256: "14bb40fd6c1771b093a907ddbf24df9aa49a4e6e379dd630602eb446e30ec629"
sha256: bf99b00023df0d7d047e04d27d496d87cbefd968f578d0bd30f342ff75570a12
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.3"
cross_file:
dependency: "direct main"
description:
@ -354,18 +354,18 @@ packages:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.11"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431"
sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544
url: "https://pub.dev"
source: hosted
version: "11.2.0"
version: "11.2.1"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -538,34 +538,34 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "366140abb55418ea23060b779893fa997c2d8e1974a4d1cc4d9590933b65c5fd"
sha256: "498c6cb8468e348a556709c745d92a52173ab3a9b906aa0593393f0787f201ea"
url: "https://pub.dev"
source: hosted
version: "11.3.6"
version: "11.4.0"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: "8e987cf977c0c8f4ad02d9950a9b25b1a9606899f37b66a322a43af05be0246b"
sha256: ccbb350554e98afdb4b59852689292d194d31232a2647b5012a66622b3711df9
url: "https://pub.dev"
source: hosted
version: "4.2.8"
version: "4.3.0"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "0b64ef9060d394bba3d3b4777f49ee098efeeea7b0afb04663c956de6a3da170"
sha256: "68e1f18fc16482c211c658e739c25f015b202a260d9ad8249c6d3d7963b8105f"
url: "https://pub.dev"
source: hosted
version: "0.5.10+5"
version: "0.5.10+6"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "15d761b95dfa2906dfcc31b7fc6fe293188533d1a3ffe78389ba9e69bd7fdbde"
sha256: "0307c1fde82e2b8b97e0be2dab93612aff9a72f31ebe9bfac66ed8b37ef7c568"
url: "https://pub.dev"
source: hosted
version: "3.9.0"
version: "3.10.0"
firebase_core_platform_interface:
dependency: transitive
description:
@ -586,26 +586,26 @@ packages:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "151a3ee68736abf293aab66d1317ade53c88abe1db09c75a0460aebf7767bbdf"
sha256: "48a8a59197c1c5174060ba9aa1e0036e9b5a0d28a0cc22d19c1fcabc67fafe3c"
url: "https://pub.dev"
source: hosted
version: "15.1.6"
version: "15.2.0"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: f331ee51e40c243f90cc7bc059222dfec4e5df53125b08d31fb28961b00d2a9d
sha256: "9770a8e91f54296829dcaa61ce9b7c2f9ae9abbf99976dd3103a60470d5264dd"
url: "https://pub.dev"
source: hosted
version: "4.5.49"
version: "4.6.0"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: efaf3fdc54cd77e0eedb8e75f7f01c808828c64d052ddbf94d3009974e47d30f
sha256: "329ca4ef45ec616abe6f1d5e58feed0934a50840a65aa327052354ad3c64ed77"
url: "https://pub.dev"
source: hosted
version: "3.9.5"
version: "3.10.0"
fixnum:
dependency: transitive
description:
@ -618,10 +618,10 @@ packages:
dependency: "direct main"
description:
name: fl_chart
sha256: c724234b05e378383e958f3e82ca84a3e1e3c06a0898462044dd8a24b1ee9864
sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237"
url: "https://pub.dev"
source: hosted
version: "0.70.0"
version: "0.70.2"
flutter:
dependency: "direct main"
description: flutter
@ -679,10 +679,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5"
sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c
url: "https://pub.dev"
source: hosted
version: "0.14.2"
version: "0.14.3"
flutter_lints:
dependency: "direct dev"
description:
@ -700,10 +700,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "255b00afa1a7bad19727da6a7780cf3db6c3c12e68d302d85e0ff1fdf173db9e"
sha256: e37f4c69a07b07bb92622ef6b131a53c9aae48f64b176340af9e8e5238718487
url: "https://pub.dev"
source: hosted
version: "0.7.4+3"
version: "0.7.5"
flutter_native_splash:
dependency: "direct dev"
description:
@ -740,10 +740,10 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123"
sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
url: "https://pub.dev"
source: hosted
version: "2.0.16"
version: "2.0.17"
flutter_test:
dependency: "direct dev"
description: flutter
@ -766,10 +766,10 @@ packages:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: e82ffd0d0b79621c5554eed73509d7f5bd286d57fef29a573846785c65237fb1
sha256: "188401cc3275bc4f1f965babdff6cac612a4b46572f1e49f49db8af5361d5712"
url: "https://pub.dev"
source: hosted
version: "0.12.5+hotfix.2"
version: "0.12.6"
freezed:
dependency: "direct dev"
description:
@ -822,10 +822,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539"
sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d"
url: "https://pub.dev"
source: hosted
version: "14.6.2"
version: "14.6.3"
google_fonts:
dependency: "direct main"
description:
@ -934,10 +934,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: aa6f1280b670861ac45220cc95adc59bb6ae130259d36f980ccb62220dc5e59f
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
url: "https://pub.dev"
source: hosted
version: "0.8.12+19"
version: "0.8.12+20"
image_picker_for_web:
dependency: transitive
description:
@ -974,10 +974,10 @@ packages:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
url: "https://pub.dev"
source: hosted
version: "2.10.0"
version: "2.10.1"
image_picker_windows:
dependency: transitive
description:
@ -1086,10 +1086,10 @@ packages:
dependency: "direct main"
description:
name: livekit_client
sha256: a19bcf8640b45e0730b1e3e3e78be7882dad680c6ebe8ae75294fd8d4612450d
sha256: "02b4653d903852d0ae86b15fbe4324747606dae6410fe860d0c07a11c79988de"
url: "https://pub.dev"
source: hosted
version: "2.3.4+hotfix.2"
version: "2.3.5"
logging:
dependency: transitive
description:
@ -1110,10 +1110,10 @@ packages:
dependency: "direct main"
description:
name: markdown
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
url: "https://pub.dev"
source: hosted
version: "7.2.2"
version: "7.3.0"
marquee:
dependency: "direct main"
description:
@ -1270,10 +1270,10 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d"
sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
version: "8.1.3"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -1502,10 +1502,10 @@ packages:
dependency: transitive
description:
name: pubspec_parse
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.5.0"
qr:
dependency: transitive
description:
@ -1630,10 +1630,10 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400"
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
url: "https://pub.dev"
source: hosted
version: "10.1.3"
version: "10.1.4"
share_plus_platform_interface:
dependency: transitive
description:
@ -1654,10 +1654,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
sha256: "138b7bbbc7f59c56236e426c37afb8f78cbc57b094ac64c440e0bb90e380a4f5"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.2"
shared_preferences_foundation:
dependency: transitive
description:
@ -1971,18 +1971,18 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.4.0"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "3.1.4"
uuid:
dependency: "direct main"
description:
@ -2003,10 +2003,10 @@ packages:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.12"
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
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.2.1+48
version: 2.2.2+54
environment:
sdk: ^3.5.4

View File

@ -1,9 +1,9 @@
id = "solian-next"
id = "solian"
[[locations]]
id = "solian-next"
host = ["sn-next.solsynth.dev"]
path = ["/"]
id = "solian"
hosts = ["sn.solsynth.dev"]
paths = ["/"]
[[locations.destinations]]
id = "solian-next-web"
uri = "files:///workdir/solian-next?fallback=index.html&index=index.html"
id = "solian-web"
uri = "files:///workdir/solian?fallback=index.html&index=index.html"