mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 10:30:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d315f01289 | ||
|
|
a45b6455be | ||
|
|
f0aa44455c | ||
|
|
99ed770017 |
No files matched your search
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart' show Divider, Icons, Scrollbar, ScrollbarOrientation;
|
import 'package:flutter/material.dart' show Divider, Icons, Scrollbar, ScrollbarOrientation;
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@@ -1376,12 +1375,7 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
|||||||
title: index == null ? context.l10n.addScript : context.l10n.editScript,
|
title: index == null ? context.l10n.addScript : context.l10n.editScript,
|
||||||
fieldLabel: context.l10n.scriptPath,
|
fieldLabel: context.l10n.scriptPath,
|
||||||
initialValue: index == null ? '' : paths[index],
|
initialValue: index == null ? '' : paths[index],
|
||||||
pickPath: Util.isDesktop()
|
pickFile: true,
|
||||||
? () async {
|
|
||||||
final file = await FilePicker.pickFile();
|
|
||||||
return file?.path;
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
if (value == null || !mounted) return;
|
if (value == null || !mounted) return;
|
||||||
_mutateConfig((config) {
|
_mutateConfig((config) {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ Future<String?> showTextSettingDialog(
|
|||||||
required String fieldLabel,
|
required String fieldLabel,
|
||||||
String initialValue = '',
|
String initialValue = '',
|
||||||
bool requireHttpUrl = false,
|
bool requireHttpUrl = false,
|
||||||
Future<String?> Function()? pickPath,
|
bool pickFile = false,
|
||||||
Future<void> Function(String value)? onTest,
|
Future<void> Function(String value)? onTest,
|
||||||
}) async {
|
}) async {
|
||||||
final controller = TextEditingController(text: initialValue);
|
final controller = TextEditingController(text: initialValue);
|
||||||
@@ -189,16 +189,12 @@ Future<String?> showTextSettingDialog(
|
|||||||
children: [
|
children: [
|
||||||
Text(fieldLabel, style: TextStyle(color: palette.textSecondary, fontSize: 12)),
|
Text(fieldLabel, style: TextStyle(color: palette.textSecondary, fontSize: 12)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
if (pickPath == null)
|
if (!pickFile)
|
||||||
AppTextField(controller: controller)
|
AppTextField(controller: controller)
|
||||||
else
|
else
|
||||||
AppPathPickerField(
|
AppPathPickerField.file(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
desktopWidth: AppDesignTokens.settingsFormControlWidth,
|
desktopWidth: AppDesignTokens.settingsFormControlWidth,
|
||||||
onPick: () async {
|
|
||||||
final path = await pickPath();
|
|
||||||
if (path != null && path.isNotEmpty) controller.text = path;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (validationMessage != null || testMessage != null) ...[
|
if (validationMessage != null || testMessage != null) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:app_links/app_links.dart';
|
import 'package:app_links/app_links.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -17,6 +18,11 @@ import 'util/util.dart';
|
|||||||
|
|
||||||
Future<void> main(List<String> args) async {
|
Future<void> main(List<String> args) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
if (Util.isMacos()) {
|
||||||
|
// Gopeed is not sandboxed. Configure each engine before the main/child
|
||||||
|
// window split so native file and directory dialogs work in both windows.
|
||||||
|
await FilePicker.skipEntitlementsChecks();
|
||||||
|
}
|
||||||
if (Util.isAndroid()) {
|
if (Util.isAndroid()) {
|
||||||
FlutterForegroundTask.initCommunicationPort();
|
FlutterForegroundTask.initCommunicationPort();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart' show Icons;
|
import 'package:flutter/material.dart' show Icons;
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||||
|
|
||||||
import '../../core/utils/breakpoints.dart';
|
import '../../core/utils/breakpoints.dart';
|
||||||
|
import '../../util/util.dart';
|
||||||
import '../services/download_directory_picker.dart';
|
import '../services/download_directory_picker.dart';
|
||||||
import 'app_text_field.dart';
|
import 'app_text_field.dart';
|
||||||
import 'app_tooltip.dart';
|
import 'app_tooltip.dart';
|
||||||
@@ -24,7 +26,28 @@ class AppPathPickerField extends StatefulWidget {
|
|||||||
this.padding,
|
this.padding,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.pickerButtonStyle = AppPathPickerButtonStyle.ghost,
|
this.pickerButtonStyle = AppPathPickerButtonStyle.ghost,
|
||||||
}) : platformDownloadDirectory = false,
|
}) : platformFile = false,
|
||||||
|
platformDownloadDirectory = false,
|
||||||
|
onDirectoryPicked = null,
|
||||||
|
allowAndroidEditing = false;
|
||||||
|
|
||||||
|
/// Selects a native desktop file path; Web and mobile accept manual paths only.
|
||||||
|
const AppPathPickerField.file({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
this.fieldKey,
|
||||||
|
this.pickerKey,
|
||||||
|
this.hintText,
|
||||||
|
this.desktopWidth,
|
||||||
|
this.filled,
|
||||||
|
this.border,
|
||||||
|
this.borderRadius,
|
||||||
|
this.padding,
|
||||||
|
this.onChanged,
|
||||||
|
this.pickerButtonStyle = AppPathPickerButtonStyle.outline,
|
||||||
|
}) : onPick = null,
|
||||||
|
platformFile = true,
|
||||||
|
platformDownloadDirectory = false,
|
||||||
onDirectoryPicked = null,
|
onDirectoryPicked = null,
|
||||||
allowAndroidEditing = false;
|
allowAndroidEditing = false;
|
||||||
|
|
||||||
@@ -44,6 +67,7 @@ class AppPathPickerField extends StatefulWidget {
|
|||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.pickerButtonStyle = AppPathPickerButtonStyle.outline,
|
this.pickerButtonStyle = AppPathPickerButtonStyle.outline,
|
||||||
}) : onPick = null,
|
}) : onPick = null,
|
||||||
|
platformFile = false,
|
||||||
platformDownloadDirectory = true;
|
platformDownloadDirectory = true;
|
||||||
|
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
@@ -58,6 +82,7 @@ class AppPathPickerField extends StatefulWidget {
|
|||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
final ValueChanged<String>? onChanged;
|
final ValueChanged<String>? onChanged;
|
||||||
final AppPathPickerButtonStyle pickerButtonStyle;
|
final AppPathPickerButtonStyle pickerButtonStyle;
|
||||||
|
final bool platformFile;
|
||||||
final bool platformDownloadDirectory;
|
final bool platformDownloadDirectory;
|
||||||
final ValueChanged<String>? onDirectoryPicked;
|
final ValueChanged<String>? onDirectoryPicked;
|
||||||
final bool allowAndroidEditing;
|
final bool allowAndroidEditing;
|
||||||
@@ -97,6 +122,16 @@ class _AppPathPickerFieldState extends State<AppPathPickerField> {
|
|||||||
setState(() => _tooltipPath = path);
|
setState(() => _tooltipPath = path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _pickFile() async {
|
||||||
|
if (!Util.isDesktop()) return;
|
||||||
|
final file = await FilePicker.pickFile();
|
||||||
|
if (!mounted) return;
|
||||||
|
final path = file?.path;
|
||||||
|
if (path == null || path.isEmpty || path == widget.controller.text) return;
|
||||||
|
widget.controller.text = path;
|
||||||
|
widget.onChanged?.call(path);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _pickPlatformDirectory() async {
|
Future<void> _pickPlatformDirectory() async {
|
||||||
final path = await DownloadDirectoryPicker.pick(context, currentPath: widget.controller.text.trim());
|
final path = await DownloadDirectoryPicker.pick(context, currentPath: widget.controller.text.trim());
|
||||||
if (!mounted || path == null || path.isEmpty || path == widget.controller.text) return;
|
if (!mounted || path == null || path.isEmpty || path == widget.controller.text) return;
|
||||||
@@ -110,6 +145,8 @@ class _AppPathPickerFieldState extends State<AppPathPickerField> {
|
|||||||
final desktop = MediaQuery.sizeOf(context).width >= Breakpoints.mobile;
|
final desktop = MediaQuery.sizeOf(context).width >= Breakpoints.mobile;
|
||||||
final onPick = widget.platformDownloadDirectory
|
final onPick = widget.platformDownloadDirectory
|
||||||
? (DownloadDirectoryPicker.canPick ? _pickPlatformDirectory : null)
|
? (DownloadDirectoryPicker.canPick ? _pickPlatformDirectory : null)
|
||||||
|
: widget.platformFile
|
||||||
|
? (Util.isDesktop() ? _pickFile : null)
|
||||||
: widget.onPick;
|
: widget.onPick;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: desktop ? widget.desktopWidth : double.infinity,
|
width: desktop ? widget.desktopWidth : double.infinity,
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:gopeed/shared/theme/app_component_themes.dart';
|
||||||
|
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||||
|
import 'package:gopeed/shared/widgets/app_path_picker_field.dart';
|
||||||
|
import 'package:gopeed/shared/widgets/app_text_field.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('file paths remain editable and Web hides native file selection', (tester) async {
|
||||||
|
final controller = TextEditingController(text: '/scripts/old.sh');
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
shad.ShadcnApp(
|
||||||
|
theme: AppTheme.light(),
|
||||||
|
materialTheme: AppTheme.materialLight(),
|
||||||
|
home: AppComponentThemes(
|
||||||
|
child: Center(
|
||||||
|
child: AppPathPickerField.file(
|
||||||
|
controller: controller,
|
||||||
|
desktopWidth: 320,
|
||||||
|
pickerKey: const ValueKey('file-picker'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const ValueKey('file-picker')), kIsWeb ? findsNothing : findsOneWidget);
|
||||||
|
expect(tester.widget<AppTextField>(find.byType(AppTextField)).readOnly, isFalse);
|
||||||
|
await tester.enterText(find.byType(EditableText), '/scripts/custom.sh');
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.text, '/scripts/custom.sh');
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user