diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart index 05aff5967..1d0ae9e0f 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; @@ -28,6 +29,13 @@ class _ManageThemesViewState extends ConsumerState { Future> future = Future(() => []); + void _onInstallPressed() { + showDialog( + context: context, + builder: (context) => const InstallThemeFromFileDialog(), + ); + } + @override void initState() { _showThemes = ref.read(prefsChangeNotifierProvider).externalCalls; @@ -72,7 +80,7 @@ class _ManageThemesViewState extends ConsumerState { padding: const EdgeInsets.all(16), child: SecondaryButton( label: "Install theme file", - onPressed: () {}, + onPressed: _onInstallPressed, ), ), ], @@ -106,7 +114,7 @@ class _ManageThemesViewState extends ConsumerState { ), SecondaryButton( label: "Install theme file", - onPressed: () {}, + onPressed: _onInstallPressed, ), const Spacer(), ], diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart new file mode 100644 index 000000000..563561c30 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart @@ -0,0 +1,182 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class InstallThemeFromFileDialog extends ConsumerStatefulWidget { + const InstallThemeFromFileDialog({Key? key}) : super(key: key); + + @override + ConsumerState createState() => + _InstallThemeFromFileDialogState(); +} + +class _InstallThemeFromFileDialogState + extends ConsumerState { + late final TextEditingController controller; + + Future _install() async { + try { + final fileBytes = await File(controller.text).readAsBytes(); + await ref.read(pThemeService).install( + themeArchive: ByteData.view( + fileBytes.buffer, + ), + ); + return true; + } catch (e, s) { + Logging.instance.log( + "Failed to install theme: $e\n$s", + level: LogLevel.Warning, + ); + return false; + } + } + + Future _pickFile() async { + try { + final result = await FilePicker.platform.pickFiles( + dialogTitle: "Choose theme file", + type: FileType.custom, + allowedExtensions: ["zip"], + lockParentWindow: true, // windows only + ); + + if (result != null && mounted) { + setState(() { + controller.text = result.paths.first ?? ""; + }); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + } + + @override + void initState() { + controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Install theme file", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 12, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + onTap: _pickFile, + controller: controller, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Choose file...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.folder, + color: + Theme.of(context).extension()!.textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + readOnly: true, + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Install", + enabled: controller.text.isNotEmpty, + onPressed: () async { + final result = await showLoading( + whileFuture: _install(), + context: context, + message: "Installing ${controller.text}...", + ); + if (mounted) { + Navigator.of(context).pop(); + if (!result) { + unawaited( + showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to install theme:", + message: controller.text, + ), + ), + ); + } else { + unawaited( + showDialog( + context: context, + builder: (_) => const StackOkDialog( + title: "Theme install succeeded!", + ), + ), + ); + } + } + }, + ), + ), + ], + ) + ], + ), + ); + } +}