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?", "unpinPostHint": "Are you sure you want to unpin this post?",
"all": "All", "all": "All",
"statusPresent": "Present", "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(); 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( Future<List<LocalChatMessage>> searchMessages(
String roomId, String roomId,
String query, String query,

View File

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

View File

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

View File

@@ -6,8 +6,8 @@ part of 'room_detail.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$chatMemberListNotifierHash() => String _$totalMessagesCountHash() =>
r'3ea30150278523e9f6b23f9200ea9a9fbae9c973'; r'a15c03461f25c2d4d39c0926509bf626ae2550a6';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { 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 abstract class _$ChatMemberListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> { extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnChatMember>> {
late final String roomId; late final String roomId;

View File

@@ -32,7 +32,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
}, []); }, []);
return AppScaffold( return AppScaffold(
appBar: AppBar(title: const Text('Search Messages')), appBar: AppBar(title: const Text('searchMessages').tr()),
body: Column( body: Column(
children: [ children: [
Column( Column(
@@ -40,7 +40,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
TextField( TextField(
controller: searchController, controller: searchController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Search messages...', hintText: 'searchMessagesHint'.tr(),
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
@@ -72,7 +72,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
Expanded( Expanded(
child: CheckboxListTile( child: CheckboxListTile(
secondary: const Icon(Symbols.link), secondary: const Icon(Symbols.link),
title: const Text('Links'), title: const Text('searchLinks').tr(),
value: withLinks.value, value: withLinks.value,
onChanged: (bool? value) { onChanged: (bool? value) {
withLinks.value = value!; withLinks.value = value!;
@@ -87,7 +87,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
Expanded( Expanded(
child: CheckboxListTile( child: CheckboxListTile(
secondary: const Icon(Symbols.file_copy), secondary: const Icon(Symbols.file_copy),
title: const Text('Attachments'), title: const Text('searchAttachments').tr(),
value: withAttachments.value, value: withAttachments.value,
onChanged: (bool? value) { onChanged: (bool? value) {
withAttachments.value = value!; withAttachments.value = value!;
@@ -109,7 +109,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
data: data:
(messageList) => (messageList) =>
messageList.isEmpty messageList.isEmpty
? Center(child: Text('No messages found'.tr())) ? Center(child: Text('noMessagesFound'.tr()))
: SuperListView.builder( : SuperListView.builder(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
reverse: true, // Show newest messages at the bottom reverse: true, // Show newest messages at the bottom
@@ -129,7 +129,7 @@ class SearchMessagesScreen extends HookConsumerWidget {
}, },
), ),
loading: () => const Center(child: CircularProgressIndicator()), 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 @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 2); final tabController = useTabController(initialLength: 2);
final appData = ref.watch(customAppProvider(publisherName, projectId, appId)); final appData = ref.watch(
customAppProvider(publisherName, projectId, appId),
);
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
@@ -35,23 +37,43 @@ class AppDetailScreen extends HookConsumerWidget {
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Symbols.edit), icon: const Icon(Symbols.edit),
onPressed: appData.value == null onPressed:
? null appData.value == null
: () { ? null
context.pushNamed( : () {
'developerAppEdit', context.pushNamed(
pathParameters: { 'developerAppEdit',
'name': publisherName, pathParameters: {
'projectId': projectId, 'name': publisherName,
'id': appId, 'projectId': projectId,
}, 'id': appId,
); },
}, );
},
), ),
], ],
bottom: TabBar( bottom: TabBar(
controller: tabController, 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( body: appData.when(
@@ -70,12 +92,14 @@ class AppDetailScreen extends HookConsumerWidget {
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => ResponseErrorWidget( error:
error: err, (err, stack) => ResponseErrorWidget(
onRetry: () => ref.invalidate( error: err,
customAppProvider(publisherName, projectId, appId), onRetry:
), () => ref.invalidate(
), customAppProvider(publisherName, projectId, appId),
),
),
), ),
); );
} }
@@ -98,12 +122,13 @@ class _AppOverview extends StatelessWidget {
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: app.background != null child:
? CloudFileWidget( app.background != null
item: app.background!, ? CloudFileWidget(
fit: BoxFit.cover, item: app.background!,
) fit: BoxFit.cover,
: const SizedBox.shrink(), )
: const SizedBox.shrink(),
), ),
Positioned( Positioned(
left: 20, left: 20,

View File

@@ -52,7 +52,26 @@ class BotDetailScreen extends HookConsumerWidget {
], ],
bottom: TabBar( bottom: TabBar(
controller: tabController, 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( body: botData.when(

View File

@@ -58,7 +58,26 @@ class ProjectDetailScreen extends HookConsumerWidget {
], ],
bottom: TabBar( bottom: TabBar(
controller: tabController, 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( body: TabBarView(

View File

@@ -143,8 +143,26 @@ class ArticlesScreen extends ConsumerWidget {
bottom: TabBar( bottom: TabBar(
isScrollable: true, isScrollable: true,
tabs: [ tabs: [
const Tab(text: 'All'), Tab(
...feeds.map((feed) => Tab(text: feed.title)), 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 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 3.2.0+128 version: 3.2.0+129
environment: environment:
sdk: ^3.7.2 sdk: ^3.7.2
@@ -139,6 +139,7 @@ dependencies:
material_color_utilities: ^0.11.1 material_color_utilities: ^0.11.1
screenshot: ^3.0.0 screenshot: ^3.0.0
flutter_card_swiper: ^7.0.2 flutter_card_swiper: ^7.0.2
file_saver: ^0.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -236,3 +237,4 @@ msix_config:
msix_version: 3.2.0.0 msix_version: 3.2.0.0
logo_path: .\assets\icons\icon.png logo_path: .\assets\icons\icon.png
capabilities: internetClientServer, location, microphone, webcam capabilities: internetClientServer, location, microphone, webcam