Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Run `flutter doctor` and fix everything it complains before proceeding
- Make sure you have `gem` installed with `sudo gem install`
- If on MacOS arm, `sudo gem install ffi -- --enable-libffi-alloc`

If the iOS build complains that MobileNebula.xcframework is stale or missing, run `./ensure-mobile-nebula.sh` from the repo root. It rebuilds the gomobile framework only when the Go code in `nebula/` changes, Xcode runs it automatically as a pre-build action on the Runner scheme.
After a fresh checkout, run `./ensure-mobile-nebula.sh` once before building for iOS. Swift package resolution needs MobileNebula.xcframework to exist before the pre-build action that maintains it gets a chance to run. After that, Xcode keeps it fresh automatically, rebuilding only when the Go code in `nebula/` changes. If an iOS build ever complains the framework is stale or missing, run the script again by hand.

# Formatting

Expand Down
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class AppState extends State<App> {

@override
void initState() {
unawaited(Utils.clearLegacyPickedFiles());

//TODO: wait until settings is ready?
settings.onChange().listen((_) {
setState(() {
Expand Down
2 changes: 1 addition & 1 deletion lib/screens/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class MainScreenState extends State<MainScreen> {
// Remove the modal
Navigator.pop(context);

final rawContent = await Utils.pickFile(context);
final rawContent = await Utils.pickFile();
if (rawContent == null) {
return Utils.popError('Load YAML config', 'File was empty');
}
Expand Down
2 changes: 1 addition & 1 deletion lib/screens/siteConfig/add_certificate_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class AddCertificateScreenState extends State<AddCertificateScreen> {
content: Center(child: Text('Choose a file')),
onPressed: () async {
try {
final content = await Utils.pickFile(context);
final content = await Utils.pickFile();
if (content == null) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/screens/siteConfig/ca_list_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class CAListScreenState extends State<CAListScreen> {
content: Text('Choose a file'),
onPressed: () async {
try {
final content = await Utils.pickFile(context);
final content = await Utils.pickFile();
if (content == null) {
return;
}
Expand Down
64 changes: 57 additions & 7 deletions lib/services/utils.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:mobile_nebula/main.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher_string.dart';

final _log = Logger('utils');

class Utils {
/// Minimum size (width or height) of a interactive component
static const double minInteractiveSize = 48;
Expand Down Expand Up @@ -126,15 +131,60 @@ class Utils {
}
}

static Future<String?> pickFile(BuildContext context) async {
await FilePicker.clearTemporaryFiles();
final result = await FilePicker.pickFiles(allowMultiple: false);
if (result == null) {
static Future<String?> pickFile() async {
final file = await openFile();
if (file == null) {
return null;
}

final file = File(result.files.first.path!);
return file.readAsString();
final content = await file.readAsString();

// It is a copy, so don't leave a plaintext key around. Android puts each in its own
// <cacheDir>/<uuid>/, so drop the empty parent too; on iOS that throws into the catch.
try {
final copy = File(file.path);
await copy.delete();
await copy.parent.delete();
} catch (err) {
_log.warning('Failed to delete imported file copy, plaintext may remain in temp', err);
}

return content;
}

/// Removes copies the old file_picker left in temp; it only cleared them on the next
/// pick, so the last thing imported lingers.
static Future<void> clearLegacyPickedFiles() async {
if (Platform.isAndroid) {
// file_picker's copies live under getTemporaryDirectory()/file_picker/.
try {
final cacheDir = await getTemporaryDirectory();
final legacyCache = Directory(p.join(cacheDir.path, 'file_picker'));
if (await legacyCache.exists()) {
await legacyCache.delete(recursive: true);
}
} catch (err) {
_log.warning('Failed to clear legacy file_picker cache', err);
}
} else if (Platform.isIOS) {
// Old file_picker left loose files at the top of tmp, file_selector leaves -Inbox
// stragglers; sweep both. systemTemp because getTemporaryDirectory is Caches on iOS, and
// nothing of ours is loose in tmp (Share also writes to Caches). Only safe because we
// register no document types, so nothing but our own picker ever creates an -Inbox.
try {
await for (final entry in Directory.systemTemp.list()) {
try {
if (entry is File || (entry is Directory && entry.path.endsWith('-Inbox'))) {
await entry.delete(recursive: true);
}
} catch (err) {
_log.warning('Failed to delete temp entry ${entry.path}', err);
}
}
} catch (err) {
_log.warning('Failed to list temp directory for cleanup', err);
}
}
}

static TextTheme createTextTheme() {
Expand Down
80 changes: 60 additions & 20 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.0"
dbus:
dependency: transitive
description:
name: dbus
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
url: "https://pub.dev"
source: hosted
version: "0.7.14"
fake_async:
dependency: transitive
description:
Expand Down Expand Up @@ -193,14 +185,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
file_picker:
file_selector:
dependency: "direct main"
description:
name: file_picker
sha256: fdc6a37f715d19f35b131decf1ce39242eeed5ddae18c0818c3eccb731ab76be
name: file_selector
sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a
url: "https://pub.dev"
source: hosted
version: "1.1.0"
file_selector_android:
dependency: transitive
description:
name: file_selector_android
sha256: "6a26687fa65cbc28a5345c7ae6f227e89f0b47740978a4c475b1a625da7a331b"
url: "https://pub.dev"
source: hosted
version: "0.5.2+8"
file_selector_ios:
dependency: transitive
description:
name: file_selector_ios
sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca
url: "https://pub.dev"
source: hosted
version: "0.5.3+5"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "12.0.0-beta.7"
version: "2.7.0"
file_selector_web:
dependency: transitive
description:
name: file_selector_web
sha256: "73181fbc5257776d8ecaa6a94ab3c8e920ad143b9132a6d984a9271dfc6928d3"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
fixnum:
dependency: transitive
description:
Expand All @@ -222,14 +270,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
url: "https://pub.dev"
source: hosted
version: "2.0.35"
flutter_svg:
dependency: "direct main"
description:
Expand Down
7 changes: 3 additions & 4 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,15 @@ dependencies:
sdk: flutter

path_provider: ^2.0.11
# 12.0.0 drops the DKImagePickerController dependency chain on iOS in favor of
# PHPickerViewController, keeping photo library permissions out of our binary
file_picker: ^12.0.0-beta.7
# We only pick documents (certs, keys, configs). file_selector is first party and
# has no media picker code, so photo library permissions can never sneak into our binary
file_selector: ^1.1.0
Comment thread
nbrownus marked this conversation as resolved.
uuid: ^4.5.2
package_info_plus: ^10.2.0
url_launcher: ^6.3.2
pull_to_refresh: ^2.0.0
flutter_svg: ^2.2.3
intl: ^0.20.2
# 13.x is required to share the win32 constraint with file_picker 12
share_plus: ^13.2.0
sentry_flutter: ^9.12.0
sentry_dart_plugin: ^3.2.1
Expand Down
Loading