Skip to content

Commit 2396a91

Browse files
committed
Add delayed autosave, closes #883
1 parent 162ae72 commit 2396a91

File tree

12 files changed

+121
-31
lines changed

12 files changed

+121
-31
lines changed

app/lib/bloc/document_bloc.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,10 +1296,10 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
12961296
state.assetService?.dispose();
12971297
}
12981298

1299-
Future<void> save() {
1299+
Future<void> save({AssetLocation? location, bool force = false}) {
13001300
final current = state;
13011301
if (current is! DocumentLoadSuccess) return Future.value();
1302-
return current.save();
1302+
return current.save(location: location, force: force);
13031303
}
13041304

13051305
bool isInBounds(Offset globalPosition) {

app/lib/bloc/document_state.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,16 @@ class DocumentLoadSuccess extends DocumentLoaded {
255255
?.hasDocumentCached(location.path) ??
256256
false))));
257257

258-
Future<AssetLocation> save({AssetLocation? location, bool force = false}) =>
259-
currentIndexCubit.save(this, location: location, force: force);
258+
Future<AssetLocation> save({
259+
AssetLocation? location,
260+
bool force = false,
261+
bool isAutosave = false,
262+
}) => currentIndexCubit.save(
263+
this,
264+
location: location,
265+
force: force,
266+
isAutosave: isAutosave,
267+
);
260268

261269
ExternalStorage? getRemoteStorage() => currentIndexCubit.getRemoteStorage();
262270

app/lib/cubits/current_index.dart

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import '../view_painter.dart';
3333

3434
part 'current_index.freezed.dart';
3535

36-
enum SaveState { saved, saving, unsaved, absoluteRead }
36+
enum SaveState { saved, saving, unsaved, delay, absoluteRead }
3737

3838
enum HideState { visible, keyboard, touch }
3939

@@ -1514,18 +1514,31 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
15141514
DocumentState blocState, {
15151515
AssetLocation? location,
15161516
bool force = false,
1517+
bool isAutosave = false,
15171518
}) async {
15181519
if (!force && state.saved == SaveState.saved) {
15191520
return state.location;
15201521
}
15211522
if (state.networkingService.isClient) {
15221523
return AssetLocation.empty;
15231524
}
1525+
if (state.saved == SaveState.delay && isAutosave) {
1526+
return state.location;
1527+
}
15241528
final storage = getRemoteStorage();
15251529
final fileSystem = blocState.fileSystem.buildDocumentSystem(storage);
15261530
while (_currentlySaving) {
15271531
await Future.delayed(const Duration(milliseconds: 100));
15281532
}
1533+
final isDelayed = state.settingsCubit.state.delayedAutosave;
1534+
if (isDelayed && isAutosave && state.saved == SaveState.unsaved) {
1535+
final seconds = max(0, state.settingsCubit.state.autosaveDelaySeconds);
1536+
emit(state.copyWith(saved: SaveState.delay));
1537+
await Future.delayed(Duration(seconds: seconds));
1538+
if (state.saved != SaveState.delay) {
1539+
return state.location;
1540+
}
1541+
}
15291542
_currentlySaving = true;
15301543
emit(
15311544
state.copyWith(
@@ -1639,7 +1652,7 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
16391652
}
16401653
AssetLocation? path = current.location;
16411654
if (current.hasAutosave()) {
1642-
path = await current.save(location: path);
1655+
path = await current.save(location: path, isAutosave: true);
16431656
}
16441657
}
16451658

app/lib/cubits/settings.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ class SRGBConverter extends JsonConverter<SRGBColor, int> {
276276
int toJson(SRGBColor object) => object.value;
277277
}
278278

279+
enum SaveMethod { manual, autosave, delayedAutosave }
280+
279281
@freezed
280282
sealed class ButterflySettings with _$ButterflySettings, LeapSettings {
281283
const ButterflySettings._();
@@ -326,6 +328,8 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings {
326328
@Default(true) bool autosave,
327329
@Default(true) bool showSaveButton,
328330
@Default(1) int toolbarRows,
331+
@Default(true) bool delayedAutosave,
332+
@Default(3) int autosaveDelaySeconds,
329333
@Default(false) bool hideCursorWhileDrawing,
330334
@Default(UtilitiesState()) UtilitiesState utilities,
331335
@Default(StartupBehavior.openHomeScreen) StartupBehavior onStartup,
@@ -424,6 +428,8 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings {
424428
highContrast: prefs.getBool('high_contrast') ?? false,
425429
gridView: prefs.getBool('grid_view') ?? false,
426430
autosave: prefs.getBool('autosave') ?? true,
431+
delayedAutosave: prefs.getBool('delayed_autosave') ?? false,
432+
autosaveDelaySeconds: prefs.getInt('autosave_delay_seconds') ?? 5,
427433
toolbarSize: prefs.containsKey('toolbar_size')
428434
? ToolbarSize.values.byName(prefs.getString('toolbar_size')!)
429435
: ToolbarSize.normal,
@@ -536,6 +542,8 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings {
536542
await prefs.setBool('high_contrast', highContrast);
537543
await prefs.setBool('grid_view', gridView);
538544
await prefs.setBool('autosave', autosave);
545+
await prefs.setBool('delayed_autosave', delayedAutosave);
546+
await prefs.setInt('autosave_delay_seconds', autosaveDelaySeconds);
539547
await prefs.setString('toolbar_size', toolbarSize.name);
540548
await prefs.setInt('toolbar_rows', toolbarRows);
541549
await prefs.setBool('hide_cursor_while_drawing', hideCursorWhileDrawing);
@@ -1039,13 +1047,23 @@ class SettingsCubit extends Cubit<ButterflySettings>
10391047

10401048
Future<void> toggleGridView() => changeGridView(!state.gridView);
10411049

1042-
Future<void> changeAutosave(bool? value) {
1050+
Future<void> changeAutosave(bool? value, {bool delayed = false}) {
10431051
emit(
1044-
state.copyWith(autosave: value ?? true, showSaveButton: value == null),
1052+
state.copyWith(
1053+
autosave: value ?? true,
1054+
showSaveButton: value == null,
1055+
delayedAutosave: delayed,
1056+
),
10451057
);
10461058
return save();
10471059
}
10481060

1061+
Future<void> changeAutosaveDelaySeconds(int seconds) {
1062+
final normalized = seconds.clamp(1, 3600);
1063+
emit(state.copyWith(autosaveDelaySeconds: normalized));
1064+
return save();
1065+
}
1066+
10491067
Future<void> changeToolbarRows(int value) {
10501068
emit(state.copyWith(toolbarRows: value));
10511069
return save();

app/lib/cubits/settings.freezed.dart

Lines changed: 20 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/cubits/settings.g.dart

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/l10n/app_en.arb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,5 +706,11 @@
706706
"ignorePressure": "Ignore pressure",
707707
"ignoreFirstPressureDescription": "On some devices, the first pressure value is not accurate. This setting will ignore the first pressure value and use the pressure of the second event instead.",
708708
"temporary": "Temporary",
709-
"simpleToolbarVisibility": "Simple toolbar visibility"
709+
"simpleToolbarVisibility": "Simple toolbar visibility",
710+
"autosaveDelay": "Autosave delay",
711+
"saved": "Saved",
712+
"saving": "Saving...",
713+
"readOnly": "Read only",
714+
"saveDelayed": "Save delayed",
715+
"unsaved": "Unsaved"
710716
}

app/lib/settings/behaviors.dart

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ class BehaviorsSettingsPage extends StatelessWidget {
6767
),
6868
subtitle: Text(
6969
state.autosave
70-
? state.showSaveButton
70+
? state.delayedAutosave
71+
? AppLocalizations.of(context).delay
72+
: state.showSaveButton
7173
? AppLocalizations.of(
7274
context,
7375
).yesButShowButtons
@@ -76,6 +78,21 @@ class BehaviorsSettingsPage extends StatelessWidget {
7678
),
7779
onTap: () => _openAutosaveModal(context),
7880
),
81+
if (state.autosave && state.delayedAutosave)
82+
ExactSlider(
83+
leading: const PhosphorIcon(PhosphorIconsLight.clock),
84+
header: Text(
85+
AppLocalizations.of(context).autosaveDelay,
86+
),
87+
value: state.autosaveDelaySeconds.toDouble(),
88+
min: 1,
89+
max: 10,
90+
defaultValue: 5,
91+
fractionDigits: 0,
92+
onChangeEnd: (value) => context
93+
.read<SettingsCubit>()
94+
.changeAutosaveDelaySeconds(value.toInt()),
95+
),
7996
ListTile(
8097
title: Text(AppLocalizations.of(context).onStartup),
8198
subtitle: Text(
@@ -228,27 +245,34 @@ class BehaviorsSettingsPage extends StatelessWidget {
228245
final cubit = context.read<SettingsCubit>();
229246
final autosave = cubit.state.autosave;
230247
final showSaveButton = cubit.state.showSaveButton;
248+
final delayed = cubit.state.delayedAutosave;
231249

232250
showLeapBottomSheet(
233251
context: context,
234252
titleBuilder: (context) => Text(AppLocalizations.of(context).autosave),
235253
childrenBuilder: (context) {
236-
void changeAutosave(bool? autosave) {
237-
cubit.changeAutosave(autosave);
254+
void changeAutosave(bool? autosave, {bool delayed = false}) {
255+
cubit.changeAutosave(autosave, delayed: delayed);
238256
Navigator.of(context).pop();
239257
}
240258

241259
return [
242260
ListTile(
243261
title: Text(AppLocalizations.of(context).yes),
244262
leading: Icon(PhosphorIconsLight.check),
245-
selected: autosave && showSaveButton == false,
263+
selected: autosave && !showSaveButton && !delayed,
246264
onTap: () => changeAutosave(true),
247265
),
266+
ListTile(
267+
title: Text(AppLocalizations.of(context).delay),
268+
leading: Icon(PhosphorIconsLight.clock),
269+
selected: autosave && delayed,
270+
onTap: () => changeAutosave(null, delayed: true),
271+
),
248272
ListTile(
249273
title: Text(AppLocalizations.of(context).yesButShowButtons),
250274
leading: Icon(PhosphorIconsLight.question),
251-
selected: autosave && showSaveButton,
275+
selected: autosave && showSaveButton && !delayed,
252276
onTap: () => changeAutosave(null),
253277
),
254278
ListTile(

app/lib/views/app_bar.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,20 @@ class _AppBarTitleState extends State<_AppBarTitle> {
351351
SaveState.unsaved ||
352352
SaveState.absoluteRead => PhosphorIconsLight.floppyDisk,
353353
SaveState.saving => PhosphorIconsLight.download,
354+
SaveState.delay => PhosphorIconsLight.clock,
354355
});
356+
String tooltip = switch (currentIndex.saved) {
357+
SaveState.saved => AppLocalizations.of(context).saved,
358+
SaveState.unsaved => AppLocalizations.of(context).unsaved,
359+
SaveState.absoluteRead => AppLocalizations.of(
360+
context,
361+
).readOnly,
362+
SaveState.saving => AppLocalizations.of(context).saving,
363+
SaveState.delay => AppLocalizations.of(context).saveDelayed,
364+
};
355365
return IconButton(
356366
icon: icon,
357-
tooltip: AppLocalizations.of(context).save,
367+
tooltip: tooltip,
358368
onPressed: () {
359369
Actions.maybeInvoke<SaveIntent>(
360370
context,

app/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1683,4 +1683,4 @@ packages:
16831683
version: "3.1.3"
16841684
sdks:
16851685
dart: ">=3.9.0 <4.0.0"
1686-
flutter: ">=3.35.6"
1686+
flutter: ">=3.35.5"

0 commit comments

Comments
 (0)