From 604e4cfde7024c515bf4297dc165d69ad2f8abc6 Mon Sep 17 00:00:00 2001
From: julian <julian@cypherstack.com>
Date: Tue, 9 May 2023 17:18:13 -0600
Subject: [PATCH] install theme from zip archive functionality

---
 lib/themes/theme_service.dart | 66 +++++++++++++++++++++++++++++++++--
 pubspec.lock                  |  2 +-
 pubspec.yaml                  |  1 +
 3 files changed, 66 insertions(+), 3 deletions(-)

diff --git a/lib/themes/theme_service.dart b/lib/themes/theme_service.dart
index 47ae4b235..dbd5ba330 100644
--- a/lib/themes/theme_service.dart
+++ b/lib/themes/theme_service.dart
@@ -1,9 +1,14 @@
+import 'dart:convert';
+import 'dart:io';
 import 'dart:typed_data';
 
+import 'package:archive/archive_io.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:isar/isar.dart';
 import 'package:stackwallet/db/isar/main_db.dart';
 import 'package:stackwallet/models/isar/stack_theme.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/stack_file_system.dart';
 
 final pThemeService = Provider<ThemeService>((ref) {
   return ThemeService.instance;
@@ -20,11 +25,68 @@ class ThemeService {
   void init(MainDB db) => _db ??= db;
 
   Future<void> install({required ByteData themeArchive}) async {
-    // todo unzip, install, and create theme object and add to db
+    final themesDir = await StackFileSystem.applicationThemesDirectory();
+
+    final byteStream = InputStream(themeArchive);
+    final archive = ZipDecoder().decodeBuffer(byteStream);
+
+    final themeJsonFiles = archive.files.where((e) => e.name == "theme.json");
+
+    if (themeJsonFiles.length != 1) {
+      throw Exception("Invalid theme archive: Missing theme.json");
+    }
+
+    final OutputStream os = OutputStream();
+    themeJsonFiles.first.decompress(os);
+    final String jsonString = utf8.decode(os.getBytes());
+    final json = jsonDecode(jsonString) as Map;
+
+    final theme = StackTheme.fromJson(
+      json: Map<String, dynamic>.from(json),
+      applicationThemesDirectoryPath: themesDir.path,
+    );
+
+    final String assetsPath = "${themesDir.path}/${theme.themeId}";
+
+    for (final file in archive.files) {
+      if (file.isFile) {
+        // TODO more sanitation?
+        if (file.name.contains("..")) {
+          Logging.instance.log(
+            "Bad theme asset file path: ${file.name}",
+            level: LogLevel.Error,
+          );
+        } else {
+          final os = OutputFileStream("$assetsPath/${file.name}");
+          file.writeContent(os);
+          await os.close();
+        }
+      }
+    }
+
+    await db.isar.writeTxn(() async {
+      await db.isar.stackThemes.put(theme);
+    });
   }
 
   Future<void> remove({required String themeId}) async {
-    // todo delete local files and remove from db
+    final themesDir = await StackFileSystem.applicationThemesDirectory();
+    final isarId = await db.isar.stackThemes
+        .where()
+        .themeIdEqualTo(themeId)
+        .idProperty()
+        .findFirst();
+    if (isarId != null) {
+      await db.isar.writeTxn(() async {
+        await db.isar.stackThemes.delete(isarId);
+      });
+      await Directory("${themesDir.path}/$themeId").delete(recursive: true);
+    } else {
+      Logging.instance.log(
+        "Failed to delete theme $themeId",
+        level: LogLevel.Warning,
+      );
+    }
   }
 
   Future<List<Map<String, dynamic>>> fetchThemeList() async {
diff --git a/pubspec.lock b/pubspec.lock
index d9654c4b6..50a09b6fa 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -42,7 +42,7 @@ packages:
     source: hosted
     version: "4.2.0"
   archive:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: archive
       sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb"
diff --git a/pubspec.yaml b/pubspec.yaml
index 91ed72013..9b48c2cf9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -151,6 +151,7 @@ dependencies:
   dart_bs58check: ^3.0.2
   hex: ^0.2.0
   rational: ^2.2.2
+  archive: ^3.3.2
 
 dev_dependencies:
   flutter_test: