incognito theme management

This commit is contained in:
julian 2023-05-11 11:28:37 -06:00
parent ee29025364
commit 6f97b06569
3 changed files with 230 additions and 48 deletions

View file

@ -1,7 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/stack_theme.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/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_service.dart';
@ -14,6 +18,7 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
class ManageThemesView extends ConsumerStatefulWidget {
const ManageThemesView({Key? key}) : super(key: key);
@ -85,39 +90,51 @@ class _ManageThemesViewState extends ConsumerState<ManageThemesView> {
),
],
)
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
RoundedWhiteContainer(
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.",
style: STextStyles.smallMed12(context),
),
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: IntrinsicHeight(
child: Column(
children: [
RoundedWhiteContainer(
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.",
style: STextStyles.smallMed12(context),
),
),
const SizedBox(
height: 12,
),
PrimaryButton(
label: "Load themes",
onPressed: () {
setState(() {
_showThemes = true;
future = ref.watch(pThemeService).fetchThemes();
});
},
),
const SizedBox(
height: 12,
),
SecondaryButton(
label: "Install theme file",
onPressed: _onInstallPressed,
),
const SizedBox(
height: 16,
),
const Expanded(
child: _IncognitoInstalledThemes(),
),
const SizedBox(
height: 16,
),
],
),
const SizedBox(
height: 12,
),
PrimaryButton(
label: "Load themes",
onPressed: () {
setState(() {
_showThemes = true;
future = ref.watch(pThemeService).fetchThemes();
});
},
),
const SizedBox(
height: 12,
),
SecondaryButton(
label: "Install theme file",
onPressed: _onInstallPressed,
),
const Spacer(),
],
),
),
),
),
@ -161,3 +178,75 @@ class _ManageThemesViewState extends ConsumerState<ManageThemesView> {
);
}
}
class _IncognitoInstalledThemes extends ConsumerStatefulWidget {
const _IncognitoInstalledThemes({Key? key}) : super(key: key);
@override
ConsumerState<_IncognitoInstalledThemes> createState() =>
_IncognitoInstalledThemesState();
}
class _IncognitoInstalledThemesState
extends ConsumerState<_IncognitoInstalledThemes> {
late final StreamSubscription<void> _subscription;
List<Tuple2<String, String>> installedThemeIdNames = [];
void _updateInstalledList() {
installedThemeIdNames = ref
.read(pThemeService)
.installedThemes
.where((e) => e.themeId != "light" && e.themeId != "dark")
.map((e) => Tuple2(e.themeId, e.name))
.toList();
}
@override
void initState() {
_updateInstalledList();
_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(
spacing: 16,
runSpacing: 16,
children: installedThemeIdNames
.map(
(e) => SizedBox(
width: (MediaQuery.of(context).size.width - 48) / 2,
child: StackThemeCard(
data: StackThemeMetaData(
name: e.item2,
id: e.item1,
sha256: "",
size: "",
previewImageUrl: "",
),
),
),
)
.toList(),
);
}
}

View file

@ -31,12 +31,20 @@ class _InstallThemeFromFileDialogState
Future<bool> _install() async {
try {
final fileBytes = await File(controller.text).readAsBytes();
await ref.read(pThemeService).install(
themeArchive: ByteData.view(
fileBytes.buffer,
),
final timedFuture = Future<void>.delayed(const Duration(seconds: 2));
final installFuture = File(controller.text).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(

View file

@ -1,14 +1,20 @@
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: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/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/widgets/animated_text.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -30,6 +36,7 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
late final StreamSubscription<void> _subscription;
late bool _hasTheme;
String? _cachedSize;
Future<bool> _downloadAndInstall() async {
final service = ref.read(pThemeService);
@ -88,6 +95,34 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
prefs.systemBrightnessLightThemeId == themeId;
}
Future<String> getThemeDirectorySize() async {
final themesDir = await StackFileSystem.applicationThemesDirectory();
final themeDir = Directory("${themesDir.path}/${widget.data.id}");
int bytes = 0;
if (await themeDir.exists()) {
await for (FileSystemEntity entity in themeDir.list(recursive: true)) {
if (entity is File) {
bytes += await entity.length();
}
}
} else if (widget.data.size.isNotEmpty) {
return widget.data.size;
}
if (bytes < 1024) {
return '$bytes B';
} else if (bytes < 1048576) {
double kbSize = bytes / 1024;
return '${kbSize.toStringAsFixed(2)} KB';
} else if (bytes < 1073741824) {
double mbSize = bytes / 1048576;
return '${mbSize.toStringAsFixed(2)} MB';
} else {
double gbSize = bytes / 1073741824;
return '${gbSize.toStringAsFixed(2)} GB';
}
}
@override
void initState() {
_hasTheme = ref
@ -142,27 +177,77 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
padding: const EdgeInsets.symmetric(
horizontal: 18,
),
child: AspectRatio(
aspectRatio: 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network(
widget.data.previewImageUrl,
),
),
),
child: widget.data.previewImageUrl.isNotEmpty
? AspectRatio(
aspectRatio: 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network(
widget.data.previewImageUrl,
),
),
)
: Builder(
builder: (context) {
final incognitoFile = ref.watch(
themeProvider.select(
(value) => value.assets.personaIncognito,
),
);
return (incognitoFile.endsWith(".png"))
? Image.file(
File(
incognitoFile,
),
height: 100,
)
: SvgPicture.file(
File(
incognitoFile,
),
height: 100,
);
},
),
),
const SizedBox(
height: 12,
),
Text(
widget.data.name,
style: STextStyles.itemSubtitle12(context),
),
const SizedBox(
height: 6,
),
Text(
widget.data.size,
FutureBuilder(
future: getThemeDirectorySize(),
builder: (
context,
AsyncSnapshot<String> snapshot,
) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_cachedSize = snapshot.data;
}
if (_cachedSize == null) {
return AnimatedText(
stringsToLoopThrough: const [
"Calculating size ",
"Calculating size. ",
"Calculating size.. ",
"Calculating size...",
],
style: STextStyles.label(context),
);
} else {
return Text(
_cachedSize!,
style: STextStyles.label(context),
);
}
},
),
const SizedBox(
height: 12,