Compare commits

...

3 Commits

Author SHA1 Message Date
LittleSheep
c9b71701c8 🚀 Launch 3.2.0+129 2025-08-26 01:33:57 +08:00
LittleSheep
28e98488f1 🌐 Localize new stuff 2025-08-26 01:24:58 +08:00
LittleSheep
b4d476613e 🐛 Optimzation and bug fixes 2025-08-26 01:18:54 +08:00
13 changed files with 338 additions and 80 deletions

View File

@@ -944,5 +944,21 @@
"unpinPostHint": "Are you sure you want to unpin this post?",
"all": "All",
"statusPresent": "Present",
"accountAutomated": "Automated"
}
"accountAutomated": "Automated",
"chatBreakClearButton": "Clear",
"chatBreak5m": "5m",
"chatBreak10m": "10m",
"chatBreak15m": "15m",
"chatBreak30m": "30m",
"chatBreakCustomMinutes": "Custom (minutes)",
"chatBreakEnterMinutes": "Enter minutes",
"errorGeneric": "Error: {}",
"searchMessages": "Search Messages",
"messagesCount": "{} messages",
"dotSeparator": "·",
"roleValidationHint": "Role must be between 0 and 100",
"searchMessagesHint": "Search messages...",
"searchLinks": "Links",
"searchAttachments": "Attachments",
"noMessagesFound": "No messages found"
}

View File

@@ -68,6 +68,10 @@ class AppDatabase extends _$AppDatabase {
return (delete(chatMessages)..where((m) => m.id.equals(id))).go();
}
Future<int> getTotalMessagesForRoom(String roomId) {
return (select(chatMessages)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
}
Future<List<LocalChatMessage>> searchMessages(
String roomId,
String query,

View File

@@ -95,8 +95,24 @@ class LevelingScreen extends HookConsumerWidget {
title: Text('levelingProgress'.tr()),
bottom: TabBar(
tabs: [
Tab(text: 'leveling'.tr()),
Tab(text: 'stellarProgram'.tr()),
Tab(
child: Text(
'leveling'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'stellarProgram'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
),
),

View File

@@ -355,17 +355,20 @@ class MessagesNotifier extends _$MessagesNotifier {
offset: offset,
limit: take,
);
dbMessages = chatMessagesFromDb.map(_database.companionToMessage).toList();
dbMessages =
chatMessagesFromDb.map(_database.companionToMessage).toList();
}
List<LocalChatMessage> filteredMessages = dbMessages;
if (_withLinks == true) {
filteredMessages = filteredMessages.where((msg) => _hasLink(msg)).toList();
filteredMessages =
filteredMessages.where((msg) => _hasLink(msg)).toList();
}
if (_withAttachments == true) {
filteredMessages = filteredMessages.where((msg) => _hasAttachment(msg)).toList();
filteredMessages =
filteredMessages.where((msg) => _hasAttachment(msg)).toList();
}
final dbLocalMessages = filteredMessages;
@@ -513,7 +516,9 @@ class MessagesNotifier extends _$MessagesNotifier {
showErrorAlert(err);
} finally {
developer.log('Finished message sync', name: 'MessagesNotifier');
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = false);
Future.microtask(
() => ref.read(isSyncingProvider.notifier).state = false,
);
_isSyncing = false;
}
}
@@ -524,7 +529,9 @@ class MessagesNotifier extends _$MessagesNotifier {
bool synced = false,
}) async {
try {
if (offset == 0 && !synced && (_searchQuery == null || _searchQuery!.isEmpty)) {
if (offset == 0 &&
!synced &&
(_searchQuery == null || _searchQuery!.isEmpty)) {
_fetchAndCacheMessages(offset: 0, take: take).catchError((_) {
return <LocalChatMessage>[];
});
@@ -584,7 +591,9 @@ class MessagesNotifier extends _$MessagesNotifier {
_hasMore = false;
}
state = AsyncValue.data(_sortMessages([...currentMessages, ...newMessages]));
state = AsyncValue.data(
_sortMessages([...currentMessages, ...newMessages]),
);
} catch (err, stackTrace) {
developer.log(
'Error loading more messages',
@@ -1481,12 +1490,6 @@ class ChatRoomScreen extends HookConsumerWidget {
),
),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
context.pushNamed('searchMessages', pathParameters: {'id': id});
},
),
AudioCallButton(roomId: id),
IconButton(
icon: const Icon(Icons.more_vert),
@@ -1496,14 +1499,15 @@ class ChatRoomScreen extends HookConsumerWidget {
),
const Gap(8),
],
bottom: isSyncing
? const PreferredSize(
preferredSize: Size.fromHeight(2),
child: LinearProgressIndicator(
borderRadius: BorderRadius.zero,
),
)
: null,
bottom:
isSyncing
? const PreferredSize(
preferredSize: Size.fromHeight(2),
child: LinearProgressIndicator(
borderRadius: BorderRadius.zero,
),
)
: null,
),
body: Stack(
children: [

View File

@@ -6,7 +6,7 @@ part of 'room.dart';
// RiverpodGenerator
// **************************************************************************
String _$messagesNotifierHash() => r'dda98f5bf525f3b2bc0a7c89bc6eaa3c8b95f142';
String _$messagesNotifierHash() => r'fc3b66dfb8dd3fc55d142dae5c5e7bdc67eca5d4';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -20,10 +20,17 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:island/pods/database.dart';
part 'room_detail.freezed.dart';
part 'room_detail.g.dart';
@riverpod
Future<int> totalMessagesCount(Ref ref, String roomId) async {
final database = ref.watch(databaseProvider);
return database.getTotalMessagesForRoom(roomId);
}
class ChatDetailScreen extends HookConsumerWidget {
final String id;
const ChatDetailScreen({super.key, required this.id});
@@ -32,6 +39,7 @@ class ChatDetailScreen extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(chatroomProvider(id));
final roomIdentity = ref.watch(chatroomIdentityProvider(id));
final totalMessages = ref.watch(totalMessagesCountProvider(id));
const kNotifyLevelText = [
'chatNotifyLevelAll',
@@ -132,7 +140,7 @@ class ChatDetailScreen extends HookConsumerWidget {
const Text('chatBreakDescription').tr(),
const Gap(16),
ListTile(
title: const Text('Clear').tr(),
title: const Text('chatBreakClearButton').tr(),
subtitle: const Text('chatBreakClear').tr(),
leading: const Icon(Icons.notifications_active),
onTap: () {
@@ -144,8 +152,8 @@ class ChatDetailScreen extends HookConsumerWidget {
},
),
ListTile(
title: const Text('5m'),
subtitle: const Text('chatBreakHour').tr(args: ['5m']),
title: const Text('chatBreak5m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['chatBreak5m'.tr()]),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 5)));
@@ -156,8 +164,8 @@ class ChatDetailScreen extends HookConsumerWidget {
},
),
ListTile(
title: const Text('10m'),
subtitle: const Text('chatBreakHour').tr(args: ['10m']),
title: const Text('chatBreak10m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['chatBreak10m'.tr()]),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 10)));
@@ -168,8 +176,8 @@ class ChatDetailScreen extends HookConsumerWidget {
},
),
ListTile(
title: const Text('15m'),
subtitle: const Text('chatBreakHour').tr(args: ['15m']),
title: const Text('chatBreak15m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['chatBreak15m'.tr()]),
leading: const Icon(Symbols.timer_3),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 15)));
@@ -180,8 +188,8 @@ class ChatDetailScreen extends HookConsumerWidget {
},
),
ListTile(
title: const Text('30m'),
subtitle: const Text('chatBreakHour').tr(args: ['30m']),
title: const Text('chatBreak30m').tr(),
subtitle: const Text('chatBreakHour').tr(args: ['chatBreak30m'.tr()]),
leading: const Icon(Symbols.timer),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 30)));
@@ -195,8 +203,8 @@ class ChatDetailScreen extends HookConsumerWidget {
TextField(
controller: durationController,
decoration: InputDecoration(
labelText: 'Custom (minutes)'.tr(),
hintText: 'Enter minutes'.tr(),
labelText: 'chatBreakCustomMinutes'.tr(),
hintText: 'chatBreakEnterMinutes'.tr(),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.check),
@@ -239,7 +247,7 @@ class ChatDetailScreen extends HookConsumerWidget {
return AppScaffold(
body: roomState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')),
error: (error, _) => Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
data:
(currentRoom) => CustomScrollView(
slivers: [
@@ -365,7 +373,12 @@ class ChatDetailScreen extends HookConsumerWidget {
),
leading: const Icon(Icons.search),
trailing: const Icon(Symbols.chevron_right),
title: const Text('Search Messages').tr(),
title: const Text('searchMessages').tr(),
subtitle: totalMessages.when(
data: (count) => Text('messagesCount'.tr(args: [count.toString()])),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('errorGeneric'.tr(args: [err.toString()])),
),
onTap: () {
context.pushNamed('searchMessages', pathParameters: {'id': id});
},
@@ -703,7 +716,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
? 'permissionModerator'
: 'permissionMember',
).tr(),
Text('·').bold().padding(horizontal: 6),
Text('dotSeparator').bold().padding(horizontal: 6),
Expanded(child: Text("@${member.account.name}")),
],
),
@@ -863,7 +876,7 @@ class _ChatMemberRoleSheet extends HookConsumerWidget {
try {
final newRole = int.parse(roleController.text);
if (newRole < 0 || newRole > 100) {
throw 'Role must be between 0 and 100';
throw 'roleValidationHint'.tr();
}
final apiClient = ref.read(apiClientProvider);

View File

@@ -6,8 +6,8 @@ part of 'room_detail.dart';
// RiverpodGenerator
// **************************************************************************
String _$chatMemberListNotifierHash() =>
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973';
String _$totalMessagesCountHash() =>
r'a15c03461f25c2d4d39c0926509bf626ae2550a6';
/// Copied from Dart SDK
class _SystemHash {
@@ -30,6 +30,128 @@ class _SystemHash {
}
}
/// See also [totalMessagesCount].
@ProviderFor(totalMessagesCount)
const totalMessagesCountProvider = TotalMessagesCountFamily();
/// See also [totalMessagesCount].
class TotalMessagesCountFamily extends Family<AsyncValue<int>> {
/// See also [totalMessagesCount].
const TotalMessagesCountFamily();
/// See also [totalMessagesCount].
TotalMessagesCountProvider call(String roomId) {
return TotalMessagesCountProvider(roomId);
}
@override
TotalMessagesCountProvider getProviderOverride(
covariant TotalMessagesCountProvider provider,
) {
return call(provider.roomId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'totalMessagesCountProvider';
}
/// See also [totalMessagesCount].
class TotalMessagesCountProvider extends AutoDisposeFutureProvider<int> {
/// See also [totalMessagesCount].
TotalMessagesCountProvider(String roomId)
: this._internal(
(ref) => totalMessagesCount(ref as TotalMessagesCountRef, roomId),
from: totalMessagesCountProvider,
name: r'totalMessagesCountProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$totalMessagesCountHash,
dependencies: TotalMessagesCountFamily._dependencies,
allTransitiveDependencies:
TotalMessagesCountFamily._allTransitiveDependencies,
roomId: roomId,
);
TotalMessagesCountProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.roomId,
}) : super.internal();
final String roomId;
@override
Override overrideWith(
FutureOr<int> Function(TotalMessagesCountRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TotalMessagesCountProvider._internal(
(ref) => create(ref as TotalMessagesCountRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
roomId: roomId,
),
);
}
@override
AutoDisposeFutureProviderElement<int> createElement() {
return _TotalMessagesCountProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TotalMessagesCountProvider && other.roomId == roomId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, roomId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin TotalMessagesCountRef on AutoDisposeFutureProviderRef<int> {
/// The parameter `roomId` of this provider.
String get roomId;
}
class _TotalMessagesCountProviderElement
extends AutoDisposeFutureProviderElement<int>
with TotalMessagesCountRef {
_TotalMessagesCountProviderElement(super.provider);
@override
String get roomId => (origin as TotalMessagesCountProvider).roomId;
}
String _$chatMemberListNotifierHash() =>
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973';
abstract class _$ChatMemberListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> {
late final String roomId;

View File

@@ -32,7 +32,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
}, []);
return AppScaffold(
appBar: AppBar(title: const Text('Search Messages')),
appBar: AppBar(title: const Text('searchMessages').tr()),
body: Column(
children: [
Column(
@@ -40,7 +40,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'Search messages...',
hintText: 'searchMessagesHint'.tr(),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.only(
@@ -72,7 +72,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
Expanded(
child: CheckboxListTile(
secondary: const Icon(Symbols.link),
title: const Text('Links'),
title: const Text('searchLinks').tr(),
value: withLinks.value,
onChanged: (bool? value) {
withLinks.value = value!;
@@ -87,7 +87,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
Expanded(
child: CheckboxListTile(
secondary: const Icon(Symbols.file_copy),
title: const Text('Attachments'),
title: const Text('searchAttachments').tr(),
value: withAttachments.value,
onChanged: (bool? value) {
withAttachments.value = value!;
@@ -109,7 +109,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
data:
(messageList) =>
messageList.isEmpty
? Center(child: Text('No messages found'.tr()))
? Center(child: Text('noMessagesFound'.tr()))
: SuperListView.builder(
padding: const EdgeInsets.symmetric(vertical: 16),
reverse: true, // Show newest messages at the bottom
@@ -129,7 +129,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')),
error: (error, _) => Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
),
),
],

View File

@@ -27,7 +27,9 @@ class AppDetailScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 2);
final appData = ref.watch(customAppProvider(publisherName, projectId, appId));
final appData = ref.watch(
customAppProvider(publisherName, projectId, appId),
);
return AppScaffold(
appBar: AppBar(
@@ -35,23 +37,43 @@ class AppDetailScreen extends HookConsumerWidget {
actions: [
IconButton(
icon: const Icon(Symbols.edit),
onPressed: appData.value == null
? null
: () {
context.pushNamed(
'developerAppEdit',
pathParameters: {
'name': publisherName,
'projectId': projectId,
'id': appId,
},
);
},
onPressed:
appData.value == null
? null
: () {
context.pushNamed(
'developerAppEdit',
pathParameters: {
'name': publisherName,
'projectId': projectId,
'id': appId,
},
);
},
),
],
bottom: TabBar(
controller: tabController,
tabs: [Tab(text: 'overview'.tr()), Tab(text: 'secrets'.tr())],
tabs: [
Tab(
child: Text(
'overview'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'secrets'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
),
),
body: appData.when(
@@ -70,12 +92,14 @@ class AppDetailScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry: () => ref.invalidate(
customAppProvider(publisherName, projectId, appId),
),
),
error:
(err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
customAppProvider(publisherName, projectId, appId),
),
),
),
);
}
@@ -98,12 +122,13 @@ class _AppOverview extends StatelessWidget {
children: [
Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: app.background != null
? CloudFileWidget(
item: app.background!,
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
child:
app.background != null
? CloudFileWidget(
item: app.background!,
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
),
Positioned(
left: 20,

View File

@@ -52,7 +52,26 @@ class BotDetailScreen extends HookConsumerWidget {
],
bottom: TabBar(
controller: tabController,
tabs: [Tab(text: 'overview'.tr()), Tab(text: 'keys'.tr())],
tabs: [
Tab(
child: Text(
'overview'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'keys'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
),
),
body: botData.when(

View File

@@ -58,7 +58,26 @@ class ProjectDetailScreen extends HookConsumerWidget {
],
bottom: TabBar(
controller: tabController,
tabs: [Tab(text: 'customApps'.tr()), Tab(text: 'bots'.tr())],
tabs: [
Tab(
child: Text(
'customApps'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'bots'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
),
),
body: TabBarView(

View File

@@ -143,8 +143,26 @@ class ArticlesScreen extends ConsumerWidget {
bottom: TabBar(
isScrollable: true,
tabs: [
const Tab(text: 'All'),
...feeds.map((feed) => Tab(text: feed.title)),
Tab(
child: Text(
'All',
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
...feeds.map(
(feed) => Tab(
child: Text(
feed.title,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
),
],
),
),

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: 3.2.0+128
version: 3.2.0+129
environment:
sdk: ^3.7.2
@@ -139,6 +139,7 @@ dependencies:
material_color_utilities: ^0.11.1
screenshot: ^3.0.0
flutter_card_swiper: ^7.0.2
file_saver: ^0.3.1
dev_dependencies:
flutter_test:
@@ -235,4 +236,5 @@ msix_config:
identity_name: dev.solian.app
msix_version: 3.2.0.0
logo_path: .\assets\icons\icon.png
capabilities: internetClientServer, location, microphone, webcam
capabilities: internetClientServer, location, microphone, webcam