feat: 确认退出App提示

This commit is contained in:
chaos-zhu
2026-05-24 17:56:36 +08:00
parent 27cb2eb814
commit 66d2563666
4 changed files with 98 additions and 28 deletions
+16 -6
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../core/api/api_result.dart';
import '../../core/ui/palette.dart';
@@ -721,7 +722,10 @@ class _ServerCard extends StatelessWidget {
children: [
Row(
children: [
_ServerOsIcon(enabled: server.canConnect),
_ServerOsIcon(
enabled: server.canConnect,
isWindows: server.isWindows,
),
const SizedBox(width: 12),
Expanded(
child: InkWell(
@@ -939,12 +943,14 @@ class _IconPillButton extends StatelessWidget {
}
class _ServerOsIcon extends StatelessWidget {
const _ServerOsIcon({required this.enabled});
const _ServerOsIcon({required this.enabled, required this.isWindows});
final bool enabled;
final bool isWindows;
@override
Widget build(BuildContext context) {
final asset = isWindows ? 'assets/windows.svg' : 'assets/linux.svg';
return Container(
width: 40,
height: 40,
@@ -953,10 +959,14 @@ class _ServerOsIcon extends StatelessWidget {
color: enabled ? AppPalette.banner : AppPalette.chip,
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.monitor_outlined,
size: 22,
color: enabled ? AppPalette.primary : AppPalette.muted,
child: Opacity(
opacity: enabled ? 1 : 0.45,
child: SvgPicture.asset(
asset,
width: 22,
height: 22,
fit: BoxFit.contain,
),
),
);
}
+76 -22
View File
@@ -1,14 +1,17 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/ui/palette.dart';
import '../../l10n/app_localizations.dart';
import '../../state/auth_notifier.dart';
import '../../state/terminal_providers.dart';
import '../servers/servers_tab.dart';
import 'scripts_tab.dart';
import 'settings_tab.dart';
import 'sftp_session_manager.dart';
import 'sftp_tab.dart';
/// Top-level shell shown after login. Hosts the four bottom-nav tabs the
@@ -39,34 +42,85 @@ class _MainShellPageState extends ConsumerState<MainShellPage> {
ref.listen(authProvider, (_, _) {});
final l = AppLocalizations.of(context);
return Scaffold(
backgroundColor: AppPalette.canvas,
body: SafeArea(
child: IndexedStack(index: _index, children: _tabs),
final sftpManager = ref.watch(sftpSessionManagerProvider);
return AnimatedBuilder(
animation: sftpManager,
builder: (context, child) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
if (_canGoUpInSftp(sftpManager)) {
sftpManager.goParent();
} else {
_confirmExit(context);
}
},
child: child!,
);
},
child: Scaffold(
backgroundColor: AppPalette.canvas,
body: SafeArea(
child: IndexedStack(index: _index, children: _tabs),
),
bottomNavigationBar: _WarmBottomBar(
selectedIndex: _index,
onSelected: (i) => setState(() => _index = i),
items: [
_WarmBottomBarItem(
icon: Icons.monitor_outlined,
label: l.tr('tabs.servers'),
),
_WarmBottomBarItem(
icon: Icons.folder_outlined,
label: l.tr('tabs.sftp'),
),
_WarmBottomBarItem(
icon: Icons.article_outlined,
label: l.tr('tabs.scripts'),
),
_WarmBottomBarItem(
icon: Icons.settings_outlined,
label: l.tr('tabs.settings'),
),
],
),
),
bottomNavigationBar: _WarmBottomBar(
selectedIndex: _index,
onSelected: (i) => setState(() => _index = i),
items: [
_WarmBottomBarItem(
icon: Icons.monitor_outlined,
label: l.tr('tabs.servers'),
);
}
bool _canGoUpInSftp(SftpSessionManager manager) {
if (_index != 1) return false;
final session = manager.activeSession;
if (session == null) return false;
if (session.status != SftpConnectionStatus.connected) return false;
final path = session.currentPath;
return path.isNotEmpty && path != '/' && path != '~';
}
Future<void> _confirmExit(BuildContext context) async {
final l = AppLocalizations.of(context);
final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(l.tr('common.exitAppTitle')),
content: Text(l.tr('common.exitAppBody')),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: Text(l.tr('common.cancel')),
),
_WarmBottomBarItem(
icon: Icons.folder_outlined,
label: l.tr('tabs.sftp'),
),
_WarmBottomBarItem(
icon: Icons.article_outlined,
label: l.tr('tabs.scripts'),
),
_WarmBottomBarItem(
icon: Icons.settings_outlined,
label: l.tr('tabs.settings'),
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
child: Text(l.tr('common.exitApp')),
),
],
),
);
if (confirmed == true) {
await SystemNavigator.pop();
}
}
}
+3
View File
@@ -21,6 +21,9 @@ const Map<String, String> stringsEn = {
'common.system': 'System default',
'common.delete': 'Delete',
'common.optional': 'Optional',
'common.exitApp': 'Exit',
'common.exitAppTitle': 'Exit app?',
'common.exitAppBody': 'Are you sure you want to exit the app?',
// Login page
'login.serverAddress': 'Server address',
+3
View File
@@ -20,6 +20,9 @@ const Map<String, String> stringsZh = {
'common.system': '跟随系统',
'common.delete': '删除',
'common.optional': '可选',
'common.exitApp': '退出',
'common.exitAppTitle': '退出 App?',
'common.exitAppBody': '确认退出 App?',
// Login
'login.serverAddress': '服务端地址',