Compare commits

..

16 Commits

Author SHA1 Message Date
8e76ff3f84 Optimize user loading api usage 2025-02-27 20:51:47 +08:00
bd26602299 Code highlighting 2025-02-26 23:29:02 +08:00
52ab1d0d10 🐛 Fix chat last message displaying inconsistences 2025-02-26 00:29:35 +08:00
f746e06f65 ⚗️ Experimental user first badge showing on chat 2025-02-26 00:25:42 +08:00
d11069a2be 🐛 Bug fixes on notification page 2025-02-26 00:00:53 +08:00
d6dc487d9e Latex Rendering, closed #9 2025-02-25 23:49:48 +08:00
a07c7cdede 🐛 Fix infinite loading own sticker 2025-02-25 22:56:30 +08:00
acbc125dec 🚀 Launch 2.3.2+75 2025-02-24 23:21:06 +08:00
ad0ee971c1 Desktop mute notification
🐛 Bug fixes on tray icon
2025-02-24 22:46:02 +08:00
52d6bb083e 🐛 Fix macos titlebar not centered 2025-02-24 22:38:08 +08:00
2027eab49b 💄 Optimize displaying of message 2025-02-24 22:35:14 +08:00
566ebde1dd 🐛 Fix windows tray issue 2025-02-24 21:59:41 +08:00
9e039cc532 🐛 Fix editing message 2025-02-24 21:31:12 +08:00
c4b95d7084 🐛 Fix account settings screen error cause by locale 2025-02-24 21:25:12 +08:00
a66129a9ba 🐛 Bug fixes 2025-02-24 21:18:49 +08:00
44e1a8bf67 🚀 Launch 2.3.2+74 2025-02-23 22:45:01 +08:00
25 changed files with 540 additions and 221 deletions

View File

@ -719,6 +719,7 @@
"stickersNewDescription": "Create a new sticker belongs to this pack.",
"stickersPackNew": "New Sticker Pack",
"trayMenuShow": "Show",
"trayMenuMuteNotification": "Do Not Disturb",
"update": "Update",
"forceUpdate": "Force Update",
"forceUpdateDescription": "Force to show the application update popup, even the new version is not available."

View File

@ -717,6 +717,7 @@
"stickersNewDescription": "创建一个新的贴图。",
"stickersPackNew": "新建贴图包",
"trayMenuShow": "显示",
"trayMenuMuteNotification": "静音通知",
"update": "更新",
"forceUpdate": "强制更新",
"forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。"

View File

@ -717,6 +717,7 @@
"stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示",
"trayMenuMuteNotification": "靜音通知",
"update": "更新",
"forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"

View File

@ -717,6 +717,7 @@
"stickersNewDescription": "創建一個新的貼圖。",
"stickersPackNew": "新建貼圖包",
"trayMenuShow": "顯示",
"trayMenuMuteNotification": "靜音通知",
"update": "更新",
"forceUpdate": "強制更新",
"forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。"

View File

@ -235,7 +235,7 @@ PODS:
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.49.0)
- sqlite3 (~> 3.49.1)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
@ -445,7 +445,7 @@ SPEC CHECKSUMS:
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe

View File

@ -194,9 +194,11 @@ class ChatMessageController extends ChangeNotifier {
channelId: channel!.id,
createdAt: Value(message.createdAt),
),
onConflict: DoUpdate((_) => SnLocalChatMessageCompanion.custom(
content: Constant(jsonEncode(message.toJson())),
)),
onConflict: DoUpdate(
(_) => SnLocalChatMessageCompanion.custom(
content: Constant(jsonEncode(message.toJson())),
),
),
);
} else {
incomeStrandedQueue.add(message);
@ -212,21 +214,21 @@ class ChatMessageController extends ChangeNotifier {
final idx =
messages.indexWhere((x) => x.id == message.relatedEventId);
if (idx != -1) {
final newBody = message.body;
final newBody = Map<String, dynamic>.from(message.body);
newBody.remove('related_event');
messages[idx] = messages[idx].copyWith(
body: newBody,
updatedAt: message.updatedAt,
);
if (message.relatedEventId != null) {
await (_dt.db.snLocalChatMessage.update()
..where((e) => e.id.equals(message.relatedEventId!)))
.write(
SnLocalChatMessageCompanion.custom(
content: Constant(jsonEncode(messages[idx].toJson())),
),
);
}
}
if (message.relatedEventId != null) {
await (_dt.db.snLocalChatMessage.update()
..where((e) => e.id.equals(message.relatedEventId!)))
.write(
SnLocalChatMessageCompanion.custom(
content: Constant(jsonEncode(messages[idx].toJson())),
),
);
}
}
case 'messages.delete':

View File

@ -333,6 +333,31 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
}
}
final Menu _appTrayMenu = Menu(
items: [
MenuItem(
key: 'version_label',
label: 'Solian',
disabled: true,
),
MenuItem.separator(),
MenuItem.checkbox(
checked: false,
key: 'mute_notification',
label: 'trayMenuMuteNotification'.tr(),
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
),
],
);
Future<void> _trayInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
@ -344,32 +369,20 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
trayManager.addListener(this);
await trayManager.setIcon(icon);
Menu menu = Menu(
items: [
MenuItem(
key: 'version_label',
label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
disabled: true,
),
MenuItem.separator(),
MenuItem(
key: 'window_show',
label: 'trayMenuShow'.tr(),
),
MenuItem(
key: 'exit',
label: 'trayMenuExit'.tr(),
),
],
_appTrayMenu.items![0] = MenuItem(
key: 'version_label',
label: 'Solian ${appVersion.version}+${appVersion.buildNumber}',
disabled: true,
);
await trayManager.setContextMenu(menu);
await trayManager.setContextMenu(_appTrayMenu);
}
Future<void> _notifyInitialization() async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
await localNotifier.setup(
appName: 'solian',
appName: 'Solian',
shortcutPolicy: ShortcutPolicy.requireCreate,
);
}
@ -424,12 +437,23 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
@override
void onTrayMenuItemClick(MenuItem menuItem) {
switch (menuItem.key) {
case 'mute_notification':
final nty = context.read<NotificationProvider>();
nty.isMuted = !nty.isMuted;
_appTrayMenu.items![2].checked = nty.isMuted;
trayManager.setContextMenu(_appTrayMenu);
break;
case 'window_show':
appWindow.show();
// To prevent the window from being hide after just show on macOS
Timer(const Duration(milliseconds: 100), () => appWindow.show());
break;
case 'exit':
_appLifecycleListener?.dispose();
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
if (Platform.isWindows) {
appWindow.close();
} else {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
break;
}
}

View File

@ -79,6 +79,7 @@ class NotificationProvider extends ChangeNotifier {
List<SnNotification> notifications = List.empty(growable: true);
int? skippableNotifyChannel;
bool isMuted = false;
void listen() {
_ws.pk.stream.listen((event) {
@ -88,7 +89,8 @@ class NotificationProvider extends ChangeNotifier {
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
if (doHaptic) HapticFeedback.mediumImpact();
if (notification.topic == 'messaging.message') {
if (notification.topic == 'messaging.message' &&
skippableNotifyChannel != null) {
if (notification.metadata['channel_id'] != null &&
notification.metadata['channel_id'] == skippableNotifyChannel) {
return;
@ -106,7 +108,7 @@ class NotificationProvider extends ChangeNotifier {
notifyListeners();
updateTray();
if (!kIsWeb) {
if (!kIsWeb && !isMuted) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
LocalNotification notify = LocalNotification(
title: notification.title,

View File

@ -41,7 +41,8 @@ class SnAttachmentProvider {
return out;
}
Future<List<SnAttachment?>> getMultiple(List<String> rids, {noCache = false}) async {
Future<List<SnAttachment?>> getMultiple(List<String> rids,
{noCache = false}) async {
final result = List<SnAttachment?>.filled(rids.length, null);
final Map<String, int> randomMapping = {};
for (int i = 0; i < rids.length; i++) {
@ -62,8 +63,10 @@ class SnAttachmentProvider {
'id': pendingFetch.join(','),
},
);
final List<SnAttachment?> out =
resp.data['data'].map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e)).cast<SnAttachment?>().toList();
final List<SnAttachment?> out = resp.data['data']
.map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e))
.cast<SnAttachment?>()
.toList();
for (final item in out) {
if (item == null) continue;
@ -77,7 +80,13 @@ class SnAttachmentProvider {
return result;
}
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime', 'mp4': 'video/mp4'};
static Map<String, String> mimetypeOverrides = {
'mov': 'video/quicktime',
'mp4': 'video/mp4',
'm4a': 'audio/mp4',
'apng': 'image/apng',
'webp': 'image/webp',
};
Future<SnAttachment> directUploadOne(
Uint8List data,
@ -89,8 +98,11 @@ class SnAttachmentProvider {
bool analyzeNow = false,
}) async {
final filePayload = MultipartFile.fromBytes(data, filename: filename);
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename;
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
final fileAlt = filename.contains('.')
? filename.substring(0, filename.lastIndexOf('.'))
: filename;
final fileExt =
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
String? mimetypeOverride;
if (mimetype != null) {
@ -127,8 +139,11 @@ class SnAttachmentProvider {
Map<String, dynamic>? metadata, {
String? mimetype,
}) async {
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename;
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
final fileAlt = filename.contains('.')
? filename.substring(0, filename.lastIndexOf('.'))
: filename;
final fileExt =
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
String? mimetypeOverride;
if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) {
@ -146,7 +161,10 @@ class SnAttachmentProvider {
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
});
return (SnAttachmentFragment.fromJson(resp.data['meta']), resp.data['chunk_size'] as int);
return (
SnAttachmentFragment.fromJson(resp.data['meta']),
resp.data['chunk_size'] as int
);
}
Future<dynamic> _chunkedUploadOnePart(
@ -197,7 +215,10 @@ class SnAttachmentProvider {
(entry.value + 1) * chunkSize,
await file.length(),
);
final data = Uint8List.fromList(await file.openRead(beginCursor, endCursor).expand((chunk) => chunk).toList());
final data = Uint8List.fromList(await file
.openRead(beginCursor, endCursor)
.expand((chunk) => chunk)
.toList());
final result = await _chunkedUploadOnePart(
data,

View File

@ -27,8 +27,11 @@ class UserDirectoryProvider {
plannedQuery.add(item);
}
}
final resp = await _sn.client.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});
final respDecoded = resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList();
if (plannedQuery.isEmpty) return out;
final resp = await _sn.client
.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});
final respDecoded =
resp.data.map((e) => SnAccount.fromJson(e)).cast<SnAccount>().toList();
var sideIdx = 0;
for (var idx = 0; idx < out.length; idx++) {
if (out[idx] != null) continue;

View File

@ -54,14 +54,20 @@ class AccountSettingsScreen extends StatelessWidget {
child: DropdownButton2<Locale?>(
isExpanded: true,
items: [
...EasyLocalization.of(context)!.supportedLocales.mapIndexed((idx, ele) {
...EasyLocalization.of(context)!
.supportedLocales
.mapIndexed((idx, ele) {
return DropdownMenuItem<Locale?>(
value: Locale.parse(ele.toString()),
child: Text('${ele.languageCode}-${ele.countryCode}').fontSize(14),
child: Text('${ele.languageCode}-${ele.countryCode}')
.fontSize(14),
);
}),
],
value: ua.user?.language != null ? Locale.parse(ua.user!.language) : Locale.parse('en-US'),
value: ua.user?.language != null
? (Locale.tryParse(ua.user!.language) ??
Locale.parse('en-US'))
: Locale.parse('en-US'),
onChanged: (Locale? value) {
if (value == null) return;
_setAccountLanguage(context, value);

View File

@ -43,7 +43,8 @@ class UserScreen extends StatefulWidget {
State<UserScreen> createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateMixin {
class _UserScreenState extends State<UserScreen>
with SingleTickerProviderStateMixin {
late final ScrollController _scrollController = ScrollController();
SnAccount? _account;
@ -67,7 +68,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
Future<List<SnCheckInRecord>> _getCheckInRecords() async {
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14');
final resp =
await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14');
return List.from(
resp.data['data']?.map((x) => SnCheckInRecord.fromJson(x)) ?? [],
);
@ -98,7 +100,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
Future<void> _fetchPublishers() async {
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/publishers?user=${widget.name}');
final resp =
await sn.client.get('/cgi/co/publishers?user=${widget.name}');
_publishers = List<SnPublisher>.from(
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
);
@ -144,7 +147,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
'related': _account!.name,
});
if (!mounted) return;
context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
context.showSnackbar(
'userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
@ -160,9 +164,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
try {
final rel = context.read<SnRelationshipProvider>();
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {});
await rel.updateRelationship(
_account!.id, 1, _accountRelationship?.permNodes ?? {});
if (!mounted) return;
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
context.showSnackbar(
'userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
@ -188,12 +194,14 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
double _appBarBlur = 0.0;
late final _appBarWidth = MediaQuery.of(context).size.width;
late final _appBarHeight = (_appBarWidth * kBannerAspectRatio).roundToDouble();
late final _appBarHeight =
(_appBarWidth * kBannerAspectRatio).roundToDouble();
void _updateAppBarBlur() {
if (_scrollController.offset > _appBarHeight) return;
setState(() {
_appBarBlur = (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
_appBarBlur =
(_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
});
}
@ -260,18 +268,20 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
text: TextSpan(children: [
TextSpan(
text: _account!.nick,
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Colors.white,
shadows: labelShadows,
),
style:
Theme.of(context).textTheme.titleLarge!.copyWith(
color: Colors.white,
shadows: labelShadows,
),
),
const TextSpan(text: '\n'),
TextSpan(
text: '@${_account!.name}',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.white,
shadows: labelShadows,
),
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.white,
shadows: labelShadows,
),
),
]),
),
@ -339,7 +349,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
PopupMenuButton(
padding: EdgeInsets.zero,
style: ButtonStyle(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
visualDensity:
VisualDensity(horizontal: -4, vertical: -4),
),
itemBuilder: (context) => [
PopupMenuItem(
@ -399,7 +410,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
Symbols.circle,
fill: 1,
size: 16,
color: (_status?.isOnline ?? false) ? Colors.green : Colors.grey,
color: (_status?.isOnline ?? false)
? Colors.green
: Colors.grey,
).padding(all: 4),
const Gap(8),
Text(
@ -409,7 +422,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
: 'accountStatusOffline'.tr()
: 'loading'.tr(),
),
if (_status != null && !_status!.isOnline && _status!.lastSeenAt != null)
if (_status != null &&
!_status!.isOnline &&
_status!.lastSeenAt != null)
Text(
'accountStatusLastSeen'.tr(args: [
_status!.lastSeenAt != null
@ -429,11 +444,14 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
(ele) => Tooltip(
richMessage: TextSpan(
children: [
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
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),
style: const TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
@ -442,7 +460,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
kBadgesMeta[ele.type]?.$2 ??
Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
@ -458,7 +477,9 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
children: [
const Icon(Symbols.calendar_add_on),
const Gap(8),
Text('publisherJoinedAt').tr(args: [DateFormat('y/M/d').format(_account!.createdAt)]),
Text('publisherJoinedAt').tr(args: [
DateFormat('y/M/d').format(_account!.createdAt)
]),
],
),
Row(
@ -491,17 +512,24 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
children: [
const Icon(Symbols.star),
const Gap(8),
Text('Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'),
Text(
'Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'),
const Gap(8),
Text(calcLevelUpProgressLevel(_account?.profile?.experience ?? 0)).fontSize(11).opacity(0.5),
Text(calcLevelUpProgressLevel(
_account?.profile?.experience ?? 0))
.fontSize(11)
.opacity(0.5),
const Gap(8),
Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 160),
child: LinearProgressIndicator(
value: calcLevelUpProgress(_account?.profile?.experience ?? 0),
value: calcLevelUpProgress(
_account?.profile?.experience ?? 0),
borderRadius: BorderRadius.circular(8),
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainer,
).alignment(Alignment.centerLeft),
),
],
@ -515,6 +543,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
const SliverGap(12),
SliverToBoxAdapter(
child: FutureBuilder<List<SnCheckInRecord>>(
key: GlobalKey(),
future: _getCheckInRecords(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox.shrink();
@ -522,7 +551,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
return Text(
'accountCheckInNoRecords',
textAlign: TextAlign.center,
).tr().fontWeight(FontWeight.bold).center().padding(horizontal: 20, vertical: 8);
)
.tr()
.fontWeight(FontWeight.bold)
.center()
.padding(horizontal: 20, vertical: 8);
}
final records = snapshot.data!;
return SizedBox(
@ -544,7 +577,11 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('accountBadge').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
Text('accountBadge')
.bold()
.fontSize(17)
.tr()
.padding(horizontal: 20, bottom: 4),
SizedBox(
height: 80,
width: double.infinity,
@ -558,7 +595,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
child: Card(
child: ListTile(
leading: Icon(
kBadgesMeta[badge.type]?.$2 ?? Symbols.question_mark,
kBadgesMeta[badge.type]?.$2 ??
Symbols.question_mark,
color: kBadgesMeta[badge.type]?.$3,
fill: 1,
),
@ -568,7 +606,8 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
subtitle: badge.metadata['title'] != null
? Text(badge.metadata['title'])
: Text(
DateFormat('y/M/d').format(badge.createdAt),
DateFormat('y/M/d')
.format(badge.createdAt),
),
),
),
@ -664,7 +703,8 @@ class CheckInRecordChart extends StatelessWidget {
),
)
.toList(),
getTooltipColor: (_) => Theme.of(context).colorScheme.surfaceContainerHigh,
getTooltipColor: (_) =>
Theme.of(context).colorScheme.surfaceContainerHigh,
),
),
titlesData: FlTitlesData(

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
@ -41,6 +42,7 @@ class _ChatScreenState extends State<ChatScreen> {
Future<void> _fetchWhatsNew() async {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/im/whats-new');
if (resp.data == null) return;
final List<dynamic> out = resp.data;
setState(() {
_unreadCounts = {for (var v in out) v['channel_id']: v['count']};
@ -72,18 +74,20 @@ class _ChatScreenState extends State<ChatScreen> {
if (!mounted) return;
final ud = context.read<UserDirectoryProvider>();
final idSet = <int>{};
for (final channel in channels) {
if (channel.type == 1) {
await ud.listAccount(
idSet.addAll(
channel.members
?.cast<SnChannelMember?>()
.map((ele) => ele?.accountId)
.where((ele) => ele != null)
.toSet() ??
{},
.cast<int>() ??
[],
);
}
}
if (idSet.isNotEmpty) await ud.listAccount(idSet);
if (mounted) setState(() => _channels = channels);
})
@ -135,6 +139,28 @@ class _ChatScreenState extends State<ChatScreen> {
_fetchWhatsNew();
}
void _onTapChannel(SnChannel channel) {
final doExpand = ResponsiveBreakpoints.of(context).largerOrEqualTo(DESKTOP);
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
_unreadCounts?[channel.id] = 0;
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
}
@override
Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>();
@ -266,10 +292,34 @@ class _ChatScreenState extends State<ChatScreen> {
],
),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
? Row(
children: [
Expanded(
child: Text(
lastMessage.body['text'] ??
'Unable preview',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Gap(4),
Text(
DateFormat(
lastMessage.createdAt.toLocal().day ==
DateTime.now().day
? 'HH:mm'
: lastMessage.createdAt
.toLocal()
.year ==
DateTime.now().year
? 'MM/dd'
: 'yy/MM/dd',
).format(lastMessage.createdAt.toLocal()),
style: GoogleFonts.robotoMono(
fontSize: 12,
),
),
],
)
: Text(
channel.description,
@ -284,23 +334,7 @@ class _ChatScreenState extends State<ChatScreen> {
?.avatar,
),
onTap: () {
if (doExpand) {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
_unreadCounts?[channel.id] = 0;
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
_onTapChannel(channel);
},
);
}
@ -318,10 +352,46 @@ class _ChatScreenState extends State<ChatScreen> {
],
),
subtitle: lastMessage != null
? Text(
'${ud.getAccountFromCache(lastMessage.sender.accountId)?.nick}: ${lastMessage.body['text'] ?? 'Unable preview'}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
? Row(
children: [
Badge(
label: Text(ud
.getAccountFromCache(
lastMessage.sender.accountId)
?.nick ??
'unknown'.tr()),
backgroundColor:
Theme.of(context).colorScheme.primary,
textColor:
Theme.of(context).colorScheme.onPrimary,
),
const Gap(6),
Expanded(
child: Text(
lastMessage.body['text'] ??
'Unable preview',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Gap(4),
Text(
DateFormat(
lastMessage.createdAt.toLocal().day ==
DateTime.now().day
? 'HH:mm'
: lastMessage.createdAt
.toLocal()
.year ==
DateTime.now().year
? 'MM/dd'
: 'yy/MM/dd',
).format(lastMessage.createdAt.toLocal()),
style: GoogleFonts.robotoMono(
fontSize: 12,
),
),
],
)
: Text(
channel.description,
@ -331,7 +401,7 @@ class _ChatScreenState extends State<ChatScreen> {
contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: null,
content: channel.realm?.avatar,
fallbackWidget: const Icon(Symbols.chat, size: 20),
),
onTap: () {
@ -340,18 +410,7 @@ class _ChatScreenState extends State<ChatScreen> {
setState(() => _focusChannel = channel);
return;
}
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
).then((value) {
if (mounted) {
setState(() => _unreadCounts?[channel.id] = 0);
_refreshChannels(noRemote: true);
}
});
_onTapChannel(channel);
},
);
},

View File

@ -13,6 +13,7 @@ import 'package:surface/controllers/chat_message_controller.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart';
import 'package:surface/providers/notification.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart';
@ -84,6 +85,10 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
orElse: () => null,
);
}
if (!mounted) return;
final nty = context.read<NotificationProvider>();
nty.skippableNotifyChannel = _channel!.id;
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
@ -232,6 +237,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
void dispose() {
_wsSubscription?.cancel();
_messageController.dispose();
final nty = context.read<NotificationProvider>();
nty.skippableNotifyChannel = null;
super.dispose();
}

View File

@ -2,7 +2,6 @@ import 'dart:math' as math;
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
@ -94,8 +93,12 @@ class _HomeScreenState extends State<HomeScreen> {
: MainAxisAlignment.start,
children: [
_HomeDashUpdateWidget(
padding: const EdgeInsets.only(
bottom: 8, left: 8, right: 8)),
padding: const EdgeInsets.only(
bottom: 8,
left: 8,
right: 8,
),
),
_HomeDashSpecialDayWidget().padding(horizontal: 8),
StaggeredGrid.extent(
maxCrossAxisExtent: 280,

View File

@ -29,6 +29,7 @@ const Map<String, IconData> kNotificationTopicIcons = {
'passport.security.otp': Symbols.password,
'interactive.subscription': Symbols.subscriptions,
'interactive.feedback': Symbols.add_reaction,
'interactive.reply': Symbols.reply,
'messaging.callStart': Symbols.call_received,
'wallet.transaction.new': Symbols.receipt,
};
@ -57,10 +58,17 @@ class _NotificationScreenState extends State<NotificationScreen> {
try {
final sn = context.read<SnNetworkProvider>();
final nty = context.read<NotificationProvider>();
final resp = await sn.client.get('/cgi/id/notifications?take=10');
final resp =
await sn.client.get('/cgi/id/notifications', queryParameters: {
'take': 10,
'offset': _notifications.length,
});
_totalCount = resp.data['count'];
_notifications.addAll(
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [],
resp.data['data']
?.map((e) => SnNotification.fromJson(e))
.cast<SnNotification>() ??
[],
);
nty.updateTray();
} catch (err) {
@ -186,7 +194,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
_fetchNotifications();
},
isLoading: _isBusy,
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
hasReachedMax: _totalCount != null &&
_notifications.length >= _totalCount!,
itemBuilder: (context, idx) {
final nty = _notifications[idx];
return Row(
@ -218,13 +227,17 @@ class _NotificationScreenState extends State<NotificationScreen> {
isAutoWarp: true,
),
),
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
.contains(nty.topic) &&
if ([
'interactive.reply',
'interactive.feedback',
'interactive.subscription'
].contains(nty.topic) &&
nty.metadata['related_post'] != null)
GestureDetector(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
borderRadius: const BorderRadius.all(
Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
@ -243,7 +256,9 @@ class _NotificationScreenState extends State<NotificationScreen> {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {
'slug': nty.metadata['related_post']!['id'].toString(),
'slug': nty
.metadata['related_post']!['id']
.toString(),
},
);
},
@ -272,8 +287,10 @@ class _NotificationScreenState extends State<NotificationScreen> {
IconButton(
icon: const Icon(Symbols.check),
padding: EdgeInsets.all(0),
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
visualDensity:
const VisualDensity(horizontal: -4, vertical: -4),
onPressed:
_isSubmitting ? null : () => _markOneAsRead(nty),
),
],
).padding(horizontal: 16);

View File

@ -179,7 +179,9 @@ class _StickerScreenState extends State<StickerScreen>
child: InfiniteList(
itemCount: _packs.length,
onFetchData: _fetchPacks,
hasReachedMax: _totalCount != null && _packs.length >= _totalCount!,
hasReachedMax:
(_totalCount != null && _packs.length >= _totalCount!) ||
_tabController.index == 2,
isLoading: _isBusy,
itemBuilder: (context, idx) {
final pack = _packs[idx];

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:surface/providers/config.dart';
@ -11,7 +12,8 @@ class ThemeSet {
ThemeSet({required this.light, required this.dark});
}
Future<ThemeSet> createAppThemeSet({Color? seedColorOverride, bool? useMaterial3}) async {
Future<ThemeSet> createAppThemeSet(
{Color? seedColorOverride, bool? useMaterial3}) async {
return ThemeSet(
light: await createAppTheme(Brightness.light, useMaterial3: useMaterial3),
dark: await createAppTheme(Brightness.dark, useMaterial3: useMaterial3),
@ -26,20 +28,24 @@ Future<ThemeData> createAppTheme(
final prefs = await SharedPreferences.getInstance();
final seedColorString = prefs.getInt(kAppColorSchemeStoreKey);
final seedColor = seedColorString != null ? Color(seedColorString) : Colors.indigo;
final seedColor =
seedColorString != null ? Color(seedColorString) : Colors.indigo;
final colorScheme = ColorScheme.fromSeed(
seedColor: seedColorOverride ?? seedColor,
brightness: brightness,
);
final hasAppBarTransparent = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
final useM3 = useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
final hasAppBarTransparent =
prefs.getBool(kAppbarTransparentStoreKey) ?? false;
final useM3 =
useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
return ThemeData(
useMaterial3: useM3,
colorScheme: colorScheme,
brightness: brightness,
textTheme: GoogleFonts.rubikTextTheme(),
iconTheme: IconThemeData(
fill: 0,
weight: 400,
@ -52,8 +58,10 @@ Future<ThemeData> createAppTheme(
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: hasAppBarTransparent ? 0 : null,
backgroundColor: hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
foregroundColor: hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
backgroundColor:
hasAppBarTransparent ? Colors.transparent : colorScheme.primary,
foregroundColor:
hasAppBarTransparent ? colorScheme.onSurface : colorScheme.onPrimary,
),
pageTransitionsTheme: PageTransitionsTheme(
builders: {

View File

@ -10,6 +10,7 @@ class AccountImage extends StatelessWidget {
final Color? foregroundColor;
final double? radius;
final Widget? fallbackWidget;
final Widget? badge;
const AccountImage({
super.key,
@ -18,6 +19,7 @@ class AccountImage extends StatelessWidget {
this.foregroundColor,
this.radius,
this.fallbackWidget,
this.badge,
});
@override
@ -26,26 +28,36 @@ class AccountImage extends StatelessWidget {
final url = sn.getAttachmentUrl(content ?? '');
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return CircleAvatar(
key: Key('attachment-${content.hashCode}'),
radius: radius,
backgroundColor: backgroundColor,
backgroundImage: (content?.isNotEmpty ?? false)
? ResizeImage(
UniversalImage.provider(url),
width: ((radius ?? 20) * devicePixelRatio * 2).round(),
height: ((radius ?? 20) * devicePixelRatio * 2).round(),
policy: ResizeImagePolicy.fit,
)
: null,
child: (content?.isEmpty ?? true)
? (fallbackWidget ??
Icon(
Symbols.account_circle,
size: radius != null ? radius! * 1.2 : 24,
color: foregroundColor,
))
: null,
return Stack(
children: [
CircleAvatar(
key: Key('attachment-${content.hashCode}'),
radius: radius,
backgroundColor: backgroundColor,
backgroundImage: (content?.isNotEmpty ?? false)
? ResizeImage(
UniversalImage.provider(url),
width: ((radius ?? 20) * devicePixelRatio * 2).round(),
height: ((radius ?? 20) * devicePixelRatio * 2).round(),
policy: ResizeImagePolicy.fit,
)
: null,
child: (content?.isEmpty ?? true)
? (fallbackWidget ??
Icon(
Symbols.account_circle,
size: radius != null ? radius! * 1.2 : 24,
color: foregroundColor,
))
: null,
),
if (badge != null)
Positioned(
right: -4,
bottom: -4,
child: badge!,
),
],
);
}
}

View File

@ -11,6 +11,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_popover.dart';
@ -105,6 +106,22 @@ class ChatMessage extends StatelessWidget {
GestureDetector(
child: AccountImage(
content: user?.avatar,
badge: (user?.badges.isNotEmpty ?? false)
? Icon(
kBadgesMeta[user!.badges.first.type]?.$2 ??
Symbols.question_mark,
color: kBadgesMeta[user.badges.first.type]?.$3,
fill: 1,
size: 18,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 5.0,
color: Color.fromARGB(150, 0, 0, 0),
),
],
)
: null,
),
onTap: () {
if (user == null) return;
@ -161,7 +178,7 @@ class ChatMessage extends StatelessWidget {
if (data.preload?.quoteEvent != null)
StyledWidget(Container(
constraints: BoxConstraints(
maxWidth: 480,
maxWidth: 360,
),
decoration: BoxDecoration(
borderRadius:
@ -210,9 +227,8 @@ class ChatMessage extends StatelessWidget {
AttachmentList(
data: data.preload!.attachments!,
bordered: true,
maxHeight: 560,
maxWidth: 480,
minWidth: 480,
maxHeight: 360,
maxWidth: 480 - 48 - padding.left,
padding: padding.copyWith(top: 8, left: 48 + padding.left),
),
if (!hasMerged && !isCompact)
@ -292,14 +308,11 @@ class _ChatMessageText extends StatelessWidget {
buttonItems: items,
);
},
child: Container(
constraints: const BoxConstraints(maxWidth: 480),
child: MarkdownTextContent(
content: data.body['text'],
isAutoWarp: true,
isEnlargeSticker:
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
),
child: MarkdownTextContent(
content: data.body['text'],
isAutoWarp: true,
isEnlargeSticker:
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
),
),
if (data.updatedAt != data.createdAt)

View File

@ -1,6 +1,9 @@
import 'package:dismissible_page/dismissible_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_highlight/flutter_highlight.dart';
import 'package:flutter_highlight/theme_map.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:markdown/markdown.dart' as markdown;
@ -72,21 +75,27 @@ class MarkdownTextContent extends StatelessWidget {
),
code: GoogleFonts.robotoMono(height: 1),
),
builders: {},
builders: {
'latex': LatexElementBuilder(),
'code': HighlightBuilder(),
},
softLineBreak: true,
extensionSet: markdown.ExtensionSet(
<markdown.BlockSyntax>[
markdown.CodeBlockSyntax(),
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
markdown.CodeBlockSyntax(),
markdown.FencedCodeBlockSyntax(),
LatexBlockSyntax(),
],
<markdown.InlineSyntax>[
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes,
if (isAutoWarp) markdown.LineBreakSyntax(),
_UserNameCardInlineSyntax(),
_CustomEmoteInlineSyntax(context),
markdown.AutolinkSyntax(),
markdown.AutolinkExtensionSyntax(),
markdown.CodeSyntax(),
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes
LatexInlineSyntax(),
],
),
onTapLink: (text, href, title) async {
@ -260,3 +269,56 @@ class _CustomEmoteInlineSyntax extends markdown.InlineSyntax {
return true;
}
}
class HighlightBuilder extends MarkdownElementBuilder {
@override
Widget? visitElementAfterWithContext(
BuildContext context,
markdown.Element element,
TextStyle? preferredStyle,
TextStyle? parentStyle,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
if (element.attributes['class'] == null &&
!element.textContent.trim().contains('\n')) {
return Container(
padding:
EdgeInsets.only(top: 0.0, right: 4.0, bottom: 1.75, left: 4.0),
margin: EdgeInsets.symmetric(horizontal: 2.0),
color: Colors.black12,
child: Text(
element.textContent,
style: GoogleFonts.robotoMono(textStyle: preferredStyle),
));
} else {
var language = 'plaintext';
final pattern = RegExp(r'^language-(.+)$');
if (element.attributes['class'] != null &&
pattern.hasMatch(element.attributes['class'] ?? '')) {
language =
pattern.firstMatch(element.attributes['class'] ?? '')?.group(1) ??
'plaintext';
}
return ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: HighlightView(
element.textContent.trim(),
language: language,
theme: {
...(isDark ? themeMap['a11y-dark']! : themeMap['a11y-light']!),
'root': (isDark
? TextStyle(
backgroundColor: Colors.transparent,
color: Color(0xfff8f8f2))
: TextStyle(
backgroundColor: Colors.transparent,
color: Color(0xff545454)))
},
padding: EdgeInsets.all(12),
textStyle: GoogleFonts.robotoMono(textStyle: preferredStyle),
),
);
}
}
}

View File

@ -188,29 +188,19 @@ class AppRootScaffold extends StatelessWidget {
child: Text(
'Solar Network',
style: GoogleFonts.spaceGrotesk(),
textAlign: !kIsWeb
? Platform.isMacOS
? TextAlign.center
: null
: null,
textAlign: Platform.isMacOS
? TextAlign.center
: TextAlign.start,
).padding(horizontal: 12, vertical: 5),
),
if (!Platform.isMacOS)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: MoveWindow()),
Row(
children: [
MinimizeWindowButton(
colors: windowButtonColor),
MaximizeWindowButton(
colors: windowButtonColor),
CloseWindowButton(
colors: windowButtonColor),
],
),
],
MinimizeWindowButton(colors: windowButtonColor),
if (!Platform.isMacOS)
MaximizeWindowButton(colors: windowButtonColor),
if (!Platform.isMacOS)
CloseWindowButton(
colors: windowButtonColor,
onPressed: () => appWindow.hide(),
),
],
),
@ -226,16 +216,18 @@ class AppRootScaffold extends StatelessWidget {
child: NotifyIndicator()),
if (ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE))
Positioned(
bottom: safeBottom > 0 ? safeBottom : 16,
left: 0,
right: 0,
child: ConnectionIndicator())
bottom: safeBottom > 0 ? safeBottom : 16,
left: 0,
right: 0,
child: ConnectionIndicator(),
)
else
Positioned(
top: safeTop > 0 ? safeTop : 16,
left: 0,
right: 0,
child: ConnectionIndicator()),
top: safeTop > 0 ? safeTop : 16,
left: 0,
right: 0,
child: ConnectionIndicator(),
),
],
),
drawer: !isExpandedDrawer ? AppNavigationDrawer() : null,

View File

@ -193,7 +193,7 @@ PODS:
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.49.0)
- sqlite3 (~> 3.49.1)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
@ -378,7 +378,7 @@ SPEC CHECKSUMS:
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
sqlite3_flutter_libs: cc304edcb8e1d8c595d1b08c7aeb46a47691d9db
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f

View File

@ -405,10 +405,10 @@ packages:
dependency: transitive
description:
name: dio_web_adapter
sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
dismissible_page:
dependency: "direct main"
description:
@ -525,10 +525,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c"
sha256: "9467b7c4eedf0bd4c9306b0ec12455b278f6366962be061d0978a446c103c111"
url: "https://pub.dev"
source: hosted
version: "9.0.0"
version: "9.0.1"
file_saver:
dependency: "direct main"
description:
@ -710,6 +710,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
flutter_highlight:
dependency: "direct main"
description:
name: flutter_highlight
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
flutter_inappwebview:
dependency: "direct main"
description:
@ -803,6 +811,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.6+2"
flutter_markdown_latex:
dependency: "direct main"
description:
name: flutter_markdown_latex
sha256: "839e76a84abb3632ffcebbd450cf93c7e9894af65622527d23f0084cee1bfd04"
url: "https://pub.dev"
source: hosted
version: "0.3.4"
flutter_math_fork:
dependency: transitive
description:
name: flutter_math_fork
sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
flutter_native_splash:
dependency: "direct dev"
description:
@ -941,6 +965,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
highlight:
dependency: transitive
description:
name: highlight
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
home_widget:
dependency: "direct main"
description:
@ -1393,18 +1425,18 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35"
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
url: "https://pub.dev"
source: hosted
version: "8.2.1"
version: "8.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76"
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.2.0"
pasteboard:
dependency: "direct main"
description:
@ -1950,10 +1982,10 @@ packages:
dependency: transitive
description:
name: sqlite3_flutter_libs
sha256: "57fafacd815c981735406215966ff7caaa8eab984b094f52e692accefcbd9233"
sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3"
url: "https://pub.dev"
source: hosted
version: "0.5.30"
version: "0.5.31"
sqlparser:
dependency: transitive
description:
@ -2050,6 +2082,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.2"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
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.3.2+73
version: 2.3.2+75
environment:
sdk: ^3.5.4
@ -128,6 +128,8 @@ dependencies:
drift: ^2.25.1
drift_flutter: ^0.2.4
local_notifier: ^0.1.6
flutter_markdown_latex: ^0.3.4
flutter_highlight: ^0.7.0
dev_dependencies:
flutter_test: