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
|
// 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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user