From 025ff527ee8bfc01767c5df94cb399ca3b98cf27 Mon Sep 17 00:00:00 2001 From: DMzS <75044136+Domenic-MZS@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:27:47 -0400 Subject: [PATCH] fix(ui): Support free-scroll and auto-scroll for the installer logs (#1736) (#1836) --- lib/ui/views/installer/installer_view.dart | 87 ++++++++++++------- .../views/installer/installer_viewmodel.dart | 62 +++++++++++-- 2 files changed, 109 insertions(+), 40 deletions(-) diff --git a/lib/ui/views/installer/installer_view.dart b/lib/ui/views/installer/installer_view.dart index c3175b7f..4d16b750 100644 --- a/lib/ui/views/installer/installer_view.dart +++ b/lib/ui/views/installer/installer_view.dart @@ -75,43 +75,64 @@ class InstallerView extends StatelessWidget { ), ), ), - body: CustomScrollView( - controller: model.scrollController, - slivers: [ - CustomSliverAppBar( - title: Text( - model.headerLogs, - style: GoogleFonts.inter( - color: Theme.of(context).textTheme.titleLarge!.color, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - onBackButtonPressed: () => Navigator.maybePop(context), - bottom: PreferredSize( - preferredSize: const Size(double.infinity, 1.0), - child: GradientProgressIndicator(progress: model.progress), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(20.0), - sliver: SliverList( - delegate: SliverChildListDelegate.fixed( - [ - CustomCard( - child: Text( - model.logs, - style: GoogleFonts.jetBrainsMono( - fontSize: 13, - height: 1.5, - ), + body: NotificationListener( + onNotification: model.handleAutoScrollNotification, + child: Stack( + children: [ + CustomScrollView( + key: model.logCustomScrollKey, + controller: model.scrollController, + slivers: [ + CustomSliverAppBar( + title: Text( + model.headerLogs, + style: GoogleFonts.inter( + color: + Theme.of(context).textTheme.titleLarge!.color, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + onBackButtonPressed: () => Navigator.maybePop(context), + bottom: PreferredSize( + preferredSize: const Size(double.infinity, 1.0), + child: GradientProgressIndicator( + progress: model.progress, ), ), - ], + ), + SliverPadding( + padding: const EdgeInsets.all(20.0), + sliver: SliverList( + delegate: SliverChildListDelegate.fixed( + [ + CustomCard( + child: Text( + model.logs, + style: GoogleFonts.jetBrainsMono( + fontSize: 13, + height: 1.5, + ), + ), + ), + ], + ), + ), + ), + ], + ), + Visibility( + visible: model.showAutoScrollButton, + child: Align( + alignment: const Alignment(0.9, 0.97), + child: FloatingActionButton( + onPressed: model.scrollToBottom, + child: const Icon(Icons.arrow_downward_rounded), + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 0602eec7..c806a839 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -30,6 +30,7 @@ class InstallerViewModel extends BaseViewModel { static const _installerChannel = MethodChannel( 'app.revanced.manager.flutter/installer', ); + final Key logCustomScrollKey = UniqueKey(); final ScrollController scrollController = ScrollController(); final ScreenshotCallback screenshotCallback = ScreenshotCallback(); double? progress = 0.0; @@ -44,6 +45,57 @@ class InstallerViewModel extends BaseViewModel { bool cancel = false; bool showPopupScreenshotWarning = true; + bool showAutoScrollButton = false; + bool _isAutoScrollEnabled = true; + bool _isAutoScrolling = false; + + double get getCurrentScrollPercentage { + final maxScrollExtent = scrollController.position.maxScrollExtent; + final currentPosition = scrollController.position.pixels; + + return currentPosition / maxScrollExtent; + } + + bool handleAutoScrollNotification(ScrollNotification event) { + if (_isAutoScrollEnabled && event is ScrollStartNotification) { + _isAutoScrollEnabled = _isAutoScrolling; + showAutoScrollButton = false; + notifyListeners(); + + return true; + } + + if (event is ScrollEndNotification) { + const anchorThreshold = 0.987; + + _isAutoScrollEnabled = + _isAutoScrolling || getCurrentScrollPercentage >= anchorThreshold; + + showAutoScrollButton = !_isAutoScrollEnabled && !_isAutoScrolling; + notifyListeners(); + + return true; + } + + return false; + } + + void scrollToBottom() { + _isAutoScrolling = true; + + WidgetsBinding.instance.addPostFrameCallback((_) async { + final maxScrollExtent = scrollController.position.maxScrollExtent; + + await scrollController.animateTo( + maxScrollExtent, + duration: const Duration(milliseconds: 100), + curve: Curves.fastOutSlowIn, + ); + + _isAutoScrolling = false; + }); + } + Future initialize(BuildContext context) async { isRooted = await _rootAPI.isRooted(); if (await Permission.ignoreBatteryOptimizations.isGranted) { @@ -123,13 +175,9 @@ class InstallerViewModel extends BaseViewModel { if (logs[logs.length - 1] == '\n') { logs = logs.substring(0, logs.length - 1); } - Future.delayed(const Duration(milliseconds: 100)).then((value) { - scrollController.animateTo( - scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 100), - curve: Curves.fastOutSlowIn, - ); - }); + if (_isAutoScrollEnabled) { + scrollToBottom(); + } } notifyListeners(); }