Compare commits

..

25 Commits

Author SHA1 Message Date
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
57279eb3e4 🚀 Launch 2.2.1+48 2025-01-05 13:41:38 +08:00
c403a2914a 💄 Optimized article attachments displaying strategy 2025-01-05 13:34:37 +08:00
bcb176344c 💄 Optimize some styling 2025-01-05 13:29:39 +08:00
ecf362cffc 🚀 Launch 2.2.1+47 2025-01-05 12:13:43 +08:00
f4ab7671d8 🐛 Bug fixes on resetting post write controller 2025-01-05 12:06:49 +08:00
a2a3018917 🚀 Launch 2.2.1+46 2025-01-04 22:05:39 +08:00
0bdb664000 ⚗️ Remove initialization screen 2025-01-04 21:51:22 +08:00
9c3b61ce57 Lunar calendar festivals 2025-01-04 21:49:48 +08:00
d06df3d278 Stickers 2025-01-04 21:26:28 +08:00
547ba19e61 🐛 Fix attachment zoom meta throw error 2025-01-04 19:14:40 +08:00
40 changed files with 1333 additions and 222 deletions

View File

@ -7,11 +7,7 @@ meta {
post {
url: {{endpoint}}/cgi/uc/boosts/1/activate
body: none
auth: bearer
}
auth:bearer {
token: {{atk}}
auth: inherit
}
body:json {

View File

@ -0,0 +1,19 @@
meta {
name: Create Sticker Pack
type: http
seq: 1
}
post {
url: {{endpoint}}/cgi/uc/stickers/packs
body: json
auth: inherit
}
body:json {
{
"prefix": "cat",
"name": "Solar Network full of Cats!",
"description": "The sticker packs is full of stickers which related with cats!"
}
}

View File

@ -0,0 +1,20 @@
meta {
name: Create Sticker
type: http
seq: 2
}
post {
url: {{endpoint}}/cgi/uc/stickers
body: json
auth: inherit
}
body:json {
{
"alias": "AteChip",
"name": "Cat ate chips",
"attachment_id": "d0b692cc64054463",
"pack_id": 2
}
}

View File

@ -7,11 +7,7 @@ meta {
post {
url: {{endpoint}}/cgi/id/dev/notify/all
body: json
auth: bearer
}
auth:bearer {
token: {{atk}}
auth: inherit
}
body:json {

7
api/collection.bru Normal file
View File

@ -0,0 +1,7 @@
auth {
mode: bearer
}
auth:bearer {
token: {{atk}}
}

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",
@ -414,6 +416,9 @@
"celebrateBirthday": "Happy birthday, {}!",
"celebrateMerryXmas": "Merry christmas, {}",
"celebrateNewYear": "Happy new year, {}",
"celebrateLunarNewYear": "Happy lunar new year, {}",
"celebrateMidAutumn": "Happy mid-autumn festival, {}",
"celebrateDragonBoat": "Happy dragon boat festival, {}",
"celebrateValentineDay": "Today is valentine's day, {}!",
"celebrateLaborDay": "Today is labor day, {}.",
"celebrateMotherDay": "Today is mother's day, {}.",
@ -423,6 +428,9 @@
"celebrateThanksgiving": "Today is thanksgiving day, {}!",
"pendingBirthday": "Birthday in {}",
"pendingMerryXmas": "Christmas in {}",
"pendingLunarNewYear": "Lunar new year in {}",
"pendingMidAutumn": "Mid-autumn festival in {}",
"pendingDragonBoat": "Dragon boat festival in {}",
"pendingNewYear": "New year in {}",
"pendingValentineDay": "Valentine's day in {}",
"pendingLaborDay": "Labor day in {}",

View File

@ -185,6 +185,8 @@
"settingsThemeMaterial3Description": "将应用主题设置为 Material 3 设计范式的主题。",
"settingsAppBarTransparent": "透明顶栏",
"settingsAppBarTransparentDescription": "为顶栏启用透明效果。",
"settingsDrawerPreferCollapse": "侧边栏偏好折叠",
"settingsDrawerPreferCollapseDescription": "将侧边栏优先折叠,即使屏幕宽度足够大去放下整个侧边栏。",
"settingsColorScheme": "主题色",
"settingsColorSchemeDescription": "设置应用主题色。",
"settingsColorSeed": "预设色彩主题",
@ -410,6 +412,9 @@
"dailyCheckNegativeHint6": "出门",
"dailyCheckNegativeHint6Description": "忘带伞遇上大雨",
"celebrateBirthday": "生日快乐,{}",
"celebrateLunarNewYear": "春节快乐,{}",
"celebrateMidAutumn": "中秋节快乐,{}",
"celebrateDragonBoat": "端午节快乐,{}",
"celebrateMerryXmas": "圣诞快乐,{}",
"celebrateNewYear": "新年快乐,{}",
"celebrateValentineDay": "今天是情人节,{}",
@ -419,6 +424,9 @@
"celebrateFatherDay": "今天是父亲节,{}。",
"celebrateHalloween": "快乐在圣诞节,{}",
"celebrateThanksgiving": "今天是感恩节,{}",
"pendingLunarNewYear": "{} 过春节",
"pendingMidAutumn": "{} 过中秋节",
"pendingDragonBoat": "{} 过端午节",
"pendingBirthday": "{} 过生日",
"pendingMerryXmas": "{} 过圣诞节",
"pendingNewYear": "{} 跨年",

View File

@ -185,6 +185,8 @@
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
"settingsAppBarTransparent": "透明頂欄",
"settingsAppBarTransparentDescription": "為頂欄啓用透明效果。",
"settingsDrawerPreferCollapse": "側邊欄偏好摺疊",
"settingsDrawerPreferCollapseDescription": "將側邊欄優先摺疊,即使屏幕寬度足夠大去放下整個側邊欄。",
"settingsColorScheme": "主題色",
"settingsColorSchemeDescription": "設置應用主題色。",
"settingsColorSeed": "預設色彩主題",
@ -279,6 +281,10 @@
"one": "{} 個附件",
"other": "{} 個附件"
},
"messageTyping": {
"one": "{} 正在輸入",
"other": "{} 正在輸入"
},
"fieldAttachmentRandomId": "訪問 ID",
"fieldAttachmentAlt": "概述文字",
"addAttachmentFromAlbum": "從相冊中添加附件",
@ -406,6 +412,9 @@
"dailyCheckNegativeHint6": "出門",
"dailyCheckNegativeHint6Description": "忘帶傘遇上大雨",
"celebrateBirthday": "生日快樂,{}",
"celebrateLunarNewYear": "春節快樂,{}",
"celebrateMidAutumn": "中秋節快樂,{}",
"celebrateDragonBoat": "端午節快樂,{}",
"celebrateMerryXmas": "聖誕快樂,{}",
"celebrateNewYear": "新年快樂,{}",
"celebrateValentineDay": "今天是情人節,{}",
@ -415,6 +424,9 @@
"celebrateFatherDay": "今天是父親節,{}。",
"celebrateHalloween": "快樂在聖誕節,{}",
"celebrateThanksgiving": "今天是感恩節,{}",
"pendingLunarNewYear": "{} 過春節",
"pendingMidAutumn": "{} 過中秋節",
"pendingDragonBoat": "{} 過端午節",
"pendingBirthday": "{} 過生日",
"pendingMerryXmas": "{} 過聖誕節",
"pendingNewYear": "{} 跨年",

View File

@ -185,6 +185,8 @@
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
"settingsAppBarTransparent": "透明頂欄",
"settingsAppBarTransparentDescription": "為頂欄啟用透明效果。",
"settingsDrawerPreferCollapse": "側邊欄偏好摺疊",
"settingsDrawerPreferCollapseDescription": "將側邊欄優先摺疊,即使屏幕寬度足夠大去放下整個側邊欄。",
"settingsColorScheme": "主題色",
"settingsColorSchemeDescription": "設置應用主題色。",
"settingsColorSeed": "預設色彩主題",
@ -279,6 +281,10 @@
"one": "{} 個附件",
"other": "{} 個附件"
},
"messageTyping": {
"one": "{} 正在輸入",
"other": "{} 正在輸入"
},
"fieldAttachmentRandomId": "訪問 ID",
"fieldAttachmentAlt": "概述文字",
"addAttachmentFromAlbum": "從相冊中添加附件",
@ -406,6 +412,9 @@
"dailyCheckNegativeHint6": "出門",
"dailyCheckNegativeHint6Description": "忘帶傘遇上大雨",
"celebrateBirthday": "生日快樂,{}",
"celebrateLunarNewYear": "春節快樂,{}",
"celebrateMidAutumn": "中秋節快樂,{}",
"celebrateDragonBoat": "端午節快樂,{}",
"celebrateMerryXmas": "聖誕快樂,{}",
"celebrateNewYear": "新年快樂,{}",
"celebrateValentineDay": "今天是情人節,{}",
@ -415,6 +424,9 @@
"celebrateFatherDay": "今天是父親節,{}。",
"celebrateHalloween": "快樂在聖誕節,{}",
"celebrateThanksgiving": "今天是感恩節,{}",
"pendingLunarNewYear": "{} 過春節",
"pendingMidAutumn": "{} 過中秋節",
"pendingDragonBoat": "{} 過端午節",
"pendingBirthday": "{} 過生日",
"pendingMerryXmas": "{} 過聖誕節",
"pendingNewYear": "{} 跨年",

View File

@ -380,7 +380,7 @@ SPEC CHECKSUMS:
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
flutter_webrtc: 1a53bd24f97bcfeff512f13699e721897f261563
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5

View File

@ -83,7 +83,6 @@ class ChatMessageController extends ChangeNotifier {
if (member.id == profile?.id) break;
if (!typingMembers.any((x) => x.id == member.id)) {
typingMembers.add(member);
print('Typing member: ${typingMembers.map((ele) => member.id)}');
notifyListeners();
}
typingInactiveTimer[member.id]?.cancel();

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();
@ -623,13 +627,15 @@ class PostWriteController extends ChangeNotifier {
void reset() {
publishedAt = null;
publishedUntil = null;
thumbnail = null;
visibility = 0;
titleController.clear();
descriptionController.clear();
contentController.clear();
aliasController.clear();
tags.clear();
categories.clear();
attachments.clear();
tags = List.empty(growable: true);
categories = List.empty(growable: true);
attachments = List.empty(growable: true);
editingPost = null;
replyingPost = null;
repostingPost = null;

View File

@ -10,7 +10,6 @@ import 'package:easy_localization_loader/easy_localization_loader.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:package_info_plus/package_info_plus.dart';
@ -18,7 +17,6 @@ import 'package:provider/provider.dart';
import 'package:relative_time/relative_time.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/firebase_options.dart';
import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart';
@ -30,6 +28,7 @@ import 'package:surface/providers/post.dart';
import 'package:surface/providers/relationship.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_sticker.dart';
import 'package:surface/providers/special_day.dart';
import 'package:surface/providers/theme.dart';
import 'package:surface/providers/user_directory.dart';
@ -41,7 +40,6 @@ import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/version_label.dart';
import 'package:version/version.dart';
import 'package:workmanager/workmanager.dart';
import 'package:in_app_review/in_app_review.dart';
@ -144,6 +142,7 @@ class SolianApp extends StatelessWidget {
Provider(create: (ctx) => SnPostContentProvider(ctx)),
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
Provider(create: (ctx) => SnStickerProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)),
@ -208,8 +207,6 @@ class _AppSplashScreen extends StatefulWidget {
}
class _AppSplashScreenState extends State<_AppSplashScreen> {
bool _isReady = false;
void _tryRequestRating() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('first_boot_time')) {
@ -261,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;
@ -282,8 +283,6 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
} catch (err) {
if (!mounted) return;
await context.showErrorDialog(err);
} finally {
setState(() => _isReady = true);
}
}
@ -303,32 +302,17 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
@override
Widget build(BuildContext context) {
if (!_isReady) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: Container(
constraints: const BoxConstraints(maxWidth: 180),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (MediaQuery.of(context).platformBrightness == Brightness.dark)
Image.asset("assets/icon/icon-dark.png", width: 64, height: 64)
else
Image.asset("assets/icon/icon.png", width: 64, height: 64),
const Gap(6),
LinearProgressIndicator(
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
),
const Gap(20),
Text('appInitializing'.tr(), textAlign: TextAlign.center),
AppVersionLabel(),
],
),
).center(),
);
}
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

@ -0,0 +1,38 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
class SnStickerProvider {
late final SnNetworkProvider _sn;
final Map<String, SnSticker?> _cache = {};
SnStickerProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
}
bool hasNotSticker(String alias) {
return _cache.containsKey(alias) && _cache[alias] == null;
}
Future<SnSticker?> lookupSticker(String alias) async {
if (_cache.containsKey(alias)) {
return _cache[alias];
}
try {
final resp = await _sn.client.get('/cgi/uc/stickers/lookup/$alias');
final sticker = SnSticker.fromJson(resp.data);
_cache[alias] = sticker;
return sticker;
} catch (err) {
_cache[alias] = null;
log('[Sticker] Failed to lookup sticker $alias: $err');
}
return null;
}
}

View File

@ -3,9 +3,12 @@ import 'package:provider/provider.dart';
import 'package:surface/providers/userinfo.dart';
// Stored as key: month, day
const Map<String, (int, int)> kSpecialDays = {
final Map<String, (int, int)> kSpecialDays = {
// Birthday is dynamically generated according to the user's profile
'NewYear': (1, 1),
'LunarNewYear': (lunarToGregorian(null, 1, 1).month, lunarToGregorian(null, 1, 1).day),
'MidAutumn': (lunarToGregorian(null, 8, 15).month, lunarToGregorian(null, 8, 15).day),
'DragonBoat': (lunarToGregorian(null, 5, 5).month, lunarToGregorian(null, 5, 5).day),
'ValentineDay': (2, 14),
'LaborDay': (5, 1),
'MotherDay': (5, 11),
@ -19,6 +22,9 @@ const Map<String, (int, int)> kSpecialDays = {
const Map<String, String> kSpecialDaysSymbol = {
'Birthday': '🎂',
'NewYear': '🎉',
'LunarNewYear': '🎉',
'MidAutumn': '🥮',
'DragonBoat': '🐲',
'MerryXmas': '🎄',
'ValentineDay': '💑',
'LaborDay': '🏋️',
@ -134,3 +140,45 @@ class SpecialDayProvider {
return (elapsedDuration / totalDuration).clamp(0.0, 1.0);
}
}
final Map<int, LunarYear> lunarYearData = {
2025: LunarYear(
startDate: DateTime(2025, 1, 29),
months: [29, 30, 30, 29, 30, 29, 29, 30, 30, 29, 30, 29],
leapMonth: 0,
),
};
class LunarYear {
final DateTime startDate;
final List<int> months;
final int leapMonth;
LunarYear({required this.startDate, required this.months, required this.leapMonth});
}
DateTime lunarToGregorian(int? year, int month, int day, {bool isLeapMonth = false}) {
year = year ?? DateTime.now().year;
final lunarYear = lunarYearData[year];
if (lunarYear == null) {
throw Exception('Lunar data for year $year not found');
}
int leapMonth = lunarYear.leapMonth;
if (isLeapMonth && (leapMonth == 0 || leapMonth != month)) {
throw Exception('Invalid leap month for year $year');
}
int daysFromStart = 0;
for (int i = 0; i < month - 1; i++) {
daysFromStart += lunarYear.months[i];
}
if (isLeapMonth) {
daysFromStart += lunarYear.months[month - 1];
}
daysFromStart += day - 1;
return lunarYear.startDate.add(Duration(days: daysFromStart));
}

View File

@ -1,3 +0,0 @@
class StickerProvider {
}

View File

@ -35,7 +35,7 @@ class WebSocketProvider extends ChangeNotifier {
Future<void> connect({noRetry = false}) async {
if (!_ua.isAuthorized) return;
if (isConnected) {
if (isConnected || conn != null) {
disconnect();
}
@ -97,7 +97,7 @@ class WebSocketProvider extends ChangeNotifier {
onError: (err) {
isConnected = false;
notifyListeners();
Future.delayed(const Duration(seconds: 11), () => connect());
Future.delayed(const Duration(seconds: 1), () => connect());
},
);
}

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

@ -197,7 +197,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
return Card(
child: ListTile(
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
title: Text('pending$name').tr(args: [RelativeTime(context).format(date)]),
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
subtitle: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [

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

@ -96,6 +96,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
),
),
]),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
: Text('postDetail').tr(),
),

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;

View File

@ -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

@ -48,7 +48,7 @@ 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,

View File

@ -141,3 +141,39 @@ class SnAttachmentBoost with _$SnAttachmentBoost {
factory SnAttachmentBoost.fromJson(Map<String, Object?> json) => _$SnAttachmentBoostFromJson(json);
}
@freezed
class SnSticker with _$SnSticker {
const factory SnSticker({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String alias,
required String name,
required int attachmentId,
required SnAttachment attachment,
required int packId,
required SnStickerPack pack,
required int accountId,
}) = _SnSticker;
factory SnSticker.fromJson(Map<String, Object?> json) => _$SnStickerFromJson(json);
}
@freezed
class SnStickerPack with _$SnStickerPack {
const factory SnStickerPack({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String prefix,
required String name,
required String description,
required List<SnSticker>? stickers,
required int accountId,
}) = _SnStickerPack;
factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json);
}

View File

@ -2272,3 +2272,738 @@ abstract class _SnAttachmentBoost implements SnAttachmentBoost {
_$$SnAttachmentBoostImplCopyWith<_$SnAttachmentBoostImpl> get copyWith =>
throw _privateConstructorUsedError;
}
SnSticker _$SnStickerFromJson(Map<String, dynamic> json) {
return _SnSticker.fromJson(json);
}
/// @nodoc
mixin _$SnSticker {
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
DateTime? get deletedAt => throw _privateConstructorUsedError;
String get alias => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
int get attachmentId => throw _privateConstructorUsedError;
SnAttachment get attachment => throw _privateConstructorUsedError;
int get packId => throw _privateConstructorUsedError;
SnStickerPack get pack => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
/// Serializes this SnSticker to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnStickerCopyWith<SnSticker> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnStickerCopyWith<$Res> {
factory $SnStickerCopyWith(SnSticker value, $Res Function(SnSticker) then) =
_$SnStickerCopyWithImpl<$Res, SnSticker>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
int attachmentId,
SnAttachment attachment,
int packId,
SnStickerPack pack,
int accountId});
$SnAttachmentCopyWith<$Res> get attachment;
$SnStickerPackCopyWith<$Res> get pack;
}
/// @nodoc
class _$SnStickerCopyWithImpl<$Res, $Val extends SnSticker>
implements $SnStickerCopyWith<$Res> {
_$SnStickerCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? alias = null,
Object? name = null,
Object? attachmentId = null,
Object? attachment = null,
Object? packId = null,
Object? pack = null,
Object? accountId = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
alias: null == alias
? _value.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
attachmentId: null == attachmentId
? _value.attachmentId
: attachmentId // ignore: cast_nullable_to_non_nullable
as int,
attachment: null == attachment
? _value.attachment
: attachment // ignore: cast_nullable_to_non_nullable
as SnAttachment,
packId: null == packId
? _value.packId
: packId // ignore: cast_nullable_to_non_nullable
as int,
pack: null == pack
? _value.pack
: pack // ignore: cast_nullable_to_non_nullable
as SnStickerPack,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAttachmentCopyWith<$Res> get attachment {
return $SnAttachmentCopyWith<$Res>(_value.attachment, (value) {
return _then(_value.copyWith(attachment: value) as $Val);
});
}
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnStickerPackCopyWith<$Res> get pack {
return $SnStickerPackCopyWith<$Res>(_value.pack, (value) {
return _then(_value.copyWith(pack: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$SnStickerImplCopyWith<$Res>
implements $SnStickerCopyWith<$Res> {
factory _$$SnStickerImplCopyWith(
_$SnStickerImpl value, $Res Function(_$SnStickerImpl) then) =
__$$SnStickerImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
int attachmentId,
SnAttachment attachment,
int packId,
SnStickerPack pack,
int accountId});
@override
$SnAttachmentCopyWith<$Res> get attachment;
@override
$SnStickerPackCopyWith<$Res> get pack;
}
/// @nodoc
class __$$SnStickerImplCopyWithImpl<$Res>
extends _$SnStickerCopyWithImpl<$Res, _$SnStickerImpl>
implements _$$SnStickerImplCopyWith<$Res> {
__$$SnStickerImplCopyWithImpl(
_$SnStickerImpl _value, $Res Function(_$SnStickerImpl) _then)
: super(_value, _then);
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? alias = null,
Object? name = null,
Object? attachmentId = null,
Object? attachment = null,
Object? packId = null,
Object? pack = null,
Object? accountId = null,
}) {
return _then(_$SnStickerImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
alias: null == alias
? _value.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
attachmentId: null == attachmentId
? _value.attachmentId
: attachmentId // ignore: cast_nullable_to_non_nullable
as int,
attachment: null == attachment
? _value.attachment
: attachment // ignore: cast_nullable_to_non_nullable
as SnAttachment,
packId: null == packId
? _value.packId
: packId // ignore: cast_nullable_to_non_nullable
as int,
pack: null == pack
? _value.pack
: pack // ignore: cast_nullable_to_non_nullable
as SnStickerPack,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnStickerImpl implements _SnSticker {
const _$SnStickerImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.alias,
required this.name,
required this.attachmentId,
required this.attachment,
required this.packId,
required this.pack,
required this.accountId});
factory _$SnStickerImpl.fromJson(Map<String, dynamic> json) =>
_$$SnStickerImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final String alias;
@override
final String name;
@override
final int attachmentId;
@override
final SnAttachment attachment;
@override
final int packId;
@override
final SnStickerPack pack;
@override
final int accountId;
@override
String toString() {
return 'SnSticker(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, attachmentId: $attachmentId, attachment: $attachment, packId: $packId, pack: $pack, accountId: $accountId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnStickerImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.alias, alias) || other.alias == alias) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.attachmentId, attachmentId) ||
other.attachmentId == attachmentId) &&
(identical(other.attachment, attachment) ||
other.attachment == attachment) &&
(identical(other.packId, packId) || other.packId == packId) &&
(identical(other.pack, pack) || other.pack == pack) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
alias,
name,
attachmentId,
attachment,
packId,
pack,
accountId);
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnStickerImplCopyWith<_$SnStickerImpl> get copyWith =>
__$$SnStickerImplCopyWithImpl<_$SnStickerImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnStickerImplToJson(
this,
);
}
}
abstract class _SnSticker implements SnSticker {
const factory _SnSticker(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final String alias,
required final String name,
required final int attachmentId,
required final SnAttachment attachment,
required final int packId,
required final SnStickerPack pack,
required final int accountId}) = _$SnStickerImpl;
factory _SnSticker.fromJson(Map<String, dynamic> json) =
_$SnStickerImpl.fromJson;
@override
int get id;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
String get alias;
@override
String get name;
@override
int get attachmentId;
@override
SnAttachment get attachment;
@override
int get packId;
@override
SnStickerPack get pack;
@override
int get accountId;
/// Create a copy of SnSticker
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnStickerImplCopyWith<_$SnStickerImpl> get copyWith =>
throw _privateConstructorUsedError;
}
SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) {
return _SnStickerPack.fromJson(json);
}
/// @nodoc
mixin _$SnStickerPack {
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
DateTime? get deletedAt => throw _privateConstructorUsedError;
String get prefix => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get description => throw _privateConstructorUsedError;
List<SnSticker>? get stickers => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
/// Serializes this SnStickerPack to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnStickerPackCopyWith<SnStickerPack> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnStickerPackCopyWith<$Res> {
factory $SnStickerPackCopyWith(
SnStickerPack value, $Res Function(SnStickerPack) then) =
_$SnStickerPackCopyWithImpl<$Res, SnStickerPack>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String prefix,
String name,
String description,
List<SnSticker>? stickers,
int accountId});
}
/// @nodoc
class _$SnStickerPackCopyWithImpl<$Res, $Val extends SnStickerPack>
implements $SnStickerPackCopyWith<$Res> {
_$SnStickerPackCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? prefix = null,
Object? name = null,
Object? description = null,
Object? stickers = freezed,
Object? accountId = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
prefix: null == prefix
? _value.prefix
: prefix // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
stickers: freezed == stickers
? _value.stickers
: stickers // ignore: cast_nullable_to_non_nullable
as List<SnSticker>?,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$SnStickerPackImplCopyWith<$Res>
implements $SnStickerPackCopyWith<$Res> {
factory _$$SnStickerPackImplCopyWith(
_$SnStickerPackImpl value, $Res Function(_$SnStickerPackImpl) then) =
__$$SnStickerPackImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String prefix,
String name,
String description,
List<SnSticker>? stickers,
int accountId});
}
/// @nodoc
class __$$SnStickerPackImplCopyWithImpl<$Res>
extends _$SnStickerPackCopyWithImpl<$Res, _$SnStickerPackImpl>
implements _$$SnStickerPackImplCopyWith<$Res> {
__$$SnStickerPackImplCopyWithImpl(
_$SnStickerPackImpl _value, $Res Function(_$SnStickerPackImpl) _then)
: super(_value, _then);
/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? prefix = null,
Object? name = null,
Object? description = null,
Object? stickers = freezed,
Object? accountId = null,
}) {
return _then(_$SnStickerPackImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
prefix: null == prefix
? _value.prefix
: prefix // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
stickers: freezed == stickers
? _value._stickers
: stickers // ignore: cast_nullable_to_non_nullable
as List<SnSticker>?,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnStickerPackImpl implements _SnStickerPack {
const _$SnStickerPackImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.prefix,
required this.name,
required this.description,
required final List<SnSticker>? stickers,
required this.accountId})
: _stickers = stickers;
factory _$SnStickerPackImpl.fromJson(Map<String, dynamic> json) =>
_$$SnStickerPackImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final String prefix;
@override
final String name;
@override
final String description;
final List<SnSticker>? _stickers;
@override
List<SnSticker>? get stickers {
final value = _stickers;
if (value == null) return null;
if (_stickers is EqualUnmodifiableListView) return _stickers;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final int accountId;
@override
String toString() {
return 'SnStickerPack(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, prefix: $prefix, name: $name, description: $description, stickers: $stickers, accountId: $accountId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnStickerPackImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.prefix, prefix) || other.prefix == prefix) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
const DeepCollectionEquality().equals(other._stickers, _stickers) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
prefix,
name,
description,
const DeepCollectionEquality().hash(_stickers),
accountId);
/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith =>
__$$SnStickerPackImplCopyWithImpl<_$SnStickerPackImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnStickerPackImplToJson(
this,
);
}
}
abstract class _SnStickerPack implements SnStickerPack {
const factory _SnStickerPack(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final String prefix,
required final String name,
required final String description,
required final List<SnSticker>? stickers,
required final int accountId}) = _$SnStickerPackImpl;
factory _SnStickerPack.fromJson(Map<String, dynamic> json) =
_$SnStickerPackImpl.fromJson;
@override
int get id;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
String get prefix;
@override
String get name;
@override
String get description;
@override
List<SnSticker>? get stickers;
@override
int get accountId;
/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -218,3 +218,66 @@ Map<String, dynamic> _$$SnAttachmentBoostImplToJson(
'attachment': instance.attachment.toJson(),
'account': instance.account,
};
_$SnStickerImpl _$$SnStickerImplFromJson(Map<String, dynamic> json) =>
_$SnStickerImpl(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
alias: json['alias'] as String,
name: json['name'] as String,
attachmentId: (json['attachment_id'] as num).toInt(),
attachment:
SnAttachment.fromJson(json['attachment'] as Map<String, dynamic>),
packId: (json['pack_id'] as num).toInt(),
pack: SnStickerPack.fromJson(json['pack'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$$SnStickerImplToJson(_$SnStickerImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'alias': instance.alias,
'name': instance.name,
'attachment_id': instance.attachmentId,
'attachment': instance.attachment.toJson(),
'pack_id': instance.packId,
'pack': instance.pack.toJson(),
'account_id': instance.accountId,
};
_$SnStickerPackImpl _$$SnStickerPackImplFromJson(Map<String, dynamic> json) =>
_$SnStickerPackImpl(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
prefix: json['prefix'] as String,
name: json['name'] as String,
description: json['description'] as String,
stickers: (json['stickers'] as List<dynamic>?)
?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>))
.toList(),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'prefix': instance.prefix,
'name': instance.name,
'description': instance.description,
'stickers': instance.stickers?.map((e) => e.toJson()).toList(),
'account_id': instance.accountId,
};

View File

@ -312,11 +312,6 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
]),
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['ShutterSpeed'] != null)
Text(
item.metadata['exif']?['ShutterSpeed'],
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['ISO'] != null)
Text(
'ISO${item.metadata['exif']?['ISO']}',
@ -334,7 +329,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
)
else
Text(
'${item.size} Bytes',
item.size.formatBytes(),
style: metaTextStyle,
),
if (item.metadata['width'] != null && item.metadata['height'] != null)

View File

@ -124,10 +124,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 +153,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 +189,72 @@ 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: 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;
@ -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,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

@ -1,39 +1,38 @@
import 'dart:ui';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:markdown/markdown.dart' as markdown;
import 'package:styled_widget/styled_widget.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_sticker.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/universal_image.dart';
import 'package:syntax_highlight/syntax_highlight.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:path/path.dart';
import 'package:uuid/uuid.dart';
import 'attachment/attachment_zoom.dart';
class MarkdownTextContent extends StatelessWidget {
final String content;
final bool isSelectable;
final bool isAutoWarp;
final bool isEnlargeSticker;
final TextScaler? textScaler;
final List<SnAttachment?>? attachments;
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(),
@ -42,33 +41,33 @@ class MarkdownTextContent extends StatelessWidget {
styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context),
).copyWith(
textScaler: textScaler,
blockquote: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
blockquoteDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 1.0,
color: Theme.of(context).dividerColor,
),
),
),
codeblockDecoration: BoxDecoration(
border: Border.all(
textScaler: textScaler,
blockquote: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
blockquoteDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 1.0,
color: Theme.of(context).dividerColor,
width: 0.3,
),
borderRadius: const BorderRadius.all(Radius.circular(4)),
color: Theme.of(context).colorScheme.surface.withOpacity(0.5),
)),
builders: {
'code': _MarkdownTextCodeElement(),
},
),
),
codeblockDecoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
width: 0.3,
),
borderRadius: const BorderRadius.all(Radius.circular(4)),
color: Theme.of(context).colorScheme.surface.withOpacity(0.5),
),
code: GoogleFonts.robotoMono(height: 1),
),
builders: {},
softLineBreak: true,
extensionSet: markdown.ExtensionSet(
<markdown.BlockSyntax>[
@ -78,6 +77,7 @@ class MarkdownTextContent extends StatelessWidget {
<markdown.InlineSyntax>[
if (isAutoWarp) markdown.LineBreakSyntax(),
_UserNameCardInlineSyntax(),
_CustomEmoteInlineSyntax(context),
markdown.AutolinkSyntax(),
markdown.AutolinkExtensionSyntax(),
markdown.CodeSyntax(),
@ -108,9 +108,41 @@ class MarkdownTextContent extends StatelessWidget {
if (url.startsWith('solink://')) {
final segments = url.replaceFirst('solink://', '').split('/');
switch (segments[0]) {
case 'stickers':
final alias = segments[1];
final st = context.read<SnStickerProvider>();
final sn = context.read<SnNetworkProvider>();
final double size = isEnlargeSticker ? 128 : 32;
return Container(
width: size,
height: size,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: FutureBuilder<SnSticker?>(
future: st.lookupSticker(alias),
builder: (context, snapshot) {
if (snapshot.hasData) {
return UniversalImage(
sn.getAttachmentUrl(snapshot.data!.attachment.rid),
fit: BoxFit.cover,
width: size,
height: size,
cacheHeight: size,
cacheWidth: size,
);
}
return const SizedBox.shrink();
},
),
),
);
case 'attachments':
final attachment = attachments?.firstWhere(
(ele) => ele?.rid == segments[1],
(ele) => ele?.rid == segments[1],
orElse: () => null,
);
if (attachment != null) {
@ -168,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 {
@ -194,45 +218,24 @@ class _UserNameCardInlineSyntax extends markdown.InlineSyntax {
}
}
class _MarkdownTextCodeElement extends MarkdownElementBuilder {
@override
Widget? visitElementAfter(
markdown.Element element,
TextStyle? preferredStyle,
) {
var language = '';
class _CustomEmoteInlineSyntax extends markdown.InlineSyntax {
final BuildContext context;
if (element.attributes['class'] != null) {
String lg = element.attributes['class'] as String;
language = lg.substring(9).trim();
_CustomEmoteInlineSyntax(this.context) : super(r':([-\w]+):');
@override
bool onMatch(markdown.InlineParser parser, Match match) {
final SnStickerProvider st = context.read<SnStickerProvider>();
final alias = match[1]!.toUpperCase();
if (st.hasNotSticker(alias)) {
parser.advanceBy(1);
return false;
}
return SizedBox(
child: FutureBuilder(
future: (() async {
final docPath = '../../../';
final highlightingPath = join(docPath, 'assets/highlighting', language);
await Highlighter.initialize([highlightingPath]);
return Highlighter(
language: highlightingPath,
theme: PlatformDispatcher.instance.platformBrightness == Brightness.light
? await HighlighterTheme.loadLightTheme()
: await HighlighterTheme.loadDarkTheme(),
);
})(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final highlighter = snapshot.data!;
return Text.rich(
highlighter.highlight(element.textContent.trim()),
style: GoogleFonts.robotoMono(),
);
}
return Text(
element.textContent.trim(),
style: GoogleFonts.robotoMono(),
);
},
),
).padding(all: 8);
final element = markdown.Element.empty('img');
element.attributes['src'] = 'solink://stickers/$alias';
parser.addNode(element);
return true;
}
}

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

@ -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';
@ -57,10 +59,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 +84,7 @@ class AppRootScaffold extends StatelessWidget {
),
),
),
child: isExpandDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(),
child: isExpandedDrawer ? AppNavigationDrawer(elevation: 0) : AppRailNavigation(),
),
Expanded(child: body),
],
@ -147,7 +150,7 @@ class AppRootScaffold extends StatelessWidget {
Expanded(child: innerWidget),
],
),
drawer: !isExpandDrawer ? AppNavigationDrawer() : null,
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,
drawerEdgeDragWidth: isPopable ? 0 : null,
bottomNavigationBar: isShowBottomNavigation ? AppBottomNavigationBar() : null,
),

View File

@ -20,6 +20,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/types/post.dart';
import 'package:surface/types/reaction.dart';
import 'package:surface/widgets/account/account_image.dart';
@ -112,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();
@ -198,6 +199,10 @@ class PostItem extends StatelessWidget {
).center();
}
final displayableAttachments = data.preload?.attachments
?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -247,9 +252,9 @@ class PostItem extends StatelessWidget {
],
),
),
if ((data.preload?.attachments?.isNotEmpty ?? false) && data.type != 'article')
if (displayableAttachments?.isNotEmpty ?? false)
AttachmentList(
data: data.preload!.attachments!,
data: displayableAttachments!,
bordered: true,
gridded: true,
maxHeight: showFullPost ? null : 480,
@ -874,12 +879,17 @@ 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(
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

@ -618,10 +618,10 @@ packages:
dependency: "direct main"
description:
name: fl_chart
sha256: c724234b05e378383e958f3e82ca84a3e1e3c06a0898462044dd8a24b1ee9864
sha256: "10ddaf334fe84d59333a12d153043e366f243e0bdfff2df0313e1e249f5bf926"
url: "https://pub.dev"
source: hosted
version: "0.70.0"
version: "0.70.1"
flutter:
dependency: "direct main"
description: flutter
@ -700,18 +700,18 @@ 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:
name: flutter_native_splash
sha256: "1152ab0067ca5a2ebeb862fe0a762057202cceb22b7e62692dcbabf6483891bb"
sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "2.4.4"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -766,10 +766,10 @@ packages:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: "3efe9828f19a07d29a51a726759ad0c70a840d231548a1c7d0332075a94db1df"
sha256: e82ffd0d0b79621c5554eed73509d7f5bd286d57fef29a573846785c65237fb1
url: "https://pub.dev"
source: hosted
version: "0.12.5+hotfix.1"
version: "0.12.5+hotfix.2"
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:
@ -902,10 +902,10 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
version: "4.1.2"
icons_launcher:
dependency: "direct dev"
description:
@ -950,10 +950,10 @@ packages:
dependency: transitive
description:
name: image_picker_ios
sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b"
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
url: "https://pub.dev"
source: hosted
version: "0.8.12+1"
version: "0.8.12+2"
image_picker_linux:
dependency: transitive
description:
@ -1086,10 +1086,10 @@ packages:
dependency: "direct main"
description:
name: livekit_client
sha256: "7cdeb3eaeec7fb70a4cf88d9caabccbef9e3bd5f0b23c086320bc5c9acb2770b"
sha256: a19bcf8640b45e0730b1e3e3e78be7882dad680c6ebe8ae75294fd8d4612450d
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.4+hotfix.2"
logging:
dependency: transitive
description:
@ -1142,10 +1142,10 @@ packages:
dependency: "direct main"
description:
name: material_symbols_icons
sha256: "64404f47f8e0a9d20478468e5decef867a688660bad7173adcd20418d7f892c9"
sha256: "89aac72d25dd49303f71b3b1e70f8374791846729365b25bebc2a2531e5b86cd"
url: "https://pub.dev"
source: hosted
version: "4.2801.0"
version: "4.2801.1"
media_kit:
dependency: "direct main"
description:
@ -1646,10 +1646,10 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93"
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.5"
shared_preferences_android:
dependency: transitive
description:
@ -1795,10 +1795,10 @@ packages:
dependency: transitive
description:
name: sqflite_darwin
sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474"
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.1+1"
sqflite_platform_interface:
dependency: transitive
description:
@ -1863,14 +1863,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
syntax_highlight:
dependency: "direct main"
description:
name: syntax_highlight
sha256: ee33b6aa82cc722bb9b40152a792181dee222353b486c0255fde666a3e3a4997
url: "https://pub.dev"
source: hosted
version: "0.4.0"
term_glyph:
dependency: transitive
description:
@ -2131,10 +2123,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29"
url: "https://pub.dev"
source: hosted
version: "5.9.0"
version: "5.10.0"
win32_registry:
dependency: transitive
description:
@ -2177,4 +2169,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"
flutter: ">=3.24.0"

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+44
version: 2.2.2+52
environment:
sdk: ^3.5.4
@ -54,7 +54,6 @@ dependencies:
flutter_markdown: ^0.7.4+1
url_launcher: ^6.3.1
flutter_animate: ^4.5.0
syntax_highlight: ^0.4.0
google_fonts: ^6.2.1
path: ^1.9.0
relative_time: ^5.0.0