From dcb73215a40371179b31e5a1b276ae45a310606f Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:46:48 +0530 Subject: [PATCH] refactor(notes): Improve code formatting and replace platform-specific refresh controls --- lib/features/chat/views/chat_page.dart | 4 ++ .../navigation/widgets/chats_drawer.dart | 28 +++++----- lib/features/notes/views/notes_list_page.dart | 40 ++++++------- lib/shared/widgets/loading_states.dart | 56 ++++++++++++++++++- 4 files changed, 93 insertions(+), 35 deletions(-) diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 81c7e15..a2dbcdb 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -2030,6 +2030,10 @@ class _ChatPageState extends ConsumerState { // Messages Area fills entire space with pull-to-refresh Positioned.fill( child: ConduitRefreshIndicator( + // Position indicator below the floating app bar + edgeOffset: + MediaQuery.of(context).padding.top + + kToolbarHeight, onRefresh: () async { // Reload active conversation messages from server final api = ref.read(apiServiceProvider); diff --git a/lib/features/navigation/widgets/chats_drawer.dart b/lib/features/navigation/widgets/chats_drawer.dart index d7be8ab..6bf343c 100644 --- a/lib/features/navigation/widgets/chats_drawer.dart +++ b/lib/features/navigation/widgets/chats_drawer.dart @@ -13,6 +13,7 @@ import '../../../shared/theme/theme_extensions.dart'; import '../../chat/providers/chat_providers.dart' as chat; import '../../../core/utils/debug_logger.dart'; import '../../../core/services/navigation_service.dart'; +import '../../../shared/widgets/loading_states.dart'; import '../../../shared/widgets/themed_dialogs.dart'; import 'package:conduit/l10n/app_localizations.dart'; import '../../../core/utils/user_display_name.dart'; @@ -112,19 +113,6 @@ class _ChatsDrawerState extends ConsumerState { // Legacy helper removed: drawer now uses slivers with lazy delegates. Widget _buildRefreshableScrollableSlivers({required List slivers}) { - if (Platform.isIOS) { - final scroll = CustomScrollView( - key: const PageStorageKey('chats_drawer_scroll'), - controller: _listController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - CupertinoSliverRefreshControl(onRefresh: _refreshChats), - ...slivers, - ], - ); - return CupertinoScrollbar(controller: _listController, child: scroll); - } - final scroll = CustomScrollView( key: const PageStorageKey('chats_drawer_scroll'), controller: _listController, @@ -132,10 +120,20 @@ class _ChatsDrawerState extends ConsumerState { cacheExtent: 800, slivers: slivers, ); - return RefreshIndicator( + + final refreshableScroll = ConduitRefreshIndicator( onRefresh: _refreshChats, - child: Scrollbar(controller: _listController, child: scroll), + child: scroll, ); + + if (Platform.isIOS) { + return CupertinoScrollbar( + controller: _listController, + child: refreshableScroll, + ); + } + + return Scrollbar(controller: _listController, child: refreshableScroll); } @override diff --git a/lib/features/notes/views/notes_list_page.dart b/lib/features/notes/views/notes_list_page.dart index dc67316..c8cb5f8 100644 --- a/lib/features/notes/views/notes_list_page.dart +++ b/lib/features/notes/views/notes_list_page.dart @@ -16,6 +16,7 @@ import '../../../core/widgets/error_boundary.dart'; import '../../../shared/theme/theme_extensions.dart'; import '../../../shared/utils/ui_utils.dart'; import '../../../shared/widgets/improved_loading_states.dart'; +import '../../../shared/widgets/loading_states.dart'; import '../../../shared/widgets/themed_dialogs.dart'; import '../../../shared/widgets/middle_ellipsis_text.dart'; import '../../../shared/utils/conversation_context_menu.dart'; @@ -346,18 +347,7 @@ class _NotesListPageState extends ConsumerState { } Widget _buildRefreshableScrollView(List slivers) { - if (Platform.isIOS) { - return CustomScrollView( - controller: _scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - CupertinoSliverRefreshControl(onRefresh: _refreshNotes), - ...slivers, - ], - ); - } - - return RefreshIndicator( + return ConduitRefreshIndicator( onRefresh: _refreshNotes, child: CustomScrollView( controller: _scrollController, @@ -551,7 +541,9 @@ class _NotesListPageState extends ConsumerState { Text( preview, style: AppTypography.bodySmallStyle.copyWith( - color: sidebarTheme.foreground.withValues(alpha: 0.6), + color: sidebarTheme.foreground.withValues( + alpha: 0.6, + ), height: 1.4, ), maxLines: 2, @@ -566,23 +558,30 @@ class _NotesListPageState extends ConsumerState { Platform.isIOS ? CupertinoIcons.clock : Icons.schedule_rounded, - color: sidebarTheme.foreground.withValues(alpha: 0.4), + color: sidebarTheme.foreground.withValues( + alpha: 0.4, + ), size: 12, ), const SizedBox(width: 4), Text( timeText, style: AppTypography.tiny.copyWith( - color: sidebarTheme.foreground.withValues(alpha: 0.5), + color: sidebarTheme.foreground.withValues( + alpha: 0.5, + ), fontWeight: FontWeight.w500, ), ), - if (note.user != null && note.user!.name != null) ...[ + if (note.user != null && + note.user!.name != null) ...[ const SizedBox(width: Spacing.sm), Text( 'ยท', style: AppTypography.tiny.copyWith( - color: sidebarTheme.foreground.withValues(alpha: 0.3), + color: sidebarTheme.foreground.withValues( + alpha: 0.3, + ), ), ), const SizedBox(width: Spacing.sm), @@ -590,7 +589,9 @@ class _NotesListPageState extends ConsumerState { child: Text( note.user!.name!, style: AppTypography.tiny.copyWith( - color: sidebarTheme.foreground.withValues(alpha: 0.5), + color: sidebarTheme.foreground.withValues( + alpha: 0.5, + ), fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, @@ -618,7 +619,8 @@ class _NotesListPageState extends ConsumerState { minWidth: TouchTarget.badge, minHeight: TouchTarget.badge, ), - onPressed: () => _showNoteContextMenu(buttonContext, note), + onPressed: () => + _showNoteContextMenu(buttonContext, note), ), ), ], diff --git a/lib/shared/widgets/loading_states.dart b/lib/shared/widgets/loading_states.dart index 4005083..8bda6cc 100644 --- a/lib/shared/widgets/loading_states.dart +++ b/lib/shared/widgets/loading_states.dart @@ -433,23 +433,77 @@ class LoadingButton extends StatelessWidget { } } -/// Refresh indicator with Conduit styling +/// Refresh indicator with Conduit styling. +/// +/// Uses platform-appropriate refresh controls: +/// - iOS: Native Cupertino-style refresh control (when child is CustomScrollView) +/// - Android/Other: Material RefreshIndicator +/// +/// Set [edgeOffset] to position the indicator below an app bar or other +/// overlay. For example, use `MediaQuery.of(context).padding.top + kToolbarHeight` +/// to position below a transparent/floating app bar. +/// +/// Note: On iOS with a CustomScrollView child, [edgeOffset] is ignored since +/// CupertinoSliverRefreshControl naturally positions itself based on scroll +/// content. The scroll view's existing padding should handle app bar clearance. class ConduitRefreshIndicator extends StatelessWidget { final Widget child; final Future Function() onRefresh; + /// The distance from the top of the scroll view where the refresh indicator + /// will appear. Useful for positioning below a floating/transparent app bar. + /// + /// Note: This is only effective on Android/non-iOS platforms, or on iOS when + /// the child is not a CustomScrollView. For iOS with CustomScrollView, the + /// refresh control naturally positions based on scroll content. + final double edgeOffset; + const ConduitRefreshIndicator({ super.key, required this.child, required this.onRefresh, + this.edgeOffset = 0.0, }); @override Widget build(BuildContext context) { + // On iOS, try to use CupertinoSliverRefreshControl for native feel + // when the child is directly a CustomScrollView + if (Platform.isIOS && child is CustomScrollView) { + final csv = child as CustomScrollView; + return CustomScrollView( + key: csv.key, + controller: csv.controller, + scrollDirection: csv.scrollDirection, + reverse: csv.reverse, + primary: csv.primary, + physics: csv.physics, + shrinkWrap: csv.shrinkWrap, + cacheExtent: csv.cacheExtent, + keyboardDismissBehavior: csv.keyboardDismissBehavior, + clipBehavior: csv.clipBehavior, + center: csv.center, + anchor: csv.anchor, + semanticChildCount: csv.semanticChildCount, + dragStartBehavior: csv.dragStartBehavior, + restorationId: csv.restorationId, + slivers: [ + // CupertinoSliverRefreshControl naturally positions itself based on + // scroll content; the scroll view's existing padding handles app bar + // clearance, so no edgeOffset adjustment is needed here. + CupertinoSliverRefreshControl(onRefresh: onRefresh), + ...csv.slivers, + ], + ); + } + + // For Android, other platforms, or when child is not a CustomScrollView, + // use Material RefreshIndicator which works with any scrollable return RefreshIndicator( onRefresh: onRefresh, color: context.conduitTheme.buttonPrimary, backgroundColor: context.conduitTheme.surfaceBackground, + edgeOffset: edgeOffset, child: child, ); }