641 lines
20 KiB
Dart
641 lines
20 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:yumi/app_localizations.dart';
|
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
|
import 'package:yumi/main.dart' show routeObserver;
|
|
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
|
import 'package:yumi/modules/home/index_home_page.dart';
|
|
import 'package:yumi/modules/chat/message/sc_message_page.dart';
|
|
import 'package:yumi/modules/user/task/task_page.dart';
|
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
|
import 'package:yumi/shared/tools/sc_dialog_utils.dart';
|
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
|
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
|
import '../../shared/tools/sc_heartbeat_utils.dart';
|
|
import '../../shared/tools/sc_entry_popup_coordinator.dart';
|
|
import '../../shared/tools/sc_lk_event_bus.dart';
|
|
import '../../shared/data_sources/models/enum/sc_heartbeat_status.dart';
|
|
import '../../ui_kit/components/sc_float_ichart.dart';
|
|
import '../../ui_kit/widgets/daily_sign_in/daily_sign_in_dialog.dart';
|
|
import '../../ui_kit/widgets/register_reward/register_reward_dialog.dart';
|
|
import '../user/me_page2.dart';
|
|
|
|
/// 首页
|
|
class SCIndexPage extends StatefulWidget {
|
|
const SCIndexPage({super.key});
|
|
|
|
@override
|
|
State<SCIndexPage> createState() => _SCIndexPageState();
|
|
}
|
|
|
|
class _SCIndexPageState extends State<SCIndexPage>
|
|
with WidgetsBindingObserver, RouteAware {
|
|
static const Duration _registerRewardSocketWaitTimeout = Duration(seconds: 6);
|
|
|
|
int _currentIndex = 0;
|
|
final List<Widget> _pages = [];
|
|
final Set<int> _builtPageIndexes = <int>{0};
|
|
final List<BottomNavigationBarItem> _bottomItems = [];
|
|
final ValueNotifier<int> _currentIndexNotifier = ValueNotifier<int>(0);
|
|
final ValueNotifier<int> _taskClaimableCount = ValueNotifier<int>(0);
|
|
SCAppGeneralManager? generalProvider;
|
|
Locale? _lastLocale;
|
|
bool _hasShownEntryDialogs = false;
|
|
bool _hasShownRegisterRewardDialog = false;
|
|
bool _hasRequestedFirstRechargeDialog = false;
|
|
bool _isShowingDailySignInDialog = false;
|
|
bool _showRegisterRewardAfterDailySignIn = false;
|
|
bool _isOpeningFirstRegisterRoomGame = false;
|
|
StreamSubscription<RegisterRewardGrantedEvent>? _registerRewardSubscription;
|
|
Completer<void>? _registerRewardSocketCompleter;
|
|
PageRoute<dynamic>? _routeObserverRoute;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
_registerRewardSubscription = eventBus
|
|
.on<RegisterRewardGrantedEvent>()
|
|
.listen(_onRegisterRewardGranted);
|
|
_initializePages();
|
|
unawaited(_loadTaskClaimableCount());
|
|
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
|
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
|
rtcProvider.initializeRealTimeCommunicationManager(context);
|
|
unawaited(
|
|
rtcProvider.prewarmRtcEngine().catchError((error, stackTrace) {}),
|
|
);
|
|
Provider.of<RtmProvider>(context, listen: false).init(context);
|
|
Provider.of<SocialChatUserProfileManager>(
|
|
context,
|
|
listen: false,
|
|
).getUserIdentity();
|
|
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
|
SCHeartbeatUtils.scheduleHeartbeat(SCHeartbeatStatus.ONLINE.name, false);
|
|
DataPersistence.setLang(SCGlobalConfig.lang);
|
|
OverlayManager().activate();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
WakelockPlus.enable();
|
|
_showEntryDialogs();
|
|
});
|
|
unawaited(rtcProvider.cleanupPersistedVoiceRoomSessionBeforeEntry());
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
routeObserver.unsubscribe(this);
|
|
OverlayManager().setHomeRootTabsVisible(false);
|
|
_registerRewardSubscription?.cancel();
|
|
_currentIndexNotifier.dispose();
|
|
_taskClaimableCount.dispose();
|
|
WakelockPlus.disable();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
if (state != AppLifecycleState.resumed) {
|
|
return;
|
|
}
|
|
unawaited(
|
|
Provider.of<RtmProvider>(
|
|
context,
|
|
listen: false,
|
|
).syncRoomRedPacketBroadcastGroup(),
|
|
);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
unawaited(_showEntryPopupIfNeeded());
|
|
});
|
|
unawaited(_loadTaskClaimableCount());
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
SCFloatIchart().init(context);
|
|
_rebuildBottomItemsIfNeeded();
|
|
final route = ModalRoute.of(context);
|
|
if (route is PageRoute<dynamic> && _routeObserverRoute != route) {
|
|
if (_routeObserverRoute != null) {
|
|
routeObserver.unsubscribe(this);
|
|
}
|
|
_routeObserverRoute = route;
|
|
routeObserver.subscribe(this, route);
|
|
_setHomeRootTabsVisible(route.isCurrent);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didPush() {
|
|
_setHomeRootTabsVisible(true);
|
|
}
|
|
|
|
@override
|
|
void didPopNext() {
|
|
_setHomeRootTabsVisible(true);
|
|
unawaited(_loadTaskClaimableCount());
|
|
}
|
|
|
|
@override
|
|
void didPushNext() {
|
|
_setHomeRootTabsVisible(false);
|
|
}
|
|
|
|
@override
|
|
void didPop() {
|
|
_setHomeRootTabsVisible(false);
|
|
}
|
|
|
|
void _setHomeRootTabsVisible(bool visible) {
|
|
if (!mounted && visible) {
|
|
return;
|
|
}
|
|
OverlayManager().setHomeRootTabsVisible(visible);
|
|
if (visible) {
|
|
unawaited(
|
|
Provider.of<RtmProvider>(
|
|
context,
|
|
listen: false,
|
|
).syncRoomRedPacketBroadcastGroup(),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
|
if (didPop) {
|
|
return;
|
|
}
|
|
final shouldPop = await _doubleExit();
|
|
if (shouldPop && context.mounted) {
|
|
Navigator.of(context).pop(result);
|
|
}
|
|
},
|
|
child: Stack(
|
|
children: [
|
|
Image.asset(
|
|
"sc_images/index/sc_icon_index_bg.png",
|
|
width: ScreenUtil().screenWidth,
|
|
height: ScreenUtil().screenHeight,
|
|
fit: BoxFit.fill,
|
|
),
|
|
SafeArea(
|
|
top: false,
|
|
child: Scaffold(
|
|
resizeToAvoidBottomInset: false,
|
|
backgroundColor: Colors.transparent,
|
|
body: _buildCachedBody(),
|
|
bottomNavigationBar: Container(
|
|
height: 85.w,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage(
|
|
"sc_images/index/sc_index_bottom_navigation_bar_bg.png",
|
|
),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: Theme(
|
|
data: Theme.of(context).copyWith(
|
|
splashFactory: NoSplash.splashFactory,
|
|
splashColor: Colors.transparent,
|
|
highlightColor: Colors.transparent,
|
|
hoverColor: Colors.transparent,
|
|
),
|
|
child: BottomNavigationBar(
|
|
elevation: 0,
|
|
enableFeedback: false,
|
|
backgroundColor: Colors.transparent,
|
|
selectedLabelStyle: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14.sp,
|
|
),
|
|
unselectedLabelStyle: TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 13.sp,
|
|
),
|
|
type: BottomNavigationBarType.fixed,
|
|
selectedItemColor: Color(0xffBF854A),
|
|
unselectedItemColor: Color(0xffC4C4C4),
|
|
showUnselectedLabels: true,
|
|
showSelectedLabels: true,
|
|
items: _bottomItems,
|
|
currentIndex: _currentIndex,
|
|
onTap: _switchBottomTab,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
///双击退出
|
|
int _lastClickTime = 0;
|
|
|
|
Future<bool> _doubleExit() {
|
|
int nowTime = DateTime.now().microsecondsSinceEpoch;
|
|
if (_lastClickTime != 0 && nowTime - _lastClickTime > 1500) {
|
|
return Future.value(true);
|
|
} else {
|
|
_lastClickTime = DateTime.now().microsecondsSinceEpoch;
|
|
Future.delayed(const Duration(milliseconds: 1500), () {
|
|
_lastClickTime = 0;
|
|
});
|
|
return Future.value(false);
|
|
}
|
|
}
|
|
|
|
void _initializePages() {
|
|
if (_pages.isNotEmpty) {
|
|
return;
|
|
}
|
|
|
|
_pages.add(SCIndexHomePage());
|
|
_pages.add(
|
|
TaskPage(
|
|
showBackButton: false,
|
|
onTaskStatusChanged: () => unawaited(_loadTaskClaimableCount()),
|
|
),
|
|
);
|
|
_pages.add(SCMessagePage());
|
|
_pages.add(MePage2(currentIndexListenable: _currentIndexNotifier));
|
|
}
|
|
|
|
Widget _buildCachedBody() {
|
|
return IndexedStack(
|
|
index: _currentIndex,
|
|
children: List<Widget>.generate(_pages.length, (index) {
|
|
if (!_builtPageIndexes.contains(index)) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
return TickerMode(
|
|
enabled: index == _currentIndex,
|
|
child: _pages[index],
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
void _switchBottomTab(int index) {
|
|
if (index == 1) {
|
|
unawaited(_loadTaskClaimableCount());
|
|
}
|
|
if (index == _currentIndex) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_currentIndex = index;
|
|
_currentIndexNotifier.value = index;
|
|
_builtPageIndexes.add(index);
|
|
});
|
|
}
|
|
|
|
void _rebuildBottomItemsIfNeeded() {
|
|
final locale = Localizations.localeOf(context);
|
|
if (_lastLocale == locale && _bottomItems.isNotEmpty) {
|
|
return;
|
|
}
|
|
_lastLocale = locale;
|
|
_bottomItems.clear();
|
|
|
|
_bottomItems.add(
|
|
BottomNavigationBarItem(
|
|
icon: _buildBottomTabIcon(
|
|
active: false,
|
|
svgaPath: "sc_images/index/sc_icon_home_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_home_no.png",
|
|
),
|
|
activeIcon: _buildBottomTabIcon(
|
|
active: true,
|
|
svgaPath: "sc_images/index/sc_icon_home_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_home_en.png",
|
|
),
|
|
label: SCAppLocalizations.of(context)!.home,
|
|
),
|
|
);
|
|
_bottomItems.add(
|
|
BottomNavigationBarItem(
|
|
icon: _buildTaskTabIcon(active: false),
|
|
activeIcon: _buildTaskTabIcon(active: true),
|
|
label: SCAppLocalizations.of(context)!.task,
|
|
),
|
|
);
|
|
|
|
_bottomItems.add(
|
|
BottomNavigationBarItem(
|
|
icon: Selector<RtmProvider, int>(
|
|
selector: (c, p) => p.allUnReadCount,
|
|
shouldRebuild: (prev, next) => prev != next,
|
|
builder: (_, allUnReadCount, __) {
|
|
return allUnReadCount > 0
|
|
? Badge(
|
|
backgroundColor: Colors.red,
|
|
label: text(
|
|
"${allUnReadCount > 99 ? "99+" : allUnReadCount}",
|
|
fontSize: 9.sp,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
alignment: AlignmentDirectional.topEnd,
|
|
child: _buildBottomTabIcon(
|
|
active: false,
|
|
svgaPath: "sc_images/index/sc_icon_message_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_message_no.png",
|
|
),
|
|
)
|
|
: _buildBottomTabIcon(
|
|
active: false,
|
|
svgaPath: "sc_images/index/sc_icon_message_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_message_no.png",
|
|
);
|
|
},
|
|
),
|
|
activeIcon: Selector<RtmProvider, int>(
|
|
selector: (c, p) => p.allUnReadCount,
|
|
shouldRebuild: (prev, next) => prev != next,
|
|
builder: (_, allUnReadCount, __) {
|
|
return allUnReadCount > 0
|
|
? Badge(
|
|
backgroundColor: Colors.red,
|
|
label: text(
|
|
"${allUnReadCount > 99 ? "99+" : allUnReadCount}",
|
|
fontSize: 9.sp,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
alignment: AlignmentDirectional.topEnd,
|
|
child: _buildBottomTabIcon(
|
|
active: true,
|
|
svgaPath: "sc_images/index/sc_icon_message_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_message_en.png",
|
|
),
|
|
)
|
|
: _buildBottomTabIcon(
|
|
active: true,
|
|
svgaPath: "sc_images/index/sc_icon_message_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_message_en.png",
|
|
);
|
|
},
|
|
),
|
|
label: SCAppLocalizations.of(context)!.message,
|
|
),
|
|
);
|
|
_bottomItems.add(
|
|
BottomNavigationBarItem(
|
|
icon: _buildBottomTabIcon(
|
|
active: false,
|
|
svgaPath: "sc_images/index/sc_icon_me_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_me_no.png",
|
|
),
|
|
activeIcon: _buildBottomTabIcon(
|
|
active: true,
|
|
svgaPath: "sc_images/index/sc_icon_me_anim.svga",
|
|
fallbackPath: "sc_images/index/sc_icon_me_en.png",
|
|
),
|
|
label: SCAppLocalizations.of(context)!.me,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _loadTaskClaimableCount() async {
|
|
try {
|
|
final count = await SCAccountRepository().taskClaimableCount();
|
|
if (!mounted) return;
|
|
_taskClaimableCount.value = count;
|
|
} catch (error) {
|
|
if (!mounted) return;
|
|
_taskClaimableCount.value = 0;
|
|
}
|
|
}
|
|
|
|
Future<void> _showEntryDialogs() async {
|
|
if (_hasShownEntryDialogs || !mounted) {
|
|
return;
|
|
}
|
|
_hasShownEntryDialogs = true;
|
|
|
|
await _waitForRegisterRewardSocketIfNeeded();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
await _showRegisterRewardDialogIfNeeded();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
await _showEntryPopupIfNeeded();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
await _showDailySignInDialogIfNeeded();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
await _showFirstRechargeDialogIfNeeded();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
await _openFirstRegisterRoomGameIfNeeded();
|
|
}
|
|
|
|
Future<void> _showDailySignInDialogIfNeeded() async {
|
|
final dialogData = await _loadDailySignInDialogData();
|
|
if (!mounted || dialogData == null) {
|
|
return;
|
|
}
|
|
_isShowingDailySignInDialog = true;
|
|
await DailySignInDialog.show(context, data: dialogData);
|
|
_isShowingDailySignInDialog = false;
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (_showRegisterRewardAfterDailySignIn) {
|
|
_showRegisterRewardAfterDailySignIn = false;
|
|
await _showRegisterRewardDialogIfNeeded();
|
|
}
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Future<void> _showEntryPopupIfNeeded() async {
|
|
if (!mounted || !(ModalRoute.of(context)?.isCurrent ?? false)) {
|
|
return;
|
|
}
|
|
await SCEntryPopupCoordinator.showIfNeeded(
|
|
context,
|
|
onSelectRootTab: _switchBottomTab,
|
|
);
|
|
}
|
|
|
|
Future<void> _showFirstRechargeDialogIfNeeded() async {
|
|
if (_hasRequestedFirstRechargeDialog ||
|
|
SCGlobalConfig.isReview ||
|
|
AccountStorage().getToken().isEmpty ||
|
|
!mounted ||
|
|
!(ModalRoute.of(context)?.isCurrent ?? false)) {
|
|
return;
|
|
}
|
|
_hasRequestedFirstRechargeDialog = true;
|
|
await SCDialogUtils.showFirstRechargeDialog(context, forceRefresh: true);
|
|
}
|
|
|
|
Future<void> _waitForRegisterRewardSocketIfNeeded() async {
|
|
if (!DataPersistence.getAwaitRegisterRewardSocket() ||
|
|
DataPersistence.getPendingRegisterRewardDialog()) {
|
|
return;
|
|
}
|
|
|
|
final completer = Completer<void>();
|
|
_registerRewardSocketCompleter = completer;
|
|
await Future.any([
|
|
completer.future,
|
|
Future<void>.delayed(_registerRewardSocketWaitTimeout),
|
|
]);
|
|
if (identical(_registerRewardSocketCompleter, completer)) {
|
|
_registerRewardSocketCompleter = null;
|
|
}
|
|
if (!DataPersistence.getPendingRegisterRewardDialog()) {
|
|
await DataPersistence.clearAwaitRegisterRewardSocket();
|
|
}
|
|
}
|
|
|
|
Future<void> _showRegisterRewardDialogIfNeeded() async {
|
|
if (_hasShownRegisterRewardDialog ||
|
|
!DataPersistence.getPendingRegisterRewardDialog()) {
|
|
return;
|
|
}
|
|
|
|
await DataPersistence.clearPendingRegisterRewardDialog();
|
|
await DataPersistence.clearAwaitRegisterRewardSocket();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
_hasShownRegisterRewardDialog = true;
|
|
await RegisterRewardDialog.show(context);
|
|
}
|
|
|
|
void _onRegisterRewardGranted(RegisterRewardGrantedEvent event) {
|
|
final completer = _registerRewardSocketCompleter;
|
|
if (completer != null && !completer.isCompleted) {
|
|
completer.complete();
|
|
}
|
|
|
|
if (!mounted || _hasShownRegisterRewardDialog) {
|
|
return;
|
|
}
|
|
|
|
if (_isShowingDailySignInDialog) {
|
|
_showRegisterRewardAfterDailySignIn = true;
|
|
return;
|
|
}
|
|
|
|
if (_hasShownEntryDialogs) {
|
|
unawaited(_showRegisterRewardDialogIfNeeded());
|
|
}
|
|
}
|
|
|
|
Future<DailySignInDialogData?> _loadDailySignInDialogData() async {
|
|
try {
|
|
final statusRes = await SCAccountRepository().signInRewardStatus();
|
|
if (!statusRes.canShowDialog) {
|
|
return null;
|
|
}
|
|
final dialogData = DailySignInDialogData.fromStatus(statusRes: statusRes);
|
|
return dialogData;
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _openFirstRegisterRoomGameIfNeeded() async {
|
|
if (_isOpeningFirstRegisterRoomGame ||
|
|
!DataPersistence.getPendingFirstRegisterRoomGameEvent()) {
|
|
return;
|
|
}
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
_isOpeningFirstRegisterRoomGame = true;
|
|
try {
|
|
await DataPersistence.clearPendingFirstRegisterRoomGameEvent();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
await SCEntryPopupCoordinator.openFirstPartyRoomRandomGame(context);
|
|
} catch (_) {
|
|
// The entry popup is optional; failure should not block the home page.
|
|
} finally {
|
|
_isOpeningFirstRegisterRoomGame = false;
|
|
}
|
|
}
|
|
|
|
Widget _buildBottomTabIcon({
|
|
required bool active,
|
|
required String svgaPath,
|
|
required String fallbackPath,
|
|
}) {
|
|
return SCSvgaAssetWidget(
|
|
assetPath: svgaPath,
|
|
width: 35.w,
|
|
height: 35.w,
|
|
active: active,
|
|
loop: false,
|
|
fallback: Image.asset(fallbackPath, width: 35.w, height: 35.w),
|
|
);
|
|
}
|
|
|
|
Widget _buildTaskTabIcon({required bool active}) {
|
|
return ValueListenableBuilder<int>(
|
|
valueListenable: _taskClaimableCount,
|
|
builder: (_, claimableCount, __) {
|
|
final icon = _buildBottomTabIcon(
|
|
active: active,
|
|
svgaPath: "sc_images/index/sc_icon_explore_anim.svga",
|
|
fallbackPath:
|
|
active
|
|
? "sc_images/index/sc_icon_explore_en.png"
|
|
: "sc_images/index/sc_icon_explore_no.png",
|
|
);
|
|
if (claimableCount <= 0) {
|
|
return icon;
|
|
}
|
|
return Badge(
|
|
backgroundColor: Colors.red,
|
|
label: text(
|
|
claimableCount > 99 ? "99+" : "$claimableCount",
|
|
fontSize: 9.sp,
|
|
textColor: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
alignment: AlignmentDirectional.topEnd,
|
|
child: icon,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|