feat: auto hide fab

This commit is contained in:
lollipopkit
2024-05-09 12:31:59 +08:00
parent f70449d67d
commit faacbe088b
8 changed files with 186 additions and 87 deletions

View File

@@ -45,16 +45,16 @@ Future<SSHClient> genClient(
ServerPrivateInfo spi, { ServerPrivateInfo spi, {
void Function(GenSSHClientStatus)? onStatus, void Function(GenSSHClientStatus)? onStatus,
/// Must pass this param when use multi-thread and key login /// Only pass this param if using multi-threading and key login
String? privateKey, String? privateKey,
/// Must pass this param when use multi-thread and key login /// Only pass this param if using multi-threading and key login
String? jumpPrivateKey, String? jumpPrivateKey,
Duration timeout = const Duration(seconds: 5), Duration timeout = const Duration(seconds: 5),
/// [ServerPrivateInfo] of the jump server /// [ServerPrivateInfo] of the jump server
/// ///
/// Must pass this param when use multi-thread and key login /// Must pass this param if using multi-threading and key login
ServerPrivateInfo? jumpSpi, ServerPrivateInfo? jumpSpi,
/// Handle keyboard-interactive authentication /// Handle keyboard-interactive authentication
@@ -113,8 +113,8 @@ Future<SSHClient> genClient(
username: spi.user, username: spi.user,
onPasswordRequest: () => spi.pwd, onPasswordRequest: () => spi.pwd,
onUserInfoRequest: onKeyboardInteractive, onUserInfoRequest: onKeyboardInteractive,
printDebug: debugPrint, // printDebug: debugPrint,
printTrace: debugPrint, // printTrace: debugPrint,
); );
} }
privateKey ??= getPrivateKey(keyId); privateKey ??= getPrivateKey(keyId);
@@ -126,7 +126,7 @@ Future<SSHClient> genClient(
// Must use [compute] here, instead of [Computer.shared.start] // Must use [compute] here, instead of [Computer.shared.start]
identities: await compute(loadIndentity, privateKey), identities: await compute(loadIndentity, privateKey),
onUserInfoRequest: onKeyboardInteractive, onUserInfoRequest: onKeyboardInteractive,
printDebug: debugPrint, // printDebug: debugPrint,
printTrace: debugPrint, // printTrace: debugPrint,
); );
} }

View File

@@ -6,7 +6,7 @@ import 'package:dartssh2/dartssh2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/core/extension/ssh_client.dart'; import 'package:toolbox/core/extension/ssh_client.dart';
import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/extension/stringx.dart';
import 'package:toolbox/core/utils/auth.dart'; import 'package:toolbox/core/utils/ssh_auth.dart';
import 'package:toolbox/core/utils/platform/path.dart'; import 'package:toolbox/core/utils/platform/path.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/server/system.dart'; import 'package:toolbox/data/model/server/system.dart';

View File

@@ -127,22 +127,37 @@ class _HomePageState extends State<HomePage>
super.build(context); super.build(context);
Pros.app.ctx = context; Pros.app.ctx = context;
return Scaffold( final appBar = widget.landscape
drawer: _buildDrawer(),
appBar: widget.landscape
? null ? null
: _AppBar( : _AppBar(
selectIndex: _selectIndex, selectIndex: _selectIndex,
centerTitle: false, centerTitle: false,
title: const Text(BuildData.name), title: const Text(BuildData.name),
actions: <Widget>[ actions: <Widget>[
ValBuilder(
listenable:
Stores.setting.serverStatusUpdateInterval.listenable(),
builder: (interval) {
if (interval != 0) return UIs.placeholder;
return IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Refresh',
onPressed: () async {
await Pros.server.refresh();
},
);
},
),
IconButton( IconButton(
icon: const Icon(Icons.developer_mode, size: 21), icon: const Icon(Icons.developer_mode, size: 21),
tooltip: l10n.debug, tooltip: l10n.debug,
onPressed: () => AppRoute.debug().go(context), onPressed: () => AppRoute.debug().go(context),
), ),
], ],
), );
return Scaffold(
drawer: _buildDrawer(),
appBar: appBar,
body: PageView.builder( body: PageView.builder(
controller: _pageController, controller: _pageController,
itemCount: AppTab.values.length, itemCount: AppTab.values.length,

View File

@@ -12,13 +12,13 @@ import 'package:toolbox/core/extension/listx.dart';
import 'package:toolbox/core/extension/media_queryx.dart'; import 'package:toolbox/core/extension/media_queryx.dart';
import 'package:toolbox/core/extension/numx.dart'; import 'package:toolbox/core/extension/numx.dart';
import 'package:toolbox/core/extension/ssh_client.dart'; import 'package:toolbox/core/extension/ssh_client.dart';
import 'package:toolbox/core/utils/platform/base.dart';
import 'package:toolbox/core/utils/share.dart'; import 'package:toolbox/core/utils/share.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/server/try_limiter.dart'; import 'package:toolbox/data/model/server/try_limiter.dart';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:toolbox/data/res/store.dart';
import 'package:toolbox/view/widget/auto_hide_fab.dart';
import 'package:toolbox/view/widget/percent_circle.dart'; import 'package:toolbox/view/widget/percent_circle.dart';
import '../../../core/route.dart'; import '../../../core/route.dart';
@@ -53,6 +53,8 @@ class _ServerPageState extends State<ServerPage>
String? _tag; String? _tag;
bool _useDoubleColumn = false; bool _useDoubleColumn = false;
final _scrollController = ScrollController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -99,12 +101,15 @@ class _ServerPageState extends State<ServerPage>
return _buildBody(); return _buildBody();
}, },
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: AutoHideFab(
controller: _scrollController,
child: FloatingActionButton(
heroTag: 'addServer', heroTag: 'addServer',
onPressed: () => AppRoute.serverEdit().go(context), onPressed: () => AppRoute.serverEdit().go(context),
tooltip: l10n.addAServer, tooltip: l10n.addAServer,
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),
),
); );
} }
@@ -201,13 +206,7 @@ class _ServerPageState extends State<ServerPage>
}, },
); );
// Desktop doesn't support pull to refresh return child;
if (isDesktop) return child;
return RefreshIndicator(
onRefresh: () async => await Pros.server.refresh(onlyFailed: true),
child: child,
);
} }
TagSwitcher _buildTagsSwitcher(ServerProvider provider) { TagSwitcher _buildTagsSwitcher(ServerProvider provider) {
@@ -228,6 +227,7 @@ class _ServerPageState extends State<ServerPage>
}) { }) {
final count = filtered.length + 1; final count = filtered.length + 1;
return ListView.builder( return ListView.builder(
controller: _scrollController,
padding: padding, padding: padding,
itemCount: count, itemCount: count,
itemBuilder: (_, index) { itemBuilder: (_, index) {
@@ -461,21 +461,22 @@ class _ServerPageState extends State<ServerPage>
} }
Widget _buildTopRightWidget(Server s) { Widget _buildTopRightWidget(Server s) {
Widget rightCorner = UIs.placeholder; return switch (s.state) {
if (s.state == ServerState.connecting) { ServerState.connecting ||
rightCorner = Padding( ServerState.loading ||
ServerState.connected =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 7), padding: const EdgeInsets.symmetric(horizontal: 7),
child: SizedBox( child: SizedBox(
width: 21, width: 19,
height: 21, height: 19,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(primaryColor), valueColor: AlwaysStoppedAnimation(primaryColor),
), ),
), ),
); ),
} else if (s.state == ServerState.failed) { ServerState.failed => InkWell(
rightCorner = InkWell(
onTap: () { onTap: () {
TryLimiter.reset(s.spi.id); TryLimiter.reset(s.spi.id);
Pros.server.refresh(spi: s.spi); Pros.server.refresh(spi: s.spi);
@@ -488,10 +489,8 @@ class _ServerPageState extends State<ServerPage>
color: Colors.grey, color: Colors.grey,
), ),
), ),
); ),
} else if (!(s.spi.autoConnect ?? true) && ServerState.disconnected when !(s.spi.autoConnect ?? true) => InkWell(
s.state == ServerState.disconnected) {
rightCorner = InkWell(
onTap: () => Pros.server.refresh(spi: s.spi), onTap: () => Pros.server.refresh(spi: s.spi),
child: const Padding( child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 7), padding: EdgeInsets.symmetric(horizontal: 7),
@@ -501,28 +500,26 @@ class _ServerPageState extends State<ServerPage>
color: Colors.grey, color: Colors.grey,
), ),
), ),
); ),
} else if (Stores.setting.serverTabUseOldUI.fetch()) { _ when Stores.setting.serverTabUseOldUI.fetch() =>
rightCorner = ServerFuncBtnsTopRight(spi: s.spi); ServerFuncBtnsTopRight(spi: s.spi),
} _ => UIs.placeholder,
return rightCorner; };
} }
Widget _buildTopRightText(Server s) { Widget _buildTopRightText(Server s) {
if (s.state == ServerState.failed && s.status.err != null) { final hasErr = s.state == ServerState.failed && s.status.err != null;
return GestureDetector( return GestureDetector(
onTap: () => _showFailReason(s.status), onTap: () {
if (!hasErr) return;
_showFailReason(s.status);
},
child: Text( child: Text(
l10n.viewErr, hasErr ? l10n.viewErr : s.getTopRightStr(s.spi),
style: UIs.text13Grey, style: UIs.text13Grey,
), ),
); );
} }
return Text(
s.getTopRightStr(s.spi),
style: UIs.text13Grey,
);
}
void _showFailReason(ServerStatus ss) { void _showFailReason(ServerStatus ss) {
context.showRoundDialog( context.showRoundDialog(

View File

@@ -9,7 +9,7 @@ import 'package:provider/provider.dart';
import 'package:toolbox/core/extension/context/common.dart'; import 'package:toolbox/core/extension/context/common.dart';
import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/dialog.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/core/utils/auth.dart'; import 'package:toolbox/core/utils/ssh_auth.dart';
import 'package:toolbox/core/utils/platform/base.dart'; import 'package:toolbox/core/utils/platform/base.dart';
import 'package:toolbox/core/utils/server.dart'; import 'package:toolbox/core/utils/server.dart';
import 'package:toolbox/core/utils/share.dart'; import 'package:toolbox/core/utils/share.dart';

View File

@@ -5,6 +5,7 @@ import 'package:toolbox/data/res/store.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
/// System status bar height
static double? barHeight; static double? barHeight;
static bool drawTitlebar = false; static bool drawTitlebar = false;

View File

@@ -0,0 +1,86 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
final class AutoHideFab extends StatefulWidget {
final Widget child;
final ScrollController controller;
const AutoHideFab({
super.key,
required this.child,
required this.controller,
});
@override
State<AutoHideFab> createState() => _AutoHideFabState();
}
final class _AutoHideFabState extends State<AutoHideFab> {
bool _visible = true;
bool _isScrolling = false;
Timer? _timer;
@override
void initState() {
super.initState();
widget.controller.addListener(_scrollListener);
}
@override
void dispose() {
widget.controller.removeListener(_scrollListener);
_timer?.cancel();
_timer = null;
super.dispose();
}
void _setupTimer() {
if (_timer != null) {
_timer!.cancel();
_timer = null;
}
_timer = Timer.periodic(const Duration(seconds: 3), (_) {
if (_isScrolling) return;
if (!_visible) return;
if (widget.controller.position.maxScrollExtent <= 0) return;
setState(() {
_visible = false;
});
});
}
void _scrollListener() {
if (_isScrolling) return;
_isScrolling = true;
if (widget.controller.position.userScrollDirection ==
ScrollDirection.reverse) {
if (_visible) {
setState(() {
_visible = false;
});
_timer?.cancel();
_timer = null;
}
} else {
if (!_visible) {
setState(() {
_visible = true;
});
_setupTimer();
}
}
_isScrolling = false;
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Durations.medium1,
curve: Curves.easeInOutCubic,
transform: Matrix4.translationValues(_visible ? 0.0 : 55, 0.0, 0.0),
child: widget.child,
);
}
}