diff --git a/assets/svg/file-upload.svg b/assets/svg/file-upload.svg
new file mode 100644
index 000000000..0e3576cbe
--- /dev/null
+++ b/assets/svg/file-upload.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/svg/file.svg b/assets/svg/file.svg
new file mode 100644
index 000000000..2c4f26fa8
--- /dev/null
+++ b/assets/svg/file.svg
@@ -0,0 +1,3 @@
+
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 84722a626..e25368ece 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
@@ -34,7 +34,7 @@ class ManageThemesView extends ConsumerStatefulWidget {
class _ManageThemesViewState extends ConsumerState {
late bool _showThemes;
- Future> future = Future(() => []);
+ Future> Function() future = () async => [];
void _onInstallPressed() {
showDialog(
@@ -46,6 +46,9 @@ class _ManageThemesViewState extends ConsumerState {
@override
void initState() {
_showThemes = ref.read(prefsChangeNotifierProvider).externalCalls;
+ if (_showThemes) {
+ future = ref.read(pThemeService).fetchThemes;
+ }
super.initState();
}
@@ -121,7 +124,7 @@ class _ManageThemesViewState extends ConsumerState {
child: Text(
"You are using Incognito Mode. Please press the"
" button below to load available themes from our server"
- " or upload a theme file manually from your device.",
+ " or install a theme file manually from your device.",
style: STextStyles.smallMed12(context),
),
),
@@ -133,7 +136,7 @@ class _ManageThemesViewState extends ConsumerState {
onPressed: () {
setState(() {
_showThemes = true;
- future = ref.watch(pThemeService).fetchThemes();
+ future = ref.watch(pThemeService).fetchThemes;
});
},
),
@@ -147,8 +150,11 @@ class _ManageThemesViewState extends ConsumerState {
const SizedBox(
height: 16,
),
- const Expanded(
- child: _IncognitoInstalledThemes(),
+ Expanded(
+ child: IncognitoInstalledThemes(
+ cardWidth:
+ (MediaQuery.of(context).size.width - 48) / 2,
+ ),
),
const SizedBox(
height: 16,
@@ -164,7 +170,7 @@ class _ManageThemesViewState extends ConsumerState {
mainAxisAlignment: MainAxisAlignment.center,
children: [
FutureBuilder(
- future: future,
+ future: future(),
builder: (
context,
AsyncSnapshot> snapshot,
@@ -177,6 +183,7 @@ class _ManageThemesViewState extends ConsumerState {
children: snapshot.data!
.map(
(e) => SizedBox(
+ key: Key("ManageThemesView_card_${e.id}_key"),
width: (MediaQuery.of(context).size.width - 48) / 2,
child: StackThemeCard(
data: e,
@@ -200,16 +207,21 @@ class _ManageThemesViewState extends ConsumerState {
}
}
-class _IncognitoInstalledThemes extends ConsumerStatefulWidget {
- const _IncognitoInstalledThemes({Key? key}) : super(key: key);
+class IncognitoInstalledThemes extends ConsumerStatefulWidget {
+ const IncognitoInstalledThemes({
+ Key? key,
+ required this.cardWidth,
+ }) : super(key: key);
+
+ final double cardWidth;
@override
- ConsumerState<_IncognitoInstalledThemes> createState() =>
+ ConsumerState createState() =>
_IncognitoInstalledThemesState();
}
class _IncognitoInstalledThemesState
- extends ConsumerState<_IncognitoInstalledThemes> {
+ extends ConsumerState {
late final StreamSubscription _subscription;
List> installedThemeIdNames = [];
@@ -255,7 +267,8 @@ class _IncognitoInstalledThemesState
children: installedThemeIdNames
.map(
(e) => SizedBox(
- width: (MediaQuery.of(context).size.width - 48) / 2,
+ key: Key("IncognitoInstalledThemes_card_${e.item1}_key"),
+ width: widget.cardWidth,
child: StackThemeCard(
data: StackThemeMetaData(
name: e.item2,
diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart
index f8d62192b..b3ecd0b85 100644
--- a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart
+++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart
@@ -8,12 +8,14 @@ import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/stack_theme.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/themes/theme_service.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/stack_file_system.dart';
import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
@@ -33,6 +35,7 @@ class StackThemeCard extends ConsumerStatefulWidget {
}
class _StackThemeCardState extends ConsumerState {
+ final isDesktop = Util.isDesktop;
late final StreamSubscription _subscription;
late bool _hasTheme;
@@ -170,6 +173,10 @@ class _StackThemeCardState extends ConsumerState {
@override
Widget build(BuildContext context) {
return RoundedWhiteContainer(
+ radiusMultiplier: isDesktop ? 2.5 : 1,
+ borderColor: isDesktop
+ ? Theme.of(context).extension()!.textSubtitle6
+ : null,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -259,13 +266,13 @@ class _StackThemeCardState extends ConsumerState {
: CrossFadeState.showFirst,
firstChild: PrimaryButton(
label: "Download",
- buttonHeight: ButtonHeight.l,
+ buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
onPressed: _downloadPressed,
),
secondChild: SecondaryButton(
- label: themeIsInUse ? "Theme is active" : "Remove from device",
+ label: themeIsInUse ? "Theme is active" : "Remove",
enabled: !themeIsInUse,
- buttonHeight: ButtonHeight.l,
+ buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
onPressed: _uninstallThemePressed,
),
),
diff --git a/lib/pages_desktop_specific/settings/desktop_settings_view.dart b/lib/pages_desktop_specific/settings/desktop_settings_view.dart
index 704647f0d..b30018370 100644
--- a/lib/pages_desktop_specific/settings/desktop_settings_view.dart
+++ b/lib/pages_desktop_specific/settings/desktop_settings_view.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart';
-import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings.dart';
+import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/currency_settings/currency_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/language_settings/language_settings.dart';
diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart
similarity index 84%
rename from lib/pages_desktop_specific/settings/settings_menu/appearance_settings.dart
rename to lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart
index 213b2ac0c..7b165395a 100644
--- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings.dart
+++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart
@@ -1,8 +1,12 @@
+import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/models/isar/stack_theme.dart';
+import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
@@ -200,6 +204,7 @@ class ThemeToggle extends ConsumerStatefulWidget {
}
class _ThemeToggle extends ConsumerState {
+ late final StreamSubscription _subscription;
late int _current;
List> installedThemeIdNames = [];
@@ -261,13 +266,24 @@ class _ThemeToggle extends ConsumerState {
}
}
- @override
- void initState() {
+ void _updateInstalledList() {
installedThemeIdNames = ref
.read(pThemeService)
.installedThemes
.map((e) => Tuple3(e.themeId, e.name, e.assets.themePreview))
.toList();
+ }
+
+ void _manageThemesPressed() {
+ showDialog(
+ context: context,
+ builder: (_) => const DesktopManageThemesDialog(),
+ );
+ }
+
+ @override
+ void initState() {
+ _updateInstalledList();
if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) {
_current = installedThemeIdNames.length;
@@ -282,9 +298,26 @@ class _ThemeToggle extends ConsumerState {
}
}
+ _subscription =
+ ref.read(mainDBProvider).isar.stackThemes.watchLazy().listen((_) {
+ if (mounted) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ setState(() {
+ _updateInstalledList();
+ });
+ });
+ }
+ });
+
super.initState();
}
+ @override
+ void dispose() {
+ _subscription.cancel();
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return Wrap(
@@ -372,7 +405,56 @@ class _ThemeToggle extends ConsumerState {
),
),
),
- )
+ ),
+ Container(
+ decoration: BoxDecoration(
+ border: Border.all(
+ width: 2.5,
+ color: Theme.of(context).extension()!.popupBG,
+ ),
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Semantics(
+ label: "Manage themes",
+ button: true,
+ excludeSemantics: true,
+ child: RawMaterialButton(
+ onPressed: _manageThemesPressed,
+ elevation: 0,
+ focusElevation: 0,
+ hoverElevation: 0,
+ highlightElevation: 0,
+ fillColor: Theme.of(context)
+ .extension()!
+ .textFieldActiveBG,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ child: Container(
+ color: Colors.transparent,
+ height: 160,
+ width: 200,
+ child: Center(
+ child: SvgPicture.asset(
+ Assets.svg.circlePlusFilled,
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle2,
+ width: 20,
+ height: 20,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
],
);
}
diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart
new file mode 100644
index 000000000..4c94f532d
--- /dev/null
+++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart
@@ -0,0 +1,331 @@
+import 'dart:typed_data';
+
+import 'package:desktop_drop/desktop_drop.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:share_plus/share_plus.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/widgets/desktop/outline_blue_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/rounded_container.dart';
+
+class DesktopInstallTheme extends ConsumerStatefulWidget {
+ const DesktopInstallTheme({Key? key}) : super(key: key);
+
+ @override
+ ConsumerState createState() =>
+ _DesktopInstallThemeState();
+}
+
+class _DesktopInstallThemeState extends ConsumerState {
+ final _boxKey = GlobalKey(debugLabel: "selectThemeFileBoxKey");
+
+ XFile? _selectedFile;
+ bool? _installedState;
+ Size? _size;
+ bool _dragging = false;
+
+ Future _install() async {
+ try {
+ final timedFuture = Future.delayed(const Duration(seconds: 2));
+ final installFuture = _selectedFile!.readAsBytes().then(
+ (fileBytes) => ref.read(pThemeService).install(
+ themeArchive: ByteData.view(
+ fileBytes.buffer,
+ ),
+ ),
+ );
+
+ // wait for at least 2 seconds to prevent annoying screen flashing
+ await Future.wait([
+ installFuture,
+ timedFuture,
+ ]);
+ return true;
+ } catch (e, s) {
+ Logging.instance.log(
+ "Failed to install theme: $e\n$s",
+ level: LogLevel.Warning,
+ );
+ return false;
+ }
+ }
+
+ Future _chooseFile() 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) {
+ if (result.paths.isNotEmpty && result.paths.first != null) {
+ setState(() {
+ _selectedFile = XFile(result.paths.first!);
+ });
+ }
+ }
+ } catch (e, s) {
+ Logging.instance.log("$e\n$s", level: LogLevel.Error);
+ }
+ }
+
+ void setBoxSize() {
+ if (_size == null) {
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ setState(() {
+ _size = _boxKey.currentContext?.size;
+ });
+ });
+ }
+ }
+
+ @override
+ void initState() {
+ setBoxSize();
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Row(
+ children: [
+ Text(
+ "Install theme file",
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ DropTarget(
+ onDragDone: (detail) {
+ setState(() {
+ if (detail.files.isNotEmpty) {
+ _selectedFile = detail.files.first;
+ }
+ });
+ },
+ onDragEntered: (detail) {
+ setState(() {
+ _dragging = true;
+ });
+ },
+ onDragExited: (detail) {
+ setState(() {
+ _dragging = false;
+ });
+ },
+ child: RoundedContainer(
+ key: _boxKey,
+ height: _size?.height,
+ color: _dragging
+ ? Theme.of(context).extension()!.textSubtitle6
+ : Theme.of(context).extension()!.popupBG,
+ borderColor:
+ Theme.of(context).extension()!.textSubtitle6,
+ child: _selectedFile == null
+ ? Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(
+ height: 12,
+ ),
+ SvgPicture.asset(
+ Assets.svg.fileUpload,
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle2,
+ width: 24,
+ height: 24,
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Text(
+ "Drag and drop your file here",
+ style: STextStyles.fieldLabel(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ OutlineBlueButton(
+ label: "Browse",
+ buttonHeight: ButtonHeight.s,
+ width: 140,
+ onPressed: _chooseFile,
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ ],
+ )
+ : Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ RoundedContainer(
+ padding: EdgeInsets.zero,
+ color: Theme.of(context)
+ .extension()!
+ .textFieldActiveBG,
+ width: 300,
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 10,
+ ),
+ SvgPicture.asset(
+ Assets.svg.file,
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ width: 16,
+ height: 16,
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Expanded(
+ child: Text(
+ _selectedFile!.name,
+ style: STextStyles.w500_14(context),
+ ),
+ ),
+ IconButton(
+ onPressed: () {
+ setState(() {
+ _selectedFile = null;
+ });
+ },
+ icon: SvgPicture.asset(
+ Assets.svg.circleX,
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle2,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ if (_selectedFile != null)
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(top: 16, bottom: 20),
+ child: PrimaryButton(
+ label: "Install",
+ buttonHeight: ButtonHeight.s,
+ width: 140,
+ enabled: _installedState == null,
+ onPressed: () async {
+ final result = await showLoading(
+ whileFuture: _install(),
+ context: context,
+ message: "Installing ${_selectedFile!.name}...",
+ );
+ if (mounted) {
+ setState(() {
+ _installedState = result;
+ });
+
+ await Future.delayed(
+ const Duration(milliseconds: 2000),
+ ).then((_) {
+ if (mounted) {
+ setState(() {
+ _selectedFile = null;
+ _installedState = null;
+ });
+ }
+ });
+ }
+ },
+ ),
+ ),
+ ],
+ ),
+ if (_installedState == true)
+ RoundedContainer(
+ color:
+ Theme.of(context).extension()!.snackBarBackSuccess,
+ child: Row(
+ children: [
+ SvgPicture.asset(
+ Assets.svg.circleX,
+ color: Theme.of(context)
+ .extension()!
+ .snackBarTextSuccess,
+ width: 16,
+ height: 16,
+ ),
+ const SizedBox(
+ width: 10,
+ ),
+ Text(
+ "${_selectedFile?.name} theme installed",
+ style:
+ STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .snackBarTextSuccess,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ],
+ ),
+ ),
+ if (_installedState == false)
+ RoundedContainer(
+ color:
+ Theme.of(context).extension()!.snackBarBackError,
+ child: Row(
+ children: [
+ SvgPicture.asset(
+ Assets.svg.circleX,
+ color: Theme.of(context)
+ .extension()!
+ .snackBarTextError,
+ width: 16,
+ height: 16,
+ ),
+ const SizedBox(
+ width: 10,
+ ),
+ Text(
+ "Failed to install ${_selectedFile?.name} theme",
+ style:
+ STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .snackBarTextError,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart
new file mode 100644
index 000000000..277bc0c9e
--- /dev/null
+++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart
@@ -0,0 +1,112 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart';
+import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
+import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
+import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
+import 'package:stackwallet/widgets/toggle.dart';
+
+class DesktopManageThemesDialog extends ConsumerStatefulWidget {
+ const DesktopManageThemesDialog({Key? key}) : super(key: key);
+
+ @override
+ ConsumerState createState() =>
+ _DesktopManageThemesDialogState();
+}
+
+class _DesktopManageThemesDialogState
+ extends ConsumerState {
+ static const width = 580.0;
+ bool _isInstallFromFile = false;
+
+ @override
+ Widget build(BuildContext context) {
+ return DesktopDialog(
+ maxWidth: width,
+ maxHeight: 708,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 32),
+ child: Text(
+ "Add more themes",
+ style: STextStyles.desktopH3(context),
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: SizedBox(
+ height: 56,
+ child: Toggle(
+ isOn: _isInstallFromFile,
+ onValueChanged: (value) {
+ if (value != _isInstallFromFile) {
+ setState(() {
+ _isInstallFromFile = value;
+ });
+ }
+ },
+ onColor: Theme.of(context)
+ .extension()!
+ .rateTypeToggleDesktopColorOn,
+ offColor: Theme.of(context)
+ .extension()!
+ .rateTypeToggleDesktopColorOff,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onText: "Theme gallery",
+ offText: "Install file",
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ Expanded(
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return AnimatedCrossFade(
+ crossFadeState: _isInstallFromFile
+ ? CrossFadeState.showSecond
+ : CrossFadeState.showFirst,
+ duration: const Duration(
+ milliseconds: 300,
+ ),
+ firstChild: SizedBox(
+ height: constraints.maxHeight,
+ child: const DesktopThemeGallery(
+ dialogWidth: width,
+ ),
+ ),
+ secondChild: SizedBox(
+ height: constraints.maxHeight,
+ child: const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 32),
+ child: DesktopInstallTheme(),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ const SizedBox(
+ height: 32,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart
new file mode 100644
index 000000000..3e0bf3ec3
--- /dev/null
+++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart
@@ -0,0 +1,153 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/manage_themes.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';
+import 'package:stackwallet/themes/theme_service.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/loading_indicator.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+
+class DesktopThemeGallery extends ConsumerStatefulWidget {
+ const DesktopThemeGallery({
+ Key? key,
+ required this.dialogWidth,
+ }) : super(key: key);
+
+ final double dialogWidth;
+
+ @override
+ ConsumerState createState() =>
+ _DesktopThemeGalleryState();
+}
+
+class _DesktopThemeGalleryState extends ConsumerState {
+ late bool _showThemes;
+ Future> Function() future = () async => [];
+
+ @override
+ void initState() {
+ _showThemes = ref.read(prefsChangeNotifierProvider).externalCalls;
+ if (_showThemes) {
+ future = ref.read(pThemeService).fetchThemes;
+ }
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Row(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 32),
+ child: Text(
+ "Theme Gallery",
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: SingleChildScrollView(
+ child: _showThemes
+ ? Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ FutureBuilder(
+ future: future(),
+ builder: (
+ context,
+ AsyncSnapshot> snapshot,
+ ) {
+ if (snapshot.connectionState ==
+ ConnectionState.done &&
+ snapshot.hasData) {
+ return Wrap(
+ spacing: 16,
+ runSpacing: 16,
+ children: snapshot.data!
+ .map(
+ (e) => SizedBox(
+ key: Key(
+ "_DesktopThemeGalleryState_card_${e.id}_key"),
+ width:
+ (widget.dialogWidth - 64 - 32) / 3,
+ child: StackThemeCard(
+ data: e,
+ ),
+ ),
+ )
+ .toList(),
+ );
+ } else {
+ return const Center(
+ child: LoadingIndicator(
+ width: 200,
+ ),
+ );
+ }
+ },
+ ),
+ ],
+ )
+ : Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ RoundedWhiteContainer(
+ borderColor: Theme.of(context)
+ .extension()!
+ .textSubtitle6,
+ child: Text(
+ "You are using Incognito Mode."
+ " Please press the button below to load "
+ "available themes from our server or install a "
+ "theme file manually from your computer.",
+ style:
+ STextStyles.desktopTextExtraExtraSmall(context),
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ PrimaryButton(
+ label: "Load themes",
+ width: 140,
+ buttonHeight: ButtonHeight.s,
+ onPressed: () {
+ setState(() {
+ _showThemes = true;
+ future = ref.read(pThemeService).fetchThemes;
+ });
+ },
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 20,
+ ),
+ IncognitoInstalledThemes(
+ cardWidth: (widget.dialogWidth - 64 - 32) / 3,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 2c079377b..3ff594299 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -101,7 +101,6 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
-import 'package:stackwallet/widgets/choose_coin_view.dart';
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
@@ -136,7 +135,7 @@ import 'package:stackwallet/pages_desktop_specific/password/forgot_password_desk
import 'package:stackwallet/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart';
import 'package:stackwallet/pages_desktop_specific/settings/desktop_settings_view.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart';
-import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings.dart';
+import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/currency_settings/currency_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart';
@@ -151,6 +150,7 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
+import 'package:stackwallet/widgets/choose_coin_view.dart';
import 'package:tuple/tuple.dart';
class RouteGenerator {
diff --git a/lib/themes/stack_colors.dart b/lib/themes/stack_colors.dart
index 667355848..2aeade5b9 100644
--- a/lib/themes/stack_colors.dart
+++ b/lib/themes/stack_colors.dart
@@ -1749,6 +1749,30 @@ class StackColors extends ThemeExtension {
),
);
+ ButtonStyle? getOutlineBlueButtonStyle(BuildContext context) =>
+ Theme.of(context).textButtonTheme.style?.copyWith(
+ backgroundColor: MaterialStateProperty.all(
+ Colors.transparent,
+ ),
+ side: MaterialStateProperty.all(
+ BorderSide(
+ color: customTextButtonEnabledText,
+ ),
+ ),
+ );
+
+ ButtonStyle? getOutlineBlueButtonDisabledStyle(BuildContext context) =>
+ Theme.of(context).textButtonTheme.style?.copyWith(
+ backgroundColor: MaterialStateProperty.all(
+ Colors.transparent,
+ ),
+ side: MaterialStateProperty.all(
+ BorderSide(
+ color: customTextButtonDisabledText,
+ ),
+ ),
+ );
+
ButtonStyle? getSecondaryEnabledButtonStyle(BuildContext context) =>
Theme.of(context).textButtonTheme.style?.copyWith(
backgroundColor: MaterialStateProperty.all(
diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart
index d473b3ef8..2a0909966 100644
--- a/lib/utilities/assets.dart
+++ b/lib/utilities/assets.dart
@@ -182,6 +182,8 @@ class _SVG {
String get tokens => "assets/svg/tokens.svg";
String get circlePlusDark => "assets/svg/circle-plus.svg";
String get creditCard => "assets/svg/cc.svg";
+ String get file => "assets/svg/file.svg";
+ String get fileUpload => "assets/svg/file-upload.svg";
String get ellipse1 => "assets/svg/Ellipse-43.svg";
String get ellipse2 => "assets/svg/Ellipse-42.svg";
diff --git a/lib/widgets/desktop/outline_blue_button.dart b/lib/widgets/desktop/outline_blue_button.dart
new file mode 100644
index 000000000..406337f84
--- /dev/null
+++ b/lib/widgets/desktop/outline_blue_button.dart
@@ -0,0 +1,188 @@
+import 'package:flutter/material.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/desktop/custom_text_button.dart';
+
+export 'package:stackwallet/widgets/desktop/custom_text_button.dart';
+
+class OutlineBlueButton extends StatelessWidget {
+ const OutlineBlueButton({
+ Key? key,
+ this.width,
+ this.height,
+ this.label,
+ this.icon,
+ this.onPressed,
+ this.enabled = true,
+ this.buttonHeight,
+ this.iconSpacing = 10,
+ }) : super(key: key);
+
+ final double? width;
+ final double? height;
+ final String? label;
+ final VoidCallback? onPressed;
+ final bool enabled;
+ final Widget? icon;
+ final ButtonHeight? buttonHeight;
+ final double? iconSpacing;
+
+ TextStyle getStyle(bool isDesktop, BuildContext context) {
+ if (isDesktop) {
+ if (buttonHeight == null) {
+ return enabled
+ ? STextStyles.desktopButtonEnabled(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .customTextButtonEnabledText,
+ )
+ : STextStyles.desktopButtonDisabled(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .customTextButtonDisabledText,
+ );
+ }
+
+ switch (buttonHeight!) {
+ case ButtonHeight.xxs:
+ case ButtonHeight.xs:
+ case ButtonHeight.s:
+ return STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: enabled
+ ? Theme.of(context)
+ .extension()!
+ .customTextButtonEnabledText
+ : Theme.of(context)
+ .extension()!
+ .customTextButtonDisabledText,
+ );
+
+ case ButtonHeight.m:
+ case ButtonHeight.l:
+ return STextStyles.desktopTextExtraSmall(context).copyWith(
+ color: enabled
+ ? Theme.of(context)
+ .extension()!
+ .customTextButtonEnabledText
+ : Theme.of(context)
+ .extension()!
+ .customTextButtonDisabledText,
+ );
+
+ case ButtonHeight.xl:
+ case ButtonHeight.xxl:
+ return enabled
+ ? STextStyles.desktopButtonEnabled(context)
+ : STextStyles.desktopButtonDisabled(context);
+ }
+ } else {
+ if (buttonHeight == ButtonHeight.l) {
+ return STextStyles.button(context).copyWith(
+ fontSize: 10,
+ color: enabled
+ ? Theme.of(context)
+ .extension()!
+ .customTextButtonEnabledText
+ : Theme.of(context)
+ .extension()!
+ .customTextButtonDisabledText,
+ );
+ }
+ return STextStyles.button(context).copyWith(
+ color: enabled
+ ? Theme.of(context)
+ .extension()!
+ .customTextButtonEnabledText
+ : Theme.of(context)
+ .extension()!
+ .customTextButtonDisabledText,
+ );
+ }
+ }
+
+ double? _getHeight() {
+ if (buttonHeight == null) {
+ return height;
+ }
+
+ if (Util.isDesktop) {
+ switch (buttonHeight!) {
+ case ButtonHeight.xxs:
+ return 32;
+ case ButtonHeight.xs:
+ return 37;
+ case ButtonHeight.s:
+ return 40;
+ case ButtonHeight.m:
+ return 48;
+ case ButtonHeight.l:
+ return 56;
+ case ButtonHeight.xl:
+ return 70;
+ case ButtonHeight.xxl:
+ return 96;
+ }
+ } else {
+ switch (buttonHeight!) {
+ case ButtonHeight.xxs:
+ case ButtonHeight.xs:
+ case ButtonHeight.s:
+ case ButtonHeight.m:
+ return 28;
+ case ButtonHeight.l:
+ return 30;
+ case ButtonHeight.xl:
+ return 46;
+ case ButtonHeight.xxl:
+ return 56;
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final isDesktop = Util.isDesktop;
+
+ return CustomTextButtonBase(
+ height: _getHeight(),
+ width: width,
+ textButton: TextButton(
+ onPressed: enabled ? onPressed : null,
+ style: enabled
+ ? Theme.of(context)
+ .extension()!
+ .getOutlineBlueButtonStyle(context)
+ : Theme.of(context)
+ .extension()!
+ .getOutlineBlueButtonDisabledStyle(context),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (icon != null) icon!,
+ if (icon != null && label != null)
+ SizedBox(
+ width: iconSpacing,
+ ),
+ if (label != null)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ label!,
+ style: getStyle(isDesktop, context),
+ ),
+ if (buttonHeight != null && buttonHeight == ButtonHeight.s)
+ const SizedBox(
+ height: 2,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index a4836c367..cb1ecd755 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -329,6 +329,8 @@ flutter:
- assets/svg/framed-gear.svg
- assets/svg/list-ul.svg
- assets/svg/cc.svg
+ - assets/svg/file.svg
+ - assets/svg/file-upload.svg
- assets/svg/trocador_rating_a.svg
- assets/svg/trocador_rating_b.svg
- assets/svg/trocador_rating_c.svg