Skip to content

Commit

Permalink
Implemented import flow in example application
Browse files Browse the repository at this point in the history
Minor documentation improvements
  • Loading branch information
JaffaKetchup committed Aug 12, 2024
1 parent 348aac3 commit 169f82e
Show file tree
Hide file tree
Showing 16 changed files with 571 additions and 226 deletions.
9 changes: 9 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'src/screens/home/config_view/panels/stores/state/export_selection_provider.dart';
import 'src/screens/home/home.dart';
import 'src/screens/home/map_view/state/region_selection_provider.dart';
import 'src/screens/import/import.dart';
import 'src/screens/initialisation_error/initialisation_error.dart';
import 'src/screens/store_editor/store_editor.dart';
import 'src/shared/misc/shared_preferences.dart';
Expand Down Expand Up @@ -51,6 +52,14 @@ class _AppContainer extends StatelessWidget {
fullscreenDialog: true,
),
),
ImportPopup.route: (
std: null,
custom: (context, settings) => MaterialPageRoute(
builder: (context) => const ImportPopup(),
settings: settings,
fullscreenDialog: true,
),
),
/*
ProfileScreen.route: (
std: (BuildContext context) => const ProfileScreen(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class ExportStoresButton extends StatelessWidget {
final fileNameTime =
DateTime.now().toString().split('.').first.replaceAll(':', '-');

late final String filePath;
final String filePath;
late final String tempDir;
if (Platform.isAndroid || Platform.isIOS) {
tempDir = p.join(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';

import '../../../../../import/import.dart';
import '../../../../../store_editor/store_editor.dart';

class NewStoreButton extends StatelessWidget {
Expand Down Expand Up @@ -28,7 +29,7 @@ class NewStoreButton extends StatelessWidget {
IconButton.outlined(
icon: const Icon(Icons.file_open),
tooltip: 'Import store',
onPressed: () {},
onPressed: () => ImportPopup.start(context),
),
],
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';

import '../../../../../import/import.dart';
import '../../../../../store_editor/store_editor.dart';

class NoStores extends StatelessWidget {
Expand Down Expand Up @@ -44,7 +45,7 @@ class NoStores extends StatelessWidget {
height: 42,
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () {},
onPressed: () => ImportPopup.start(context),
icon: const Icon(Icons.file_open),
label: const Text('Import a store'),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,8 @@ class _StoreTileState extends State<StoreTile> {
}

Future<void> _emptyStore() async {
_toolsAutoHiderTimer?.cancel();
setState(() => _toolsEmptyLoading = true);
await widget.store.manage.reset();
await _hideTools();
setState(() => _toolsEmptyLoading = false);
}

Expand Down
3 changes: 2 additions & 1 deletion example/lib/src/screens/home/map_view/map_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ class _MapViewState extends State<MapView> with TickerProviderStateMixin {
stream: _storesStream,
builder: (context, snapshot) {
if (snapshot.data == null) {
return const AbsorbPointer(child: LoadingIndicator('Preparing map'));
return const AbsorbPointer(
child: SharedLoadingIndicator('Preparing map'));
}

final stores = snapshot.data!;
Expand Down
148 changes: 148 additions & 0 deletions example/lib/src/screens/import/import.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import 'dart:async';
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

import 'stages/complete.dart';
import 'stages/error.dart';
import 'stages/loading.dart';
import 'stages/progress.dart';
import 'stages/selection.dart';

class ImportPopup extends StatefulWidget {
const ImportPopup({super.key});

static const String route = '/import';

static Future<void> start(BuildContext context) async {
final pickerResult = Platform.isAndroid || Platform.isIOS
? FilePicker.platform.pickFiles()
: FilePicker.platform.pickFiles(
dialogTitle: 'Import Archive',
type: FileType.custom,
allowedExtensions: ['fmtc'],
);
final filePath = (await pickerResult)?.paths.single;

if (filePath == null || !context.mounted) return;

await Navigator.of(context).pushNamed(
ImportPopup.route,
arguments: filePath,
);
}

@override
State<ImportPopup> createState() => _ImportPopupState();
}

class _ImportPopupState extends State<ImportPopup> {
RootExternal? fmtcExternal;

int stage = 1;

late Object error; // Stage 0

late Map<String, bool> availableStores; // Stage 1 -> 2

late Set<String> selectedStores; // Stage 2 -> 3
late ImportConflictStrategy conflictStrategy; // Stage 2 -> 3

late int importTilesResult; // Stage 3 -> 4
late Duration importDuration; // Stage 3 -> 4

@override
void didChangeDependencies() {
super.didChangeDependencies();

fmtcExternal ??= FMTCRoot.external(
pathToArchive: ModalRoute.of(context)!.settings.arguments! as String,
);
}

@override
Widget build(BuildContext context) => PopScope(
canPop: stage != 3,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"We don't recommend leaving this screen while the import is "
'in progress',
),
),
);
}
},
child: Scaffold(
appBar: AppBar(
title: const Text('Import Archive'),
automaticallyImplyLeading: stage != 3,
elevation: 1,
),
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeInOut,
transitionBuilder: (child, animation) => SlideTransition(
position: (animation.value == 1
? Tween(begin: const Offset(-1, 0), end: Offset.zero)
: Tween(begin: const Offset(1, 0), end: Offset.zero))
.animate(animation),
child: child,
),
child: switch (stage) {
0 => ImportErrorStage(error: error),
1 => ImportLoadingStage(
fmtcExternal: fmtcExternal!,
nextStage: Completer()
..future.then(
(availableStores) => setState(() {
this.availableStores = availableStores;
stage++;
}),
onError: (err) => setState(() {
error = err;
stage = 0;
}),
),
),
2 => ImportSelectionStage(
fmtcExternal: fmtcExternal!,
availableStores: availableStores,
nextStage: (selectedStores, conflictStrategy) => setState(() {
this.selectedStores = selectedStores;
this.conflictStrategy = conflictStrategy;
stage++;
}),
),
3 => ImportProgressStage(
fmtcExternal: fmtcExternal!,
selectedStores: selectedStores,
conflictStrategy: conflictStrategy,
nextStage: Completer()
..future.then(
(result) => setState(() {
importTilesResult = result.tiles;
importDuration = result.duration;
stage++;
}),
onError: (err) => setState(() {
error = err;
stage = 0;
}),
),
),
4 => ImportCompleteStage(
tiles: importTilesResult,
duration: importDuration,
),
_ => throw UnimplementedError(),
},
),
),
);
}
45 changes: 45 additions & 0 deletions example/lib/src/screens/import/stages/complete.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';

class ImportCompleteStage extends StatelessWidget {
const ImportCompleteStage({
super.key,
required this.tiles,
required this.duration,
});

final int tiles;
final Duration duration;

@override
Widget build(BuildContext context) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(99),
color: Colors.green,
),
child: const Padding(
padding: EdgeInsets.all(12),
child: Icon(Icons.done_all, size: 48, color: Colors.white),
),
),
const SizedBox(height: 16),
Text(
'Successfully imported $tiles tiles in $duration!',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
FilledButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text(
'Exit',
textAlign: TextAlign.center,
),
),
],
),
);
}
49 changes: 49 additions & 0 deletions example/lib/src/screens/import/stages/error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';

class ImportErrorStage extends StatelessWidget {
const ImportErrorStage({super.key, required this.error});

final Object error;

@override
Widget build(BuildContext context) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.broken_image, size: 48),
const SizedBox(height: 6),
Text(
"Whoops, looks like we couldn't handle that file",
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
const Text(
"Ensure you selected the correct file, that it hasn't "
'been modified, and that it was exported from the same '
'version of FMTC.',
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
SelectableText(
'Type: ${error.runtimeType}',
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
SelectableText(
'Error: $error',
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
FilledButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text(
'Cancel',
textAlign: TextAlign.center,
),
),
],
),
);
}
Loading

0 comments on commit 169f82e

Please sign in to comment.