Skip to content

Commit

Permalink
feat: add current-tab-press-to-scroll-top feature (#162)
Browse files Browse the repository at this point in the history
from the
[task](https://www.notion.so/leftclick/Fix-feed-85f2d89ba34540fd96dcc6e556cbc00f)
> if scrolled, when pressing again on home screen on feed, it should
scroll back up

if we have some inner page opened and pressing the same tab -> open the
root tab page
if the root tab page is opened and the scroll is not on top -> scroll it
to the top


https://github.com/user-attachments/assets/778f9298-83bd-434e-9aad-86904d902eda
  • Loading branch information
ice-orion authored Aug 28, 2024
1 parent 5425726 commit c325fdc
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 14 deletions.
3 changes: 3 additions & 0 deletions lib/app/features/dapps/views/pages/dapps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:ice/app/features/dapps/views/components/categories/categories.da
import 'package:ice/app/features/dapps/views/components/favourites/favourites.dart';
import 'package:ice/app/features/dapps/views/components/wallet_header/wallet_header.dart';
import 'package:ice/app/features/dapps/views/pages/mocks/mocked_apps.dart';
import 'package:ice/app/hooks/use_scroll_top_on_tab_press.dart';
import 'package:ice/app/router/app_routes.dart';
import 'package:ice/app/router/components/navigation_app_bar/collapsing_app_bar.dart';

Expand All @@ -19,6 +20,8 @@ class DAppsPage extends HookWidget {
Widget build(BuildContext context) {
final scrollController = useScrollController();

useScrollTopOnTabPress(context, scrollController: scrollController);

return Scaffold(
body: CustomScrollView(
controller: scrollController,
Expand Down
4 changes: 4 additions & 0 deletions lib/app/features/feed/views/pages/feed_page/feed_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ice/app/components/screen_offset/screen_top_offset.dart';
import 'package:ice/app/components/scroll_view/load_more_builder.dart';
import 'package:ice/app/components/scroll_view/pull_to_refresh_builder.dart';
import 'package:ice/app/extensions/extensions.dart';
import 'package:ice/app/extensions/num.dart';
import 'package:ice/app/features/feed/model/feed_category.dart';
import 'package:ice/app/features/feed/providers/feed_current_category_provider.dart';
Expand All @@ -14,6 +15,7 @@ import 'package:ice/app/features/feed/views/pages/feed_page/components/feed_post
import 'package:ice/app/features/feed/views/pages/feed_page/components/stories/stories.dart';
import 'package:ice/app/features/feed/views/pages/feed_page/components/trending_videos/trending_videos.dart';
import 'package:ice/app/features/user/pages/pull_right_menu_page/pull_right_menu_handler.dart';
import 'package:ice/app/hooks/use_scroll_top_on_tab_press.dart';
import 'package:ice/app/router/components/navigation_app_bar/collapsing_app_bar.dart';

class FeedPage extends HookConsumerWidget {
Expand All @@ -24,6 +26,8 @@ class FeedPage extends HookConsumerWidget {
final scrollController = useScrollController();
final feedCategory = ref.watch(feedCurrentCategoryProvider);

useScrollTopOnTabPress(context, scrollController: scrollController);

final appBarSliver = CollapsingAppBar(
height: FeedControls.height,
child: FeedControls(pageScrollController: scrollController),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'package:ice/app/features/wallet/views/pages/wallet_page/components/nfts/
import 'package:ice/app/features/wallet/views/pages/wallet_page/components/tabs/tabs_header.dart';
import 'package:ice/app/features/wallet/views/pages/wallet_page/tab_type.dart';
import 'package:ice/app/hooks/use_on_init.dart';
import 'package:ice/app/hooks/use_scroll_top_on_tab_press.dart';
import 'package:ice/app/router/app_routes.dart';
import 'package:ice/app/router/components/navigation_app_bar/collapsing_app_bar.dart';

Expand All @@ -40,6 +41,8 @@ class WalletPage extends HookConsumerWidget {
hasContactsPermission,
]);

useScrollTopOnTabPress(context, scrollController: scrollController);

final activeTab = useState<WalletTabType>(WalletTabType.coins);

return Scaffold(
Expand Down
27 changes: 27 additions & 0 deletions lib/app/hooks/use_scroll_top_on_tab_press.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:ice/app/extensions/extensions.dart';
import 'package:ice/app/router/main_tabs/components/main_tab_navigation_container.dart';

void useScrollTopOnTabPress(BuildContext context, {required ScrollController scrollController}) {
final tabPressStream = MainTabNavigationContainer.of(context).tabPressStream;

useEffect(() {
final listener = tabPressStream.listen((tabPressData) {
// taking the GoRouterState here instead of out of listener,
// because otherwise it is considered the same even if the screen is changed
final routerState = GoRouterState.of(context);
if (
// if we pressed the same tab we're currently on
tabPressData.current == tabPressData.pressed &&
// if we pressed the tab that our page belongs to
tabPressData.pressed == routerState.currentTab &&
// if we're on the root tab page
routerState.topRoute?.path == routerState.fullPath) {
scrollController.animateTo(0, duration: Duration(milliseconds: 500), curve: Curves.easeOut);
}
});
return () => listener.cancel();
}, [tabPressStream]);
}
1 change: 1 addition & 0 deletions lib/app/router/main_tabs/components/components.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'main_tab_button.dart';
export 'tab_icon.dart';
export 'tab_item.dart';
export 'main_tab_navigation_container.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:ice/app/router/main_tabs/components/components.dart';

typedef TabPressSteamData = ({TabItem current, TabItem pressed});

class MainTabNavigationContainer extends InheritedWidget {
const MainTabNavigationContainer({
super.key,
required super.child,
required this.tabPressStream,
});

final Stream<TabPressSteamData> tabPressStream;

static MainTabNavigationContainer of(BuildContext context) {
final MainTabNavigationContainer? result =
context.dependOnInheritedWidgetOfExactType<MainTabNavigationContainer>();
assert(result != null, 'No MainTabNavigationContainer found in context');
return result!;
}

@override
bool updateShouldNotify(MainTabNavigationContainer oldWidget) =>
oldWidget.tabPressStream != tabPressStream;
}
39 changes: 25 additions & 14 deletions lib/app/router/main_tabs/main_tab_navigation.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:ice/app/extensions/extensions.dart';
import 'package:ice/app/router/main_tabs/components/components.dart';

class MainTabNavigation extends StatelessWidget {
class MainTabNavigation extends HookWidget {
const MainTabNavigation({
required this.shell,
required this.state,
Expand All @@ -16,9 +19,13 @@ class MainTabNavigation extends StatelessWidget {
@override
Widget build(BuildContext context) {
final currentTab = TabItem.fromNavigationIndex(shell.currentIndex);
final tabPressStreamController = useStreamController<TabPressSteamData>();

return Scaffold(
body: shell,
body: MainTabNavigationContainer(
child: shell,
tabPressStream: tabPressStreamController.stream,
),
bottomNavigationBar: Container(
decoration: state.isMainModalOpen
? null
Expand All @@ -35,7 +42,7 @@ class MainTabNavigation extends StatelessWidget {
showSelectedLabels: false,
showUnselectedLabels: false,
currentIndex: shell.currentIndex,
onTap: (index) => _onTabSelected(context, index, currentTab),
onTap: (index) => _onTabPress(context, index, currentTab, tabPressStreamController),
items: TabItem.values.map((tabItem) {
return BottomNavigationBarItem(
icon: tabItem == TabItem.main
Expand All @@ -52,23 +59,27 @@ class MainTabNavigation extends StatelessWidget {
);
}

void _onTabSelected(BuildContext context, int index, TabItem currentTab) {
final tabItem = TabItem.values[index];
if (tabItem == TabItem.main) {
void _onTabPress(
BuildContext context,
int index,
TabItem currentTab,
StreamController<TabPressSteamData> tabPressStream,
) {
final pressedTab = TabItem.values[index];
tabPressStream.add((current: currentTab, pressed: pressedTab));
if (pressedTab == TabItem.main) {
_handleMainButtonTap(context, currentTab);
} else if (currentTab != tabItem) {
_navigateToTab(context, tabItem);
} else {
_navigateToTab(context, pressedTab, initialLocation: currentTab == pressedTab);
}
}

void _handleMainButtonTap(BuildContext context, TabItem currentTab) => context.go(
state.isMainModalOpen ? currentTab.baseRouteLocation : currentTab.mainModalLocation,
);

void _navigateToTab(BuildContext context, TabItem tabItem) => state.isMainModalOpen
? context.go(tabItem.baseRouteLocation)
: shell.goBranch(
tabItem.navigationIndex,
initialLocation: true,
);
void _navigateToTab(BuildContext context, TabItem tabItem, {required bool initialLocation}) =>
state.isMainModalOpen
? context.go(tabItem.baseRouteLocation)
: shell.goBranch(tabItem.navigationIndex, initialLocation: initialLocation);
}

0 comments on commit c325fdc

Please sign in to comment.