From ecc100ac45ff496460192d763730ad58a187d08f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 6 Sep 2025 13:52:20 +0800 Subject: [PATCH] :sparkles: Extended refresh indicator (keyboard based) --- lib/screens/creators/poll/poll_list.dart | 3 +- .../creators/webfeed/webfeed_list.dart | 13 +++- lib/screens/developers/bots.dart | 3 +- lib/screens/explore.dart | 3 +- lib/screens/realm/realms.dart | 3 +- lib/widgets/account/account_devices.dart | 3 +- lib/widgets/extended_refresh_indicator.dart | 67 +++++++++++++++++++ lib/widgets/stickers/picker.dart | 3 +- 8 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 lib/widgets/extended_refresh_indicator.dart diff --git a/lib/screens/creators/poll/poll_list.dart b/lib/screens/creators/poll/poll_list.dart index 732ed010..cb77fbb8 100644 --- a/lib/screens/creators/poll/poll_list.dart +++ b/lib/screens/creators/poll/poll_list.dart @@ -9,6 +9,7 @@ import 'package:island/widgets/poll/poll_feedback.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; part 'poll_list.g.dart'; @@ -86,7 +87,7 @@ class CreatorPollListScreen extends HookConsumerWidget { onPressed: () => _createPoll(context), child: const Icon(Icons.add), ), - body: RefreshIndicator( + body: ExtendedRefreshIndicator( onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future), child: CustomScrollView( slivers: [ diff --git a/lib/screens/creators/webfeed/webfeed_list.dart b/lib/screens/creators/webfeed/webfeed_list.dart index f4b0c572..a9b04d1b 100644 --- a/lib/screens/creators/webfeed/webfeed_list.dart +++ b/lib/screens/creators/webfeed/webfeed_list.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:island/pods/webfeed.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/empty_state.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; import 'package:material_symbols_icons/symbols.dart'; class WebFeedListScreen extends ConsumerWidget { @@ -20,7 +21,10 @@ class WebFeedListScreen extends ConsumerWidget { floatingActionButton: FloatingActionButton( child: const Icon(Symbols.add), onPressed: () { - context.pushNamed('creatorFeedNew', pathParameters: {'name': pubName}); + context.pushNamed( + 'creatorFeedNew', + pathParameters: {'name': pubName}, + ); }, ), body: feedsAsync.when( @@ -32,7 +36,7 @@ class WebFeedListScreen extends ConsumerWidget { description: 'Add a new web feed to get started', ); } - return RefreshIndicator( + return ExtendedRefreshIndicator( onRefresh: () => ref.refresh(webFeedListProvider(pubName).future), child: ListView.builder( padding: EdgeInsets.only(top: 8), @@ -62,7 +66,10 @@ class WebFeedListScreen extends ConsumerWidget { ), trailing: const Icon(Symbols.chevron_right), onTap: () { - context.pushNamed('creatorFeedEdit', pathParameters: {'name': pubName, 'feedId': feed.id}); + context.pushNamed( + 'creatorFeedEdit', + pathParameters: {'name': pubName, 'feedId': feed.id}, + ); }, ), ); diff --git a/lib/screens/developers/bots.dart b/lib/screens/developers/bots.dart index 5c995317..dbc3ff54 100644 --- a/lib/screens/developers/bots.dart +++ b/lib/screens/developers/bots.dart @@ -9,6 +9,7 @@ import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; part 'bots.g.dart'; @@ -60,7 +61,7 @@ class BotsScreen extends HookConsumerWidget { ), ); } - return RefreshIndicator( + return ExtendedRefreshIndicator( onRefresh: () => ref.refresh(botsProvider(publisherName, projectId).future), child: ListView.builder( diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index e4dafb6f..2900e180 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -27,6 +27,7 @@ import 'package:island/pods/network.dart'; import 'package:island/widgets/realm/realm_card.dart'; import 'package:island/widgets/publisher/publisher_card.dart'; import 'package:island/widgets/web_article_card.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; import 'package:styled_widget/styled_widget.dart'; part 'explore.g.dart'; @@ -368,7 +369,7 @@ class ExploreScreen extends HookConsumerWidget { final isWide = isWideScreen(context); - return RefreshIndicator( + return ExtendedRefreshIndicator( onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), child: PagingHelperView( provider: activityListNotifierProvider(filter), diff --git a/lib/screens/realm/realms.dart b/lib/screens/realm/realms.dart index bff9102a..01a8b483 100644 --- a/lib/screens/realm/realms.dart +++ b/lib/screens/realm/realms.dart @@ -23,6 +23,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:island/widgets/realm/realm_list_tile.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; part 'realms.g.dart'; @@ -90,7 +91,7 @@ class RealmListScreen extends HookConsumerWidget { }, ), floatingActionButtonLocation: TabbedFabLocation(context), - body: RefreshIndicator( + body: ExtendedRefreshIndicator( child: realms.when( data: (value) => Column( diff --git a/lib/widgets/account/account_devices.dart b/lib/widgets/account/account_devices.dart index af03e89b..b7b6945b 100644 --- a/lib/widgets/account/account_devices.dart +++ b/lib/widgets/account/account_devices.dart @@ -12,6 +12,7 @@ import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/response.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; part 'account_devices.g.dart'; @@ -177,7 +178,7 @@ class AccountSessionSheet extends HookConsumerWidget { titleText: 'authSessions'.tr(), child: authDevices.when( data: - (data) => RefreshIndicator( + (data) => ExtendedRefreshIndicator( onRefresh: () => Future.sync(() => ref.invalidate(authDevicesProvider)), child: ListView.builder( diff --git a/lib/widgets/extended_refresh_indicator.dart b/lib/widgets/extended_refresh_indicator.dart new file mode 100644 index 00000000..0d2fb2af --- /dev/null +++ b/lib/widgets/extended_refresh_indicator.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class RefreshIntent extends Intent { + const RefreshIntent(); +} + +class ExtendedRefreshIndicator extends StatefulWidget { + final Widget child; + final RefreshCallback onRefresh; + + const ExtendedRefreshIndicator({ + super.key, + required this.child, + required this.onRefresh, + }); + + @override + State createState() => + _ExtendedRefreshIndicatorState(); +} + +class _ExtendedRefreshIndicatorState extends State { + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + }); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyR): + const RefreshIntent(), + LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyR): + const RefreshIntent(), + LogicalKeySet(LogicalKeyboardKey.f5): const RefreshIntent(), + }, + child: Actions( + actions: >{ + RefreshIntent: CallbackAction( + onInvoke: (RefreshIntent intent) => widget.onRefresh(), + ), + }, + child: Focus( + focusNode: _focusNode, + child: RefreshIndicator( + onRefresh: widget.onRefresh, + child: widget.child, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/stickers/picker.dart b/lib/widgets/stickers/picker.dart index e7eec73b..478dca07 100644 --- a/lib/widgets/stickers/picker.dart +++ b/lib/widgets/stickers/picker.dart @@ -12,6 +12,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:flutter_popup_card/flutter_popup_card.dart'; +import 'package:island/widgets/extended_refresh_indicator.dart'; part 'picker.g.dart'; @@ -208,7 +209,7 @@ class _PackSwitcherState extends State<_PackSwitcher> { // Content Expanded( - child: RefreshIndicator( + child: ExtendedRefreshIndicator( onRefresh: widget.onRefresh, child: _StickersGrid( pack: selectedPack,