refactor: make ReceiveTab stateful

This commit is contained in:
Tien Do Nam
2026-08-03 13:23:11 +02:00
parent 853fe4d85c
commit fdc0676e6b
2 changed files with 60 additions and 99 deletions
+60 -17
View File
@@ -1,11 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:localsend_app/gen/strings.g.dart'; import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/model/state/server/server_state.dart';
import 'package:localsend_app/pages/home_page.dart'; import 'package:localsend_app/pages/home_page.dart';
import 'package:localsend_app/pages/home_page_controller.dart'; import 'package:localsend_app/pages/home_page_controller.dart';
import 'package:localsend_app/pages/receive_history_page.dart'; import 'package:localsend_app/pages/receive_history_page.dart';
import 'package:localsend_app/pages/tabs/receive_tab_vm.dart';
import 'package:localsend_app/pages/web_share_page.dart'; import 'package:localsend_app/pages/web_share_page.dart';
import 'package:localsend_app/provider/animation_provider.dart'; import 'package:localsend_app/provider/animation_provider.dart';
import 'package:localsend_app/provider/local_ip_provider.dart';
import 'package:localsend_app/provider/network/server/server_provider.dart';
import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/util/ip_helper.dart'; import 'package:localsend_app/util/ip_helper.dart';
import 'package:localsend_app/widget/animations/initial_fade_transition.dart'; import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
import 'package:localsend_app/widget/column_list_view.dart'; import 'package:localsend_app/widget/column_list_view.dart';
@@ -13,16 +16,46 @@ import 'package:localsend_app/widget/custom_icon_button.dart';
import 'package:localsend_app/widget/local_send_logo.dart'; import 'package:localsend_app/widget/local_send_logo.dart';
import 'package:localsend_app/widget/responsive_list_view.dart'; import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:localsend_app/widget/rotating_widget.dart'; import 'package:localsend_app/widget/rotating_widget.dart';
import 'package:localsend_isolates/util/sleep.dart';
import 'package:refena_flutter/addons.dart'; import 'package:refena_flutter/addons.dart';
import 'package:refena_flutter/refena_flutter.dart'; import 'package:refena_flutter/refena_flutter.dart';
import 'package:routerino/routerino.dart'; import 'package:routerino/routerino.dart';
class ReceiveTab extends StatelessWidget { class ReceiveTab extends StatefulWidget {
const ReceiveTab(); const ReceiveTab();
@override
State<ReceiveTab> createState() => _ReceiveTabState();
}
class _ReceiveTabState extends State<ReceiveTab> {
/// Whether the advanced network info is shown
bool _showAdvanced = false;
/// Whether the history button is shown
/// This extra boolean is needed to delay the animation
bool _showHistoryButton = true;
Future<void> _toggleAdvanced() async {
if (_showAdvanced) {
setState(() => _showAdvanced = false);
await sleepAsync(200);
if (mounted) {
setState(() => _showHistoryButton = true);
}
} else {
setState(() {
_showAdvanced = true;
_showHistoryButton = false;
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final vm = context.watch(receiveTabVmProvider); final alias = context.watch(settingsProvider.select((s) => s.alias));
final serverState = context.watch(serverProvider);
final localIps = context.watch(localIpProvider.select((s) => s.localIps));
return Stack( return Stack(
children: [ children: [
@@ -47,7 +80,7 @@ class ReceiveTab extends StatelessWidget {
final activeTab = ref.watch(homePageControllerProvider.select((state) => state.currentTab)); final activeTab = ref.watch(homePageControllerProvider.select((state) => state.currentTab));
return RotatingWidget( return RotatingWidget(
duration: const Duration(seconds: 15), duration: const Duration(seconds: 15),
spinning: vm.serverState != null && animations && activeTab == HomeTab.receive, spinning: serverState != null && animations && activeTab == HomeTab.receive,
child: const LocalSendLogo(withText: false), child: const LocalSendLogo(withText: false),
); );
}, },
@@ -55,13 +88,13 @@ class ReceiveTab extends StatelessWidget {
), ),
FittedBox( FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text(vm.serverState?.alias ?? vm.aliasSettings, style: const TextStyle(fontSize: 48)), child: Text(serverState?.alias ?? alias, style: const TextStyle(fontSize: 48)),
), ),
InitialFadeTransition( InitialFadeTransition(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
delay: const Duration(milliseconds: 500), delay: const Duration(milliseconds: 500),
child: Text( child: Text(
vm.serverState == null ? t.general.offline : vm.localIps.map((ip) => '#${ip.visualId}').toSet().join(' '), serverState == null ? t.general.offline : localIps.map((ip) => '#${ip.visualId}').toSet().join(' '),
style: const TextStyle(fontSize: 24), style: const TextStyle(fontSize: 24),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -87,11 +120,15 @@ class ReceiveTab extends StatelessWidget {
), ),
), ),
), ),
_InfoBox(vm), _InfoBox(
serverState: serverState,
localIps: localIps,
showAdvanced: _showAdvanced,
),
_CornerButtons( _CornerButtons(
showAdvanced: vm.showAdvanced, showAdvanced: _showAdvanced,
showHistoryButton: vm.showHistoryButton, showHistoryButton: _showHistoryButton,
toggleAdvanced: vm.toggleAdvanced, toggleAdvanced: _toggleAdvanced,
), ),
], ],
); );
@@ -142,14 +179,20 @@ class _CornerButtons extends StatelessWidget {
} }
class _InfoBox extends StatelessWidget { class _InfoBox extends StatelessWidget {
final ReceiveTabVm vm; final ServerState? serverState;
final List<String> localIps;
final bool showAdvanced;
const _InfoBox(this.vm); const _InfoBox({
required this.serverState,
required this.localIps,
required this.showAdvanced,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedCrossFade( return AnimatedCrossFade(
crossFadeState: vm.showAdvanced ? CrossFadeState.showSecond : CrossFadeState.showFirst, crossFadeState: showAdvanced ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
firstChild: Container(), firstChild: Container(),
secondChild: Align( secondChild: Align(
@@ -172,7 +215,7 @@ class _InfoBox extends StatelessWidget {
const SizedBox(width: 10), const SizedBox(width: 10),
Padding( Padding(
padding: const EdgeInsets.only(right: 30), padding: const EdgeInsets.only(right: 30),
child: SelectableText(vm.serverState?.alias ?? '-'), child: SelectableText(serverState?.alias ?? '-'),
), ),
], ],
), ),
@@ -183,8 +226,8 @@ class _InfoBox extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (vm.localIps.isEmpty) Text(t.general.unknown), if (localIps.isEmpty) Text(t.general.unknown),
...vm.localIps.map((ip) => SelectableText(ip)), ...localIps.map((ip) => SelectableText(ip)),
], ],
), ),
], ],
@@ -193,7 +236,7 @@ class _InfoBox extends StatelessWidget {
children: [ children: [
Text(t.receiveTab.infoBox.port), Text(t.receiveTab.infoBox.port),
const SizedBox(width: 10), const SizedBox(width: 10),
SelectableText(vm.serverState?.port.toString() ?? '-'), SelectableText(serverState?.port.toString() ?? '-'),
], ],
), ),
], ],
-82
View File
@@ -1,82 +0,0 @@
import 'package:flutter/material.dart';
import 'package:localsend_app/model/state/server/server_state.dart';
import 'package:localsend_app/provider/local_ip_provider.dart';
import 'package:localsend_app/provider/network/server/server_provider.dart';
import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/widget/dialogs/quick_save_from_favorites_notice.dart';
import 'package:localsend_app/widget/dialogs/quick_save_notice.dart';
import 'package:localsend_isolates/util/sleep.dart';
import 'package:refena_flutter/refena_flutter.dart';
/// Whether the advanced network info is shown
final _showAdvancedProvider = StateProvider<bool>((ref) => false, debugLabel: '_showAdvancedProvider');
/// Whether the history button is shown
/// This extra boolean is needed to delay the animation
final _showHistoryButtonProvider = StateProvider<bool>((ref) => true, debugLabel: '_showHistoryButtonProvider');
class ReceiveTabVm {
final String aliasSettings;
final bool quickSaveSettings;
final bool quickSaveFromFavoritesSettings;
final ServerState? serverState;
final List<String> localIps;
final bool showAdvanced;
final bool showHistoryButton;
final Future<void> Function() toggleAdvanced;
final Future<void> Function(BuildContext context, bool enable) onSetQuickSave;
final Future<void> Function(BuildContext context, bool enable) onSetQuickSaveFromFavorites;
const ReceiveTabVm({
required this.aliasSettings,
required this.quickSaveSettings,
required this.quickSaveFromFavoritesSettings,
required this.serverState,
required this.localIps,
required this.showAdvanced,
required this.showHistoryButton,
required this.toggleAdvanced,
required this.onSetQuickSave,
required this.onSetQuickSaveFromFavorites,
});
}
final receiveTabVmProvider = ViewProvider((ref) {
final (alias, quickSave, quickSaveFromFavorites) = ref.watch(settingsProvider.select((s) => (s.alias, s.quickSave, s.quickSaveFromFavorites)));
final networkInfo = ref.watch(localIpProvider).localIps;
final serverState = ref.watch(serverProvider);
final showAdvanced = ref.watch(_showAdvancedProvider);
final showHistoryButton = ref.watch(_showHistoryButtonProvider);
return ReceiveTabVm(
aliasSettings: alias,
quickSaveSettings: quickSave,
quickSaveFromFavoritesSettings: quickSaveFromFavorites,
serverState: serverState,
localIps: networkInfo,
showAdvanced: showAdvanced,
showHistoryButton: showHistoryButton,
toggleAdvanced: () async {
if (showAdvanced) {
ref.notifier(_showAdvancedProvider).setState((_) => false);
await sleepAsync(200);
ref.notifier(_showHistoryButtonProvider).setState((_) => true);
} else {
ref.notifier(_showAdvancedProvider).setState((_) => true);
ref.notifier(_showHistoryButtonProvider).setState((_) => false);
}
},
onSetQuickSave: (context, enable) async {
await ref.notifier(settingsProvider).setQuickSave(enable);
if (enable && context.mounted) {
await QuickSaveNotice.open(context);
}
},
onSetQuickSaveFromFavorites: (context, enable) async {
await ref.notifier(settingsProvider).setQuickSaveFromFavorites(enable);
if (enable && context.mounted) {
await QuickSaveFromFavoritesNotice.open(context);
}
},
);
});