refactor(notes): Improve code formatting and replace platform-specific refresh controls
This commit is contained in:
@@ -2030,6 +2030,10 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
// 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);
|
||||
|
||||
@@ -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<ChatsDrawer> {
|
||||
// Legacy helper removed: drawer now uses slivers with lazy delegates.
|
||||
|
||||
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(
|
||||
key: const PageStorageKey<String>('chats_drawer_scroll'),
|
||||
controller: _listController,
|
||||
@@ -132,10 +120,20 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
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
|
||||
|
||||
@@ -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<NotesListPage> {
|
||||
}
|
||||
|
||||
Widget _buildRefreshableScrollView(List<Widget> 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<NotesListPage> {
|
||||
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<NotesListPage> {
|
||||
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<NotesListPage> {
|
||||
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<NotesListPage> {
|
||||
minWidth: TouchTarget.badge,
|
||||
minHeight: TouchTarget.badge,
|
||||
),
|
||||
onPressed: () => _showNoteContextMenu(buttonContext, note),
|
||||
onPressed: () =>
|
||||
_showNoteContextMenu(buttonContext, note),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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<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({
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user