add syncing waiting / error / failed indicator
This commit is contained in:
parent
a5fef138dd
commit
07700c1eff
6 changed files with 186 additions and 75 deletions
|
|
@ -85,10 +85,6 @@ class GomuksListenerController extends AsyncNotifier<void> {
|
|||
.read(SpaceEdgesController.provider.notifier)
|
||||
.set(spaceEdges);
|
||||
}
|
||||
|
||||
// ref
|
||||
// .read(SyncStatusController.provider.notifier)
|
||||
// .set(SyncStatus.fromJson(decodedMuksEvent));
|
||||
default:
|
||||
debugPrint("Unhandled event: $muksEventType");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/epoch_date_time_converter.dart";
|
||||
|
||||
part "sync_status.freezed.dart";
|
||||
part "sync_status.g.dart";
|
||||
|
|
@ -7,8 +8,9 @@ part "sync_status.g.dart";
|
|||
@JsonSerializable()
|
||||
class const SyncStatus({
|
||||
required final SyncStatusType type,
|
||||
required final String? error,
|
||||
required final int errorCount,
|
||||
final String? error,
|
||||
@EpochDateTimeConverter() final DateTime? lastSync,
|
||||
}) with _$SyncStatus {
|
||||
Map<String, Object?> toJson() => _$SyncStatusToJson(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ final class const ErrorDialog(
|
|||
return AlertDialog(
|
||||
title: Text("An Error Occurred"),
|
||||
content: SingleChildScrollView(
|
||||
child: SelectableText("$error\n\n$stackTrace"),
|
||||
child: SelectableText(
|
||||
"$error${stackTrace == null ? "" : "\n\n$stackTrace"}",
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (provider != null)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import "package:nexus/controllers/key.dart";
|
|||
import "package:nexus/controllers/notification_request.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
import "package:nexus/widgets/sidebar.dart";
|
||||
import "package:nexus/widgets/sync_status_banner.dart";
|
||||
import "package:nexus/widgets/room_chat/room_chat.dart";
|
||||
import "package:nexus/widgets/loading.dart";
|
||||
|
||||
|
|
@ -57,12 +58,19 @@ class const ChatPage({super.key}) extends HookConsumerWidget {
|
|||
),
|
||||
],
|
||||
)
|
||||
: Center(
|
||||
: Column(
|
||||
children: [
|
||||
const SyncStatusBanner(),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
children: [Loading(), Text("Syncing...")],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: isDesktop || !initComplete ? null : Sidebar(),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import "package:nexus/widgets/loading.dart";
|
|||
import "package:nexus/widgets/pinned_events_drawer.dart";
|
||||
import "package:nexus/widgets/member_list.dart";
|
||||
import "package:nexus/widgets/room_appbar.dart";
|
||||
import "package:nexus/widgets/sync_status_banner.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/widgets/room_chat/chat_timeline.dart";
|
||||
import "package:nexus/helpers/extensions/build_event_options.dart";
|
||||
|
|
@ -62,7 +63,12 @@ final class const RoomChat({
|
|||
isDesktop: isDesktop,
|
||||
onOpenDrawer: Scaffold.of(context).openDrawer,
|
||||
),
|
||||
body: nothing,
|
||||
body: Column(
|
||||
children: [
|
||||
const SyncStatusBanner(),
|
||||
Expanded(child: nothing),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +205,11 @@ final class const RoomChat({
|
|||
},
|
||||
onOpenPinnedMessagesList: Scaffold.of(middleContext).openEndDrawer,
|
||||
),
|
||||
body: Row(
|
||||
body: Column(
|
||||
children: [
|
||||
const SyncStatusBanner(),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(
|
||||
|
|
@ -243,13 +253,17 @@ final class const RoomChat({
|
|||
left: 0,
|
||||
right: 0,
|
||||
child: MeasureSize(
|
||||
onChange: (size) => composerSize.value = size.height,
|
||||
onChange: (size) =>
|
||||
composerSize.value = size.height,
|
||||
child: Composer(
|
||||
roomId,
|
||||
node: composerNode,
|
||||
onSend:
|
||||
(text, {required shouldMention, required tags}) =>
|
||||
notifier
|
||||
(
|
||||
text, {
|
||||
required shouldMention,
|
||||
required tags,
|
||||
}) => notifier
|
||||
.send(
|
||||
text,
|
||||
tags: tags,
|
||||
|
|
@ -271,7 +285,11 @@ final class const RoomChat({
|
|||
),
|
||||
),
|
||||
|
||||
if (memberListOpened && showMembersByDefault) MemberList(roomId),
|
||||
if (memberListOpened && showMembersByDefault)
|
||||
MemberList(roomId),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
85
lib/widgets/sync_status_banner.dart
Normal file
85
lib/widgets/sync_status_banner.dart
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/sync_status.dart";
|
||||
import "package:nexus/widgets/error_dialog.dart";
|
||||
import "package:timeago/timeago.dart";
|
||||
|
||||
class const SyncStatusBanner({super.key}) extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final status = ref.watch(SyncStatusController.provider);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
final banner = switch (status?.type) {
|
||||
null || .ok => null,
|
||||
.waiting => (
|
||||
title: "Waiting for sync...",
|
||||
icon: Icons.sync,
|
||||
background: colorScheme.primary,
|
||||
foreground: colorScheme.onPrimary,
|
||||
),
|
||||
.erroring => (
|
||||
title: "Sync is failing, retrying...",
|
||||
icon: Icons.sync_problem,
|
||||
background: colorScheme.error,
|
||||
foreground: colorScheme.onError,
|
||||
),
|
||||
.permanentlyFailed => (
|
||||
title: "Sync has permanently failed",
|
||||
icon: Icons.cloud_off,
|
||||
background: colorScheme.error,
|
||||
foreground: colorScheme.onError,
|
||||
),
|
||||
};
|
||||
|
||||
final details = status == null
|
||||
? null
|
||||
: [
|
||||
?status.error,
|
||||
if (status.errorCount > 0) "Failed attempts: ${status.errorCount}.",
|
||||
if (status.lastSync case final lastSync?)
|
||||
"Last synced ${format(lastSync)}.",
|
||||
];
|
||||
|
||||
return AnimatedSize(
|
||||
duration: const .new(milliseconds: 200),
|
||||
alignment: .topCenter,
|
||||
child: banner == null
|
||||
? const SizedBox(width: double.infinity)
|
||||
: Material(
|
||||
color: banner.background,
|
||||
child: InkWell(
|
||||
onTap: details == null
|
||||
? null
|
||||
: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
ErrorDialog(details.join("\n\n"), null),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const .symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
Icon(banner.icon, size: 20, color: banner.foreground),
|
||||
Expanded(
|
||||
child: Text(
|
||||
banner.title,
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
style: TextStyle(
|
||||
color: banner.foreground,
|
||||
fontWeight: .bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (details != null)
|
||||
Icon(Icons.info_outline, color: banner.foreground),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue