mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 08:54:08 +00:00
add promo for enabling e2ee backup
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
54538a6c31
commit
fb9acc690a
10 changed files with 264 additions and 76 deletions
|
|
@ -4171,6 +4171,18 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Share'**
|
/// **'Share'**
|
||||||
String get galleryActionShare;
|
String get galleryActionShare;
|
||||||
|
|
||||||
|
/// No description provided for @settingsStorageHidePromo.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Hide'**
|
||||||
|
String get settingsStorageHidePromo;
|
||||||
|
|
||||||
|
/// No description provided for @memoriesBackupTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Memories Backup'**
|
||||||
|
String get memoriesBackupTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -2415,4 +2415,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get galleryActionShare => 'Teilen';
|
String get galleryActionShare => 'Teilen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageHidePromo => 'Ausblenden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get memoriesBackupTitle => 'Memories Backup';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2394,4 +2394,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get galleryActionShare => 'Share';
|
String get galleryActionShare => 'Share';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageHidePromo => 'Hide';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get memoriesBackupTitle => 'Memories Backup';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9d05713a1f69e7e4de742c414df7df6c4c7925a7
|
Subproject commit f72c61787096a44e2252058c08e4c14a0a8e5cff
|
||||||
|
|
@ -103,6 +103,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool screenLockEnabled = false;
|
bool screenLockEnabled = false;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool isCloudBackupEnabled = false;
|
||||||
|
|
||||||
// > User Discovery Configurations
|
// > User Discovery Configurations
|
||||||
|
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
|
|
@ -139,6 +142,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool hideChangeLog = true;
|
bool hideChangeLog = true;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool hideMemoriesBackupPromo = false;
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool updateFCMToken = true;
|
bool updateFCMToken = true;
|
||||||
|
|
||||||
|
|
@ -150,8 +156,8 @@ class UserData {
|
||||||
@Deprecated('Use the secure storage in rust')
|
@Deprecated('Use the secure storage in rust')
|
||||||
TwonlySafeBackup? twonlySafeBackup;
|
TwonlySafeBackup? twonlySafeBackup;
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: false)
|
||||||
bool isBackupEnabled = true;
|
bool isBackupEnabled = false;
|
||||||
|
|
||||||
PasswordLessRecovery? passwordLessRecovery;
|
PasswordLessRecovery? passwordLessRecovery;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
..allowErrorTrackingViaSentry =
|
..allowErrorTrackingViaSentry =
|
||||||
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
||||||
..screenLockEnabled = json['screenLockEnabled'] as bool? ?? false
|
..screenLockEnabled = json['screenLockEnabled'] as bool? ?? false
|
||||||
|
..isCloudBackupEnabled = json['isCloudBackupEnabled'] as bool? ?? false
|
||||||
..isUserDiscoveryEnabled =
|
..isUserDiscoveryEnabled =
|
||||||
json['isUserDiscoveryEnabled'] as bool? ?? false
|
json['isUserDiscoveryEnabled'] as bool? ?? false
|
||||||
..requiredSendImages = (json['requiredSendImages'] as num?)?.toInt() ?? 4
|
..requiredSendImages = (json['requiredSendImages'] as num?)?.toInt() ?? 4
|
||||||
|
|
@ -88,6 +89,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
?.map((e) => (e as num).toInt())
|
?.map((e) => (e as num).toInt())
|
||||||
.toList()
|
.toList()
|
||||||
..hideChangeLog = json['hideChangeLog'] as bool? ?? true
|
..hideChangeLog = json['hideChangeLog'] as bool? ?? true
|
||||||
|
..hideMemoriesBackupPromo =
|
||||||
|
json['hideMemoriesBackupPromo'] as bool? ?? false
|
||||||
..updateFCMToken = json['updateFCMToken'] as bool? ?? true
|
..updateFCMToken = json['updateFCMToken'] as bool? ?? true
|
||||||
..canUseLoginTokenForAuth =
|
..canUseLoginTokenForAuth =
|
||||||
json['canUseLoginTokenForAuth'] as bool? ?? true
|
json['canUseLoginTokenForAuth'] as bool? ?? true
|
||||||
|
|
@ -96,7 +99,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
: TwonlySafeBackup.fromJson(
|
: TwonlySafeBackup.fromJson(
|
||||||
json['twonlySafeBackup'] as Map<String, dynamic>,
|
json['twonlySafeBackup'] as Map<String, dynamic>,
|
||||||
)
|
)
|
||||||
..isBackupEnabled = json['isBackupEnabled'] as bool? ?? true
|
..isBackupEnabled = json['isBackupEnabled'] as bool? ?? false
|
||||||
..passwordLessRecovery = json['passwordLessRecovery'] == null
|
..passwordLessRecovery = json['passwordLessRecovery'] == null
|
||||||
? null
|
? null
|
||||||
: PasswordLessRecovery.fromJson(
|
: PasswordLessRecovery.fromJson(
|
||||||
|
|
@ -153,6 +156,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
?.toIso8601String(),
|
?.toIso8601String(),
|
||||||
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
||||||
'screenLockEnabled': instance.screenLockEnabled,
|
'screenLockEnabled': instance.screenLockEnabled,
|
||||||
|
'isCloudBackupEnabled': instance.isCloudBackupEnabled,
|
||||||
'isUserDiscoveryEnabled': instance.isUserDiscoveryEnabled,
|
'isUserDiscoveryEnabled': instance.isUserDiscoveryEnabled,
|
||||||
'requiredSendImages': instance.requiredSendImages,
|
'requiredSendImages': instance.requiredSendImages,
|
||||||
'userDiscoveryThreshold': instance.userDiscoveryThreshold,
|
'userDiscoveryThreshold': instance.userDiscoveryThreshold,
|
||||||
|
|
@ -165,6 +169,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||||
'hideChangeLog': instance.hideChangeLog,
|
'hideChangeLog': instance.hideChangeLog,
|
||||||
|
'hideMemoriesBackupPromo': instance.hideMemoriesBackupPromo,
|
||||||
'updateFCMToken': instance.updateFCMToken,
|
'updateFCMToken': instance.updateFCMToken,
|
||||||
'canUseLoginTokenForAuth': instance.canUseLoginTokenForAuth,
|
'canUseLoginTokenForAuth': instance.canUseLoginTokenForAuth,
|
||||||
'twonlySafeBackup': instance.twonlySafeBackup,
|
'twonlySafeBackup': instance.twonlySafeBackup,
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class MemoriesCloudService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkUploads() async {
|
Future<void> checkUploads() async {
|
||||||
if (_isProcessing || !userService.currentUser.isBackupEnabled) return;
|
if (_isProcessing || !userService.currentUser.isCloudBackupEnabled) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final memories = await twonlyDB.mediaFilesDao.getMemoriesToBackup();
|
final memories = await twonlyDB.mediaFilesDao.getMemoriesToBackup();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
|
|
||||||
|
class MemoriesCloudBackupPromoComp extends StatelessWidget {
|
||||||
|
const MemoriesCloudBackupPromoComp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<void>(
|
||||||
|
stream: userService.onUserUpdated,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final user = userService.currentUser;
|
||||||
|
if (user.isCloudBackupEnabled || user.hideMemoriesBackupPromo) {
|
||||||
|
return const SliverToBoxAdapter(
|
||||||
|
child: SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
context.color.primaryContainer.withValues(alpha: 0.15),
|
||||||
|
context.color.primaryContainer.withValues(alpha: 0.05),
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
border: Border.all(
|
||||||
|
color: context.color.primary.withValues(alpha: 0.1),
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.lang.memoriesBackupTitle,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: context.color.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
context.lang.settingsStorageNoCloudBackupCard,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: context.color.onSurfaceVariant,
|
||||||
|
height: 1.3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
MyButton(
|
||||||
|
variant: MyButtonVariant.primaryDense,
|
||||||
|
onPressed: () async {
|
||||||
|
await UserService.update(
|
||||||
|
(u) => u.isCloudBackupEnabled = true,
|
||||||
|
);
|
||||||
|
unawaited(memoriesCloudService.checkUploads());
|
||||||
|
},
|
||||||
|
child: Text(context.lang.enable),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
MyButton(
|
||||||
|
variant: MyButtonVariant.secondaryDense,
|
||||||
|
onPressed: () async {
|
||||||
|
await UserService.update(
|
||||||
|
(u) => u.hideMemoriesBackupPromo = true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
context.lang.settingsStorageHidePromo,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
|
|
@ -10,6 +11,7 @@ import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||||
import 'package:twonly/src/visual/components/delete_memories_dialog.comp.dart';
|
import 'package:twonly/src/visual/components/delete_memories_dialog.comp.dart';
|
||||||
import 'package:twonly/src/visual/components/draggable_scrollbar.comp.dart';
|
import 'package:twonly/src/visual/components/draggable_scrollbar.comp.dart';
|
||||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||||
|
import 'package:twonly/src/visual/views/memories/components/cloud_backup_promo.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/memories/components/flashback_banner.comp.dart';
|
import 'package:twonly/src/visual/views/memories/components/flashback_banner.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/memories/components/memory_thumbnail.comp.dart';
|
import 'package:twonly/src/visual/views/memories/components/memory_thumbnail.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/memories/components/selection_menu.comp.dart';
|
import 'package:twonly/src/visual/views/memories/components/selection_menu.comp.dart';
|
||||||
|
|
@ -680,6 +682,7 @@ class MemoriesViewState extends State<MemoriesView>
|
||||||
onOpenFlashback: (items, idx) =>
|
onOpenFlashback: (items, idx) =>
|
||||||
_openViewer(items, idx, isFlashback: true),
|
_openViewer(items, idx, isFlashback: true),
|
||||||
),
|
),
|
||||||
|
const MemoriesCloudBackupPromoComp(),
|
||||||
for (final month in months) ...[
|
for (final month in months) ...[
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
@ -9,7 +10,9 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
|
||||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
|
|
||||||
class ManageStorageView extends StatefulWidget {
|
class ManageStorageView extends StatefulWidget {
|
||||||
const ManageStorageView({super.key});
|
const ManageStorageView({super.key});
|
||||||
|
|
@ -117,84 +120,123 @@ class _ManageStorageViewState extends State<ManageStorageView> {
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
] else ...[
|
] else ...[
|
||||||
Text(
|
Text(
|
||||||
'Memories Backup',
|
context.lang.memoriesBackupTitle,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
if (!userService.currentUser.isCloudBackupEnabled) ...[
|
||||||
_memoriesUsage != null
|
Row(
|
||||||
? '${formatBytes(_memoriesUsage!.currentBytes.toInt())} / ${formatBytes(_memoriesUsage!.maxBytes.toInt())}'
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
: '-',
|
children: [
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
Expanded(
|
||||||
fontWeight: FontWeight.bold,
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Container(
|
|
||||||
height: 24,
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.withValues(alpha: 0.2),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
if (_memoriesUsage == null ||
|
|
||||||
_memoriesUsage!.maxBytes == 0) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
final maxWidth = constraints.maxWidth;
|
|
||||||
final current = _memoriesUsage!.currentBytes.toDouble();
|
|
||||||
final max = _memoriesUsage!.maxBytes.toDouble();
|
|
||||||
final usageWidth =
|
|
||||||
((current / max).clamp(0.0, 1.0)) * maxWidth;
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: [
|
children: [
|
||||||
if (usageWidth > 0)
|
Text(
|
||||||
Container(
|
context.lang.settingsStorageNoCloudBackupCard,
|
||||||
width: usageWidth,
|
style: Theme.of(context).textTheme.bodyMedium
|
||||||
color: Colors.blue,
|
?.copyWith(
|
||||||
),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
MyButton(
|
||||||
|
variant: MyButtonVariant.primaryMiddle,
|
||||||
|
onPressed: () async {
|
||||||
|
await UserService.update(
|
||||||
|
(u) => u.isCloudBackupEnabled = true,
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
unawaited(memoriesCloudService.checkUploads());
|
||||||
|
},
|
||||||
|
child: Text(context.lang.enable),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
] else ...[
|
||||||
|
Text(
|
||||||
|
_memoriesUsage != null
|
||||||
|
? '${formatBytes(_memoriesUsage!.currentBytes.toInt())} / ${formatBytes(_memoriesUsage!.maxBytes.toInt())}'
|
||||||
|
: '-',
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 24),
|
||||||
StreamBuilder<MemoriesBackupProgress>(
|
Container(
|
||||||
initialData: memoriesCloudService.currentProgress,
|
height: 24,
|
||||||
stream: memoriesCloudService.progressStream,
|
width: double.infinity,
|
||||||
builder: (context, snapshot) {
|
decoration: BoxDecoration(
|
||||||
final progress = snapshot.data;
|
color: Colors.grey.withValues(alpha: 0.2),
|
||||||
if (progress == null || progress.totalPending == 0) {
|
borderRadius: BorderRadius.circular(12),
|
||||||
return const SizedBox.shrink();
|
),
|
||||||
}
|
child: ClipRRect(
|
||||||
final percent =
|
borderRadius: BorderRadius.circular(12),
|
||||||
(progress.currentUploaded / progress.totalPending) +
|
child: LayoutBuilder(
|
||||||
(progress.currentUploadProgress / progress.totalPending);
|
builder: (context, constraints) {
|
||||||
return Column(
|
if (_memoriesUsage == null ||
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
_memoriesUsage!.maxBytes == 0) {
|
||||||
children: [
|
return const SizedBox.shrink();
|
||||||
const SizedBox(height: 12),
|
}
|
||||||
Text(
|
|
||||||
'Syncing: ${progress.currentUploaded} / ${progress.totalPending} files (${(percent * 100).toStringAsFixed(1)}%)',
|
final maxWidth = constraints.maxWidth;
|
||||||
style: const TextStyle(fontSize: 14),
|
final current = _memoriesUsage!.currentBytes.toDouble();
|
||||||
),
|
final max = _memoriesUsage!.maxBytes.toDouble();
|
||||||
const SizedBox(height: 6),
|
final usageWidth =
|
||||||
LinearProgressIndicator(
|
((current / max).clamp(0.0, 1.0)) * maxWidth;
|
||||||
value: percent.clamp(0.0, 1.0),
|
|
||||||
),
|
return Row(
|
||||||
],
|
children: [
|
||||||
);
|
if (usageWidth > 0)
|
||||||
},
|
Container(
|
||||||
),
|
width: usageWidth,
|
||||||
const SizedBox(height: 24),
|
color: Colors.blue,
|
||||||
const Divider(),
|
),
|
||||||
const SizedBox(height: 24),
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StreamBuilder<MemoriesBackupProgress>(
|
||||||
|
initialData: memoriesCloudService.currentProgress,
|
||||||
|
stream: memoriesCloudService.progressStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final progress = snapshot.data;
|
||||||
|
if (progress == null || progress.totalPending == 0) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final percent =
|
||||||
|
(progress.currentUploaded / progress.totalPending) +
|
||||||
|
(progress.currentUploadProgress / progress.totalPending);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'Syncing: ${progress.currentUploaded} / ${progress.totalPending} files (${(percent * 100).toStringAsFixed(1)}%)',
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: percent.clamp(0.0, 1.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
Text(
|
Text(
|
||||||
isFreePlan
|
isFreePlan
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue