From a8c957e6795902f9cde974eeaa738da0a826e8a6 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 1 Feb 2024 23:48:20 -0800 Subject: [PATCH] add the ability to import themes and support for loading custom themes that aren't assets --- assets/themes/cwtch/theme.yml | 2 + lib/cwtch/cwtch.dart | 2 + lib/cwtch/ffi.dart | 30 +-- lib/cwtch/gomobile.dart | 18 +- lib/l10n/intl_cy.arb | 8 +- lib/l10n/intl_da.arb | 8 +- lib/l10n/intl_de.arb | 8 +- lib/l10n/intl_el.arb | 8 +- lib/l10n/intl_en.arb | 8 +- lib/l10n/intl_es.arb | 8 +- lib/l10n/intl_fr.arb | 8 +- lib/l10n/intl_it.arb | 8 +- lib/l10n/intl_ja.arb | 8 +- lib/l10n/intl_ko.arb | 56 +++--- lib/l10n/intl_lb.arb | 8 +- lib/l10n/intl_nl.arb | 8 +- lib/l10n/intl_no.arb | 8 +- lib/l10n/intl_pl.arb | 8 +- lib/l10n/intl_pt.arb | 8 +- lib/l10n/intl_pt_BR.arb | 8 +- lib/l10n/intl_ro.arb | 8 +- lib/l10n/intl_ru.arb | 8 +- lib/l10n/intl_sk.arb | 8 +- lib/l10n/intl_sv.arb | 8 +- lib/l10n/intl_sw.arb | 8 +- lib/l10n/intl_tr.arb | 8 +- lib/l10n/intl_uk.arb | 8 +- lib/l10n/intl_uz.arb | 8 +- lib/main.dart | 6 +- lib/themes/cwtch.dart | 4 + lib/themes/opaque.dart | 22 ++- lib/themes/yamltheme.dart | 87 ++++++++- lib/views/globalsettingsappearanceview.dart | 206 +++++++++++++++----- lib/widgets/folderpicker.dart | 8 +- 34 files changed, 497 insertions(+), 128 deletions(-) diff --git a/assets/themes/cwtch/theme.yml b/assets/themes/cwtch/theme.yml index 8e8fa9e8..d1c4f48d 100644 --- a/assets/themes/cwtch/theme.yml +++ b/assets/themes/cwtch/theme.yml @@ -61,6 +61,8 @@ themes: textfieldHintColor: mainTextColor toolbarIconColor: settings # whiteishPurple topbarColor: header # darkGreyPurple + snackbarBackgroundColor: accent + snackbarTextColor: whiteishPurple chatImageColor: purple light: colors: diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 3976bfb9..c2c04881 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -14,6 +14,8 @@ abstract class Cwtch { // ignore: non_constant_identifier_names Future ReconnectCwtchForeground(); + Future getCwtchDir(); + String getAssetsDir(); // ignore: non_constant_identifier_names diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 1ccc4817..de464a92 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -135,6 +135,7 @@ class CwtchFfi implements Cwtch { ReceivePort _receivePort = ReceivePort(); bool _isL10nInit = false; String _assetsDir = path.join(Directory.current.path, "data", "flutter_assets"); + String _cwtchDir = ""; static String getLibraryPath() { if (Platform.isWindows) { @@ -169,13 +170,12 @@ class CwtchFfi implements Cwtch { String home = ""; String bundledTor = ""; Map envVars = Platform.environment; - String cwtchDir = ""; if (Platform.isLinux) { home = envVars['HOME'] ?? ""; if (EnvironmentConfig.TEST_MODE) { - cwtchDir = envVars['CWTCH_HOME']!; + _cwtchDir = envVars['CWTCH_HOME']!; } else { - cwtchDir = envVars['CWTCH_HOME'] ?? path.join(envVars['HOME']!, ".cwtch"); + _cwtchDir = envVars['CWTCH_HOME'] ?? path.join(envVars['HOME']!, ".cwtch"); } if (await File("linux/Tor/tor").exists()) { @@ -192,7 +192,7 @@ class CwtchFfi implements Cwtch { bundledTor = "tor"; } } else if (Platform.isWindows) { - cwtchDir = envVars['CWTCH_DIR'] ?? path.join(envVars['UserProfile']!, ".cwtch"); + _cwtchDir = envVars['CWTCH_DIR'] ?? path.join(envVars['UserProfile']!, ".cwtch"); String currentTor = path.join(Directory.current.absolute.path, "Tor\\Tor\\tor.exe"); if (await File(currentTor).exists()) { bundledTor = currentTor; @@ -203,7 +203,7 @@ class CwtchFfi implements Cwtch { _assetsDir = path.join(exeDir, "data", "flutter_assets"); } } else if (Platform.isMacOS) { - cwtchDir = envVars['CWTCH_HOME'] ?? path.join(envVars['HOME']!, "Library/Application Support/Cwtch"); + _cwtchDir = envVars['CWTCH_HOME'] ?? path.join(envVars['HOME']!, "Library/Application Support/Cwtch"); _assetsDir = "/Applications/Cwtch.app/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/"; if (await File("Cwtch.app/Contents/MacOS/Tor/tor").exists()) { bundledTor = "Cwtch.app/Contents/MacOS/Tor/tor"; @@ -232,27 +232,27 @@ class CwtchFfi implements Cwtch { // if macOs and release build and no profile and is dev profile // copy dev profile to release profile if (Platform.isMacOS && EnvironmentConfig.BUILD_VER != dev_version) { - var devProfileExists = await Directory(path.join(cwtchDir, "dev", "profiles")).exists(); - var releaseProfileExists = await Directory(path.join(cwtchDir, "profiles")).exists(); + var devProfileExists = await Directory(path.join(_cwtchDir, "dev", "profiles")).exists(); + var releaseProfileExists = await Directory(path.join(_cwtchDir, "profiles")).exists(); if (devProfileExists && !releaseProfileExists) { print("MacOS one time dev -> release profile migration..."); - await Process.run("cp", ["-r", "-p", path.join(cwtchDir, "dev", "profiles"), cwtchDir]); - await Process.run("cp", ["-r", "-p", path.join(cwtchDir, "dev", "SALT"), cwtchDir]); - await Process.run("cp", ["-r", "-p", path.join(cwtchDir, "dev", "ui.globals"), cwtchDir]); + await Process.run("cp", ["-r", "-p", path.join(_cwtchDir, "dev", "profiles"), _cwtchDir]); + await Process.run("cp", ["-r", "-p", path.join(_cwtchDir, "dev", "SALT"), _cwtchDir]); + await Process.run("cp", ["-r", "-p", path.join(_cwtchDir, "dev", "ui.globals"), _cwtchDir]); } } if (EnvironmentConfig.BUILD_VER == dev_version) { - cwtchDir = path.join(cwtchDir, "dev"); + _cwtchDir = path.join(_cwtchDir, "dev"); } - print("StartCwtch( cwtchdir: $cwtchDir, torPath: $bundledTor )"); + print("StartCwtch( cwtchdir: $_cwtchDir, torPath: $bundledTor )"); var startCwtchC = library.lookup>("c_StartCwtch"); // ignore: non_constant_identifier_names final StartCwtch = startCwtchC.asFunction(); - final utf8CwtchDir = cwtchDir.toNativeUtf8(); + final utf8CwtchDir = _cwtchDir.toNativeUtf8(); StartCwtch(utf8CwtchDir, utf8CwtchDir.length, bundledTor.toNativeUtf8(), bundledTor.length); malloc.free(utf8CwtchDir); @@ -268,6 +268,10 @@ class CwtchFfi implements Cwtch { return _assetsDir; } + Future getCwtchDir() async { + return _cwtchDir; + } + // ignore: non_constant_identifier_names Future ReconnectCwtchForeground() async { var reconnectCwtch = library.lookup>("c_ReconnectCwtchForeground"); diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 7d28fcd3..fc50bfb3 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -30,6 +30,7 @@ class CwtchGomobile implements Cwtch { late Future androidLibraryDir; late Future androidHomeDirectory; String androidHomeDirectoryStr = ""; + String _cwtchDir = ""; late CwtchNotifier cwtchNotifier; bool _isL10nInit = false; @@ -55,6 +56,8 @@ class CwtchGomobile implements Cwtch { cwtchNotifier = _cwtchNotifier; cwtchNotifier.setMessageSeenCallback((String profile, int conversation, DateTime time) => {this.SetConversationAttribute(profile, conversation, LastMessageSeenTimeKey, time.toIso8601String())}); androidHomeDirectory = getApplicationDocumentsDirectory(); + + androidLibraryDir = appInfoPlatform.invokeMethod('getNativeLibDir'); // Method channel to receive libcwtch-go events via Kotlin and dispatch them to _handleAppbusEvent (sends to cwtchNotifier) @@ -67,15 +70,20 @@ class CwtchGomobile implements Cwtch { return ""; } + Future getCwtchDir() async { + androidHomeDirectoryStr = (await androidHomeDirectory).path; + var _cwtchDir = path.join(androidHomeDirectoryStr, ".cwtch"); + if (EnvironmentConfig.BUILD_VER == dev_version) { + _cwtchDir = path.join(_cwtchDir, "dev"); + } + return androidHomeDirectoryStr; + } + // ignore: non_constant_identifier_names Future Start() async { print("gomobile.dart: Start()..."); - androidHomeDirectoryStr = (await androidHomeDirectory).path; - var cwtchDir = path.join(androidHomeDirectoryStr, ".cwtch"); - if (EnvironmentConfig.BUILD_VER == dev_version) { - cwtchDir = path.join(cwtchDir, "dev"); - } String torPath = path.join(await androidLibraryDir, "libtor.so"); + String cwtchDir = await getCwtchDir(); print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)..."); cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath}); } diff --git a/lib/l10n/intl_cy.arb b/lib/l10n/intl_cy.arb index 88c53a61..2f86c1ed 100644 --- a/lib/l10n/intl_cy.arb +++ b/lib/l10n/intl_cy.arb @@ -1,6 +1,12 @@ { "@@locale": "cy", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index c3044569..0820aba3 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -1,6 +1,12 @@ { "@@locale": "da", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 4a3e2653..9e138d49 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,12 @@ { "@@locale": "de", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index d560a6ab..0035bde4 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -1,6 +1,12 @@ { "@@locale": "el", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 7078aaa9..3c52133a 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,12 @@ { "@@locale": "en", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingThemeOverwriteConfirm": "Confirm", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingsImportThemeTitle": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeButton": "Import Theme", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index d1edeba7..fa544178 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,12 @@ { "@@locale": "es", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 2d46422b..8df8503d 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,12 @@ { "@@locale": "fr", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index a06e10b6..16e1471c 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,12 @@ { "@@locale": "it", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index bd16d297..732a6250 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,6 +1,12 @@ { "@@locale": "ja", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index 9a0ea914..6cd8a7af 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,6 +1,36 @@ { "@@locale": "ko", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", + "resetTor": "초기화", + "torStatus": "Tor 상태", + "torVersion": "Tor 버전", + "passwordChangeError": "암호 변경 오류: 제공된 암호가 거부되었습니다.", + "enterProfilePassword": "프로필을 보려면 암호를 입력하세요.", + "todoPlaceholder": "할 일...", + "addNewItem": "목록에 새 항목 추가", + "addListItem": "새 목록 항목 추가", + "networkStatusConnecting": "네트워크 및 연락처에 연결 중...", + "networkStatusAttemptingTor": "Tor 네트워크에 연결을 시도 중", + "networkStatusDisconnected": "인터넷 연결 끊김, 연결 확인하세요", + "viewGroupMembershipTooltip": "그룹 구성원 보기", + "defaultScalingText": "글꼴 크기 조정", + "experimentsEnabled": "실험 사용", + "loadingTor": "tor 로딩 중...", + "builddate": "빌드 대상: %2", + "version": "버전: %1", + "versionTor": "버전 %1 및 tor %2", + "settingInterfaceZoom": "확대\/축소 수준", + "versionBuilddate": "버전: %1 빌드 대상: %2", + "error0ProfilesLoadedForPassword": "해당 비밀번호가 포함된 프로필이 0개 로드되었습니다.", + "deleteConfirmText": "삭제", + "unblockBtn": "연락처 차단 해제", + "savePeerHistoryDescription": "연락처와 연결된 기록을 삭제할지 여부를 결정합니다.", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", @@ -116,7 +146,6 @@ "server": "서버", "peerName": "이름", "peerAddress": "주소", - "builddate": "Built on: %2", "deleteProfileConfirmBtn": "프로필 삭제 확인", "deleteConfirmLabel": "DELETE를 입력하여 확인", "deleteProfileBtn": "프로필 삭제", @@ -152,7 +181,6 @@ "localeSw": "Swahili \/ Kiswahili", "localeSv": "Swedish \/ Svenska", "fontScalingDescription": "Adjust the relative font scaling factor applied to text and widgets.", - "defaultScalingText": "Font Scaling", "localeJa": "Japanese \/ 日本語", "retryConnectionTooltip": "Cwtch retries peers regularly, but you can tell Cwtch to try sooner by pushing this button.", "retryConnection": "Retry", @@ -377,9 +405,6 @@ "addServerFirst": "You need to add a server before you can create a group", "deleteProfileSuccess": "Successfully deleted profile", "sendInvite": "Send a contact or group invite", - "resetTor": "Reset", - "torStatus": "Tor Status", - "torVersion": "Tor Version", "sendAnInvitation": "You sent an invitation for: ", "contactSuggestion": "This is a contact suggestion for: ", "chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.", @@ -390,24 +415,5 @@ "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.", "invalidImportString": "Invalid import string", - "todoPlaceholder": "Todo...", - "addNewItem": "Add a new item to the list", - "addListItem": "Add a New List Item", - "networkStatusConnecting": "Connecting to network and contacts...", - "networkStatusAttemptingTor": "Attempting to connect to Tor network", - "networkStatusDisconnected": "Disconnected from the internet, check your connection", - "viewGroupMembershipTooltip": "View Group Membership", - "loadingTor": "Loading tor...", - "version": "Version %1", - "versionTor": "Version %1 with tor %2", - "experimentsEnabled": "Enable Experiments", - "settingInterfaceZoom": "Zoom level", - "versionBuilddate": "Version: %1 Built on: %2", - "error0ProfilesLoadedForPassword": "0 profiles loaded with that password", - "enterProfilePassword": "Enter a password to view your profiles", - "deleteConfirmText": "DELETE", - "passwordChangeError": "Error changing password: Supplied password rejected", - "unblockBtn": "Unblock Contact", - "savePeerHistoryDescription": "Determines whether to delete any history associated with the contact.", "pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation" } \ No newline at end of file diff --git a/lib/l10n/intl_lb.arb b/lib/l10n/intl_lb.arb index 8082b4ab..8a67e88f 100644 --- a/lib/l10n/intl_lb.arb +++ b/lib/l10n/intl_lb.arb @@ -1,6 +1,12 @@ { "@@locale": "lb", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index b586b844..86f6ea1a 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,6 +1,12 @@ { "@@locale": "nl", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_no.arb b/lib/l10n/intl_no.arb index e232357b..cc705926 100644 --- a/lib/l10n/intl_no.arb +++ b/lib/l10n/intl_no.arb @@ -1,6 +1,12 @@ { "@@locale": "no", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 73b3e44c..d4a0ebbd 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,12 @@ { "@@locale": "pl", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 4d6a08c1..b038f259 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,12 @@ { "@@locale": "pt", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_pt_BR.arb b/lib/l10n/intl_pt_BR.arb index 5636d69c..b6c37c73 100644 --- a/lib/l10n/intl_pt_BR.arb +++ b/lib/l10n/intl_pt_BR.arb @@ -1,6 +1,12 @@ { "@@locale": "pt_BR", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index 91c7220c..b54df87e 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,6 +1,12 @@ { "@@locale": "ro", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index a0b09ca4..be88ac31 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,12 @@ { "@@locale": "ru", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_sk.arb b/lib/l10n/intl_sk.arb index 1ae32d9a..cf10694c 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,6 +1,12 @@ { "@@locale": "sk", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index 93e4ed3c..20e5ac11 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,6 +1,12 @@ { "@@locale": "sv", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_sw.arb b/lib/l10n/intl_sw.arb index f2e32762..2707f58c 100644 --- a/lib/l10n/intl_sw.arb +++ b/lib/l10n/intl_sw.arb @@ -1,6 +1,12 @@ { "@@locale": "sw", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index 9746347c..936807c4 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,6 +1,12 @@ { "@@locale": "tr", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index b319ae15..1b0cc523 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,6 +1,12 @@ { "@@locale": "uk", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/l10n/intl_uz.arb b/lib/l10n/intl_uz.arb index 2e991141..91b01610 100644 --- a/lib/l10n/intl_uz.arb +++ b/lib/l10n/intl_uz.arb @@ -1,6 +1,12 @@ { "@@locale": "uz", - "@@last_modified": "2024-01-10T06:54:15+01:00", + "@@last_modified": "2024-02-02T08:42:10+01:00", + "settingsImportThemeButton": "Import Theme", + "settingsImportThemeDescription": "Select theme directory to import for use in Cwtch", + "settingsImportThemeTitle": "Import Theme", + "settingsThemeErrorInvalid": "Error: Could not import $themeName, theme.yml missing, not a theme directory", + "settingThemeOverwriteQuestion": "Theme $themeName already exists, confirm overwrite?", + "settingThemeOverwriteConfirm": "Confirm", "settingsThemeImagesDescription": "Enable display of images from themes", "settingsThemeImages": "Theme Images", "settingsGroupAbout": "About", diff --git a/lib/main.dart b/lib/main.dart index 45dd4b64..e300dab6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -109,10 +109,10 @@ class FlwtchState extends State with WindowListener { } print("initState: invoking cwtch.Start()"); // Cwtch.start can take time, we don't want it blocking first splash screen draw, so postpone a smidge to let splash render - Future.delayed(const Duration(milliseconds: 50), () { + Future.delayed(const Duration(milliseconds: 50), () async { print("actually invoking cwtch.cwtch()!!!"); - cwtch.Start(); - LoadAssetThemes(); + await cwtch.Start(); + LoadThemes(await cwtch.getCwtchDir()); }); print("initState: starting connectivityListener"); if (EnvironmentConfig.TEST_MODE == false) { diff --git a/lib/themes/cwtch.dart b/lib/themes/cwtch.dart index 175a48a6..e81d5509 100644 --- a/lib/themes/cwtch.dart +++ b/lib/themes/cwtch.dart @@ -81,6 +81,8 @@ class CwtchDark extends OpaqueThemeType { get textfieldHintColor => mainTextColor; get toolbarIconColor => settings; //whiteishPurple; get topbarColor => header; //darkGreyPurple; + get snackbarBackgroundColor => accent; + get snackbarTextColor => whitePurple; get chatImageColor => purple; } @@ -127,5 +129,7 @@ class CwtchLight extends OpaqueThemeType { get textfieldHintColor => font; get toolbarIconColor => settings; //darkPurple; get topbarColor => header; //softPurple; + get snackbarBackgroundColor => accent; + get snackbarTextColor => whitePurple; get chatImageColor => purple; } diff --git a/lib/themes/opaque.dart b/lib/themes/opaque.dart index e01dc68f..42c71bc9 100644 --- a/lib/themes/opaque.dart +++ b/lib/themes/opaque.dart @@ -7,6 +7,9 @@ import 'package:cwtch/themes/yamltheme.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/settings.dart'; import 'package:flutter/services.dart'; +import 'package:path/path.dart' as path; + +const custom_themes_subdir = "themes"; const mode_light = "light"; const mode_dark = "dark"; @@ -20,8 +23,10 @@ final TextStyle defaultDropDownMenuItemTextStyle = TextStyle(fontFamily: "Inter" Map> themes = Map(); -LoadAssetThemes() async { - themes = await loadYamlThemes(); +LoadThemes(String cwtchDir) async { + themes = await loadBuiltinThemes(); + final customThemes = await loadCustomThemes(path.join(cwtchDir, custom_themes_subdir)); + themes.addAll(customThemes); } OpaqueThemeType getTheme(String themeId, String mode) { @@ -119,6 +124,9 @@ abstract class OpaqueThemeType { get messageFromOtherBackgroundColor => red; get messageFromOtherTextColor => red; + get snackbarBackgroundColor => red; + get snackbarTextColor => red; + // Images get chatImageColor => red; @@ -241,5 +249,15 @@ ThemeData mkThemeData(Settings opaque) { splashColor: opaque.current().defaultButtonActiveColor), textSelectionTheme: TextSelectionThemeData( cursorColor: opaque.current().defaultButtonActiveColor, selectionColor: opaque.current().defaultButtonActiveColor, selectionHandleColor: opaque.current().defaultButtonActiveColor), + popupMenuTheme: PopupMenuThemeData( + color: opaque + .current() + .backgroundPaneColor + .withOpacity(0.9), + ), + snackBarTheme: SnackBarThemeData( + backgroundColor: opaque.current().snackbarBackgroundColor, + contentTextStyle: TextStyle(color: opaque.current().snackbarTextColor), + ) ); } diff --git a/lib/themes/yamltheme.dart b/lib/themes/yamltheme.dart index 0d2aaa25..97a2fc9d 100644 --- a/lib/themes/yamltheme.dart +++ b/lib/themes/yamltheme.dart @@ -7,13 +7,14 @@ import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/opaque.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:glob/list_local_fs.dart'; import 'package:provider/provider.dart'; import 'package:yaml/yaml.dart'; import 'package:path/path.dart' as path; import '../main.dart'; -Future>> loadYamlThemes() async { +Future>> loadBuiltinThemes() async { final manifestJson = await rootBundle.loadString('AssetManifest.json'); final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes')); @@ -25,7 +26,7 @@ Future>> loadYamlThemes() async { } try { - var data = await loadYamlTheme(themefile); + var data = await loadAssetYamlTheme(themefile); if (data != null) { // remove "assets/themes" and "theme.yml" from name @@ -39,24 +40,71 @@ Future>> loadYamlThemes() async { return themes; } +Future>> loadCustomThemes(String themesDirPath) async { + + Map> themes = Map(); + + Directory themesDir = Directory(themesDirPath); + if (! themesDir.existsSync()) { + themesDir = await themesDir.create(); + } + await themesDir.list(recursive: false).forEach( (themeDir) async { + //final themeDir = Directory(path.join(themesDir, themedir)); + File themefile = File(path.join(themeDir.path, "theme.yml")); + if (themefile.existsSync()) { + try { + var data = await loadFileYamlTheme(themefile.path, themeDir.path); + + if (data != null) { + themes[path.basename(themeDir.path)] = data; + } + } catch (e) { + print("Failed to load theme: $themefile with exception: $e"); + } + } + }); + + return themes; +} + Future readAssetYamlTheme(String themefile) async { final contents = await rootBundle.loadString(themefile); return loadYaml(contents); } -Future?> loadYamlTheme(String themefile) async { +Future?> loadAssetYamlTheme(String themefile) async { final yml = await readAssetYamlTheme(themefile); if (yml == null) { - print("failed to load theme: $themefile"); + print("Error: failed to load theme: $themefile"); return null; } + return loadYamlTheme(yml); +} + +Future?> loadFileYamlTheme(String themefile, String assetsDir) async { + final file = File(themefile); + if (!file.existsSync()) { + print("Error: theme file: $themefile does not exist"); + return null; + } + final contents = file.readAsStringSync(); + final yml = await loadYaml(contents); + if (yml == null) { + print("Error: failed to load theme: $themefile"); + return null; + } + return loadYamlTheme(yml, assetsDir); +} + + +Future?> loadYamlTheme(YamlMap yml, [String? assetsDir]) async { Map subthemes = Map(); if ((yml["themes"] as YamlMap).containsKey(mode_dark)) { - subthemes[mode_dark] = YmlTheme(yml, yml["themes"]["name"], mode_dark); + subthemes[mode_dark] = YmlTheme(yml, yml["themes"]["name"], mode_dark, assetsDir); } if ((yml["themes"] as YamlMap).containsKey(mode_light)) { - subthemes[mode_light] = YmlTheme(yml, yml["themes"]["name"], mode_light); + subthemes[mode_light] = YmlTheme(yml, yml["themes"]["name"], mode_light, assetsDir); } return subthemes; } @@ -65,12 +113,14 @@ class YmlTheme extends OpaqueThemeType { late YamlMap yml; late String mode; late String theme; + late String? assetsDir; late OpaqueThemeType fallbackTheme; - YmlTheme(YamlMap yml, theme, mode) { + YmlTheme(YamlMap yml, theme, mode, assetsDir) { this.yml = yml; this.theme = theme; this.mode = mode; + this.assetsDir = assetsDir; if (mode == mode_dark) { fallbackTheme = CwtchDark(); } else { @@ -92,13 +142,22 @@ class YmlTheme extends OpaqueThemeType { if (!(val is int)) { return null; } - return Color(0xFF000000 + val as int); + + if ( val <= 0xFFFFFF) { + return Color(0xFF000000 + val); + } else { + return Color(val); + } } String? getImage(String name) { var val = yml["themes"][mode]["theme"]?[name]; if (val != null) { - return path.join("assets", "themes", yml["themes"]["name"], val); + if (assetsDir == null) { + return path.join("assets", "themes", yml["themes"]["name"], val); + } else { + return val; + } } return null; } @@ -137,6 +196,8 @@ class YmlTheme extends OpaqueThemeType { get messageFromMeTextColor => getColor("messageFromMeTextColor") ?? fallbackTheme.messageFromMeTextColor; get messageFromOtherBackgroundColor => getColor("messageFromOtherBackgroundColor") ?? fallbackTheme.messageFromOtherBackgroundColor; get messageFromOtherTextColor => getColor("messageFromOtherTextColor") ?? fallbackTheme.messageFromOtherTextColor; + get snackbarBackgroundColor => getColor("snackbarBackgroundColor") ?? fallbackTheme.snackbarBackgroundColor; + get snackbarTextColor => getColor("snackbarTextColor") ?? fallbackTheme.snackbarTextColor; // Images @@ -149,6 +210,14 @@ class YmlTheme extends OpaqueThemeType { return FileImage(f); } + final assetsDir = this.assetsDir; + if (assetsDir != null) { + File f = File(path.join(assetsDir, key)); + if (f.existsSync()) { + return FileImage(f); + } + } + if (context != null) { File af = File(path.join(Provider .of(context, listen: false) diff --git a/lib/views/globalsettingsappearanceview.dart b/lib/views/globalsettingsappearanceview.dart index 8b985178..1e310d52 100644 --- a/lib/views/globalsettingsappearanceview.dart +++ b/lib/views/globalsettingsappearanceview.dart @@ -1,12 +1,20 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:path/path.dart' as path; import '../config.dart'; +import '../controllers/filesharing.dart'; import '../cwtch_icons_icons.dart'; +import '../main.dart'; +import '../models/appstate.dart'; import '../settings.dart'; import '../themes/cwtch.dart'; import '../themes/opaque.dart'; +import '../themes/yamltheme.dart'; +import '../widgets/folderpicker.dart'; import 'globalsettingsview.dart'; class GlobalSettingsAppearanceView extends StatefulWidget { @@ -15,7 +23,6 @@ class GlobalSettingsAppearanceView extends StatefulWidget { } class _GlobalSettingsAppearanceViewState extends State { - ScrollController settingsListScrollController = ScrollController(); Widget build(BuildContext context) { @@ -123,50 +130,66 @@ class _GlobalSettingsAppearanceViewState extends State>( - (String themeId) { - return DropdownMenuItem( - value: themeId, - child: Text( - getThemeName(context, themeId), - style: settings.scaleFonts( - defaultDropDownMenuItemTextStyle)), //"ddi_$themeId", key: Key("ddi_$themeId")), - ); - }).toList())), - leading: Icon(Icons.palette, - color: settings - .current() - .mainTextColor), - ), - SwitchListTile( - title: Text( - AppLocalizations.of(context)!.settingsThemeImages), - subtitle: Text(AppLocalizations.of(context)! - .settingsThemeImagesDescription), - value: settings.themeImages, - onChanged: (bool value) { - settings.themeImages = value;// Save Settings... - saveSettings(context); - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.image, - color: settings - .current() - .mainTextColor), - ), - ListTile( - title: Text(AppLocalizations.of(context)! - .settingUIColumnPortrait), - leading: Icon(Icons.table_chart, - color: settings + settings.setTheme(newValue!, settings.theme.mode); + saveSettings(context); + }); + }, + items: themes.keys.map>((String themeId) { + return DropdownMenuItem( + value: themeId, + child: Text(getThemeName(context, themeId), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)), //"ddi_$themeId", key: Key("ddi_$themeId")), + ); + }).toList())), + leading: Icon(Icons.palette, color: settings.current().mainTextColor), + ), + Visibility( + // TODO: Android support needs gomobile support for reading / writing themes, and ideally importing from a .zip or .tar.gz + visible: !Platform.isAndroid, + child: ListTile( + leading: Icon(Icons.palette, color: Provider.of(context).theme.messageFromMeTextColor), + title: Text(AppLocalizations.of(context)!.settingsImportThemeTitle), + subtitle: Text(AppLocalizations.of(context)!.settingsImportThemeDescription), + //AppLocalizations.of( + //context)! + //.fileSharingSettingsDownloadFolderDescription, + trailing: Container( + width: MediaQuery.of(context).size.width / 4, + child: ElevatedButton.icon( + label: Text(AppLocalizations.of(context)!.settingsImportThemeButton), + onPressed: Provider.of(context).disableFilePicker + ? null + : () async { + if (Platform.isAndroid) { + return; + } + var selectedDirectory = await showSelectDirectoryPicker(context); + if (selectedDirectory != null) { + selectedDirectory += path.separator; + final customThemeDir = path.join(await Provider.of(context, listen: false).cwtch.getCwtchDir(), custom_themes_subdir); + importThemeCheck(context, settings, customThemeDir, selectedDirectory); + } else { + // User canceled the picker + } + }, + //onChanged: widget.onSave, + icon: Icon(Icons.folder), + //tooltip: widget.tooltip, + )))), + SwitchListTile( + title: Text(AppLocalizations.of(context)!.settingsThemeImages), + subtitle: Text(AppLocalizations.of(context)!.settingsThemeImagesDescription), + value: settings.themeImages, + onChanged: (bool value) { + settings.themeImages = value; // Save Settings... + saveSettings(context); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.image, color: settings.current().mainTextColor), + ), + ListTile( + title: Text(AppLocalizations.of(context)!.settingUIColumnPortrait), + leading: Icon(Icons.table_chart, color: settings .current() .mainTextColor), trailing: Container( @@ -435,4 +458,97 @@ class _GlobalSettingsAppearanceViewState extends State( + context: context, + isScrollControlled: true, + builder: (BuildContext context) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: RepaintBoundary( + child: Container( + height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs + child: Center( + child: Padding( + padding: EdgeInsets.all(10.0), + child: Column(mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ + Text(AppLocalizations.of(context)!.settingThemeOverwriteQuestion.replaceAll("\$themeName", themeName)), + SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Spacer(), + Expanded( + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.settingThemeOverwriteConfirm, semanticsLabel: AppLocalizations.of(context)!.settingThemeOverwriteConfirm), + onPressed: () { + importTheme(srcDir, targetDir, themesDir, themeName, settings); + + Navigator.pop(context); + }, + )), + SizedBox( + width: 20, + ), + Expanded( + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.cancel, semanticsLabel: AppLocalizations.of(context)!.cancel), + onPressed: () { + Navigator.pop(context); + }, + )), + Spacer(), + ], + ) + ])))))); + }); + } } diff --git a/lib/widgets/folderpicker.dart b/lib/widgets/folderpicker.dart index 6051db43..bae6d72e 100644 --- a/lib/widgets/folderpicker.dart +++ b/lib/widgets/folderpicker.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:provider/provider.dart'; import '../settings.dart'; import 'buttontextfield.dart'; +import 'package:path/path.dart' as path; import 'cwtchlabel.dart'; class CwtchFolderPicker extends StatefulWidget { @@ -15,7 +16,8 @@ class CwtchFolderPicker extends StatefulWidget { final Function(String)? onSave; final Key? testKey; final TextStyle? textStyle; - const CwtchFolderPicker({Key? key, this.testKey, this.textStyle, this.label = "", this.tooltip = "", this.initialValue = "", this.onSave, this.description = ""}) : super(key: key); + final IconData icon; + const CwtchFolderPicker({Key? key, this.testKey, this.textStyle, this.label = "", this.tooltip = "", this.initialValue = "", this.onSave, this.description = "", this.icon = Icons.file_download}) : super(key: key); @override _CwtchFolderPickerState createState() => _CwtchFolderPickerState(); @@ -33,7 +35,7 @@ class _CwtchFolderPickerState extends State { @override Widget build(BuildContext context) { return ListTile( - leading: Icon(Icons.file_download, color: Provider.of(context).theme.messageFromMeTextColor, size: 16), + leading: Icon(widget.icon, color: Provider.of(context).theme.messageFromMeTextColor), title: Text(widget.label), subtitle: Text(widget.description), trailing: Container( @@ -53,7 +55,7 @@ class _CwtchFolderPickerState extends State { var selectedDirectory = await showSelectDirectoryPicker(context); if (selectedDirectory != null) { //File directory = File(selectedDirectory); - selectedDirectory += "/"; + selectedDirectory += path.separator ; ctrlrVal.text = selectedDirectory; if (widget.onSave != null) { widget.onSave!(selectedDirectory);