fix: ssh tab

This commit is contained in:
lollipopkit
2024-06-07 21:09:17 +08:00
parent a5a84c0cdd
commit b950dd2d68
3 changed files with 141 additions and 81 deletions

View File

@@ -15,37 +15,37 @@ class SSHTabPage extends StatefulWidget {
State<SSHTabPage> createState() => _SSHTabPageState(); State<SSHTabPage> createState() => _SSHTabPageState();
} }
typedef _TabMap = Map<String, ({Widget page})>;
class _SSHTabPageState extends State<SSHTabPage> class _SSHTabPageState extends State<SSHTabPage>
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
late final _tabMap = <String, ({Widget page})>{ late final _TabMap _tabMap = {
l10n.add: (page: _buildAddPage()), l10n.add: (page: _buildAddPage()),
}; };
late var _tabController = TabController( final _pageCtrl = PageController();
length: _tabMap.length, final _fabVN = 0.vn;
vsync: this, final _tabRN = RNode();
);
final _fabRN = ValueNotifier(0);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Scaffold(
appBar: TabBar( appBar: PreferredSizeListenBuilder(
controller: _tabController, listenable: _tabRN,
tabs: _tabMap.keys.map(_buildTabItem).toList(), builder: () {
isScrollable: true, return _TabBar(
tabAlignment: TabAlignment.start, idxVN: _fabVN,
dividerColor: Colors.transparent, map: _tabMap,
onTap: (value) { onTap: _onTapTab,
_fabRN.value = value; onClose: _onTapClose,
FocusScope.of(context).unfocus(); );
}, },
), ),
body: _buildBody(), body: _buildBody(),
floatingActionButton: ListenableBuilder( floatingActionButton: ListenableBuilder(
listenable: _fabRN, listenable: _fabVN,
builder: (_, __) { builder: (_, __) {
if (_fabRN.value != 0) return const SizedBox(); if (_fabVN.value != 0) return const SizedBox();
return FloatingActionButton( return FloatingActionButton(
heroTag: 'sshAddServer', heroTag: 'sshAddServer',
onPressed: () => AppRoutes.serverEdit().go(context), onPressed: () => AppRoutes.serverEdit().go(context),
@@ -57,49 +57,40 @@ class _SSHTabPageState extends State<SSHTabPage>
); );
} }
Widget _buildTabItem(String e) { void _onTapTab(int idx) async {
if (e == l10n.add) { await _toPage(idx);
return Tab(child: Text(e)); _fabVN.value = idx;
} FocusScope.of(context).unfocus();
return Tab( }
child: Row(
children: [ void _onTapClose(String name) async {
Text(e), final confirm = await showDialog<bool>(
UIs.width7, context: context,
IconBtn( builder: (context) {
icon: Icons.close, return AlertDialog(
onTap: () async { title: Text(l10n.attention),
final confirm = await showDialog<bool>( content: Text('${l10n.close} SSH ${l10n.conn}($name) ?'),
context: context, actions: [
builder: (context) { TextButton(
return AlertDialog( onPressed: () => context.pop(true),
title: Text(l10n.attention), child: Text(l10n.ok, style: UIs.textRed),
content: Text('${l10n.close} SSH ${l10n.conn}($e) ?'), ),
actions: [ TextButton(
TextButton( onPressed: () => context.pop(false),
onPressed: () => context.pop(true), child: Text(l10n.cancel),
child: Text(l10n.ok, style: UIs.textRed), ),
), ],
TextButton( );
onPressed: () => context.pop(false), },
child: Text(l10n.cancel),
),
],
);
},
);
Future.delayed(const Duration(milliseconds: 50),
FocusScope.of(context).unfocus);
if (confirm != true) {
return;
}
_tabMap.remove(e);
_refreshTabs();
},
),
],
),
); );
Future.delayed(
const Duration(milliseconds: 50), FocusScope.of(context).unfocus);
if (confirm != true) {
return;
}
_tabMap.remove(name);
_tabRN.build();
} }
Widget _buildAddPage() { Widget _buildAddPage() {
@@ -134,44 +125,113 @@ class _SSHTabPageState extends State<SSHTabPage>
} }
Widget _buildBody() { Widget _buildBody() {
return TabBarView( return ListenBuilder(
physics: const NeverScrollableScrollPhysics(), listenable: _tabRN,
controller: _tabController, builder: () {
children: _tabMap.values.map((e) => e.page).toList(), return PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: _pageCtrl,
itemCount: _tabMap.length,
itemBuilder: (_, idx) {
final name = _tabMap.keys.elementAt(idx);
return _tabMap[name]?.page ?? UIs.placeholder;
},
);
},
); );
} }
void _onTapInitCard(ServerPrivateInfo spi) { void _onTapInitCard(ServerPrivateInfo spi) async {
final name = () { final name = () {
if (_tabMap.containsKey(spi.name)) { final count = _tabMap.keys.where((e) => e.contains(spi.name)).length;
return '${spi.name}(${_tabMap.length + 1})'; if (count > 0) {
return '${spi.name}(${count + 1})';
} }
return spi.name; return spi.name;
}(); }();
_tabMap[name] = ( _tabMap[name] = (
page: SSHPage( page: SSHPage(
// Keep it, or the Flutter will works unexpectedly
key: ValueKey(spi.id),
spi: spi, spi: spi,
notFromTab: false, notFromTab: false,
onSessionEnd: () { onSessionEnd: () {
_tabMap.remove(name); _tabMap.remove(name);
_refreshTabs();
}, },
), ),
); );
_refreshTabs(); final idx = _tabMap.keys.toList().indexOf(name);
final idx = _tabMap.length - 1; _tabRN.build();
_tabController.animateTo(idx); await _toPage(idx);
_fabRN.value = idx; _fabVN.value = idx;
} }
void _refreshTabs() { Future<void> _toPage(int idx) => _pageCtrl.animateToPage(idx,
_tabController = TabController( duration: Durations.short3, curve: Curves.fastEaseInToSlowEaseOut);
length: _tabMap.length,
vsync: this,
);
setState(() {});
}
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }
final class _TabBar extends StatelessWidget implements PreferredSizeWidget {
const _TabBar({
required this.idxVN,
required this.map,
required this.onTap,
required this.onClose,
});
final ValueNotifier<int> idxVN;
final _TabMap map;
final void Function(int idx) onTap;
final void Function(String name) onClose;
List<String> get names => map.keys.toList();
@override
Size get preferredSize => const Size.fromHeight(48);
@override
Widget build(BuildContext context) {
return ListenBuilder(
listenable: idxVN,
builder: () {
return ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 5),
itemCount: names.length,
itemBuilder: (_, idx) => _buillItem(idx),
);
},
);
}
Widget _buillItem(int idx) {
final name = names[idx];
return InkWell(
borderRadius: BorderRadius.circular(13),
onTap: () => onTap(idx),
child: SizedBox(
width: idx == 0 ? 80 : 130,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: idx == 0 ? 35 : 85,
child: Text(name),
),
if (idxVN.value == idx && idx != 0) FadeIn(child: UIs.dot()),
idx == 0
// Use [IconBtn] for same size
? IconBtn(icon: Icons.add, onTap: () {})
: IconBtn(
icon: Icons.close,
onTap: () => onClose(name),
),
],
),
).paddingOnly(left: 17, right: 3),
).paddingSymmetric(horizontal: 3);
}
}

View File

@@ -401,8 +401,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.33" ref: "v1.0.39"
resolved-ref: "7c4fdde33ec7c9ee226bfe0fd0c148f2d3f3ca54" resolved-ref: "49fc10b39e390f4ecc3ee4f16f0926460b77adac"
url: "https://github.com/lppcg/fl_lib" url: "https://github.com/lppcg/fl_lib"
source: git source: git
version: "0.0.1" version: "0.0.1"

View File

@@ -53,7 +53,7 @@ dependencies:
fl_lib: fl_lib:
git: git:
url: https://github.com/lppcg/fl_lib url: https://github.com/lppcg/fl_lib
ref: v1.0.33 ref: v1.0.39
dependency_overrides: dependency_overrides:
# dartssh2: # dartssh2: