From 006841cf8263c7d3c6715977e60332f68c637006 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 22 Jun 2025 00:38:51 +0800 Subject: [PATCH] :sparkles: Pin code :bug: Bug fixes --- assets/i18n/en-US.json | 8 +- lib/models/post.dart | 2 +- lib/models/post.freezed.dart | 12 +- lib/models/post.g.dart | 2 +- lib/screens/account.dart | 6 +- .../account/me/settings_auth_factors.dart | 23 ++- lib/screens/auth/login.dart | 1 + lib/screens/chat/chat.dart | 8 +- lib/screens/explore.dart | 130 ++++++++++--- lib/screens/explore.g.dart | 181 ++++++++++++++++-- lib/screens/posts/pub_profile.dart | 114 ++++++----- lib/screens/realm/realms.dart | 5 +- lib/screens/tabs.dart | 7 - lib/services/responsive.dart | 21 ++ lib/widgets/account/leveling_progress.dart | 1 - 15 files changed, 401 insertions(+), 120 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index e793586..c63a559 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -65,6 +65,8 @@ "authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.", "authFactorInAppNotify": "In-app notification", "authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.", + "authFactorPin": "Pin Code", + "authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.", "realms": "Realms", "createRealm": "Create a Realm", "createRealmHint": "Meet friends with same interests, build communities, and more.", @@ -72,6 +74,8 @@ "deleteRealm": "Delete Realm", "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.", "explore": "Explore", + "exploreFilterSubscriptions": "Subscriptions", + "exploreFilterFriends": "Friends", "account": "Account", "name": "Name", "description": "Description", @@ -460,5 +464,7 @@ "unspecified": "Unspecified", "added": "Added", "preview": "Preview", - "togglePreview": "Toggle Preview" + "togglePreview": "Toggle Preview", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe" } diff --git a/lib/models/post.dart b/lib/models/post.dart index 7308a8a..d1ae308 100644 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -86,7 +86,7 @@ sealed class SnPublisherStats with _$SnPublisherStats { sealed class SnSubscriptionStatus with _$SnSubscriptionStatus { const factory SnSubscriptionStatus({ required bool isSubscribed, - required int publisherId, + required String publisherId, required String publisherName, }) = _SnSubscriptionStatus; diff --git a/lib/models/post.freezed.dart b/lib/models/post.freezed.dart index 0184b85..ed78355 100644 --- a/lib/models/post.freezed.dart +++ b/lib/models/post.freezed.dart @@ -789,7 +789,7 @@ as int, /// @nodoc mixin _$SnSubscriptionStatus { - bool get isSubscribed; int get publisherId; String get publisherName; + bool get isSubscribed; String get publisherId; String get publisherName; /// Create a copy of SnSubscriptionStatus /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -822,7 +822,7 @@ abstract mixin class $SnSubscriptionStatusCopyWith<$Res> { factory $SnSubscriptionStatusCopyWith(SnSubscriptionStatus value, $Res Function(SnSubscriptionStatus) _then) = _$SnSubscriptionStatusCopyWithImpl; @useResult $Res call({ - bool isSubscribed, int publisherId, String publisherName + bool isSubscribed, String publisherId, String publisherName }); @@ -843,7 +843,7 @@ class _$SnSubscriptionStatusCopyWithImpl<$Res> return _then(_self.copyWith( isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable -as int,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable +as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable as String, )); } @@ -859,7 +859,7 @@ class _SnSubscriptionStatus implements SnSubscriptionStatus { factory _SnSubscriptionStatus.fromJson(Map json) => _$SnSubscriptionStatusFromJson(json); @override final bool isSubscribed; -@override final int publisherId; +@override final String publisherId; @override final String publisherName; /// Create a copy of SnSubscriptionStatus @@ -895,7 +895,7 @@ abstract mixin class _$SnSubscriptionStatusCopyWith<$Res> implements $SnSubscrip factory _$SnSubscriptionStatusCopyWith(_SnSubscriptionStatus value, $Res Function(_SnSubscriptionStatus) _then) = __$SnSubscriptionStatusCopyWithImpl; @override @useResult $Res call({ - bool isSubscribed, int publisherId, String publisherName + bool isSubscribed, String publisherId, String publisherName }); @@ -916,7 +916,7 @@ class __$SnSubscriptionStatusCopyWithImpl<$Res> return _then(_SnSubscriptionStatus( isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable -as int,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable +as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable as String, )); } diff --git a/lib/models/post.g.dart b/lib/models/post.g.dart index 3e67440..18a9226 100644 --- a/lib/models/post.g.dart +++ b/lib/models/post.g.dart @@ -172,7 +172,7 @@ _SnSubscriptionStatus _$SnSubscriptionStatusFromJson( Map json, ) => _SnSubscriptionStatus( isSubscribed: json['is_subscribed'] as bool, - publisherId: (json['publisher_id'] as num).toInt(), + publisherId: json['publisher_id'] as String, publisherName: json['publisher_name'] as String, ); diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 422fcd0..aaa3aee 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -66,10 +66,10 @@ class AccountScreen extends HookConsumerWidget { } return AppScaffold( - extendBody: false, // Prevent conflicts with tabs navigation noBackground: isWide, - appBar: AppBar(title: const Text('account').tr()), + appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0), body: SingleChildScrollView( + padding: getTabbedPadding(context), child: Column( children: [ Card( @@ -144,7 +144,7 @@ class AccountScreen extends HookConsumerWidget { level: user.value!.profile.level, experience: user.value!.profile.experience, progress: user.value!.profile.levelingProgress, - ).padding(horizontal: 8), + ).padding(horizontal: 12), Row( children: [ Expanded( diff --git a/lib/screens/account/me/settings_auth_factors.dart b/lib/screens/account/me/settings_auth_factors.dart index a799949..026078b 100644 --- a/lib/screens/account/me/settings_auth_factors.dart +++ b/lib/screens/account/me/settings_auth_factors.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math' as math; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -138,13 +139,13 @@ class AuthFactorSheet extends HookConsumerWidget { children: [ if (factor.enabledAt == null) Badge( - label: Text('authFactorDisabled'.tr()), + label: Text('authFactorDisabled').tr(), textColor: Theme.of(context).colorScheme.onSecondary, backgroundColor: Theme.of(context).colorScheme.secondary, ) else Badge( - label: Text('authFactorEnabled'.tr()), + label: Text('authFactorEnabled').tr(), textColor: Theme.of(context).colorScheme.onPrimary, backgroundColor: Theme.of(context).colorScheme.primary, ), @@ -217,6 +218,8 @@ class AuthFactorNewSheet extends HookConsumerWidget { } } + final width = math.min(400, MediaQuery.of(context).size.width); + return SheetScaffold( titleText: 'authFactorNew'.tr(), child: Column( @@ -248,7 +251,7 @@ class AuthFactorNewSheet extends HookConsumerWidget { } }, ), - if (factorType.value == 0) + if ([0].contains(factorType.value)) TextField( controller: secretController, decoration: InputDecoration( @@ -259,6 +262,20 @@ class AuthFactorNewSheet extends HookConsumerWidget { ), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + ) + else if ([4].contains(factorType.value)) + OtpTextField( + showCursor: false, + numberOfFields: 6, + obscureText: false, + showFieldAsBox: true, + focusedBorderColor: Theme.of(context).colorScheme.primary, + fieldWidth: (width / 6) - 10, + keyboardType: TextInputType.number, + onSubmit: (String verificationCode) { + secretController.text = verificationCode; + }, + textStyle: Theme.of(context).textTheme.titleLarge!, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), diff --git a/lib/screens/auth/login.dart b/lib/screens/auth/login.dart index 4bf34c1..e045d4a 100644 --- a/lib/screens/auth/login.dart +++ b/lib/screens/auth/login.dart @@ -40,6 +40,7 @@ final Map kFactorTypes = { Symbols.notifications_active, ), 3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer), + 4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm), }; @RoutePage() diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 31df158..e489eeb 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -368,10 +368,10 @@ class ChatListScreen extends HookConsumerWidget { ref.invalidate(chatroomsJoinedProvider); }), child: ListView.builder( - padding: - callState.isConnected - ? EdgeInsets.only(bottom: 96) - : EdgeInsets.zero, + padding: getTabbedPadding( + context, + bottom: callState.isConnected ? 96 : null, + ), itemCount: items .where( diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index f68288b..6e2ac37 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/activity.dart'; @@ -46,10 +47,8 @@ class ExploreShellScreen extends ConsumerWidget { } } - - @RoutePage() -class ExploreScreen extends ConsumerWidget { +class ExploreScreen extends HookConsumerWidget { final bool isAside; const ExploreScreen({super.key, this.isAside = false}); @@ -60,12 +59,67 @@ class ExploreScreen extends ConsumerWidget { return const EmptyPageHolder(); } - final activitiesNotifier = ref.watch(activityListNotifierProvider.notifier); + final tabController = useTabController(initialLength: 3); + final currentFilter = useState(null); + + useEffect(() { + void listener() { + switch (tabController.index) { + case 0: + currentFilter.value = null; + break; + case 1: + currentFilter.value = 'subscriptions'; + break; + case 2: + currentFilter.value = 'friends'; + break; + } + } + + tabController.addListener(listener); + return () => tabController.removeListener(listener); + }, [tabController]); + + final activitiesNotifier = ref.watch( + activityListNotifierProvider(currentFilter.value).notifier, + ); return TourTriggerWidget( child: AppScaffold( extendBody: false, // Prevent conflicts with tabs navigation - appBar: AppBar(title: const Text('explore').tr()), + appBar: AppBar( + toolbarHeight: 0, + bottom: TabBar( + controller: tabController, + tabs: [ + Tab( + child: Text( + 'explore'.tr(), + style: TextStyle( + color: Theme.of(context).appBarTheme.foregroundColor!, + ), + ), + ), + Tab( + child: Text( + 'exploreFilterSubscriptions'.tr(), + style: TextStyle( + color: Theme.of(context).appBarTheme.foregroundColor!, + ), + ), + ), + Tab( + child: Text( + 'exploreFilterFriends'.tr(), + style: TextStyle( + color: Theme.of(context).appBarTheme.foregroundColor!, + ), + ), + ), + ], + ), + ), floatingActionButton: FloatingActionButton( heroTag: Key("explore-page-fab"), onPressed: () { @@ -78,32 +132,49 @@ class ExploreScreen extends ConsumerWidget { child: const Icon(Symbols.edit), ), floatingActionButtonLocation: TabbedFabLocation(context), - body: RefreshIndicator( - onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), - child: PagingHelperView( - provider: activityListNotifierProvider, - futureRefreshable: activityListNotifierProvider.future, - notifierRefreshable: activityListNotifierProvider.notifier, - contentBuilder: - (data, widgetCount, endItemView) => Center( - child: _ActivityListView( - data: data, - widgetCount: widgetCount, - endItemView: endItemView, - activitiesNotifier: activitiesNotifier, - ), - ), - ), + body: TabBarView( + controller: tabController, + children: [ + _buildActivityList(ref, null), + _buildActivityList(ref, 'subscriptions'), + _buildActivityList(ref, 'friends'), + ], ), ), ); } + + Widget _buildActivityList(WidgetRef ref, String? filter) { + final activitiesNotifier = ref.watch( + activityListNotifierProvider(filter).notifier, + ); + + return RefreshIndicator( + onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), + child: PagingHelperView( + provider: activityListNotifierProvider(filter), + futureRefreshable: activityListNotifierProvider(filter).future, + notifierRefreshable: activityListNotifierProvider(filter).notifier, + contentBuilder: + (data, widgetCount, endItemView) => Center( + child: _ActivityListView( + data: data, + widgetCount: widgetCount, + endItemView: endItemView, + activitiesNotifier: activitiesNotifier, + contentOnly: filter != null, + ), + ), + ), + ); + } } class _ActivityListView extends HookConsumerWidget { final CursorPagingData data; final int widgetCount; final Widget endItemView; + final bool contentOnly; final ActivityListNotifier activitiesNotifier; const _ActivityListView({ @@ -111,6 +182,7 @@ class _ActivityListView extends HookConsumerWidget { required this.widgetCount, required this.endItemView, required this.activitiesNotifier, + this.contentOnly = false, }); @override @@ -119,7 +191,8 @@ class _ActivityListView extends HookConsumerWidget { return CustomScrollView( slivers: [ - if (user.hasValue) SliverToBoxAdapter(child: CheckInWidget()), + if (user.hasValue && !contentOnly) + SliverToBoxAdapter(child: CheckInWidget()), SliverList.builder( itemCount: widgetCount, itemBuilder: (context, index) { @@ -178,7 +251,7 @@ class _ActivityListView extends HookConsumerWidget { return Column(children: [itemWidget, const Divider(height: 1)]); }, ), - SliverGap(MediaQuery.of(context).padding.bottom + 16), + SliverGap(getTabbedPadding(context).bottom), ], ); } @@ -188,16 +261,23 @@ class _ActivityListView extends HookConsumerWidget { class ActivityListNotifier extends _$ActivityListNotifier with CursorPagingNotifierMixin { @override - Future> build() => fetch(cursor: null); + Future> build(String? filter) => + fetch(cursor: null); @override Future> fetch({required String? cursor}) async { final client = ref.read(apiClientProvider); final take = 20; + final queryParameters = { + if (cursor != null) 'cursor': cursor, + 'take': take, + if (filter != null) 'filter': filter, + }; + final response = await client.get( '/activities', - queryParameters: {if (cursor != null) 'cursor': cursor, 'take': take}, + queryParameters: queryParameters, ); final List items = diff --git a/lib/screens/explore.g.dart b/lib/screens/explore.g.dart index fa9bdb1..a7e823a 100644 --- a/lib/screens/explore.g.dart +++ b/lib/screens/explore.g.dart @@ -7,25 +7,174 @@ part of 'explore.dart'; // ************************************************************************** String _$activityListNotifierHash() => - r'c9683035f7a66a2f331689e274642b60064fbb2e'; + r'14ec2f211c86e1e64a9a34b142d0e8f78ff6361a'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$ActivityListNotifier + extends BuildlessAutoDisposeAsyncNotifier> { + late final String? filter; + + FutureOr> build(String? filter); +} /// See also [ActivityListNotifier]. @ProviderFor(ActivityListNotifier) -final activityListNotifierProvider = AutoDisposeAsyncNotifierProvider< - ActivityListNotifier, - CursorPagingData ->.internal( - ActivityListNotifier.new, - name: r'activityListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$activityListNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); +const activityListNotifierProvider = ActivityListNotifierFamily(); + +/// See also [ActivityListNotifier]. +class ActivityListNotifierFamily + extends Family>> { + /// See also [ActivityListNotifier]. + const ActivityListNotifierFamily(); + + /// See also [ActivityListNotifier]. + ActivityListNotifierProvider call(String? filter) { + return ActivityListNotifierProvider(filter); + } + + @override + ActivityListNotifierProvider getProviderOverride( + covariant ActivityListNotifierProvider provider, + ) { + return call(provider.filter); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'activityListNotifierProvider'; +} + +/// See also [ActivityListNotifier]. +class ActivityListNotifierProvider + extends + AutoDisposeAsyncNotifierProviderImpl< + ActivityListNotifier, + CursorPagingData + > { + /// See also [ActivityListNotifier]. + ActivityListNotifierProvider(String? filter) + : this._internal( + () => ActivityListNotifier()..filter = filter, + from: activityListNotifierProvider, + name: r'activityListNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityListNotifierHash, + dependencies: ActivityListNotifierFamily._dependencies, + allTransitiveDependencies: + ActivityListNotifierFamily._allTransitiveDependencies, + filter: filter, + ); + + ActivityListNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.filter, + }) : super.internal(); + + final String? filter; + + @override + FutureOr> runNotifierBuild( + covariant ActivityListNotifier notifier, + ) { + return notifier.build(filter); + } + + @override + Override overrideWith(ActivityListNotifier Function() create) { + return ProviderOverride( + origin: this, + override: ActivityListNotifierProvider._internal( + () => create()..filter = filter, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + filter: filter, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement< + ActivityListNotifier, + CursorPagingData + > + createElement() { + return _ActivityListNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ActivityListNotifierProvider && other.filter == filter; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, filter.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin ActivityListNotifierRef + on AutoDisposeAsyncNotifierProviderRef> { + /// The parameter `filter` of this provider. + String? get filter; +} + +class _ActivityListNotifierProviderElement + extends + AutoDisposeAsyncNotifierProviderElement< + ActivityListNotifier, + CursorPagingData + > + with ActivityListNotifierRef { + _ActivityListNotifierProviderElement(super.provider); + + @override + String? get filter => (origin as ActivityListNotifierProvider).filter; +} -typedef _$ActivityListNotifier = - AutoDisposeAsyncNotifier>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/posts/pub_profile.dart b/lib/screens/posts/pub_profile.dart index fabe2fd..2f312fc 100644 --- a/lib/screens/posts/pub_profile.dart +++ b/lib/screens/posts/pub_profile.dart @@ -161,48 +161,34 @@ class PublisherProfileScreen extends HookConsumerWidget { ), ], ), - actions: [ - subStatus.when( - data: - (status) => IconButton( - onPressed: - subscribing.value - ? null - : (status.isSubscribed - ? unsubscribe - : subscribe), - icon: Icon( - status.isSubscribed - ? Icons.remove_circle - : Icons.add_circle, - shadows: [appbarShadow], - ), - ), - error: (_, _) => const SizedBox(), - loading: - () => const SizedBox( - width: 48, - height: 48, - child: Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - ), - ), - ), - ), - ), - const Gap(8), - ], ), SliverToBoxAdapter( child: Row( crossAxisAlignment: CrossAxisAlignment.start, spacing: 20, children: [ - ProfilePictureWidget(file: data.picture, radius: 32), + GestureDetector( + child: Badge( + isLabelVisible: data.type == 0, + padding: EdgeInsets.all(4), + label: Icon( + Symbols.launch, + size: 16, + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: + Theme.of(context).colorScheme.primary, + offset: Offset(0, 48), + child: ProfilePictureWidget( + file: data.picture, + radius: 32, + ), + ), + onTap: () { + Navigator.pop(context, true); + context.router.pushPath('/account/${data.name}'); + }, + ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -242,19 +228,49 @@ class PublisherProfileScreen extends HookConsumerWidget { uname: data.account!.name, padding: EdgeInsets.zero, ), - OutlinedButton.icon( - onPressed: () { - Navigator.pop(context); - context.router.pushPath( - '/account/${data.name}', - ); - }, - icon: const Icon(Symbols.launch), - label: Text('accountProfileView').tr(), - style: ButtonStyle( - visualDensity: VisualDensity(vertical: -2), - ), - ).padding(top: 8), + subStatus + .when( + data: + (status) => FilledButton.icon( + onPressed: + subscribing.value + ? null + : (status.isSubscribed + ? unsubscribe + : subscribe), + icon: Icon( + status.isSubscribed + ? Symbols.remove_circle + : Symbols.add_circle, + ), + label: + Text( + status.isSubscribed + ? 'unsubscribe' + : 'subscribe', + ).tr(), + style: ButtonStyle( + visualDensity: VisualDensity( + vertical: -2, + ), + ), + ), + error: (_, _) => const SizedBox(), + loading: + () => const SizedBox( + height: 36, + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ), + ), + ), + ) + .padding(top: 8), ], ), ), diff --git a/lib/screens/realm/realms.dart b/lib/screens/realm/realms.dart index 209843c..a4ff123 100644 --- a/lib/screens/realm/realms.dart +++ b/lib/screens/realm/realms.dart @@ -13,6 +13,7 @@ import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:island/route.gr.dart'; import 'package:island/services/file.dart'; +import 'package:island/services/responsive.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; @@ -93,9 +94,7 @@ class RealmListScreen extends HookConsumerWidget { children: [ Expanded( child: ListView.builder( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), + padding: getTabbedPadding(context), itemCount: value.length, itemBuilder: (context, item) { return ListTile( diff --git a/lib/screens/tabs.dart b/lib/screens/tabs.dart index dac767c..eb09c17 100644 --- a/lib/screens/tabs.dart +++ b/lib/screens/tabs.dart @@ -2,7 +2,6 @@ import 'dart:ui'; import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/route.gr.dart'; import 'package:island/screens/notification.dart'; @@ -55,12 +54,6 @@ class TabsScreen extends HookConsumerWidget { builder: (context, child, _) { final tabsRouter = AutoTabsRouter.of(context); - // Check if current route is a tab route - final currentRoute = context.router.topRoute; - final isTabRoute = routes.any( - (route) => route.routeName == currentRoute.name, - ); - return Stack( children: [ Positioned.fill(child: child), diff --git a/lib/services/responsive.dart b/lib/services/responsive.dart index 12fbb2f..43f5cee 100644 --- a/lib/services/responsive.dart +++ b/lib/services/responsive.dart @@ -15,3 +15,24 @@ bool isWiderScreen(BuildContext context) { bool isWidestScreen(BuildContext context) { return MediaQuery.of(context).size.width > kWidescreenWidth; } + +EdgeInsets getTabbedPadding( + BuildContext context, { + double? horizontal, + double? vertical, + double? left, + double? right, + double? top, + double? bottom, +}) { + final bottomPadding = bottom ?? MediaQuery.of(context).padding.bottom + 16; + return EdgeInsets.only( + left: left ?? horizontal ?? 0, + right: right ?? horizontal ?? 0, + top: top ?? vertical ?? 0, + bottom: + bottom != null + ? bottomPadding + : MediaQuery.of(context).padding.bottom + 16, + ); +} diff --git a/lib/widgets/account/leveling_progress.dart b/lib/widgets/account/leveling_progress.dart index 87fc896..f10f1dc 100644 --- a/lib/widgets/account/leveling_progress.dart +++ b/lib/widgets/account/leveling_progress.dart @@ -23,7 +23,6 @@ class LevelingProgressCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text('levelingProgress').tr().fontSize(16).bold(), Row( spacing: 8, crossAxisAlignment: CrossAxisAlignment.baseline,