"synced" indicator (#3512)

* "synced" indicator

* safeguards
This commit is contained in:
malik1004x
2026-08-11 19:00:10 +02:00
committed by GitHub
parent 26bc7bda3d
commit 3a71f6ad33
34 changed files with 134 additions and 38 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ String syncStatusTitle(SyncStatus syncStatus, SyncStatusDisplayMode syncStatusDi
}
if (syncStatus is SyncedSyncStatus) {
return "";
return S.current.sync_synced;
}
if (syncStatus is FailedSyncStatus) {
@@ -10,18 +10,20 @@ class ChainIcon extends StatelessWidget {
{super.key,
required this.iconPath,
required this.dashboardViewModel,
required this.isSyncHeavy});
required this.isSyncHeavy,
required this.showSyncedMessage});
final String iconPath;
final bool isSyncHeavy;
final DashboardViewModel dashboardViewModel;
final bool showSyncedMessage;
@override
Widget build(BuildContext context) {
return Observer(
builder: (_) {
final progress = dashboardViewModel.status.progress();
final done = !isSyncHeavy || progress >= 1;
final done = !showSyncedMessage && (!isSyncHeavy || progress >= 1);
return Stack(
children: [
@@ -30,10 +32,10 @@ class ChainIcon extends StatelessWidget {
opacity: done ? 0 : 1,
// Faded out means "nothing to report", so it must leave the tree too.
child: ExcludeSemantics(
excluding: done,
excluding: done || showSyncedMessage,
child: CircularProgressIndicator(
value: progress,
color: Color(0xFFFFB84E),
color: showSyncedMessage ? Color(0xFFFF12A439) : Color(0xFFFFB84E),
strokeWidth: 2,
semanticsLabel: S.of(context).synchronizing,
semanticsValue: "${(progress * 100).round()}%",
@@ -1,21 +1,26 @@
import 'package:cake_wallet/core/sync_status_title.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/pulsing_dot.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/sync_status.dart';
import "package:flutter/cupertino.dart";
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class SyncBar extends StatelessWidget {
SyncBar({super.key, required this.dashboardViewModel, required this.isSyncHeavy});
SyncBar({
super.key,
required this.dashboardViewModel,
required this.isSyncHeavy,
required this.showSyncedMessage,
});
final DashboardViewModel dashboardViewModel;
final bool isSyncHeavy;
final bool showSyncedMessage;
static const failStatuses = [
FailedSyncStatus,
@@ -37,9 +42,10 @@ class SyncBar extends StatelessWidget {
ConnectedSyncStatus,
];
static const syncedColor = Color(0xFF12A439);
@override
Widget build(BuildContext context) {
return Observer(
Widget build(BuildContext context) => Observer(
builder: (_) {
final status = dashboardViewModel.status;
final Widget? icon = _getIcon(context, status.runtimeType);
@@ -106,7 +112,6 @@ class SyncBar extends StatelessWidget {
);
},
);
}
void _openNodeManagement(BuildContext context) =>
CupertinoScaffold.showCupertinoModalBottomSheet(
@@ -130,8 +135,9 @@ class SyncBar extends StatelessWidget {
width: 20,
height: 20,
),
if (_showDot()) PulsingDot(),
],
if (_showDot()) const CupertinoActivityIndicator(radius: 8),
if(_showLightSyncCheck()) const Icon(Icons.check, color: syncedColor, size: 18),
],
);
final label = _joinLabels([
@@ -144,6 +150,9 @@ class SyncBar extends StatelessWidget {
return Semantics(label: label, child: ExcludeSemantics(child: row));
}
bool get _isShowingSyncedMessage =>
showSyncedMessage && dashboardViewModel.status.runtimeType == SyncedSyncStatus;
String _statusSemanticsLabel(BuildContext context, SyncStatus status) {
final isFailure = failStatuses.contains(status.runtimeType);
@@ -166,7 +175,7 @@ class SyncBar extends StatelessWidget {
}
Border? _getBorder(BuildContext context, Type status) {
if (progressStatuses.contains(status)) {
if (progressStatuses.contains(status) || _isShowingSyncedMessage) {
return Border.all(color: Theme.of(context).colorScheme.surfaceContainerHigh, width: 1);
}
@@ -174,19 +183,26 @@ class SyncBar extends StatelessWidget {
}
TextStyle? _getTextStyle(BuildContext context, Type status) {
final Color color;
if (failStatuses.contains(status)) {
return TextStyle(
fontSize: 12, fontWeight: FontWeight.w400, color: Theme.of(context).colorScheme.error);
color = Theme.of(context).colorScheme.error;
} else if (status == SyncedSyncStatus) {
color = syncedColor;
} else {
return TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant);
color = Theme.of(context).colorScheme.onSurfaceVariant;
}
return TextStyle(
fontSize: 12, fontWeight: FontWeight.w400, color: color);
}
Widget? _getIcon(BuildContext context, Type status) {
if (status == LostConnectionSyncStatus || status == FailedSyncStatus) {
if(status == SyncedSyncStatus) {
return Icon(Icons.check, color: syncedColor, size: 12);
}
if (status == LostConnectionSyncStatus) {
return CakeImageWidget(
imageUrl: "assets/new-ui/offline.svg",
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.error, BlendMode.srcIn),
@@ -228,11 +244,14 @@ class SyncBar extends StatelessWidget {
}
bool _showFullBar() {
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus) return false;
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus)
return isSyncHeavy && _isShowingSyncedMessage;
return isSyncHeavy || failStatuses.contains(dashboardViewModel.status.runtimeType);
}
bool _showDot() {
return !isSyncHeavy && progressStatuses.contains(dashboardViewModel.status.runtimeType);
}
bool _showDot() =>
!isSyncHeavy && progressStatuses.contains(dashboardViewModel.status.runtimeType);
bool _showLightSyncCheck() =>
!isSyncHeavy && _isShowingSyncedMessage;
}
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
@@ -6,11 +7,13 @@ import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/lightning_s
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/sync_status.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class TopBar extends StatelessWidget {
class TopBar extends StatefulWidget {
const TopBar({
super.key,
required this.lightningMode,
@@ -25,33 +28,76 @@ class TopBar extends StatelessWidget {
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return Padding(
State<TopBar> createState() => _TopBarState();
}
class _TopBarState extends State<TopBar> {
static const syncedMessageDuration = Duration(seconds: 3);
bool showSyncedMessage = false;
Timer? syncedMessageTimer;
late final ReactionDisposer? _statusReactionDisposer;
@override
void initState() {
super.initState();
_statusReactionDisposer = reaction(
(_) => widget.dashboardViewModel.status.runtimeType,
(status) {
syncedMessageTimer?.cancel();
if (status == SyncedSyncStatus) {
if(mounted) {
setState(() => showSyncedMessage = true);
}
syncedMessageTimer =
Timer(syncedMessageDuration, () => setState(() => showSyncedMessage = false));
} else {
if(mounted) {
setState(() => showSyncedMessage = false);
}
}
},
);
}
@override
void dispose() {
syncedMessageTimer?.cancel();
_statusReactionDisposer?.reaction.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Padding(
padding: EdgeInsets.only(
bottom: 10, left: 18, right: 18, top: 10 + _additionalTopPadding(context)),
child: Observer(
builder: (_) => Row(
spacing: 12,
children: [
(dashboardViewModel.hasLightning)
(widget.dashboardViewModel.hasLightning)
? LightningSwitcher(
lightningMode: lightningMode,
onLightningSwitchPress: onLightningSwitchPress,
lightningMode: widget.lightningMode,
onLightningSwitchPress: widget.onLightningSwitchPress,
)
: ChainIcon(
iconPath: dashboardViewModel.wallet.currency.flatIconPath ?? "",
dashboardViewModel: dashboardViewModel,
isSyncHeavy: dashboardViewModel.isSyncHeavy),
iconPath: widget.dashboardViewModel.wallet.currency.flatIconPath ?? "",
dashboardViewModel: widget.dashboardViewModel,
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
showSyncedMessage: showSyncedMessage),
SyncBar(
dashboardViewModel: dashboardViewModel,
isSyncHeavy: dashboardViewModel.isSyncHeavy,
dashboardViewModel: widget.dashboardViewModel,
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
showSyncedMessage: showSyncedMessage,
),
ModernButton.svg(
iconColor: Theme.of(context).colorScheme.primary,
size: 36,
onPressed: () {
HapticFeedback.mediumImpact();
onSettingsButtonPress();
widget.onSettingsButtonPress();
},
svgPath: "assets/new-ui/top-settings.svg",
semanticLabel: S.of(context).settings_title,
@@ -60,7 +106,6 @@ class TopBar extends StatelessWidget {
),
),
);
}
//FIXME remove after this gets fixed flutter-side
double _additionalTopPadding(BuildContext context) {