mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08:40 +00:00
fix #303 and bump version
This commit is contained in:
parent
fa85dbd6e2
commit
8a4632c4c4
14 changed files with 493 additions and 11 deletions
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
## 0.0.69
|
||||
|
||||
- Option to export and import memories
|
||||
- iOS support for ultra-wide-angle camera
|
||||
- Support Android Monochrome Icon
|
||||
- Multiple layout issues fixed
|
||||
- Multiple bug fixes
|
||||
|
||||
## 0.0.67
|
||||
|
|
|
|||
|
|
@ -11,6 +11,37 @@ PODS:
|
|||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.9):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.9)
|
||||
- DKImagePickerController/PhotoGallery (4.3.9):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.9)
|
||||
- DKPhotoGallery (0.0.19):
|
||||
- DKPhotoGallery/Core (= 0.0.19)
|
||||
- DKPhotoGallery/Model (= 0.0.19)
|
||||
- DKPhotoGallery/Preview (= 0.0.19)
|
||||
- DKPhotoGallery/Resource (= 0.0.19)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- emoji_picker_flutter (0.0.1):
|
||||
- Flutter
|
||||
- ffmpeg_kit_flutter_new (1.0.0):
|
||||
|
|
@ -18,6 +49,9 @@ PODS:
|
|||
- Flutter
|
||||
- ffmpeg_kit_flutter_new/full-gpl (1.0.0):
|
||||
- Flutter
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase (12.4.0):
|
||||
- Firebase/Core (= 12.4.0)
|
||||
- Firebase/Core (12.4.0):
|
||||
|
|
@ -249,6 +283,7 @@ PODS:
|
|||
- sqlite3/rtree
|
||||
- sqlite3/session
|
||||
- SwiftProtobuf (1.33.1)
|
||||
- SwiftyGif (5.4.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
|
|
@ -264,6 +299,7 @@ DEPENDENCIES:
|
|||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
||||
- ffmpeg_kit_flutter_new (from `.symlinks/plugins/ffmpeg_kit_flutter_new/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Firebase
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
|
|
@ -298,6 +334,8 @@ DEPENDENCIES:
|
|||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- Firebase
|
||||
- FirebaseAnalytics
|
||||
- FirebaseCore
|
||||
|
|
@ -318,6 +356,7 @@ SPEC REPOS:
|
|||
- Sentry
|
||||
- sqlite3
|
||||
- SwiftProtobuf
|
||||
- SwiftyGif
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
audio_waveforms:
|
||||
|
|
@ -336,6 +375,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
||||
ffmpeg_kit_flutter_new:
|
||||
:path: ".symlinks/plugins/ffmpeg_kit_flutter_new/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
firebase_core:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_messaging:
|
||||
|
|
@ -394,8 +435,11 @@ SPEC CHECKSUMS:
|
|||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
||||
ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
||||
firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594
|
||||
firebase_messaging: c17a29984eafce4b2997fe078bb0a9e0b06f5dde
|
||||
|
|
@ -439,6 +483,7 @@ SPEC CHECKSUMS:
|
|||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
SwiftProtobuf: 533a18409c3ca3a6156b2b1e46afd0f69e751aba
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||
|
||||
|
|
|
|||
|
|
@ -827,5 +827,7 @@
|
|||
"avatarSaveChangesStore": "Speichern",
|
||||
"avatarSaveChangesDiscard": "Verwerfen",
|
||||
"inProcess": "Wird verarbeitet",
|
||||
"draftMessage": "Entwurf"
|
||||
"draftMessage": "Entwurf",
|
||||
"exportMemories": "Memories exportieren (Beta)",
|
||||
"importMemories": "Memories importieren (Beta)"
|
||||
}
|
||||
|
|
@ -605,5 +605,7 @@
|
|||
"avatarSaveChangesStore": "Save",
|
||||
"avatarSaveChangesDiscard": "Discard",
|
||||
"inProcess": "In process",
|
||||
"draftMessage": "Draft"
|
||||
"draftMessage": "Draft",
|
||||
"exportMemories": "Export memories (Beta)",
|
||||
"importMemories": "Import memories (Beta)"
|
||||
}
|
||||
|
|
@ -2737,6 +2737,18 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Draft'**
|
||||
String get draftMessage;
|
||||
|
||||
/// No description provided for @exportMemories.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Export memories (Beta)'**
|
||||
String get exportMemories;
|
||||
|
||||
/// No description provided for @importMemories.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Import memories (Beta)'**
|
||||
String get importMemories;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1510,4 +1510,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get draftMessage => 'Entwurf';
|
||||
|
||||
@override
|
||||
String get exportMemories => 'Memories exportieren (Beta)';
|
||||
|
||||
@override
|
||||
String get importMemories => 'Memories importieren (Beta)';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1500,4 +1500,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get draftMessage => 'Draft';
|
||||
|
||||
@override
|
||||
String get exportMemories => 'Export memories (Beta)';
|
||||
|
||||
@override
|
||||
String get importMemories => 'Import memories (Beta)';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class MediaFileService {
|
|||
}
|
||||
|
||||
static Future<void> purgeTempFolder() async {
|
||||
final tempDirectory = MediaFileService._buildDirectoryPath(
|
||||
final tempDirectory = MediaFileService.buildDirectoryPath(
|
||||
'tmp',
|
||||
await getApplicationSupportDirectory(),
|
||||
);
|
||||
|
|
@ -239,7 +239,7 @@ class MediaFileService {
|
|||
await updateFromDB();
|
||||
}
|
||||
|
||||
static Directory _buildDirectoryPath(
|
||||
static Directory buildDirectoryPath(
|
||||
String directory,
|
||||
Directory applicationSupportDirectory,
|
||||
) {
|
||||
|
|
@ -275,7 +275,7 @@ class MediaFileService {
|
|||
}
|
||||
}
|
||||
final mediaBaseDir =
|
||||
_buildDirectoryPath(directory, applicationSupportDirectory);
|
||||
buildDirectoryPath(directory, applicationSupportDirectory);
|
||||
return File(
|
||||
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -46,10 +46,7 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
|||
_imageSaving = true;
|
||||
});
|
||||
|
||||
if (widget.mediaService.mediaFile.type == MediaType.image ||
|
||||
widget.mediaService.mediaFile.type == MediaType.gif) {
|
||||
await widget.storeImageAsOriginal();
|
||||
}
|
||||
await widget.storeImageAsOriginal();
|
||||
|
||||
String? res;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import 'package:twonly/globals.dart';
|
|||
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dart';
|
||||
import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart';
|
||||
|
||||
class DataAndStorageView extends StatefulWidget {
|
||||
const DataAndStorageView({super.key});
|
||||
|
|
@ -62,6 +64,36 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
|||
onChanged: (a) => toggleStoreInGallery(),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
context.lang.exportMemories,
|
||||
),
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return const ExportMediaView();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
context.lang.importMemories,
|
||||
),
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return const ImportMediaView();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: Text(
|
||||
|
|
|
|||
175
lib/src/views/settings/data_and_storage/export_media.view.dart
Normal file
175
lib/src/views/settings/data_and_storage/export_media.view.dart
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
|
||||
class ExportMediaView extends StatefulWidget {
|
||||
const ExportMediaView({super.key});
|
||||
|
||||
@override
|
||||
State<ExportMediaView> createState() => _ExportMediaViewState();
|
||||
}
|
||||
|
||||
class _ExportMediaViewState extends State<ExportMediaView> {
|
||||
double _progress = 0;
|
||||
String? _status;
|
||||
File? _zipFile;
|
||||
bool _isZipping = false;
|
||||
bool _zipSaved = false;
|
||||
|
||||
Future<Directory> _mediaFolder() async {
|
||||
final dir = MediaFileService.buildDirectoryPath(
|
||||
'stored',
|
||||
await getApplicationSupportDirectory(),
|
||||
);
|
||||
if (!dir.existsSync()) await dir.create(recursive: true);
|
||||
return dir;
|
||||
}
|
||||
|
||||
Future<void> _createZipFromMediaFolder() async {
|
||||
setState(() {
|
||||
_isZipping = true;
|
||||
_progress = 0.0;
|
||||
_status = 'Preparing...';
|
||||
_zipFile = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final folder = await _mediaFolder();
|
||||
final allFiles =
|
||||
folder.listSync(recursive: true).whereType<File>().toList();
|
||||
|
||||
final mediaFiles = allFiles.where((f) {
|
||||
final name = p.basename(f.path).toLowerCase();
|
||||
if (name.contains('thumbnail')) return false;
|
||||
return true;
|
||||
}).toList();
|
||||
|
||||
if (mediaFiles.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'No memories found.';
|
||||
_isZipping = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// compute total bytes for progress
|
||||
var totalBytes = 0;
|
||||
for (final f in mediaFiles) {
|
||||
totalBytes += await f.length();
|
||||
}
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final zipPath = p.join(
|
||||
tempDir.path,
|
||||
'memories.zip',
|
||||
);
|
||||
final encoder = ZipFileEncoder()..create(zipPath);
|
||||
|
||||
var processedBytes = 0;
|
||||
for (final f in mediaFiles) {
|
||||
final relative = p.relative(f.path, from: folder.path);
|
||||
setState(() {
|
||||
_status = 'Adding $relative';
|
||||
});
|
||||
|
||||
// ZipFileEncoder doesn't give per-file progress; update after adding.
|
||||
await encoder.addFile(f, relative);
|
||||
|
||||
processedBytes += await f.length();
|
||||
setState(() {
|
||||
_progress = totalBytes > 0 ? processedBytes / totalBytes : 0.0;
|
||||
});
|
||||
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
);
|
||||
}
|
||||
|
||||
await encoder.close();
|
||||
|
||||
setState(() {
|
||||
_zipFile = File(zipPath);
|
||||
_status = 'ZIP created: ${p.basename(zipPath)}';
|
||||
_progress = 1.0;
|
||||
_isZipping = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Error: $e';
|
||||
_isZipping = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveZip() async {
|
||||
if (_zipFile == null) return;
|
||||
try {
|
||||
final outputFile = await FilePicker.platform.saveFile(
|
||||
dialogTitle: 'Save your memories to desired location',
|
||||
fileName: p.basename(_zipFile!.path),
|
||||
bytes: _zipFile!.readAsBytesSync(),
|
||||
);
|
||||
if (outputFile == null) return;
|
||||
_zipSaved = true;
|
||||
_status = 'ZIP stored: ${p.basename(_zipFile!.path)}';
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
setState(() => _status = 'Save failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Export memories'),
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'Here, you can export all you memories.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_isZipping || _zipFile != null)
|
||||
LinearProgressIndicator(
|
||||
value: _isZipping ? _progress : (_zipFile != null ? 1.0 : 0.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_status != null)
|
||||
Text(
|
||||
_status!,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_zipFile == null)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.archive),
|
||||
label: Text(
|
||||
_isZipping ? 'Zipping...' : 'Create ZIP from mediafiles',
|
||||
),
|
||||
onPressed: _isZipping ? null : _createZipFromMediaFolder,
|
||||
)
|
||||
else if (!_zipSaved)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.save_alt),
|
||||
label: const Text('Save ZIP'),
|
||||
onPressed: (_zipFile != null && !_isZipping) ? _saveZip : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
192
lib/src/views/settings/data_and_storage/import_media.view.dart
Normal file
192
lib/src/views/settings/data_and_storage/import_media.view.dart
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import 'dart:io';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
|
||||
class ImportMediaView extends StatefulWidget {
|
||||
const ImportMediaView({super.key});
|
||||
|
||||
@override
|
||||
State<ImportMediaView> createState() => _ImportMediaViewState();
|
||||
}
|
||||
|
||||
class _ImportMediaViewState extends State<ImportMediaView> {
|
||||
double _progress = 0;
|
||||
String? _status;
|
||||
File? _zipFile;
|
||||
bool _isProcessing = false;
|
||||
|
||||
Future<void> _pickAndImportZip() async {
|
||||
setState(() {
|
||||
_status = null;
|
||||
_progress = 0;
|
||||
_zipFile = null;
|
||||
_isProcessing = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['zip'],
|
||||
);
|
||||
|
||||
if (result == null || result.files.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'No file selected.';
|
||||
_isProcessing = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final pickedPath = result.files.single.path;
|
||||
if (pickedPath == null) {
|
||||
setState(() {
|
||||
_status = 'Selected file has no path.';
|
||||
_isProcessing = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final pickedFile = File(pickedPath);
|
||||
if (!pickedFile.existsSync()) {
|
||||
setState(() {
|
||||
_status = 'Selected file does not exist.';
|
||||
_isProcessing = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_zipFile = pickedFile;
|
||||
_status = 'Selected ${p.basename(pickedPath)}';
|
||||
});
|
||||
|
||||
await _extractZipToMediaFolder(pickedFile);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Error: $e';
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _extractZipToMediaFolder(File zipFile) async {
|
||||
setState(() {
|
||||
_status = 'Reading archive...';
|
||||
_progress = 0;
|
||||
});
|
||||
|
||||
try {
|
||||
// Read zip bytes and decode
|
||||
final bytes = await zipFile.readAsBytes();
|
||||
final archive = ZipDecoder().decodeBytes(bytes);
|
||||
|
||||
// Optionally: compute total entries to show progress
|
||||
final entries = archive.where((e) => e.isFile).toList();
|
||||
final total = entries.length;
|
||||
var processed = 0;
|
||||
|
||||
for (final file in entries) {
|
||||
if (!file.isFile || file.isSymbolicLink) continue;
|
||||
|
||||
final extSplit = file.name.split('.');
|
||||
if (extSplit.isEmpty) continue;
|
||||
final ext = extSplit.last;
|
||||
|
||||
late MediaType type;
|
||||
switch (ext) {
|
||||
case 'webp':
|
||||
type = MediaType.image;
|
||||
case 'mp4':
|
||||
type = MediaType.video;
|
||||
case 'gif':
|
||||
type = MediaType.gif;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||
MediaFilesCompanion(
|
||||
type: Value(type),
|
||||
createdAt: Value(file.lastModDateTime),
|
||||
stored: const Value(true),
|
||||
),
|
||||
);
|
||||
final mediaService = await MediaFileService.fromMedia(mediaFile!);
|
||||
await mediaService.storedPath.writeAsBytes(file.content);
|
||||
|
||||
processed++;
|
||||
setState(() {
|
||||
_progress = total > 0 ? processed / total : 0;
|
||||
_status = 'Imported ${file.name}';
|
||||
});
|
||||
|
||||
// allow UI to update for large archives
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_status = 'Import complete. ${entries.length} entries processed.';
|
||||
_isProcessing = false;
|
||||
_progress = 1;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Extraction failed: $e';
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Import memories'),
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'Here, you can import exported memories.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_isProcessing || _zipFile != null)
|
||||
LinearProgressIndicator(
|
||||
value:
|
||||
_isProcessing ? _progress : (_zipFile != null ? 1.0 : 0.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_status != null)
|
||||
Text(
|
||||
_status!,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.file_upload),
|
||||
label: Text(
|
||||
_isProcessing
|
||||
? 'Processing...'
|
||||
: 'Select memories.zip to import',
|
||||
),
|
||||
onPressed: _isProcessing ? null : _pickAndImportZip,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
10
pubspec.lock
10
pubspec.lock
|
|
@ -34,7 +34,7 @@ packages:
|
|||
source: hosted
|
||||
version: "8.4.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: archive
|
||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
||||
|
|
@ -442,6 +442,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: f8f4ea435f791ab1f817b4e338ed958cb3d04ba43d6736ffc39958d950754967
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.6"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
|||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.0.68+68
|
||||
version: 0.0.69+69
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
|
||||
dependencies:
|
||||
archive: ^4.0.7
|
||||
audio_waveforms: ^1.3.0
|
||||
avatar_maker: ^0.4.0
|
||||
background_downloader: ^9.2.2
|
||||
|
|
@ -23,6 +24,7 @@ dependencies:
|
|||
drift_flutter: ^0.2.4
|
||||
emoji_picker_flutter: ^4.3.0
|
||||
ffmpeg_kit_flutter_new: ^4.1.0
|
||||
file_picker: ^10.3.6
|
||||
firebase_core: ^4.2.0
|
||||
firebase_messaging: ^16.0.3
|
||||
fixnum: ^1.1.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue