mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
#157 new: icloud sync
This commit is contained in:
@@ -81,6 +81,7 @@
|
|||||||
E33A3E3F2A626DCE009744AB /* StatusWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusWidget.swift; sourceTree = "<group>"; };
|
E33A3E3F2A626DCE009744AB /* StatusWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusWidget.swift; sourceTree = "<group>"; };
|
||||||
E33A3E412A626DCE009744AB /* StatusWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = StatusWidget.intentdefinition; sourceTree = "<group>"; };
|
E33A3E412A626DCE009744AB /* StatusWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = StatusWidget.intentdefinition; sourceTree = "<group>"; };
|
||||||
E33A3E442A626DD0009744AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
E33A3E442A626DD0009744AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
E35C79672AAEDF57007577EC /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
|
||||||
E398BF6A29BDB34500FE4FD5 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
E398BF6A29BDB34500FE4FD5 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||||
E3DB67EC2A31FE200027B8CB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
E3DB67EC2A31FE200027B8CB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -151,6 +152,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E35C79672AAEDF57007577EC /* RunnerDebug.entitlements */,
|
||||||
E398BF6A29BDB34500FE4FD5 /* Runner.entitlements */,
|
E398BF6A29BDB34500FE4FD5 /* Runner.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
@@ -601,7 +603,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 539;
|
CURRENT_PROJECT_VERSION = 539;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
|
|||||||
20
ios/Runner/RunnerDebug.entitlements
Normal file
20
ios/Runner/RunnerDebug.entitlements
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>aps-environment</key>
|
||||||
|
<string>production</string>
|
||||||
|
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>iCloud.tech.lolli.serverbox</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.icloud-services</key>
|
||||||
|
<array>
|
||||||
|
<string>CloudDocuments</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>iCloud.tech.lolli.serverbox</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -119,4 +119,4 @@ Future<void> _rmDownloadApks() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> get _dlDir async => joinPath((await docDir).path, 'Download');
|
Future<String> get _dlDir async => joinPath(await docDir, 'Download');
|
||||||
|
|||||||
78
lib/core/utils/backup.dart
Normal file
78
lib/core/utils/backup.dart
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../../data/model/app/backup.dart';
|
||||||
|
import '../../data/res/path.dart';
|
||||||
|
import '../../data/store/docker.dart';
|
||||||
|
import '../../data/store/private_key.dart';
|
||||||
|
import '../../data/store/server.dart';
|
||||||
|
import '../../data/store/setting.dart';
|
||||||
|
import '../../data/store/snippet.dart';
|
||||||
|
import '../../locator.dart';
|
||||||
|
|
||||||
|
final _server = locator<ServerStore>();
|
||||||
|
final _snippet = locator<SnippetStore>();
|
||||||
|
final _privateKey = locator<PrivateKeyStore>();
|
||||||
|
final _dockerHosts = locator<DockerStore>();
|
||||||
|
final _setting = locator<SettingStore>();
|
||||||
|
|
||||||
|
Future<String> get backupPath async => '${await docDir}/srvbox_bak.json';
|
||||||
|
|
||||||
|
const backupFormatVersion = 1;
|
||||||
|
|
||||||
|
Future<void> backup() async {
|
||||||
|
final result = _diyEncrtpt(
|
||||||
|
json.encode(
|
||||||
|
Backup(
|
||||||
|
version: backupFormatVersion,
|
||||||
|
date: DateTime.now().toString().split('.').first,
|
||||||
|
spis: _server.fetch(),
|
||||||
|
snippets: _snippet.fetch(),
|
||||||
|
keys: _privateKey.fetch(),
|
||||||
|
dockerHosts: _dockerHosts.fetchAll(),
|
||||||
|
settings: _setting.toJson(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await File(await backupPath).writeAsString(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore(Backup backup) {
|
||||||
|
for (final s in backup.snippets) {
|
||||||
|
_snippet.put(s);
|
||||||
|
}
|
||||||
|
for (final s in backup.spis) {
|
||||||
|
_server.put(s);
|
||||||
|
}
|
||||||
|
for (final s in backup.keys) {
|
||||||
|
_privateKey.put(s);
|
||||||
|
}
|
||||||
|
for (final k in backup.dockerHosts.keys) {
|
||||||
|
final val = backup.dockerHosts[k];
|
||||||
|
if (val != null && val is String && val.isNotEmpty) {
|
||||||
|
_dockerHosts.put(k, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Backup> decodeBackup(String raw) async {
|
||||||
|
return await compute(_decode, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Backup _decode(String raw) {
|
||||||
|
final decrypted = _diyDecrypt(raw);
|
||||||
|
return Backup.fromJson(json.decode(decrypted));
|
||||||
|
}
|
||||||
|
|
||||||
|
String _diyEncrtpt(String raw) =>
|
||||||
|
json.encode(raw.codeUnits.map((e) => e * 2 + 1).toList(growable: false));
|
||||||
|
String _diyDecrypt(String raw) {
|
||||||
|
final list = json.decode(raw);
|
||||||
|
final sb = StringBuffer();
|
||||||
|
for (final e in list) {
|
||||||
|
sb.writeCharCode((e - 1) ~/ 2);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
159
lib/core/utils/icloud.dart
Normal file
159
lib/core/utils/icloud.dart
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:icloud_storage/icloud_storage.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
import '../../data/model/app/error.dart';
|
||||||
|
import '../../data/model/app/json.dart';
|
||||||
|
import '../../data/res/path.dart';
|
||||||
|
|
||||||
|
final _logger = Logger('iCloud');
|
||||||
|
|
||||||
|
class ICloud {
|
||||||
|
static const _containerId = 'iCloud.tech.lolli.serverbox';
|
||||||
|
|
||||||
|
const ICloud();
|
||||||
|
|
||||||
|
/// Upload file to iCloud
|
||||||
|
///
|
||||||
|
/// - [relativePath] is the path relative to [docDir],
|
||||||
|
/// must not starts with `/`
|
||||||
|
/// - [localPath] has higher priority than [relativePath], but only apply
|
||||||
|
/// to the local path instead of iCloud path
|
||||||
|
///
|
||||||
|
/// Return `null` if upload success, `ICloudErr` otherwise
|
||||||
|
static Future<ICloudErr?> upload({
|
||||||
|
required String relativePath,
|
||||||
|
String? localPath,
|
||||||
|
}) async {
|
||||||
|
final completer = Completer<ICloudErr?>();
|
||||||
|
await ICloudStorage.upload(
|
||||||
|
containerId: _containerId,
|
||||||
|
filePath: localPath ?? '${await docDir}/$relativePath',
|
||||||
|
destinationRelativePath: relativePath,
|
||||||
|
onProgress: (stream) {
|
||||||
|
stream.listen(
|
||||||
|
null,
|
||||||
|
onDone: () => completer.complete(null),
|
||||||
|
onError: (e) => completer.complete(
|
||||||
|
ICloudErr(type: ICloudErrType.generic, message: '$e'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<ICloudFile>> getAll() async {
|
||||||
|
return await ICloudStorage.gather(
|
||||||
|
containerId: _containerId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> delete(String relativePath) async {
|
||||||
|
await ICloudStorage.delete(
|
||||||
|
containerId: _containerId,
|
||||||
|
relativePath: relativePath,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download file from iCloud
|
||||||
|
///
|
||||||
|
/// - [relativePath] is the path relative to [docDir],
|
||||||
|
/// must not starts with `/`
|
||||||
|
/// - [localPath] has higher priority than [relativePath], but only apply
|
||||||
|
/// to the local path instead of iCloud path
|
||||||
|
///
|
||||||
|
/// Return `null` if upload success, `ICloudErr` otherwise
|
||||||
|
static Future<ICloudErr?> download({
|
||||||
|
required String relativePath,
|
||||||
|
String? localPath,
|
||||||
|
}) async {
|
||||||
|
final completer = Completer<ICloudErr?>();
|
||||||
|
await ICloudStorage.download(
|
||||||
|
containerId: _containerId,
|
||||||
|
relativePath: relativePath,
|
||||||
|
destinationFilePath: localPath ?? '${await docDir}/$relativePath',
|
||||||
|
onProgress: (stream) {
|
||||||
|
stream.listen(
|
||||||
|
null,
|
||||||
|
onDone: () => completer.complete(null),
|
||||||
|
onError: (e) => completer.complete(
|
||||||
|
ICloudErr(type: ICloudErrType.generic, message: '$e'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sync file between iCloud and local
|
||||||
|
///
|
||||||
|
/// - [relativePath] is the path relative to [docDir],
|
||||||
|
/// must not starts with `/`
|
||||||
|
///
|
||||||
|
/// Return `null` if upload success, `ICloudErr` otherwise
|
||||||
|
///
|
||||||
|
/// TODO: consider merge strategy, use [SyncAble] and [JsonSerializable]
|
||||||
|
static Future<Iterable<ICloudErr>?> sync({
|
||||||
|
required Iterable<String> relativePaths,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final errs = <ICloudErr>[];
|
||||||
|
|
||||||
|
final allFiles = await getAll();
|
||||||
|
// remove files not in relativePaths
|
||||||
|
allFiles.removeWhere((e) => !relativePaths.contains(e.relativePath));
|
||||||
|
|
||||||
|
// upload files not in iCloud
|
||||||
|
final missed = relativePaths.where((e) {
|
||||||
|
return !allFiles.any((f) => f.relativePath == e);
|
||||||
|
});
|
||||||
|
for (final e in missed) {
|
||||||
|
final err = await upload(relativePath: e);
|
||||||
|
if (err != null) {
|
||||||
|
errs.add(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final docPath = await docDir;
|
||||||
|
// compare files in iCloud and local
|
||||||
|
for (final file in allFiles) {
|
||||||
|
final relativePath = file.relativePath;
|
||||||
|
|
||||||
|
/// Check date
|
||||||
|
final localFile = File('$docPath/$relativePath');
|
||||||
|
if (!localFile.existsSync()) {
|
||||||
|
/// Local file not found, download remote file
|
||||||
|
final err = await download(relativePath: relativePath);
|
||||||
|
if (err != null) {
|
||||||
|
errs.add(err);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final localDate = await localFile.lastModified();
|
||||||
|
if (file.contentChangeDate.isBefore(localDate)) {
|
||||||
|
/// Local is newer than remote, so upload local file
|
||||||
|
final err = await upload(relativePath: relativePath);
|
||||||
|
if (err != null) {
|
||||||
|
errs.add(err);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remote is newer than local, so download remote
|
||||||
|
final err = await download(relativePath: relativePath);
|
||||||
|
if (err != null) {
|
||||||
|
errs.add(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.info('Errs: $errs');
|
||||||
|
|
||||||
|
return errs.isEmpty ? null : errs;
|
||||||
|
} catch (e, s) {
|
||||||
|
_logger.warning('Sync failed: $relativePaths', e, s);
|
||||||
|
return [ICloudErr(type: ICloudErrType.generic, message: '$e')];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,15 @@ void showSnackBarWithAction(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showRestartSnackbar(BuildContext context, S s) {
|
||||||
|
showSnackBarWithAction(
|
||||||
|
context,
|
||||||
|
'${s.success}\n${s.needRestart}',
|
||||||
|
s.restart,
|
||||||
|
() => rebuildAll(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> openUrl(String url) async {
|
Future<bool> openUrl(String url) async {
|
||||||
return await launchUrl(url.uri, mode: LaunchMode.externalApplication);
|
return await launchUrl(url.uri, mode: LaunchMode.externalApplication);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,3 +52,19 @@ class DockerErr extends Err<DockerErrType> {
|
|||||||
return 'DockerErr<$type>: $message';
|
return 'DockerErr<$type>: $message';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ICloudErrType {
|
||||||
|
generic,
|
||||||
|
notFound,
|
||||||
|
multipleFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ICloudErr extends Err<ICloudErrType> {
|
||||||
|
ICloudErr({required ICloudErrType type, String? message})
|
||||||
|
: super(from: ErrFrom.docker, type: type, message: message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ICloudErr<$type>: $message';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
7
lib/data/model/app/json.dart
Normal file
7
lib/data/model/app/json.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
abstract class JsonSerializable<T> {
|
||||||
|
/// Convert [this] to json
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
/// Create [this] from json
|
||||||
|
T fromJson(Map<String, dynamic> json);
|
||||||
|
}
|
||||||
9
lib/data/model/app/syncable.dart
Normal file
9
lib/data/model/app/syncable.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
abstract class SyncAble<T> {
|
||||||
|
/// If [other] is newer than [this] then return true,
|
||||||
|
/// else return false
|
||||||
|
bool needSync(T other);
|
||||||
|
|
||||||
|
/// Merge [other] into [this],
|
||||||
|
/// return [this] after merge
|
||||||
|
T merge(T other);
|
||||||
|
}
|
||||||
@@ -3,23 +3,43 @@ import 'dart:io';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:toolbox/core/utils/platform.dart';
|
import 'package:toolbox/core/utils/platform.dart';
|
||||||
|
|
||||||
Future<Directory> get docDir async {
|
String? _docDir;
|
||||||
|
String? _sftpDir;
|
||||||
|
String? _fontDir;
|
||||||
|
|
||||||
|
Future<String> get docDir async {
|
||||||
|
if (_docDir != null) {
|
||||||
|
return _docDir!;
|
||||||
|
}
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
final dir = await getExternalStorageDirectory();
|
final dir = await getExternalStorageDirectory();
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
return dir;
|
_docDir = dir.path;
|
||||||
|
return dir.path;
|
||||||
}
|
}
|
||||||
// fallthrough to getApplicationDocumentsDirectory
|
// fallthrough to getApplicationDocumentsDirectory
|
||||||
}
|
}
|
||||||
return await getApplicationDocumentsDirectory();
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
_docDir = dir.path;
|
||||||
|
return dir.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Directory> get sftpDir async {
|
Future<String> get sftpDir async {
|
||||||
final dir = Directory('${(await docDir).path}/sftp');
|
if (_sftpDir != null) {
|
||||||
return dir.create(recursive: true);
|
return _sftpDir!;
|
||||||
|
}
|
||||||
|
_sftpDir = '${await docDir}/sftp';
|
||||||
|
final dir = Directory(_sftpDir!);
|
||||||
|
await dir.create(recursive: true);
|
||||||
|
return _sftpDir!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Directory> get fontDir async {
|
Future<String> get fontDir async {
|
||||||
final dir = Directory('${(await docDir).path}/font');
|
if (_fontDir != null) {
|
||||||
return dir.create(recursive: true);
|
return _fontDir!;
|
||||||
|
}
|
||||||
|
_fontDir = '${await docDir}/font';
|
||||||
|
final dir = Directory(_fontDir!);
|
||||||
|
await dir.create(recursive: true);
|
||||||
|
return _fontDir!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,13 @@ class SettingStore extends PersistentStore {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Only valid on iOS and macOS
|
||||||
|
late final icloudSync = StoreProperty(
|
||||||
|
box,
|
||||||
|
'icloudSync',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
// Never show these settings for users
|
// Never show these settings for users
|
||||||
// Guide for these settings:
|
// Guide for these settings:
|
||||||
// - key should start with `_` and be shorter as possible
|
// - key should start with `_` and be shorter as possible
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
@@ -6,6 +7,8 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:macos_window_utils/window_manipulator.dart';
|
import 'package:macos_window_utils/window_manipulator.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:toolbox/core/utils/icloud.dart';
|
||||||
|
import 'package:toolbox/data/res/path.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'core/analysis.dart';
|
import 'core/analysis.dart';
|
||||||
@@ -93,14 +96,18 @@ Future<void> initApp() async {
|
|||||||
loadFontFile(settings.fontPath.fetch());
|
loadFontFile(settings.fontPath.fetch());
|
||||||
primaryColor = Color(settings.primaryColor.fetch());
|
primaryColor = Color(settings.primaryColor.fetch());
|
||||||
|
|
||||||
// Android only
|
if (isIOS || isMacOS) {
|
||||||
if (!isAndroid) return;
|
if (settings.icloudSync.fetch()) _syncApple();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAndroid) {
|
||||||
// Only start service when [bgRun] is true.
|
// Only start service when [bgRun] is true.
|
||||||
if (locator<SettingStore>().bgRun.fetch()) {
|
if (locator<SettingStore>().bgRun.fetch()) {
|
||||||
bgRunChannel.invokeMethod('startService');
|
bgRunChannel.invokeMethod('startService');
|
||||||
}
|
}
|
||||||
// SharedPreferences is only used on Android for saving home widgets settings.
|
// SharedPreferences is only used on Android for saving home widgets settings.
|
||||||
SharedPreferences.setPrefix('');
|
SharedPreferences.setPrefix('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setupProviders() {
|
void _setupProviders() {
|
||||||
@@ -144,3 +151,12 @@ Future<void> _initMacOSWindow() async {
|
|||||||
WindowManipulator.hideTitle();
|
WindowManipulator.hideTitle();
|
||||||
await CustomAppBar.updateTitlebarHeight();
|
await CustomAppBar.updateTitlebarHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _syncApple() async {
|
||||||
|
final docPath = await docDir;
|
||||||
|
final dir = Directory(docPath);
|
||||||
|
final files = await dir.list().toList();
|
||||||
|
files.removeWhere((e) => !e.path.endsWith('.hive'));
|
||||||
|
final paths = files.map((e) => e.path.replaceFirst('$docPath/', ''));
|
||||||
|
ICloud.sync(relativePaths: paths);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,23 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:toolbox/core/extension/context.dart';
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/data/res/path.dart';
|
import 'package:toolbox/core/utils/backup.dart';
|
||||||
|
import 'package:toolbox/core/utils/platform.dart';
|
||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
import '../../core/utils/misc.dart';
|
import '../../core/utils/misc.dart';
|
||||||
import '../../core/utils/ui.dart';
|
import '../../core/utils/ui.dart';
|
||||||
import '../../data/model/app/backup.dart';
|
|
||||||
import '../../data/res/ui.dart';
|
import '../../data/res/ui.dart';
|
||||||
import '../../data/store/docker.dart';
|
|
||||||
import '../../data/store/private_key.dart';
|
|
||||||
import '../../data/store/server.dart';
|
|
||||||
import '../../data/store/setting.dart';
|
import '../../data/store/setting.dart';
|
||||||
import '../../data/store/snippet.dart';
|
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
import '../widget/custom_appbar.dart';
|
import '../widget/custom_appbar.dart';
|
||||||
|
|
||||||
const backupFormatVersion = 1;
|
|
||||||
|
|
||||||
class BackupPage extends StatelessWidget {
|
class BackupPage extends StatelessWidget {
|
||||||
BackupPage({Key? key}) : super(key: key);
|
BackupPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
final _server = locator<ServerStore>();
|
|
||||||
final _snippet = locator<SnippetStore>();
|
|
||||||
final _privateKey = locator<PrivateKeyStore>();
|
|
||||||
final _dockerHosts = locator<DockerStore>();
|
|
||||||
final _setting = locator<SettingStore>();
|
final _setting = locator<SettingStore>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -44,12 +32,12 @@ class BackupPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(BuildContext context, S s) {
|
Widget _buildBody(BuildContext context, S s) {
|
||||||
final media = MediaQuery.of(context);
|
return Column(
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (isMacOS || isIOS) _buildIcloudSync(context),
|
||||||
|
height13,
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(37),
|
padding: const EdgeInsets.all(37),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -61,7 +49,6 @@ class BackupPage extends StatelessWidget {
|
|||||||
_buildCard(
|
_buildCard(
|
||||||
s.restore,
|
s.restore,
|
||||||
Icons.download,
|
Icons.download,
|
||||||
media,
|
|
||||||
() => _onRestore(context, s),
|
() => _onRestore(context, s),
|
||||||
),
|
),
|
||||||
height13,
|
height13,
|
||||||
@@ -73,17 +60,18 @@ class BackupPage extends StatelessWidget {
|
|||||||
_buildCard(
|
_buildCard(
|
||||||
s.backup,
|
s.backup,
|
||||||
Icons.save,
|
Icons.save,
|
||||||
media,
|
() async {
|
||||||
() => _onBackup(context, s),
|
await backup();
|
||||||
|
await shareFiles(context, [await backupPath]);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCard(
|
Widget _buildCard(
|
||||||
String text,
|
String text,
|
||||||
IconData icon,
|
IconData icon,
|
||||||
MediaQueryData media,
|
|
||||||
FutureOr Function() onTap,
|
FutureOr Function() onTap,
|
||||||
) {
|
) {
|
||||||
return RoundRectCard(
|
return RoundRectCard(
|
||||||
@@ -105,6 +93,20 @@ class BackupPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildIcloudSync(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'iCloud',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
width13,
|
||||||
|
buildSwitch(context, _setting.icloudSync)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onRestore(BuildContext context, S s) async {
|
Future<void> _onRestore(BuildContext context, S s) async {
|
||||||
final path = await pickOneFile();
|
final path = await pickOneFile();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
@@ -120,25 +122,6 @@ class BackupPage extends StatelessWidget {
|
|||||||
_import(text, context, s);
|
_import(text, context, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBackup(BuildContext context, S s) async {
|
|
||||||
final result = _diyEncrtpt(
|
|
||||||
json.encode(
|
|
||||||
Backup(
|
|
||||||
version: backupFormatVersion,
|
|
||||||
date: DateTime.now().toString().split('.').first,
|
|
||||||
spis: _server.fetch(),
|
|
||||||
snippets: _snippet.fetch(),
|
|
||||||
keys: _privateKey.fetch(),
|
|
||||||
dockerHosts: _dockerHosts.fetchAll(),
|
|
||||||
settings: _setting.toJson(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final path = '${(await docDir).path}/srvbox_bak.json';
|
|
||||||
await File(path).writeAsString(result);
|
|
||||||
await shareFiles(context, [path]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _import(String text, BuildContext context, S s) async {
|
Future<void> _import(String text, BuildContext context, S s) async {
|
||||||
if (text.isEmpty) {
|
if (text.isEmpty) {
|
||||||
showSnackBar(context, Text(s.fieldMustNotEmpty));
|
showSnackBar(context, Text(s.fieldMustNotEmpty));
|
||||||
@@ -149,7 +132,7 @@ class BackupPage extends StatelessWidget {
|
|||||||
|
|
||||||
Future<void> _importBackup(String raw, BuildContext context, S s) async {
|
Future<void> _importBackup(String raw, BuildContext context, S s) async {
|
||||||
try {
|
try {
|
||||||
final backup = await compute(_decode, raw);
|
final backup = await decodeBackup(raw);
|
||||||
if (backupFormatVersion != backup.version) {
|
if (backupFormatVersion != backup.version) {
|
||||||
showSnackBar(context, Text(s.backupVersionNotMatch));
|
showSnackBar(context, Text(s.backupVersionNotMatch));
|
||||||
return;
|
return;
|
||||||
@@ -166,37 +149,9 @@ class BackupPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
for (final s in backup.snippets) {
|
restore(backup);
|
||||||
_snippet.put(s);
|
|
||||||
}
|
|
||||||
for (final s in backup.spis) {
|
|
||||||
_server.put(s);
|
|
||||||
}
|
|
||||||
for (final s in backup.keys) {
|
|
||||||
_privateKey.put(s);
|
|
||||||
}
|
|
||||||
for (final k in backup.dockerHosts.keys) {
|
|
||||||
final val = backup.dockerHosts[k];
|
|
||||||
if (val != null && val is String && val.isNotEmpty) {
|
|
||||||
_dockerHosts.put(k, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.pop();
|
context.pop();
|
||||||
showRoundDialog(
|
showRestartSnackbar(context, s);
|
||||||
context: context,
|
|
||||||
title: Text(s.restore),
|
|
||||||
child: Text(s.restoreSuccess),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => rebuildAll(context),
|
|
||||||
child: Text(s.restart),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
child: Text(s.cancel),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Text(s.ok),
|
child: Text(s.ok),
|
||||||
),
|
),
|
||||||
@@ -208,19 +163,3 @@ class BackupPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Backup _decode(String raw) {
|
|
||||||
final decrypted = _diyDecrypt(raw);
|
|
||||||
return Backup.fromJson(json.decode(decrypted));
|
|
||||||
}
|
|
||||||
|
|
||||||
String _diyEncrtpt(String raw) =>
|
|
||||||
json.encode(raw.codeUnits.map((e) => e * 2 + 1).toList(growable: false));
|
|
||||||
String _diyDecrypt(String raw) {
|
|
||||||
final list = json.decode(raw);
|
|
||||||
final sb = StringBuffer();
|
|
||||||
for (final e in list) {
|
|
||||||
sb.writeCharCode((e - 1) ~/ 2);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
_setting.primaryColor.put(_selectedColorValue.value);
|
_setting.primaryColor.put(_selectedColorValue.value);
|
||||||
primaryColor = color;
|
primaryColor = color;
|
||||||
context.pop();
|
context.pop();
|
||||||
_showRestartSnackbar();
|
showRestartSnackbar(context, _s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget _buildLaunchPage() {
|
// Widget _buildLaunchPage() {
|
||||||
@@ -560,7 +560,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
_setting.fontPath.delete();
|
_setting.fontPath.delete();
|
||||||
context.pop();
|
context.pop();
|
||||||
_showRestartSnackbar();
|
showRestartSnackbar(context, _s);
|
||||||
},
|
},
|
||||||
child: Text(_s.clear),
|
child: Text(_s.clear),
|
||||||
)
|
)
|
||||||
@@ -577,29 +577,19 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
_setting.fontPath.put(path);
|
_setting.fontPath.put(path);
|
||||||
} else {
|
} else {
|
||||||
final fontDir_ = await fontDir;
|
|
||||||
final fontFile = File(path);
|
final fontFile = File(path);
|
||||||
final newPath = '${fontDir_.path}/${path.split('/').last}';
|
final newPath = '${await fontDir}/${path.split('/').last}';
|
||||||
await fontFile.copy(newPath);
|
await fontFile.copy(newPath);
|
||||||
_setting.fontPath.put(newPath);
|
_setting.fontPath.put(newPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.pop();
|
context.pop();
|
||||||
_showRestartSnackbar();
|
showRestartSnackbar(context, _s);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSnackBar(context, Text(_s.failed));
|
showSnackBar(context, Text(_s.failed));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showRestartSnackbar() {
|
|
||||||
showSnackBarWithAction(
|
|
||||||
context,
|
|
||||||
'${_s.success}\n${_s.needRestart}',
|
|
||||||
_s.restart,
|
|
||||||
() => rebuildAll(context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBgRun() {
|
Widget _buildBgRun() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.bgRun),
|
title: Text(_s.bgRun),
|
||||||
@@ -681,7 +671,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onSelected: (String idx) {
|
onSelected: (String idx) {
|
||||||
_localeCode.value = idx;
|
_localeCode.value = idx;
|
||||||
_setting.locale.put(idx);
|
_setting.locale.put(idx);
|
||||||
_showRestartSnackbar();
|
showRestartSnackbar(context, _s);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_s.languageName,
|
_s.languageName,
|
||||||
@@ -772,7 +762,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
trailing: buildSwitch(
|
trailing: buildSwitch(
|
||||||
context,
|
context,
|
||||||
_setting.fullScreen,
|
_setting.fullScreen,
|
||||||
func: (_) => _showRestartSnackbar(),
|
func: (_) => showRestartSnackbar(context, _s),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
} else {
|
} else {
|
||||||
sftpDir.then((dir) {
|
sftpDir.then((dir) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_path = LocalPath(dir.path);
|
_path = LocalPath(dir);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -642,7 +642,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _getLocalPath(String remotePath) async {
|
Future<String> _getLocalPath(String remotePath) async {
|
||||||
return '${(await sftpDir).path}$remotePath';
|
return '${await sftpDir}$remotePath';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only return true if the path is changed
|
/// Only return true if the path is changed
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import FlutterMacOS
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import dynamic_color
|
import dynamic_color
|
||||||
|
import icloud_storage
|
||||||
import macos_window_utils
|
import macos_window_utils
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import share_plus
|
import share_plus
|
||||||
@@ -14,6 +15,7 @@ import url_launcher_macos
|
|||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||||
|
IcloudStoragePlugin.register(with: registry.registrar(forPlugin: "IcloudStoragePlugin"))
|
||||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ PODS:
|
|||||||
- dynamic_color (0.0.2):
|
- dynamic_color (0.0.2):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
|
- icloud_storage (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- macos_window_utils (1.0.0):
|
- macos_window_utils (1.0.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@@ -18,6 +20,7 @@ PODS:
|
|||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
|
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
|
- icloud_storage (from `Flutter/ephemeral/.symlinks/plugins/icloud_storage/macos`)
|
||||||
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||||
@@ -29,6 +32,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
|
icloud_storage:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/icloud_storage/macos
|
||||||
macos_window_utils:
|
macos_window_utils:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -43,6 +48,7 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
|
icloud_storage: 33b05299e26d1391d724da8d62860e702380a1cd
|
||||||
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
|
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
8B1B5C0D431C7ED686B9704F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
8B1B5C0D431C7ED686B9704F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
E35696982AAF0AF400C087D8 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
|
||||||
E5004715DDBC0F274A8B776F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
E5004715DDBC0F274A8B776F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
FFF2DE54D5055D5B946114D7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
FFF2DE54D5055D5B946114D7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -175,6 +176,7 @@
|
|||||||
33FAB671232836740065AC1E /* Runner */ = {
|
33FAB671232836740065AC1E /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E35696982AAF0AF400C087D8 /* RunnerDebug.entitlements */,
|
||||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||||
@@ -567,8 +569,10 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -693,9 +697,11 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -715,8 +721,10 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|||||||
26
macos/Runner/RunnerDebug.entitlements
Normal file
26
macos/Runner/RunnerDebug.entitlements
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>iCloud.tech.lolli.serverbox</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.icloud-services</key>
|
||||||
|
<array>
|
||||||
|
<string>CloudDocuments</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>iCloud.tech.lolli.serverbox</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -454,6 +454,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
icloud_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: icloud_storage
|
||||||
|
sha256: fa91d9c3b4264651f01a4f5b99cffa354ffe455623b13ecf92be86d88b1e26ea
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ dependencies:
|
|||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
macos_window_utils: ^1.2.0
|
macos_window_utils: ^1.2.0
|
||||||
dynamic_color: ^1.6.6
|
dynamic_color: ^1.6.6
|
||||||
|
icloud_storage: ^2.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_native_splash: ^2.1.6
|
flutter_native_splash: ^2.1.6
|
||||||
|
|||||||
Reference in New Issue
Block a user