refactor: replace file_selector with file_picker and update related code
This commit is contained in:
parent
0a512919a5
commit
cea976edc7
|
|
@ -22,8 +22,9 @@ flutter analyze
|
|||
# > run unit tests and widget tests
|
||||
flutter test
|
||||
# > run integration tests
|
||||
flutter test integration_test/ -d <device_id>
|
||||
# dart run tool/run_integration_tests.dart --device=linux (necessary for linux)
|
||||
# flutter test integration_test/ -d <device_id>
|
||||
# Examples: --device=windows | --device=linux | --device=macos | --device=chrome
|
||||
dart run tool/run_integration_tests.dart --device=<device_id>
|
||||
|
||||
# dart run tool/gen_view_wireframe_md.dart
|
||||
# flutter pub run dead_code_analyzer
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
|
@ -63,7 +63,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -119,7 +119,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -177,7 +177,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -223,7 +223,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -272,7 +272,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -324,7 +324,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -390,7 +390,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'dart:io';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pages_sidebar.dart';
|
||||
|
|
@ -47,7 +47,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -101,7 +101,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -155,7 +155,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -242,7 +242,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -308,7 +308,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
currentFile: XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:file_picker/file_picker.dart' as fp;
|
||||
import 'package:path_provider/path_provider.dart' as pp;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -72,13 +74,31 @@ class PdfExportViewModel extends ChangeNotifier {
|
|||
static Future<String?> _defaultSavePathPickerWithSuggestedName(
|
||||
String suggestedName,
|
||||
) async {
|
||||
final group = fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
|
||||
final location = await fs.getSaveLocation(
|
||||
acceptedTypeGroups: [group],
|
||||
suggestedName: suggestedName,
|
||||
confirmButtonText: 'Save',
|
||||
);
|
||||
return location?.path; // null if user cancels
|
||||
// Desktop/web platforms: show save dialog via file_picker
|
||||
// Mobile (Android/iOS): fall back to app-writable directory with suggested name
|
||||
try {
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
final result = await fp.FilePicker.platform.saveFile(
|
||||
dialogTitle: 'Save as',
|
||||
fileName: suggestedName,
|
||||
type: fp.FileType.custom,
|
||||
allowedExtensions: const ['pdf'],
|
||||
lockParentWindow: true,
|
||||
);
|
||||
return result; // null if canceled
|
||||
}
|
||||
} catch (_) {
|
||||
// Platform not available (e.g., web) falls through to default
|
||||
}
|
||||
|
||||
// Mobile or unsupported platform: build a default path in app documents
|
||||
try {
|
||||
final dir = await pp.getApplicationDocumentsDirectory();
|
||||
return '${dir.path}/$suggestedName';
|
||||
} catch (_) {
|
||||
// Last resort: let the caller handle a null path
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:file_picker/file_picker.dart' as fp;
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class PdfViewModel extends ChangeNotifier {
|
||||
|
|
@ -247,7 +247,7 @@ final pdfViewModelProvider = ChangeNotifierProvider<PdfViewModel>((ref) {
|
|||
class PdfSessionViewModel extends ChangeNotifier {
|
||||
final Ref ref;
|
||||
final GoRouter router;
|
||||
fs.XFile _currentFile = fs.XFile('');
|
||||
XFile _currentFile = XFile('');
|
||||
// Keep a human display name in addition to XFile, because on Linux via
|
||||
// xdg-desktop-portal the path can look like /run/user/.../doc/<UUID>, and
|
||||
// XFile.name derives from that basename, yielding a random UUID instead of
|
||||
|
|
@ -257,21 +257,29 @@ class PdfSessionViewModel extends ChangeNotifier {
|
|||
|
||||
PdfSessionViewModel({required this.ref, required this.router});
|
||||
|
||||
fs.XFile get currentFile => _currentFile;
|
||||
XFile get currentFile => _currentFile;
|
||||
String get displayFileName => _displayFileName;
|
||||
|
||||
Future<void> pickAndOpenPdf() async {
|
||||
final typeGroup = const fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
|
||||
final XFile? file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
||||
if (file != null) {
|
||||
Uint8List? bytes;
|
||||
final result = await fp.FilePicker.platform.pickFiles(
|
||||
type: fp.FileType.custom,
|
||||
allowedExtensions: const ['pdf'],
|
||||
withData: true,
|
||||
);
|
||||
if (result == null || result.files.isEmpty) return;
|
||||
final picked = result.files.single;
|
||||
final String name = picked.name;
|
||||
final String? path = picked.path;
|
||||
final Uint8List? bytes = picked.bytes;
|
||||
Uint8List? effectiveBytes = bytes;
|
||||
if (effectiveBytes == null && path != null && path.isNotEmpty) {
|
||||
try {
|
||||
bytes = await file.readAsBytes();
|
||||
effectiveBytes = await XFile(path).readAsBytes();
|
||||
} catch (_) {
|
||||
bytes = null;
|
||||
effectiveBytes = null;
|
||||
}
|
||||
await openPdf(path: file.path, bytes: bytes, fileName: file.name);
|
||||
}
|
||||
await openPdf(path: path, bytes: effectiveBytes, fileName: name);
|
||||
}
|
||||
|
||||
Future<void> openPdf({
|
||||
|
|
@ -289,20 +297,20 @@ class PdfSessionViewModel extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
if (path != null && path.isNotEmpty) {
|
||||
_currentFile = fs.XFile(path);
|
||||
_currentFile = XFile(path);
|
||||
} else if (bytes != null && (fileName != null && fileName.isNotEmpty)) {
|
||||
// Keep in-memory XFile so .name is available for suggestion
|
||||
try {
|
||||
_currentFile = fs.XFile.fromData(
|
||||
_currentFile = XFile.fromData(
|
||||
bytes,
|
||||
name: fileName,
|
||||
mimeType: 'application/pdf',
|
||||
);
|
||||
} catch (_) {
|
||||
_currentFile = fs.XFile(fileName);
|
||||
_currentFile = XFile(fileName);
|
||||
}
|
||||
} else {
|
||||
_currentFile = fs.XFile('');
|
||||
_currentFile = XFile('');
|
||||
}
|
||||
|
||||
// Update display name: prefer explicit fileName (from picker/drop),
|
||||
|
|
@ -325,7 +333,7 @@ class PdfSessionViewModel extends ChangeNotifier {
|
|||
void closePdf() {
|
||||
ref.read(documentRepositoryProvider.notifier).close();
|
||||
ref.read(signatureCardRepositoryProvider.notifier).clearAll();
|
||||
_currentFile = fs.XFile('');
|
||||
_currentFile = XFile('');
|
||||
_displayFileName = '';
|
||||
router.go('/');
|
||||
notifyListeners();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:file_picker/file_picker.dart' as fp;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -21,7 +22,7 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
|
|||
class PdfSignatureHomePage extends ConsumerStatefulWidget {
|
||||
final Future<void> Function() onPickPdf;
|
||||
final VoidCallback onClosePdf;
|
||||
final fs.XFile currentFile;
|
||||
final XFile currentFile;
|
||||
// Optional display name for the currently opened file. On Linux
|
||||
// xdg-desktop-portal, XFile.name/path can be a UUID-like value. When
|
||||
// available, this name preserves the user-selected filename so we can
|
||||
|
|
@ -111,15 +112,18 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
}
|
||||
|
||||
Future<img.Image?> _loadSignatureFromFile() async {
|
||||
final typeGroup = fs.XTypeGroup(
|
||||
label:
|
||||
Localizations.of<AppLocalizations>(context, AppLocalizations)?.image,
|
||||
extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
||||
final result = await fp.FilePicker.platform.pickFiles(
|
||||
type: fp.FileType.custom,
|
||||
allowedExtensions: const ['png', 'jpg', 'jpeg', 'webp'],
|
||||
withData: true,
|
||||
);
|
||||
final file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
||||
if (file == null) return null;
|
||||
final bytes = await file.readAsBytes();
|
||||
if (result == null || result.files.isEmpty) return null;
|
||||
final picked = result.files.single;
|
||||
final Uint8List? bytes =
|
||||
picked.bytes ??
|
||||
(picked.path != null ? await XFile(picked.path!).readAsBytes() : null);
|
||||
try {
|
||||
if (bytes == null) return null;
|
||||
var sigImage = img.decodeImage(bytes);
|
||||
return _toStdSignatureImage(sigImage);
|
||||
} catch (_) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ dependencies:
|
|||
flutter_riverpod: ^2.6.1
|
||||
shared_preferences: ^2.5.3
|
||||
flutter_dotenv: ^6.0.0
|
||||
file_selector: ^1.0.3
|
||||
path_provider: ^2.1.5
|
||||
pdfrx: ^2.1.9
|
||||
pdf: ^3.10.8
|
||||
|
|
@ -59,6 +58,7 @@ dependencies:
|
|||
riverpod_annotation: ^2.6.1
|
||||
colorfilter_generator: ^0.0.8
|
||||
flutter_box_transform: ^0.4.7
|
||||
file_picker: ^10.3.3
|
||||
# disable_web_context_menu: ^1.1.0
|
||||
# ml_linalg: ^13.12.6
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
|
@ -14,6 +14,26 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
|
|||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
||||
// A fake export VM that always reports success, so this widget test doesn't
|
||||
// depend on PDF validity or platform specifics.
|
||||
bool exported = false;
|
||||
|
||||
class _FakePdfExportViewModel extends PdfExportViewModel {
|
||||
_FakePdfExportViewModel(Ref ref)
|
||||
: super(ref, savePathPicker: () async => 'C:/tmp/output.pdf');
|
||||
|
||||
@override
|
||||
Future<bool> exportToPath({
|
||||
required String outputPath,
|
||||
required Size uiPageSize,
|
||||
required Uint8List? signatureImageBytes,
|
||||
double targetDpi = 144.0,
|
||||
}) async {
|
||||
exported = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('Save uses file selector (via provider) and injected exporter', (
|
||||
tester,
|
||||
|
|
@ -35,10 +55,7 @@ void main() {
|
|||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
pdfExportViewModelProvider.overrideWith(
|
||||
(ref) => PdfExportViewModel(
|
||||
ref,
|
||||
savePathPicker: () async => 'C:/tmp/output.pdf',
|
||||
),
|
||||
(ref) => _FakePdfExportViewModel(ref),
|
||||
),
|
||||
],
|
||||
child: MaterialApp(
|
||||
|
|
@ -47,7 +64,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
currentFile: XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -57,10 +74,10 @@ void main() {
|
|||
|
||||
// Trigger save directly (mark toggle no longer required)
|
||||
await tester.tap(find.byKey(const Key('btn_save_pdf')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect success UI (localized)
|
||||
expect(find.textContaining('Saved:'), findsOneWidget);
|
||||
// Basic assertion: a save flow completed and snackbar showed
|
||||
// Pump a bit to allow async export flow to run.
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
// Basic assertion: export was invoked
|
||||
expect(exported, isTrue);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
|
@ -36,7 +36,7 @@ Future<void> pumpWithOpenPdf(WidgetTester tester) async {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
currentFile: XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -413,7 +413,7 @@ Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
currentFile: XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -38,7 +38,7 @@ void main() {
|
|||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
currentFile: XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,15 @@ import 'dart:io';
|
|||
/// --reporter=compact
|
||||
/// --pattern=*.dart (all files in integration_test/)
|
||||
Future<int> main(List<String> args) async {
|
||||
String device = 'linux';
|
||||
// Default device depends on host OS for a better out-of-the-box experience.
|
||||
String device =
|
||||
Platform.isWindows
|
||||
? 'windows'
|
||||
: Platform.isMacOS
|
||||
? 'macos'
|
||||
: Platform.isLinux
|
||||
? 'linux'
|
||||
: 'chrome';
|
||||
String reporter = 'compact';
|
||||
String pattern = '*.dart';
|
||||
|
||||
|
|
@ -84,16 +92,34 @@ Future<int> main(List<String> args) async {
|
|||
return 3;
|
||||
}
|
||||
|
||||
// Normalize and map device aliases (helpful on Windows/macOS)
|
||||
device = _normalizedDeviceId(device);
|
||||
|
||||
// Preflight: ensure `flutter` is invokable in this environment.
|
||||
final flutterOk = await _checkFlutterAvailable();
|
||||
if (!flutterOk) {
|
||||
stderr.writeln(
|
||||
'Could not execute `flutter`. Ensure Flutter is installed and on PATH.',
|
||||
);
|
||||
return 4;
|
||||
}
|
||||
|
||||
stdout.writeln(
|
||||
'Running ${selected.length} integration test file(s) sequentially...',
|
||||
'Running ${selected.length} integration test file(s) sequentially on device: $device...',
|
||||
);
|
||||
final results = <String, int>{};
|
||||
|
||||
for (final f in selected) {
|
||||
final rel = f.path;
|
||||
// Convert to forward slashes for tool compatibility across platforms.
|
||||
final rel = f.path.replaceAll('\\', '/');
|
||||
stdout.writeln('\n=== Running: $rel ===');
|
||||
final args = <String>['test', rel, '-d', device, '-r', reporter];
|
||||
final proc = await Process.start('flutter', args);
|
||||
stdout.writeln('> flutter ${args.join(' ')}');
|
||||
final proc = await Process.start(
|
||||
'flutter',
|
||||
args,
|
||||
runInShell: Platform.isWindows, // ensures flutter.bat resolves on Windows
|
||||
);
|
||||
// Pipe output live
|
||||
unawaited(proc.stdout.transform(utf8.decoder).forEach(stdout.write));
|
||||
unawaited(proc.stderr.transform(utf8.decoder).forEach(stderr.write));
|
||||
|
|
@ -104,8 +130,12 @@ Future<int> main(List<String> args) async {
|
|||
} else {
|
||||
stderr.writeln('=== FAILED (exit $code): $rel ===');
|
||||
}
|
||||
// Small pause between launches to let desktop/device settle
|
||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
// Small pause between launches to let desktop/device settle (slightly longer for desktop)
|
||||
await Future<void>.delayed(
|
||||
Platform.isWindows || Platform.isMacOS || Platform.isLinux
|
||||
? const Duration(milliseconds: 1200)
|
||||
: const Duration(milliseconds: 300),
|
||||
);
|
||||
}
|
||||
|
||||
stdout.writeln('\nSummary:');
|
||||
|
|
@ -118,3 +148,38 @@ Future<int> main(List<String> args) async {
|
|||
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
String _normalizedDeviceId(String input) {
|
||||
final lower = input.toLowerCase();
|
||||
switch (lower) {
|
||||
case 'win':
|
||||
case 'windows':
|
||||
case 'windows-desktop':
|
||||
return 'windows';
|
||||
case 'mac':
|
||||
case 'macos':
|
||||
case 'darwin':
|
||||
return 'macos';
|
||||
case 'linux':
|
||||
case 'gnu/linux':
|
||||
return 'linux';
|
||||
case 'web':
|
||||
case 'chrome':
|
||||
case 'browser':
|
||||
return 'chrome';
|
||||
default:
|
||||
return input; // assume caller provided a concrete device id
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _checkFlutterAvailable() async {
|
||||
try {
|
||||
final result = await Process.run('flutter', const [
|
||||
'--version',
|
||||
'--suppress-analytics',
|
||||
], runInShell: Platform.isWindows);
|
||||
return result.exitCode == 0;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue