From 9a3a51f7f5cb6941d7cafc4b70ce1f2bc9734533 Mon Sep 17 00:00:00 2001 From: Sem Bauke Date: Tue, 20 Jun 2023 15:59:43 +0200 Subject: [PATCH] feat: top to article button (#998) * feat: top to article button * fix: set button to bottom right * feat: isolate back to top button --- .../news-bookmark/news_bookmark_view.dart | 20 ++++++++--- .../news_bookmark_viewmodel.dart | 30 +++++++++++++++++ .../news-tutorial/news_tutorial_view.dart | 8 ++++- .../news_tutorial_viewmodel.dart | 31 +++++++++++++++++ .../news/widgets/back_to_top_button.dart | 33 +++++++++++++++++++ 5 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 mobile-app/lib/ui/views/news/widgets/back_to_top_button.dart diff --git a/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_view.dart b/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_view.dart index 07cf80a96..2f31f0ea2 100644 --- a/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_view.dart +++ b/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:freecodecamp/models/news/bookmarked_tutorial_model.dart'; import 'package:freecodecamp/ui/views/news/news-tutorial/news_tutorial_viewmodel.dart'; import 'package:freecodecamp/ui/views/news/news-bookmark/news_bookmark_viewmodel.dart'; +import 'package:freecodecamp/ui/views/news/widgets/back_to_top_button.dart'; import 'package:stacked/stacked.dart'; class NewsBookmarkTutorialView extends StatelessWidget { @@ -19,12 +20,14 @@ class NewsBookmarkTutorialView extends StatelessWidget { onViewModelReady: (model) async { await model.initDB(); model.isTutorialBookmarked(tutorial); + model.goToTopButtonHandler(); }, onDispose: (model) => model.updateListView(), builder: (context, model, child) => Scaffold( backgroundColor: const Color(0xFF0a0a23), body: SafeArea( child: CustomScrollView( + controller: model.scrollController, slivers: [ const SliverAppBar( title: Text('BOOKMARKED TUTORIAL'), @@ -61,13 +64,22 @@ class NewsBookmarkTutorialView extends StatelessWidget { ], ), ), + floatingActionButton: model.gotoTopButtonVisible + ? BackToTopButton( + onPressed: () => model.goToTop(), + ) + : null, + floatingActionButtonAnimator: null, ), ); } - SliverList lazyLoadHtml(BuildContext context, BookmarkedTutorial tutorial) { - NewsTutorialViewModel model = NewsTutorialViewModel(); - var htmlToList = model.initLazyLoading( + SliverList lazyLoadHtml( + BuildContext context, + BookmarkedTutorial tutorial, + ) { + NewsTutorialViewModel localModel = NewsTutorialViewModel(); + var htmlToList = localModel.initLazyLoading( tutorial.tutorialText, context, tutorial, @@ -79,7 +91,7 @@ class NewsBookmarkTutorialView extends StatelessWidget { children: [ Expanded( child: htmlToList[index], - ) + ), ], ); }), childCount: htmlToList.length), diff --git a/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_viewmodel.dart b/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_viewmodel.dart index 0de0177b1..8cc20f514 100644 --- a/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_viewmodel.dart +++ b/mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_viewmodel.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/app/app.router.dart'; import 'package:freecodecamp/models/news/bookmarked_tutorial_model.dart'; @@ -9,6 +10,9 @@ class NewsBookmarkViewModel extends BaseViewModel { bool _isBookmarked = false; bool get bookmarked => _isBookmarked; + bool _gotoTopButtonVisible = false; + bool get gotoTopButtonVisible => _gotoTopButtonVisible; + bool _userHasBookmarkedTutorials = false; bool get userHasBookmarkedTutorials => _userHasBookmarkedTutorials; @@ -21,6 +25,8 @@ class NewsBookmarkViewModel extends BaseViewModel { final _navigationService = locator(); final _databaseService = locator(); + ScrollController scrollController = ScrollController(); + Future initDB() async { await _databaseService.initialise(); } @@ -48,6 +54,30 @@ class NewsBookmarkViewModel extends BaseViewModel { await hasBookmarkedTutorials(); } + Future goToTop() async { + await scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + Future goToTopButtonHandler() async { + scrollController.addListener(() { + if (scrollController.offset >= 100) { + if (!_gotoTopButtonVisible) { + _gotoTopButtonVisible = true; + notifyListeners(); + } + } else { + if (_gotoTopButtonVisible) { + _gotoTopButtonVisible = false; + notifyListeners(); + } + } + }); + } + Future insertTutorial(dynamic tutorial) async { bool isInDatabase = await _databaseService.isBookmarked(tutorial); diff --git a/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_view.dart b/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_view.dart index 4a423c62e..687b934ea 100644 --- a/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_view.dart +++ b/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:freecodecamp/models/news/tutorial_model.dart'; import 'package:freecodecamp/ui/views/news/news-bookmark/news_bookmark_widget.dart'; +import 'package:freecodecamp/ui/views/news/widgets/back_to_top_button.dart'; import 'package:share_plus/share_plus.dart'; import 'package:stacked/stacked.dart'; import 'news_tutorial_viewmodel.dart'; @@ -89,7 +90,7 @@ class NewsTutorialView extends StatelessWidget { child: Stack( children: [ lazyLoadHtml(tutorial!.text!, context, tutorial, model), - bottomButtons(tutorial, model) + bottomButtons(tutorial, model), ], ), ) @@ -104,6 +105,11 @@ class NewsTutorialView extends StatelessWidget { ); }, ), + floatingActionButton: model.showToTopButton + ? BackToTopButton( + onPressed: () => model.goToTop(), + ) + : null, ), viewModelBuilder: () => NewsTutorialViewModel(), ); diff --git a/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_viewmodel.dart b/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_viewmodel.dart index 1691610c6..3d19f2138 100644 --- a/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_viewmodel.dart +++ b/mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_viewmodel.dart @@ -29,6 +29,14 @@ class NewsTutorialViewModel extends BaseViewModel { final ScrollController _bottomButtonController = ScrollController(); ScrollController get bottomButtonController => _bottomButtonController; + bool _showToTopButton = false; + bool get showToTopButton => _showToTopButton; + + set showToTopButton(bool value) { + _showToTopButton = value; + notifyListeners(); + } + Future readFromFiles() async { String json = await rootBundle.loadString( 'assets/test_data/news_post.json', @@ -41,6 +49,7 @@ class NewsTutorialViewModel extends BaseViewModel { Future initState(id) async { handleBottomButtonAnimation(); + handleToTopButton(); if (await _developerService.developmentMode()) { return readFromFiles(); @@ -49,6 +58,28 @@ class NewsTutorialViewModel extends BaseViewModel { } } + Future handleToTopButton() async { + _scrollController.addListener(() { + if (scrollController.offset >= 100) { + if (!showToTopButton) { + showToTopButton = true; + } + } else { + if (showToTopButton) { + showToTopButton = false; + } + } + }); + } + + Future goToTop() async { + _scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } + Future handleBottomButtonAnimation() async { _scrollController.addListener(() async { SharedPreferences prefs = await SharedPreferences.getInstance(); diff --git a/mobile-app/lib/ui/views/news/widgets/back_to_top_button.dart b/mobile-app/lib/ui/views/news/widgets/back_to_top_button.dart new file mode 100644 index 000000000..52f3c1717 --- /dev/null +++ b/mobile-app/lib/ui/views/news/widgets/back_to_top_button.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class BackToTopButton extends StatelessWidget { + final VoidCallback onPressed; + + const BackToTopButton({Key? key, required this.onPressed}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 128), + child: FloatingActionButton( + onPressed: onPressed, + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Colors.white, + ), + borderRadius: BorderRadius.circular(100), + ), + backgroundColor: const Color.fromRGBO(0x2A, 0x2A, 0x40, 1), + child: const Icon( + Icons.keyboard_arrow_up, + size: 40, + color: Colors.white, + ), + ), + ), + ); + } +}