refactor(notes): Improve code formatting and replace platform-specific refresh controls

This commit is contained in:
cogwheel0
2025-12-15 11:46:48 +05:30
parent c21e70396d
commit dcb73215a4
4 changed files with 93 additions and 35 deletions

View File

@@ -2030,6 +2030,10 @@ class _ChatPageState extends ConsumerState<ChatPage> {
// Messages Area fills entire space with pull-to-refresh // Messages Area fills entire space with pull-to-refresh
Positioned.fill( Positioned.fill(
child: ConduitRefreshIndicator( child: ConduitRefreshIndicator(
// Position indicator below the floating app bar
edgeOffset:
MediaQuery.of(context).padding.top +
kToolbarHeight,
onRefresh: () async { onRefresh: () async {
// Reload active conversation messages from server // Reload active conversation messages from server
final api = ref.read(apiServiceProvider); final api = ref.read(apiServiceProvider);

View File

@@ -13,6 +13,7 @@ import '../../../shared/theme/theme_extensions.dart';
import '../../chat/providers/chat_providers.dart' as chat; import '../../chat/providers/chat_providers.dart' as chat;
import '../../../core/utils/debug_logger.dart'; import '../../../core/utils/debug_logger.dart';
import '../../../core/services/navigation_service.dart'; import '../../../core/services/navigation_service.dart';
import '../../../shared/widgets/loading_states.dart';
import '../../../shared/widgets/themed_dialogs.dart'; import '../../../shared/widgets/themed_dialogs.dart';
import 'package:conduit/l10n/app_localizations.dart'; import 'package:conduit/l10n/app_localizations.dart';
import '../../../core/utils/user_display_name.dart'; import '../../../core/utils/user_display_name.dart';
@@ -112,19 +113,6 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
// Legacy helper removed: drawer now uses slivers with lazy delegates. // Legacy helper removed: drawer now uses slivers with lazy delegates.
Widget _buildRefreshableScrollableSlivers({required List<Widget> slivers}) { Widget _buildRefreshableScrollableSlivers({required List<Widget> slivers}) {
if (Platform.isIOS) {
final scroll = CustomScrollView(
key: const PageStorageKey<String>('chats_drawer_scroll'),
controller: _listController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(onRefresh: _refreshChats),
...slivers,
],
);
return CupertinoScrollbar(controller: _listController, child: scroll);
}
final scroll = CustomScrollView( final scroll = CustomScrollView(
key: const PageStorageKey<String>('chats_drawer_scroll'), key: const PageStorageKey<String>('chats_drawer_scroll'),
controller: _listController, controller: _listController,
@@ -132,10 +120,20 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
cacheExtent: 800, cacheExtent: 800,
slivers: slivers, slivers: slivers,
); );
return RefreshIndicator(
final refreshableScroll = ConduitRefreshIndicator(
onRefresh: _refreshChats, 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 @override

View File

@@ -16,6 +16,7 @@ import '../../../core/widgets/error_boundary.dart';
import '../../../shared/theme/theme_extensions.dart'; import '../../../shared/theme/theme_extensions.dart';
import '../../../shared/utils/ui_utils.dart'; import '../../../shared/utils/ui_utils.dart';
import '../../../shared/widgets/improved_loading_states.dart'; import '../../../shared/widgets/improved_loading_states.dart';
import '../../../shared/widgets/loading_states.dart';
import '../../../shared/widgets/themed_dialogs.dart'; import '../../../shared/widgets/themed_dialogs.dart';
import '../../../shared/widgets/middle_ellipsis_text.dart'; import '../../../shared/widgets/middle_ellipsis_text.dart';
import '../../../shared/utils/conversation_context_menu.dart'; import '../../../shared/utils/conversation_context_menu.dart';
@@ -346,18 +347,7 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
} }
Widget _buildRefreshableScrollView(List<Widget> slivers) { Widget _buildRefreshableScrollView(List<Widget> slivers) {
if (Platform.isIOS) { return ConduitRefreshIndicator(
return CustomScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(onRefresh: _refreshNotes),
...slivers,
],
);
}
return RefreshIndicator(
onRefresh: _refreshNotes, onRefresh: _refreshNotes,
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController, controller: _scrollController,
@@ -551,7 +541,9 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
Text( Text(
preview, preview,
style: AppTypography.bodySmallStyle.copyWith( style: AppTypography.bodySmallStyle.copyWith(
color: sidebarTheme.foreground.withValues(alpha: 0.6), color: sidebarTheme.foreground.withValues(
alpha: 0.6,
),
height: 1.4, height: 1.4,
), ),
maxLines: 2, maxLines: 2,
@@ -566,23 +558,30 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
Platform.isIOS Platform.isIOS
? CupertinoIcons.clock ? CupertinoIcons.clock
: Icons.schedule_rounded, : Icons.schedule_rounded,
color: sidebarTheme.foreground.withValues(alpha: 0.4), color: sidebarTheme.foreground.withValues(
alpha: 0.4,
),
size: 12, size: 12,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
timeText, timeText,
style: AppTypography.tiny.copyWith( style: AppTypography.tiny.copyWith(
color: sidebarTheme.foreground.withValues(alpha: 0.5), color: sidebarTheme.foreground.withValues(
alpha: 0.5,
),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
if (note.user != null && note.user!.name != null) ...[ if (note.user != null &&
note.user!.name != null) ...[
const SizedBox(width: Spacing.sm), const SizedBox(width: Spacing.sm),
Text( Text(
'·', '·',
style: AppTypography.tiny.copyWith( style: AppTypography.tiny.copyWith(
color: sidebarTheme.foreground.withValues(alpha: 0.3), color: sidebarTheme.foreground.withValues(
alpha: 0.3,
),
), ),
), ),
const SizedBox(width: Spacing.sm), const SizedBox(width: Spacing.sm),
@@ -590,7 +589,9 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
child: Text( child: Text(
note.user!.name!, note.user!.name!,
style: AppTypography.tiny.copyWith( style: AppTypography.tiny.copyWith(
color: sidebarTheme.foreground.withValues(alpha: 0.5), color: sidebarTheme.foreground.withValues(
alpha: 0.5,
),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -618,7 +619,8 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
minWidth: TouchTarget.badge, minWidth: TouchTarget.badge,
minHeight: TouchTarget.badge, minHeight: TouchTarget.badge,
), ),
onPressed: () => _showNoteContextMenu(buttonContext, note), onPressed: () =>
_showNoteContextMenu(buttonContext, note),
), ),
), ),
], ],

View File

@@ -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 { class ConduitRefreshIndicator extends StatelessWidget {
final Widget child; final Widget child;
final Future<void> Function() onRefresh; final Future<void> 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({ const ConduitRefreshIndicator({
super.key, super.key,
required this.child, required this.child,
required this.onRefresh, required this.onRefresh,
this.edgeOffset = 0.0,
}); });
@override @override
Widget build(BuildContext context) { 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( return RefreshIndicator(
onRefresh: onRefresh, onRefresh: onRefresh,
color: context.conduitTheme.buttonPrimary, color: context.conduitTheme.buttonPrimary,
backgroundColor: context.conduitTheme.surfaceBackground, backgroundColor: context.conduitTheme.surfaceBackground,
edgeOffset: edgeOffset,
child: child, child: child,
); );
} }