Feature Request: Support for setting download location per-device in Windows App #563
This commit is contained in:
parent
16ccd02ad8
commit
ec2b87933a
|
|
@ -188,6 +188,7 @@ class UpdateUserPreferences implements PersistPrefs {
|
||||||
this.enableTooltips,
|
this.enableTooltips,
|
||||||
this.flexibleSearch,
|
this.flexibleSearch,
|
||||||
this.enableNativeBrowser,
|
this.enableNativeBrowser,
|
||||||
|
this.downloadsFolder,
|
||||||
this.statementIncludes,
|
this.statementIncludes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -219,6 +220,7 @@ class UpdateUserPreferences implements PersistPrefs {
|
||||||
final bool? enableTooltips;
|
final bool? enableTooltips;
|
||||||
final bool? flexibleSearch;
|
final bool? flexibleSearch;
|
||||||
final bool? enableNativeBrowser;
|
final bool? enableNativeBrowser;
|
||||||
|
final String? downloadsFolder;
|
||||||
final BuiltList<String>? statementIncludes;
|
final BuiltList<String>? statementIncludes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ PrefState prefReducer(
|
||||||
longPressReducer(state.longPressSelectionIsDefault, action)
|
longPressReducer(state.longPressSelectionIsDefault, action)
|
||||||
..tapSelectedToEdit =
|
..tapSelectedToEdit =
|
||||||
tapSelectedToEditReducer(state.tapSelectedToEdit, action)
|
tapSelectedToEditReducer(state.tapSelectedToEdit, action)
|
||||||
|
..donwloadsFolder = downloadsFolderReducer(state.donwloadsFolder, action)
|
||||||
..requireAuthentication =
|
..requireAuthentication =
|
||||||
requireAuthenticationReducer(state.requireAuthentication, action)
|
requireAuthenticationReducer(state.requireAuthentication, action)
|
||||||
..colorTheme = colorThemeReducer(state.colorTheme, action)
|
..colorTheme = colorThemeReducer(state.colorTheme, action)
|
||||||
|
|
@ -412,6 +413,12 @@ Reducer<bool> tapSelectedToEditReducer = combineReducers([
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Reducer<String> downloadsFolderReducer = combineReducers([
|
||||||
|
TypedReducer<String, UpdateUserPreferences>((downloadsFolder, action) {
|
||||||
|
return action.downloadsFolder ?? downloadsFolder;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
Reducer<bool> isPreviewVisibleReducer = combineReducers([
|
Reducer<bool> isPreviewVisibleReducer = combineReducers([
|
||||||
TypedReducer<bool, TogglePreviewSidebar>((value, action) {
|
TypedReducer<bool, TogglePreviewSidebar>((value, action) {
|
||||||
return !value;
|
return !value;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
// Flutter imports:
|
// Flutter imports:
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:filesystem_picker/filesystem_picker.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart' hide LiveText;
|
import 'package:flutter/services.dart' hide LiveText;
|
||||||
|
|
@ -7,6 +10,8 @@ import 'package:flutter/services.dart' hide LiveText;
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
|
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/company/company_selectors.dart';
|
import 'package:invoiceninja_flutter/redux/company/company_selectors.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/files.dart';
|
||||||
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||||
import 'package:timeago/timeago.dart' as timeago;
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
@ -50,6 +55,11 @@ class _DeviceSettingsState extends State<DeviceSettings>
|
||||||
|
|
||||||
TabController? _controller;
|
TabController? _controller;
|
||||||
FocusScopeNode? _focusNode;
|
FocusScopeNode? _focusNode;
|
||||||
|
String _defaultDownloadsFolder = '';
|
||||||
|
|
||||||
|
final _downloadsFolderController = TextEditingController();
|
||||||
|
|
||||||
|
List<TextEditingController> _controllers = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -61,6 +71,37 @@ class _DeviceSettingsState extends State<DeviceSettings>
|
||||||
_controller!.addListener(_onTabChanged);
|
_controller!.addListener(_onTabChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() async {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
_controllers = [
|
||||||
|
_downloadsFolderController,
|
||||||
|
];
|
||||||
|
|
||||||
|
_controllers
|
||||||
|
.forEach((dynamic controller) => controller.removeListener(_onChanged));
|
||||||
|
|
||||||
|
final prefState = widget.viewModel.state.prefState;
|
||||||
|
_downloadsFolderController.text = prefState.donwloadsFolder;
|
||||||
|
|
||||||
|
_controllers
|
||||||
|
.forEach((dynamic controller) => controller.addListener(_onChanged));
|
||||||
|
|
||||||
|
_defaultDownloadsFolder = prefState.donwloadsFolder.isEmpty
|
||||||
|
? await getAppDownloadDirectory() ?? ''
|
||||||
|
: prefState.donwloadsFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChanged() async {
|
||||||
|
widget.viewModel
|
||||||
|
.onDownloadsFolderChanged(context, _downloadsFolderController.text);
|
||||||
|
|
||||||
|
_defaultDownloadsFolder = _downloadsFolderController.text.isEmpty
|
||||||
|
? await getAppDownloadDirectory() ?? ''
|
||||||
|
: _downloadsFolderController.text;
|
||||||
|
}
|
||||||
|
|
||||||
void _onTabChanged() {
|
void _onTabChanged() {
|
||||||
final store = StoreProvider.of<AppState>(context);
|
final store = StoreProvider.of<AppState>(context);
|
||||||
store.dispatch(UpdateSettingsTab(tabIndex: _controller!.index));
|
store.dispatch(UpdateSettingsTab(tabIndex: _controller!.index));
|
||||||
|
|
@ -226,6 +267,41 @@ class _DeviceSettingsState extends State<DeviceSettings>
|
||||||
),
|
),
|
||||||
FormCard(
|
FormCard(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
if (!kIsWeb)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DecoratedFormField(
|
||||||
|
label: localization.downloadsFolder,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
hint: _defaultDownloadsFolder,
|
||||||
|
controller: _downloadsFolderController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final folder = await FilesystemPicker.open(
|
||||||
|
context: context,
|
||||||
|
fsType: FilesystemType.folder,
|
||||||
|
rootDirectory: Directory(Platform.pathSeparator),
|
||||||
|
directory: Directory(_defaultDownloadsFolder),
|
||||||
|
title: localization.downloadsFolder,
|
||||||
|
pickText: localization.saveFilesToThisFolder,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((folder ?? '').isNotEmpty) {
|
||||||
|
_downloadsFolderController.text = folder!;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Text(localization.select),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
child: AppDropdownButton<double>(
|
child: AppDropdownButton<double>(
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ class DeviceSettingsVM {
|
||||||
required this.onEnableTouchEventsChanged,
|
required this.onEnableTouchEventsChanged,
|
||||||
required this.onEnableTooltipsChanged,
|
required this.onEnableTooltipsChanged,
|
||||||
required this.onEnableFlexibleSearchChanged,
|
required this.onEnableFlexibleSearchChanged,
|
||||||
|
required this.onDownloadsFolderChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
static DeviceSettingsVM fromStore(Store<AppState> store) {
|
static DeviceSettingsVM fromStore(Store<AppState> store) {
|
||||||
|
|
@ -98,6 +99,9 @@ class DeviceSettingsVM {
|
||||||
onTapSelectedChanged: (context, value) async {
|
onTapSelectedChanged: (context, value) async {
|
||||||
store.dispatch(UpdateUserPreferences(tapSelectedToEdit: value));
|
store.dispatch(UpdateUserPreferences(tapSelectedToEdit: value));
|
||||||
},
|
},
|
||||||
|
onDownloadsFolderChanged: (context, value) async {
|
||||||
|
store.dispatch(UpdateUserPreferences(downloadsFolder: value));
|
||||||
|
},
|
||||||
onEnableTouchEventsChanged: (context, value) async {
|
onEnableTouchEventsChanged: (context, value) async {
|
||||||
store.dispatch(UpdateUserPreferences(enableTouchEvents: value));
|
store.dispatch(UpdateUserPreferences(enableTouchEvents: value));
|
||||||
store.dispatch(UpdatedSetting());
|
store.dispatch(UpdatedSetting());
|
||||||
|
|
@ -221,5 +225,6 @@ class DeviceSettingsVM {
|
||||||
final Function(BuildContext, bool) onEnableTooltipsChanged;
|
final Function(BuildContext, bool) onEnableTooltipsChanged;
|
||||||
final Function(BuildContext, bool) onEnableFlexibleSearchChanged;
|
final Function(BuildContext, bool) onEnableFlexibleSearchChanged;
|
||||||
final Function(BuildContext, double) onTextScaleFactorChanged;
|
final Function(BuildContext, double) onTextScaleFactorChanged;
|
||||||
|
final Function(BuildContext, String) onDownloadsFolderChanged;
|
||||||
final Future<bool> authenticationSupported;
|
final Future<bool> authenticationSupported;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,7 @@ class SettingsSearch extends StatelessWidget {
|
||||||
'show_pdf_preview',
|
'show_pdf_preview',
|
||||||
'pdf_preview_location#2022-10-24',
|
'pdf_preview_location#2022-10-24',
|
||||||
'refresh_data',
|
'refresh_data',
|
||||||
|
'downloads_folder#2023-10-29'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'dark_mode',
|
'dark_mode',
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ Future<String?> getAppDownloadDirectory() async {
|
||||||
if (!Directory(path).existsSync()) {
|
if (!Directory(path).existsSync()) {
|
||||||
showErrorDialog(
|
showErrorDialog(
|
||||||
message: AppLocalization.of(navigatorKey.currentContext!)!
|
message: AppLocalization.of(navigatorKey.currentContext!)!
|
||||||
.directoryDoesNotExist
|
.downloadsFolderDoesNotExist
|
||||||
.replaceFirst(':value', path));
|
.replaceFirst(':value', path));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
static final Map<String, Map<String, String>> _localizedValues = {
|
static final Map<String, Map<String, String>> _localizedValues = {
|
||||||
'en': {
|
'en': {
|
||||||
// STARTER: lang key - do not remove comment
|
// STARTER: lang key - do not remove comment
|
||||||
'total_invoiced_quotes': 'Invoiced Quotes',
|
'save_files_to_this_folder': 'Save files to this folder',
|
||||||
|
'downloads_folder': 'Downloads Folder',
|
||||||
|
'total_invoiced_quotes': 'Invoiced Quotes',
|
||||||
'total_invoice_paid_quotes': 'Invoice Paid Quotes',
|
'total_invoice_paid_quotes': 'Invoice Paid Quotes',
|
||||||
'directory_does_not_exist':
|
'downloads_folder_does_not_exist':
|
||||||
'The download directory does not exist :value',
|
'The downloads folder does not exist :value',
|
||||||
'user_logged_in_notification': 'User Logged in Notification',
|
'user_logged_in_notification': 'User Logged in Notification',
|
||||||
'user_logged_in_notification_help':
|
'user_logged_in_notification_help':
|
||||||
'Send an email when logging in from a new location',
|
'Send an email when logging in from a new location',
|
||||||
|
|
@ -109902,20 +109904,27 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
_localizedValues[localeCode]!['user_logged_in_notification_help'] ??
|
_localizedValues[localeCode]!['user_logged_in_notification_help'] ??
|
||||||
_localizedValues['en']!['user_logged_in_notification_help']!;
|
_localizedValues['en']!['user_logged_in_notification_help']!;
|
||||||
|
|
||||||
String get directoryDoesNotExist =>
|
String get downloadsFolderDoesNotExist =>
|
||||||
_localizedValues[localeCode]!['directory_does_not_exist'] ??
|
_localizedValues[localeCode]!['downloads_folder_does_not_exist'] ??
|
||||||
_localizedValues['en']!['directory_does_not_exist']!;
|
_localizedValues['en']!['downloads_folder_does_not_exist']!;
|
||||||
|
|
||||||
String get totalInvoicedQuotes =>
|
String get totalInvoicedQuotes =>
|
||||||
_localizedValues[localeCode]!['total_invoiced_quotes'] ??
|
_localizedValues[localeCode]!['total_invoiced_quotes'] ??
|
||||||
_localizedValues['en']!['total_invoiced_quotes']!;
|
_localizedValues['en']!['total_invoiced_quotes']!;
|
||||||
|
|
||||||
String get totalInvoicePaidQuotes =>
|
String get totalInvoicePaidQuotes =>
|
||||||
_localizedValues[localeCode]!['total_invoice_paid_quotes'] ??
|
_localizedValues[localeCode]!['total_invoice_paid_quotes'] ??
|
||||||
_localizedValues['en']!['total_invoice_paid_quotes']!;
|
_localizedValues['en']!['total_invoice_paid_quotes']!;
|
||||||
|
|
||||||
|
String get downloadsFolder =>
|
||||||
|
_localizedValues[localeCode]!['downloads_folder'] ??
|
||||||
|
_localizedValues['en']!['downloads_folder']!;
|
||||||
|
|
||||||
// STARTER: lang field - do not remove comment
|
String get saveFilesToThisFolder =>
|
||||||
|
_localizedValues[localeCode]!['save_files_to_this_folder'] ??
|
||||||
|
_localizedValues['en']!['save_files_to_this_folder']!;
|
||||||
|
|
||||||
|
// STARTER: lang field - do not remove comment
|
||||||
|
|
||||||
String lookup(String? key) {
|
String lookup(String? key) {
|
||||||
final lookupKey = toSnakeCase(key);
|
final lookupKey = toSnakeCase(key);
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ dependencies:
|
||||||
# quick_actions: ^0.2.1
|
# quick_actions: ^0.2.1
|
||||||
# idb_shim: ^1.11.1+1
|
# idb_shim: ^1.11.1+1
|
||||||
collection: ^1.15.0-nullsafety.4
|
collection: ^1.15.0-nullsafety.4
|
||||||
|
filesystem_picker: ^4.0.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
intl: any
|
intl: any
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+1"
|
version: "0.9.3+1"
|
||||||
|
filesystem_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: filesystem_picker
|
||||||
|
sha256: "37ab68968420c2073b68e002cae786d00ef1cfe18bd2b7255640338a0c47aa9a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ dependencies:
|
||||||
# quick_actions: ^0.2.1
|
# quick_actions: ^0.2.1
|
||||||
# idb_shim: ^1.11.1+1
|
# idb_shim: ^1.11.1+1
|
||||||
collection: ^1.15.0-nullsafety.4
|
collection: ^1.15.0-nullsafety.4
|
||||||
|
filesystem_picker: ^4.0.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
intl: any
|
intl: any
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue