diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f4c4e3..ed772f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,14 @@
# Mobi Sync Client
An App for wireless syncing of photos and videos from devices to home server. (https://mobisync.eu)
+
+## 1.0.9 Release notes (2024-09-02)
+
+### Enhancements
+* Files synced from the newest to the oldest
+* Requesting permissions for managing external storage
+* Fixed not responding buttons in release
+
## 1.0.8 Release notes (2024-08-23)
### Enhancements
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 8f42de4..104e620 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -14,12 +14,12 @@ if (localPropertiesFile.exists()) {
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
- flutterVersionCode = '108'
+ flutterVersionCode = '109'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
- flutterVersionName = '1.0.8'
+ flutterVersionName = '1.0.9'
}
def keystoreProperties = new Properties()
@@ -72,6 +72,11 @@ android {
// Signing with the debug keys for now,
// so `flutter run --release` works.
signingConfig signingConfigs.release
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile(
+ 'proguard-android-optimize.txt'),
+ 'proguard-rules.pro'
}
}
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index f27f892..d25e242 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,16 +1,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/config/router/app_router.dart b/lib/config/router/app_router.dart
index 568278f..5bde55e 100644
--- a/lib/config/router/app_router.dart
+++ b/lib/config/router/app_router.dart
@@ -17,8 +17,8 @@ limitations under the License.
import 'package:go_router/go_router.dart';
import 'package:sync_client/screens/screens.dart';
-GoRouter getAppRouter(bool isAuthenticated) {
- return GoRouter(initialLocation: isAuthenticated ? '/' : "/login", routes: [
+GoRouter getAppRouter() {
+ return GoRouter(initialLocation: '/', routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
diff --git a/lib/core/impl/background.dart b/lib/core/impl/background.dart
index 5457109..128003b 100644
--- a/lib/core/impl/background.dart
+++ b/lib/core/impl/background.dart
@@ -60,7 +60,8 @@ class BackgroundAction implements IAction {
Future _uploadFiles(StreamController syncFileController,
List files, String userName) async {
- for (var file in files) {
+ final reversedFiles = files.reversed;
+ for (FileSystemEntity file in reversedFiles) {
if (!FileSystemEntity.isDirectorySync(file.path)) {
DateTime lastFileDate = await File(file.path).lastModified();
String dateClassifier = "${lastFileDate.year}-${lastFileDate.month}";
@@ -69,10 +70,20 @@ class BackgroundAction implements IAction {
f.filename.toLowerCase() == file.path.toLowerCase() &&
(f.errorMessage ?? "").trim() == "" ||
f.failedAttempts > 3);
- if (!fileHadBeenSynced) {
- if (p.extension(file.path).isNotEmpty) {
- var syncedFile = await _transfers.sendFile(
- syncFileController, file.path, userName, dateClassifier);
+ if (fileHadBeenSynced) {
+ if (!syncFileController.isClosed) {
+ syncFileController.add(SyncedFile(file.path));
+ }
+ if (currentDeviceSettings.deleteLocalFilesEnabled ?? false) {
+ await File(file.path).delete();
+ currentDeviceSettings.syncedFiles.removeWhere(
+ (f) => f.filename.toLowerCase() == file.path.toLowerCase());
+ }
+ } else {
+ if (file is File && p.extension(file.path).isNotEmpty) {
+ final fileLength = file.lengthSync();
+ var syncedFile = await _transfers.sendFile(syncFileController,
+ file.path, userName, dateClassifier, fileLength);
if (syncedFile != null) {
if ((currentDeviceSettings.deleteLocalFilesEnabled ?? false) &&
diff --git a/lib/core/impl/server_api.dart b/lib/core/impl/server_api.dart
index 37b90cb..d5bdc3b 100644
--- a/lib/core/impl/server_api.dart
+++ b/lib/core/impl/server_api.dart
@@ -37,9 +37,10 @@ Future?> apiGetFolders(String userName, String deviceId) async {
if (response.statusCode == 200) {
final List result = json.decode(response.body);
- final List folders = result
+ final List folders = result.reversed
.map((item) => NetFolder(item["Year"],
subFolders: (List.from(item["Months"])
+ .reversed
.map((m) => NetFolder(m))
.toList())))
.toList();
@@ -70,7 +71,8 @@ Future> apiGetFiles(
if (response.statusCode == 200) {
final List result = json.decode(response.body);
- final List files = result.map((item) => item.toString()).toList();
+ final List files =
+ result.reversed.map((item) => item.toString()).toList();
return files;
}
} catch (err) {
diff --git a/lib/core/impl/transfers.dart b/lib/core/impl/transfers.dart
index dc8fe8f..bfebf9d 100644
--- a/lib/core/impl/transfers.dart
+++ b/lib/core/impl/transfers.dart
@@ -27,13 +27,18 @@ import 'request_utils.dart';
class Transfers {
Transfers();
- Future sendFile(StreamController syncFileController,
- String filename, String userName, String dateClassifier) async {
+ Future sendFile(
+ StreamController syncFileController,
+ String filename,
+ String userName,
+ String dateClassifier,
+ int fileLength) async {
SyncedFile? result;
var request = MultipartRequest('POST', getUrl("upload"));
final hdr = {
"user": utf8.encode(userName).toString(),
- "date": dateClassifier
+ "date": dateClassifier,
+ "fileLength": fileLength.toString()
};
request.headers.addEntries(hdr.entries);
diff --git a/lib/main.dart b/lib/main.dart
index 1cf763e..3dc350a 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -25,6 +25,7 @@ import 'package:permission_handler/permission_handler.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+ await loadDeviceSettings();
if (Platform.isAndroid) {
final mediaStorePlugin = MediaStore();
await mediaStorePlugin.getPlatformSDKInt();
@@ -33,7 +34,6 @@ void main() async {
await requestPermissions();
}
- await loadDeviceSettings();
runApp(const BlocProviders());
}
@@ -41,6 +41,8 @@ Future requestPermissions() async {
List permissions = [
Permission.storage,
];
+ permissions.add(Permission.manageExternalStorage);
+ permissions.add(Permission.accessMediaLocation);
permissions.add(Permission.storage);
permissions.add(Permission.photos);
permissions.add(Permission.audio);
@@ -75,11 +77,10 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final deviceService = context.read();
return MaterialApp.router(
title: 'Mobi Sync Client',
debugShowCheckedModeBanner: false,
- routerConfig: getAppRouter(deviceService.isAuthenticated()),
+ routerConfig: getAppRouter(),
theme: AppTheme.getTheme(context));
}
}
diff --git a/lib/screens/components/edit_server.dart b/lib/screens/components/edit_server.dart
index 19a3689..182286c 100644
--- a/lib/screens/components/edit_server.dart
+++ b/lib/screens/components/edit_server.dart
@@ -65,8 +65,10 @@ class EditServerFormState extends State {
String newServer) async {
if (_formKey.currentState!.validate()) {
await deviceService.edit((state) {
+ if (state.serverUrl != newServer) {
+ state.syncedFiles.clear();
+ }
state.serverUrl = newServer;
- state.syncedFiles.clear();
state.lastErrorMessage = null;
state.lastSyncDateTime = null;
state.deleteLocalFilesEnabled = false;
diff --git a/lib/screens/components/folder_item.dart b/lib/screens/components/folder_item.dart
index 83783d3..63c0631 100644
--- a/lib/screens/components/folder_item.dart
+++ b/lib/screens/components/folder_item.dart
@@ -44,7 +44,6 @@ class FolderItem extends StatelessWidget {
if (menuItem == FolderMenuOption.delete) {
await deviceService.edit((state) {
state.mediaDirectories.remove(folder);
- state.syncedFiles.clear();
state.lastSyncDateTime = null;
state.lastErrorMessage = null;
});
diff --git a/lib/screens/components/status_widgets.dart b/lib/screens/components/status_widgets.dart
index f180bd6..fbe2766 100644
--- a/lib/screens/components/status_widgets.dart
+++ b/lib/screens/components/status_widgets.dart
@@ -29,7 +29,8 @@ List getStateWidgets(
List children;
if (snapshot.hasError) {
- children = _errorState(context, syncService, snapshot.error as CustomError);
+ children = _errorState(
+ context, syncService, deviceService, snapshot.error as CustomError);
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
@@ -45,15 +46,28 @@ List getStateWidgets(
return children;
}
-List _errorState(
- BuildContext context, SyncServicesCubit syncService, CustomError error) {
+List _errorState(BuildContext context, SyncServicesCubit syncService,
+ DeviceServicesCubit deviceService, CustomError error) {
return [
const Icon(Icons.error_outline, color: Colors.red, size: 30),
- Padding(
+ Center(
+ child: Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
'${error is SyncCanceledError ? "" : "Error: "}${error.message}'),
- ),
+ )),
+ okButton(context, "Stop", onPressed: () {
+ if (syncService.state != null) {
+ if (!syncService.state!.isClosed) {
+ syncService.state!.addError(SyncCanceledError());
+ syncService.state!.close();
+ syncService.reset();
+ }
+ }
+ deviceService.edit((state) {
+ state.isSyncing = false;
+ });
+ }),
];
}
diff --git a/lib/screens/deleting_enabled_screen.dart b/lib/screens/deleting_enabled_screen.dart
index 0f9bd4f..5485398 100644
--- a/lib/screens/deleting_enabled_screen.dart
+++ b/lib/screens/deleting_enabled_screen.dart
@@ -87,7 +87,6 @@ class _DeletingEnabledScreenView extends StatelessWidget {
await deviceService.edit((state) {
state.deleteLocalFilesEnabled =
!(state.deleteLocalFilesEnabled ?? false);
- state.syncedFiles.clear();
state.lastErrorMessage = null;
});
} else {
@@ -117,7 +116,6 @@ class _DeletingEnabledScreenView extends StatelessWidget {
await deviceService.edit((state) {
state.deleteLocalFilesEnabled =
!(state.deleteLocalFilesEnabled ?? false);
- state.syncedFiles.clear();
state.lastErrorMessage = null;
});
if (!context.mounted) return;
diff --git a/lib/screens/folders_list_screen.dart b/lib/screens/folders_list_screen.dart
index c2a1fe5..b3047d4 100644
--- a/lib/screens/folders_list_screen.dart
+++ b/lib/screens/folders_list_screen.dart
@@ -42,12 +42,10 @@ class FoldersListScreen extends StatelessWidget {
BuildContext context, DeviceServicesCubit deviceService) async {
String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
- if (selectedDirectory == null) {
- return;
- }
await deviceService.edit((state) {
- state.mediaDirectories.add(selectedDirectory);
- state.syncedFiles.clear();
+ if (selectedDirectory != null) {
+ state.mediaDirectories.add(selectedDirectory);
+ }
state.lastErrorMessage = null;
});
}
diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart
index a00d60e..44811f0 100644
--- a/lib/screens/home_screen.dart
+++ b/lib/screens/home_screen.dart
@@ -71,6 +71,9 @@ class HomeScreenState extends State {
}
Future> getAllFolders(DeviceServicesCubit deviceService) async {
+ if ((deviceService.state.serverUrl ?? "") == "") {
+ return [];
+ }
List? folders = await apiGetFolders(
deviceService.state.currentUser!.email, deviceService.state.id);
@@ -78,6 +81,17 @@ class HomeScreenState extends State {
return allFolders;
}
+ Future> getAllFiles(
+ DeviceServicesCubit deviceService, String folder) async {
+ if ((deviceService.state.serverUrl ?? "") == "") {
+ return [];
+ }
+ List? files = await apiGetFiles(
+ deviceService.state.currentUser!.email, deviceService.state.id, folder);
+
+ return files;
+ }
+
Widget itemsView(BuildContext context) {
final DeviceServicesCubit deviceService =
context.read();
@@ -90,51 +104,59 @@ class HomeScreenState extends State {
return Container(
margin: const EdgeInsets.only(
left: 10.0, right: 10.0, top: 30.0, bottom: 30.0),
- child: FutureBuilder>(
- future: getAllFolders(deviceService),
- builder: (context, snapshot) {
- if (snapshot.hasError) {
- return Text((snapshot.error as CustomError).message);
- } else {
- switch (snapshot.connectionState) {
- case ConnectionState.none:
- case ConnectionState.waiting:
- return const CircularProgressIndicator();
- case ConnectionState.active:
- case ConnectionState.done:
- if (snapshot.hasData) {
- final folders = snapshot.data!;
- return ListView(
- physics: const PageScrollPhysics(),
- children: photoGridWidgets(folders, deviceService));
+ child: ((deviceService.state.serverUrl ?? "") == "")
+ ? const Text(
+ "There is no files synced to the server or the server is not configured.")
+ : FutureBuilder>(
+ future: getAllFolders(deviceService),
+ builder: (context, snapshot) {
+ if (snapshot.hasError) {
+ return Text((snapshot.error as CustomError).message);
} else {
- return const Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Text(
- "There is no synced photos/videos from this device and nickname.",
- textAlign: TextAlign.center,
- ),
- Text(
- "Please go the menu and select 'Sync' to setup configurations.",
- textAlign: TextAlign.center,
- ),
- Padding(
- padding: EdgeInsets.only(top: 10),
- child: Text(
- "Go to MOBISYNC.EU for help.",
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 16, fontWeight: FontWeight.bold),
- ))
- ]));
+ switch (snapshot.connectionState) {
+ case ConnectionState.none:
+ case ConnectionState.waiting:
+ return const CircularProgressIndicator();
+ case ConnectionState.active:
+ case ConnectionState.done:
+ if (snapshot.hasData) {
+ final folders = snapshot.data!;
+ return folders.isEmpty
+ ? const Text(
+ "There is no files synced to the server or the server is not configured.")
+ : ListView(
+ physics: const PageScrollPhysics(),
+ children:
+ photoGridWidgets(folders, deviceService));
+ } else {
+ return const Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ "There is no synced photos/videos from this device and nickname.",
+ textAlign: TextAlign.center,
+ ),
+ Text(
+ "Please go the menu and select 'Sync' to setup configurations.",
+ textAlign: TextAlign.center,
+ ),
+ Padding(
+ padding: EdgeInsets.only(top: 10),
+ child: Text(
+ "Go to MOBISYNC.EU for help.",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold),
+ ))
+ ]));
+ }
+ }
}
- }
- }
- },
- ));
+ },
+ ));
}
List photoGridWidgets(
@@ -147,8 +169,7 @@ class HomeScreenState extends State {
child: Text(folder,
style: const TextStyle(fontWeight: FontWeight.bold)))));
result.add(FutureBuilder>(
- future: apiGetFiles(deviceService.state.currentUser!.email,
- deviceService.state.id, folder),
+ future: getAllFiles(deviceService, folder),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
diff --git a/lib/storage/storage.dart b/lib/storage/storage.dart
index 900e76f..b86250c 100644
--- a/lib/storage/storage.dart
+++ b/lib/storage/storage.dart
@@ -17,7 +17,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:uuid/uuid.dart';
-import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sync_client/storage/schema.dart';
@@ -29,15 +28,7 @@ late DeviceSettings currentDeviceSettings;
Future updateCurrentDevice(
DeviceSettings deviceSettings) async {
- final deviceInfoPlugin = DeviceInfoPlugin();
- final deviceInfo = await deviceInfoPlugin.deviceInfo;
-
- String? deviceId = deviceInfo.data["deviceId"];
- deviceId ??= deviceInfo.data["id"];
- deviceId ??= deviceInfo.data["systemGUID"];
- deviceId ??= const Uuid().v4();
- deviceSettings.id = deviceId;
- deviceSettings.model = deviceInfo.data["model"];
+ deviceSettings.id = const Uuid().v4();
return deviceSettings;
}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 80159e7..e777c67 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,10 +5,8 @@
import FlutterMacOS
import Foundation
-import device_info_plus
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
- DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}
diff --git a/macos/Podfile.lock b/macos/Podfile.lock
index 253aba1..afa6ff0 100644
--- a/macos/Podfile.lock
+++ b/macos/Podfile.lock
@@ -1,26 +1,20 @@
PODS:
- - device_info_plus (0.0.1):
- - FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
EXTERNAL SOURCES:
- device_info_plus:
- :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
SPEC CHECKSUMS:
- device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
diff --git a/pubspec.yaml b/pubspec.yaml
index df2d4b1..0af1b0f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,7 +2,7 @@ name: sync_client
description: Mobile and Desktop device application for automatically uploading image, video and audio files
publish_to: 'none'
-version: 1.0.8
+version: 1.0.9
environment:
sdk: '>=3.2.0 <5.0.0'
@@ -15,7 +15,6 @@ dependencies:
cupertino_icons: ^1.0.2
file_picker: ^8.0.6
- device_info_plus: ^10.1.0
http_parser: ^4.0.2
mime: ^1.0.4
path: ^1.8.3