From 6990d60b9b38d5f36d896b99c0b6b6fd611bb614 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 10 May 2023 16:49:56 -0600 Subject: [PATCH] various fixes, layout tweaks, and refactoring --- lib/models/isar/stack_theme.dart | 4 +- .../appearance_settings/manage_themes.dart | 156 ++++++---------- .../sub_widgets/stack_theme_card.dart | 167 ++++++++++++++++++ lib/themes/theme_service.dart | 7 +- 4 files changed, 234 insertions(+), 100 deletions(-) create mode 100644 lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index 3bea158b2..273ec0c39 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -1645,7 +1645,9 @@ class StackTheme { backgroundInt: parseColor(json["colors"]["background"] as String), backgroundAppBarInt: parseColor(json["colors"]["background_app_bar"] as String), - gradientBackgroundString: json["colors"]["gradients"] as String?, + gradientBackgroundString: json["colors"]["gradients"] != null + ? jsonEncode(json["colors"]["gradients"]) + : null, standardBoxShadowString: jsonEncode(json["colors"]["box_shadows"]["standard"] as Map), homeViewButtonBarBoxShadowString: 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 a0d7e5870..816db38d4 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,24 +1,25 @@ 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/stack_theme_card.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/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; -import 'package:stackwallet/widgets/rounded_container.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; -class ManageThemesView extends StatefulWidget { +class ManageThemesView extends ConsumerStatefulWidget { const ManageThemesView({Key? key}) : super(key: key); static const String routeName = "/manageThemes"; @override - State createState() => _ManageThemesViewState(); + ConsumerState createState() => _ManageThemesViewState(); } -class _ManageThemesViewState extends State { +class _ManageThemesViewState extends ConsumerState { @override Widget build(BuildContext context) { return ConditionalParent( @@ -36,105 +37,64 @@ class _ManageThemesViewState extends State { style: STextStyles.navBarTitle(context), ), ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ), ), - child: child, - ), + Padding( + padding: const EdgeInsets.all(16), + child: SecondaryButton( + label: "Install theme file", + onPressed: () {}, + ), + ), + ], ), ), child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox( - height: 16, - ), - GridView.builder( - shrinkWrap: true, - primary: false, - itemCount: 100, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 16, - mainAxisSpacing: 16, - childAspectRatio: 2 / 2.7, - ), - itemBuilder: (_, index) { - return StackThemeCard( - name: index.toString(), - size: "lol GB", - ); + FutureBuilder( + future: ref.watch(pThemeService).fetchThemes(), + builder: ( + context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + return Wrap( + spacing: 16, + runSpacing: 16, + children: snapshot.data! + .map( + (e) => SizedBox( + width: (MediaQuery.of(context).size.width - 48) / 2, + child: StackThemeCard( + data: e, + ), + ), + ) + .toList(), + ); + } else { + return Center( + child: LoadingIndicator( + width: (MediaQuery.of(context).size.width - 48) / 2, + ), + ); + } }, ), - const SizedBox( - height: 28, - ), - SecondaryButton( - label: "Install theme file", - onPressed: () {}, - ), - const SizedBox( - height: 16, - ), - ], - ), - ); - } -} - -class StackThemeCard extends StatefulWidget { - const StackThemeCard({ - Key? key, - required this.name, - required this.size, - }) : super(key: key); - - final String name; - final String size; - - @override - State createState() => _StackThemeCardState(); -} - -class _StackThemeCardState extends State { - String buttonLabel = "Download"; - - @override - Widget build(BuildContext context) { - return RoundedWhiteContainer( - child: Column( - children: [ - const Padding( - padding: EdgeInsets.symmetric( - horizontal: 18, - ), - child: AspectRatio( - aspectRatio: 1, - child: RoundedContainer( - color: Colors.grey, - radiusMultiplier: 100, - ), - ), - ), - const SizedBox( - height: 12, - ), - Text( - widget.name, - ), - const SizedBox( - height: 6, - ), - Text( - widget.size, - ), - const Spacer(), - PrimaryButton( - label: buttonLabel, - buttonHeight: ButtonHeight.l, - onPressed: () {}, - ), ], ), ); 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 new file mode 100644 index 000000000..0a983c614 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart @@ -0,0 +1,167 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +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/themes/theme_service.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class StackThemeCard extends ConsumerStatefulWidget { + const StackThemeCard({ + Key? key, + required this.data, + }) : super(key: key); + + final StackThemeMetaData data; + + @override + ConsumerState createState() => _StackThemeCardState(); +} + +class _StackThemeCardState extends ConsumerState { + String buttonLabel = "Download"; + + late bool _hasTheme; + + Future _downloadAndInstall() async { + final service = ref.read(pThemeService); + + try { + final data = await service.fetchTheme( + themeMetaData: widget.data, + ); + + await service.install(themeArchive: data); + return true; + } catch (e, s) { + Logging.instance.log( + "Failed _downloadAndInstall of ${widget.data.id}: $e\n$s", + level: LogLevel.Warning, + ); + return false; + } + } + + Future _downloadPressed() async { + final result = await showLoading( + whileFuture: _downloadAndInstall(), + context: context, + message: "Downloading and installing theme...", + ); + + if (mounted) { + final message = result + ? "${widget.data.name} theme installed!" + : "Failed to install ${widget.data.name} theme"; + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: message, + onOkPressed: (_) { + setState(() { + _hasTheme = result; + }); + }, + ), + ); + } + } + + late final StreamSubscription _subscription; + + @override + void initState() { + _hasTheme = ref + .read(mainDBProvider) + .isar + .stackThemes + .where() + .themeIdEqualTo(widget.data.id) + .countSync() > + 0; + + _subscription = ref + .read(mainDBProvider) + .isar + .stackThemes + .watchLazy() + .listen((event) async { + final hasTheme = (await ref + .read(mainDBProvider) + .isar + .stackThemes + .where() + .themeIdEqualTo(widget.data.id) + .count()) > + 0; + if (_hasTheme != hasTheme && mounted) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() { + _hasTheme = hasTheme; + }); + }); + } + }); + + _subscription.resume(); + super.initState(); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 18, + ), + child: AspectRatio( + aspectRatio: 1, + child: ClipRRect( + borderRadius: BorderRadius.circular(100), + child: Image.network( + widget.data.previewImageUrl, + ), + ), + ), + ), + const SizedBox( + height: 12, + ), + Text( + widget.data.name, + ), + const SizedBox( + height: 6, + ), + Text( + widget.data.size, + ), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: buttonLabel, + enabled: !_hasTheme, + buttonHeight: ButtonHeight.l, + onPressed: _downloadPressed, + ), + ], + ), + ); + } +} diff --git a/lib/themes/theme_service.dart b/lib/themes/theme_service.dart index cbdecfe56..b79524e3f 100644 --- a/lib/themes/theme_service.dart +++ b/lib/themes/theme_service.dart @@ -117,7 +117,7 @@ class ThemeService { } } - Future> fetchThemeList() async { + Future> fetchThemes() async { try { final response = await get(Uri.parse("$baseServerUrl/themes")); @@ -125,6 +125,7 @@ class ThemeService { final result = List>.from(jsonList) .map((e) => StackThemeMetaData.fromMap(e)) + .where((e) => e.id != "light" && e.id != "dark") .toList(); return result; @@ -178,12 +179,14 @@ class StackThemeMetaData { final String name; final String id; final String sha256; + final String size; final String previewImageUrl; StackThemeMetaData({ required this.name, required this.id, required this.sha256, + required this.size, required this.previewImageUrl, }); @@ -193,6 +196,7 @@ class StackThemeMetaData { name: map["name"] as String, id: map["id"] as String, sha256: map["sha256"] as String, + size: map["size"] as String, previewImageUrl: map["previewImageUrl"] as String, ); } catch (e, s) { @@ -210,6 +214,7 @@ class StackThemeMetaData { "name: $name, " "id: $id, " "sha256: $sha256, " + "size: $size, " "previewImageUrl: $previewImageUrl" ")"; }