Pin code

🐛 Bug fixes
This commit is contained in:
LittleSheep 2025-06-22 00:38:51 +08:00
parent f73cf10a54
commit 006841cf82
15 changed files with 401 additions and 120 deletions

View File

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

View File

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

View File

@ -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<String, dynamic> 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,
));
}

View File

@ -172,7 +172,7 @@ _SnSubscriptionStatus _$SnSubscriptionStatusFromJson(
Map<String, dynamic> 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,
);

View File

@ -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: <Widget>[
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(

View File

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

View File

@ -40,6 +40,7 @@ final Map<int, (String, String, IconData)> kFactorTypes = {
Symbols.notifications_active,
),
3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
};
@RoutePage()

View File

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

View File

@ -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<String?>(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<SnActivity> 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<SnActivity> {
@override
Future<CursorPagingData<SnActivity>> build() => fetch(cursor: null);
Future<CursorPagingData<SnActivity>> build(String? filter) =>
fetch(cursor: null);
@override
Future<CursorPagingData<SnActivity>> 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<SnActivity> items =

View File

@ -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<CursorPagingData<SnActivity>> {
late final String? filter;
FutureOr<CursorPagingData<SnActivity>> build(String? filter);
}
/// See also [ActivityListNotifier].
@ProviderFor(ActivityListNotifier)
final activityListNotifierProvider = AutoDisposeAsyncNotifierProvider<
ActivityListNotifier,
CursorPagingData<SnActivity>
>.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<AsyncValue<CursorPagingData<SnActivity>>> {
/// 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<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'activityListNotifierProvider';
}
/// See also [ActivityListNotifier].
class ActivityListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ActivityListNotifier,
CursorPagingData<SnActivity>
> {
/// 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<CursorPagingData<SnActivity>> 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<SnActivity>
>
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<CursorPagingData<SnActivity>> {
/// The parameter `filter` of this provider.
String? get filter;
}
class _ActivityListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnActivity>
>
with ActivityListNotifierRef {
_ActivityListNotifierProviderElement(super.provider);
@override
String? get filter => (origin as ActivityListNotifierProvider).filter;
}
typedef _$ActivityListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnActivity>>;
// 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

View File

@ -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),
],
),
),

View File

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

View File

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

View File

@ -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,
);
}

View File

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