mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-10-06 21:53:29 +08:00
feat: 确认退出App提示
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '服务端地址',
|
||||
|
||||
Reference in New Issue
Block a user