language picker, console_page refactor, app settings controller #482
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "profile mode",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release mode",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "debug (fdroid)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"fdroid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "debug (production)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"production"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "debug (nightly)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"nightly"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,3 +1,10 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def localProperties = new Properties()
|
def localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
|
@ -6,10 +13,6 @@ if (localPropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
|
@ -21,14 +24,9 @@ if (flutterVersionName == null) {
|
||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'org.selfprivacy.app'
|
namespace 'org.selfprivacy.app'
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -43,13 +41,16 @@ android {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'InvalidPackage'
|
disable 'InvalidPackage'
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId "org.selfprivacy.app"
|
applicationId "org.selfprivacy.app"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
|
@ -57,31 +58,33 @@ android {
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
|
||||||
flavorDimensions "default"
|
|
||||||
productFlavors {
|
|
||||||
fdroid {
|
|
||||||
applicationId "pro.kherel.selfprivacy"
|
|
||||||
}
|
}
|
||||||
production {
|
profile {
|
||||||
applicationIdSuffix ""
|
|
||||||
}
|
}
|
||||||
nightly {
|
release {
|
||||||
applicationIdSuffix ".nightly"
|
|
||||||
versionCode project.getVersionCode()
|
|
||||||
versionName "nightly-" + project.getVersionCode()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buildFeatures {
|
||||||
|
flavorDimensions = ["default"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
flavorDimensions "default"
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
fdroid {
|
fdroid {
|
||||||
|
dimension 'default'
|
||||||
applicationId "pro.kherel.selfprivacy"
|
applicationId "pro.kherel.selfprivacy"
|
||||||
}
|
}
|
||||||
production {
|
production {
|
||||||
applicationIdSuffix ""
|
dimension 'default'
|
||||||
}
|
}
|
||||||
nightly {
|
nightly {
|
||||||
|
dimension 'default'
|
||||||
applicationIdSuffix ".nightly"
|
applicationIdSuffix ".nightly"
|
||||||
versionCode project.getVersionCode()
|
versionCode project.getVersionCode()
|
||||||
versionName "nightly-" + project.getVersionCode()
|
versionName "nightly-" + project.getVersionCode()
|
||||||
|
@ -93,6 +96,5 @@ flutter {
|
||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {}
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.9.21'
|
ext.getVersionCode = { ->
|
||||||
ext.getVersionCode = { ->
|
|
||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
|
@ -13,15 +12,6 @@ buildscript {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx4G
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.bundle.enableUncompressedNativeLibs=false
|
android.bundle.enableUncompressedNativeLibs=false
|
||||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||||
|
|
|
@ -1,11 +1,25 @@
|
||||||
include ':app'
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
def properties = new Properties()
|
|
||||||
|
|
||||||
assert localPropertiesFile.exists()
|
repositories {
|
||||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
plugins {
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
id "com.android.application" version "7.3.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.9.21" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
|
@ -333,14 +333,12 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "إعدادات التطبيق",
|
"title": "إعدادات التطبيق",
|
||||||
"system_dark_theme_title": "الوضع الافتراضي للنظام",
|
"system_theme_mode_title": "الوضع الافتراضي للنظام",
|
||||||
"system_dark_theme_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
|
"system_theme_mode_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
|
||||||
"dark_theme_title": "الوضع الداكن",
|
"dark_theme_title": "الوضع الداكن",
|
||||||
|
"change_application_theme": "قم بتبديل وضع التطبيق",
|
||||||
"dangerous_settings": "إعدادات خطرة",
|
"dangerous_settings": "إعدادات خطرة",
|
||||||
"reset_config_title": "قم بإعادة ضبط إعدادات التطبيق",
|
"reset_config_title": "قم بإعادة ضبط إعدادات التطبيق",
|
||||||
"delete_server_title": "قم بحذف الخادم",
|
|
||||||
"delete_server_description": "سيزيل هذا الخادم الخاص بك، حيث أنه لن تتمكن من الوصول إليه بعد ذلك.",
|
|
||||||
"dark_theme_description": "قم بتبديل وضع التطبيق",
|
|
||||||
"reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز."
|
"reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز."
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
|
|
|
@ -54,15 +54,15 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Tətbiq parametrləri",
|
"title": "Tətbiq parametrləri",
|
||||||
|
"system_theme_mode_title": "Defolt sistem mövzusu",
|
||||||
|
"system_theme_mode_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin",
|
||||||
"dark_theme_title": "Qaranlıq mövzu",
|
"dark_theme_title": "Qaranlıq mövzu",
|
||||||
|
"change_application_theme": "Rəng mövzusunu dəyişdirin",
|
||||||
|
"dangerous_settings": "Təhlükəli Parametrlər",
|
||||||
"reset_config_title": "Tətbiq Sıfırlayın",
|
"reset_config_title": "Tətbiq Sıfırlayın",
|
||||||
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın.",
|
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın."
|
||||||
"delete_server_title": "Serveri silin",
|
|
||||||
"dark_theme_description": "Rəng mövzusunu dəyişdirin",
|
|
||||||
"delete_server_description": "Əməliyyat serveri siləcək. Bundan sonra o, əlçatmaz olacaq.",
|
|
||||||
"system_dark_theme_title": "Defolt sistem mövzusu",
|
|
||||||
"system_dark_theme_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin",
|
|
||||||
"dangerous_settings": "Təhlükəli Parametrlər"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH açarları",
|
"title": "SSH açarları",
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"connect_to_server_provider": "Аўтарызавацца ў ",
|
"connect_to_server_provider": "Аўтарызавацца ў ",
|
||||||
"connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер",
|
"connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер",
|
||||||
"steps": {
|
"steps": {
|
||||||
"nixos_installation": "Ўстаноўка NixOS",
|
"nixos_installation": "Ўсталёўка NixOS",
|
||||||
|
|||||||
"hosting": "Хостынг",
|
"hosting": "Хостынг",
|
||||||
"server_type": "Тып сервера",
|
"server_type": "Тып сервера",
|
||||||
"dns_provider": "DNS правайдэр",
|
"dns_provider": "DNS правайдэр",
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
"domain": "Дамен",
|
"domain": "Дамен",
|
||||||
"master_account": "Майстар акаўнт",
|
"master_account": "Майстар акаўнт",
|
||||||
"server": "Сервер",
|
"server": "Сервер",
|
||||||
"dns_setup": "Устаноўка DNS",
|
"dns_setup": "Усталёўка DNS",
|
||||||
"server_reboot": "Перазагрузка сервера",
|
"server_reboot": "Перазагрузка сервера",
|
||||||
"final_checks": "Фінальныя праверкі"
|
"final_checks": "Фінальныя праверкі"
|
||||||
},
|
},
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
"modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен",
|
"modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен",
|
||||||
"modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу",
|
"modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу",
|
||||||
"fallback_select_provider_console": "Доступ да кансолі хостынгу.",
|
"fallback_select_provider_console": "Доступ да кансолі хостынгу.",
|
||||||
"provider_connected_description": "Сувязь устаноўлена. Увядзіце свой токен з доступам да {}:",
|
"provider_connected_description": "Сувязь наладжана. Увядзіце свой токен з доступам да {}:",
|
||||||
"choose_server": "Выберыце сервер",
|
"choose_server": "Выберыце сервер",
|
||||||
"no_servers": "На вашым акаўнце няма даступных сэрвэраў.",
|
"no_servers": "На вашым акаўнце няма даступных сэрвэраў.",
|
||||||
"modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.",
|
"modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.",
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
"authorize_new_device": "Аўтарызаваць новую прыладу",
|
"authorize_new_device": "Аўтарызаваць новую прыладу",
|
||||||
"access_granted_on": "Доступ выдадзены {}",
|
"access_granted_on": "Доступ выдадзены {}",
|
||||||
"tip": "Націсніце на прыладу, каб адклікаць доступ.",
|
"tip": "Націсніце на прыладу, каб адклікаць доступ.",
|
||||||
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыкладанне SelfPrivacy."
|
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыладу SelfPrivacy."
|
||||||
},
|
},
|
||||||
"add_new_device_screen": {
|
"add_new_device_screen": {
|
||||||
"description": "Увядзіце гэты ключ на новай прыладзе:",
|
"description": "Увядзіце гэты ключ на новай прыладзе:",
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
"revoke_device_alert": {
|
"revoke_device_alert": {
|
||||||
"header": "Адклікаць доступ?",
|
"header": "Адклікаць доступ?",
|
||||||
"yes": "Адклікаць",
|
"yes": "Адклікаць",
|
||||||
"no": "Адменіць",
|
"no": "Адхіліць",
|
||||||
"description": "Прылада {} больш не зможа кіраваць серверам."
|
"description": "Прылада {} больш не зможа кіраваць серверам."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
"later": "Прапусціць і наладзіць потым",
|
"later": "Прапусціць і наладзіць потым",
|
||||||
"no_data": "Няма дадзеных",
|
"no_data": "Няма дадзеных",
|
||||||
"services": "Сэрвісы",
|
"services": "Сэрвісы",
|
||||||
"users": "Ужыткоўнікі",
|
"users": "Карыстальнікі",
|
||||||
"more": "Дадаткова",
|
"more": "Дадаткова",
|
||||||
"got_it": "Зразумеў",
|
"got_it": "Зразумеў",
|
||||||
"settings": "Налады",
|
"settings": "Налады",
|
||||||
|
@ -234,7 +234,7 @@
|
||||||
},
|
},
|
||||||
"more_page": {
|
"more_page": {
|
||||||
"configuration_wizard": "Майстар наладкі",
|
"configuration_wizard": "Майстар наладкі",
|
||||||
"onboarding": "Прівітанне",
|
"onboarding": "Прывітанне",
|
||||||
"create_ssh_key": "SSH ключы адміністратара"
|
"create_ssh_key": "SSH ключы адміністратара"
|
||||||
},
|
},
|
||||||
"about_application_page": {
|
"about_application_page": {
|
||||||
|
@ -244,16 +244,16 @@
|
||||||
"privacy_policy": "Палітыка прыватнасці"
|
"privacy_policy": "Палітыка прыватнасці"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"reset_config_description": "Скінуць API ключы i суперкарыстальніка.",
|
|
||||||
"delete_server_description": "Дзеянне прывядзе да выдалення сервера. Пасля гэтага ён будзе недаступны.",
|
|
||||||
"title": "Налады праграмы",
|
"title": "Налады праграмы",
|
||||||
|
"system_theme_mode_title": "Сістэмная тэма па-змаўчанні",
|
||||||
|
"system_theme_mode_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
|
||||||
"dark_theme_title": "Цёмная тэма",
|
"dark_theme_title": "Цёмная тэма",
|
||||||
"dark_theme_description": "Змяніць каляровую тэму",
|
"change_application_theme": "Змяніць каляровую тэму",
|
||||||
|
"language": "Мова",
|
||||||
|
"click_to_change_locale":"Націсніце, каб адчыніць меню выбару мовы",
|
||||||
|
"dangerous_settings": "Небяспечныя налады",
|
||||||
"reset_config_title": "Скід налад",
|
"reset_config_title": "Скід налад",
|
||||||
"delete_server_title": "Выдаліць сервер",
|
"reset_config_description": "Скінуць API ключы i суперкарыстальніка."
|
||||||
"system_dark_theme_title": "Сістэмная тэма па-змаўчанні",
|
|
||||||
"system_dark_theme_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
|
|
||||||
"dangerous_settings": "Небяспечныя наладкі"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.",
|
"root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.",
|
||||||
|
|
|
@ -54,15 +54,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Nastavení aplikace",
|
"title": "Nastavení aplikace",
|
||||||
|
"system_theme_mode_title": "Výchozí téma systému",
|
||||||
|
"system_theme_mode_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému",
|
||||||
"dark_theme_title": "Tmavé téma",
|
"dark_theme_title": "Tmavé téma",
|
||||||
|
"change_application_theme": "Přepnutí tématu aplikace",
|
||||||
|
"dangerous_settings": "Nebezpečná nastavení",
|
||||||
"reset_config_title": "Obnovení konfigurace aplikace",
|
"reset_config_title": "Obnovení konfigurace aplikace",
|
||||||
"reset_config_description": "Obnovení klíčů API a uživatele root.",
|
"reset_config_description": "Obnovení klíčů API a uživatele root."
|
||||||
"delete_server_title": "Odstranit server",
|
|
||||||
"dark_theme_description": "Přepnutí tématu aplikace",
|
|
||||||
"delete_server_description": "Tím odstraníte svůj server. Nebude již přístupný.",
|
|
||||||
"system_dark_theme_title": "Výchozí téma systému",
|
|
||||||
"system_dark_theme_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému",
|
|
||||||
"dangerous_settings": "Nebezpečná nastavení"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "Klíče SSH",
|
"title": "Klíče SSH",
|
||||||
|
|
|
@ -57,15 +57,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Anwendungseinstellungen",
|
"title": "Anwendungseinstellungen",
|
||||||
|
"system_theme_mode_title": "Standard-Systemthema",
|
||||||
|
"system_theme_mode_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema",
|
||||||
"dark_theme_title": "Dunkles Thema",
|
"dark_theme_title": "Dunkles Thema",
|
||||||
"dark_theme_description": "Ihr Anwendungsdesign wechseln",
|
"change_application_theme": "Ihr Anwendungsdesign wechseln",
|
||||||
|
"dangerous_settings": "Gefährliche Einstellungen",
|
||||||
"reset_config_title": "Anwendungseinstellungen zurücksetzen",
|
"reset_config_title": "Anwendungseinstellungen zurücksetzen",
|
||||||
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen.",
|
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen."
|
||||||
"delete_server_title": "Server löschen",
|
|
||||||
"delete_server_description": "Das wird Ihren Server löschen. Es wird nicht mehr zugänglich sein.",
|
|
||||||
"system_dark_theme_title": "Standard-Systemthema",
|
|
||||||
"system_dark_theme_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema",
|
|
||||||
"dangerous_settings": "Gefährliche Einstellungen"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH Schlüssel",
|
"title": "SSH Schlüssel",
|
||||||
|
|
|
@ -47,7 +47,29 @@
|
||||||
"console_page": {
|
"console_page": {
|
||||||
"title": "Console",
|
"title": "Console",
|
||||||
"waiting": "Waiting for initialization…",
|
"waiting": "Waiting for initialization…",
|
||||||
"copy": "Copy"
|
"copy": "Copy",
|
||||||
|
"copy_raw": "Raw response",
|
||||||
|
"historyEmpty": "No data yet",
|
||||||
|
"error":"Error",
|
||||||
|
"log":"Log",
|
||||||
|
"rest_api_request":"Rest API Request",
|
||||||
|
"rest_api_response":"Rest API Response",
|
||||||
|
"graphql_request":"GraphQL Request",
|
||||||
|
"graphql_response":"GraphQL Response",
|
||||||
|
"logged_at": "Logged at",
|
||||||
|
"data": "Data",
|
||||||
|
"errors":"Errors",
|
||||||
|
"error_path": "Path",
|
||||||
|
"error_locations": "Locations",
|
||||||
|
"error_extensions": "Extensions",
|
||||||
|
"request_data": "Request data",
|
||||||
|
"headers": "Headers",
|
||||||
|
"response_data": "Response data",
|
||||||
|
"context": "Context",
|
||||||
|
"operation": "Operation",
|
||||||
|
"operation_type": "Operation type",
|
||||||
|
"operation_name": "Operation name",
|
||||||
|
"variables": "Variables"
|
||||||
},
|
},
|
||||||
"about_application_page": {
|
"about_application_page": {
|
||||||
"title": "About & support",
|
"title": "About & support",
|
||||||
|
@ -75,10 +97,12 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Application settings",
|
"title": "Application settings",
|
||||||
"system_dark_theme_title": "System default theme",
|
"system_theme_mode_title": "System default theme",
|
||||||
"system_dark_theme_description": "Use light or dark theme depending on system settings",
|
"system_theme_mode_description": "Use light or dark theme depending on system settings",
|
||||||
"dark_theme_title": "Dark theme",
|
"dark_theme_title": "Dark theme",
|
||||||
"dark_theme_description": "Switch your application theme",
|
"change_application_theme": "Switch your application theme",
|
||||||
|
"language": "Language",
|
||||||
|
"click_to_change_locale": "Click to open language list",
|
||||||
"dangerous_settings": "Dangerous settings",
|
"dangerous_settings": "Dangerous settings",
|
||||||
"reset_config_title": "Reset application config",
|
"reset_config_title": "Reset application config",
|
||||||
"reset_config_description": "Resets API keys and root user."
|
"reset_config_description": "Resets API keys and root user."
|
||||||
|
|
|
@ -39,16 +39,14 @@
|
||||||
"test": "es-test",
|
"test": "es-test",
|
||||||
"locale": "es",
|
"locale": "es",
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"reset_config_title": "Restablecer la configuración de la aplicación",
|
|
||||||
"dark_theme_description": "Cambia el tema de tu aplicación",
|
|
||||||
"reset_config_description": "Restablecer claves API y usuario root.",
|
|
||||||
"delete_server_title": "Eliminar servidor",
|
|
||||||
"delete_server_description": "Esto elimina su servidor. Ya no será accesible.",
|
|
||||||
"title": "Ajustes de la aplicación",
|
"title": "Ajustes de la aplicación",
|
||||||
|
"system_theme_mode_title": "Tema del sistema",
|
||||||
|
"system_theme_mode_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
|
||||||
"dark_theme_title": "Tema oscuro",
|
"dark_theme_title": "Tema oscuro",
|
||||||
"system_dark_theme_title": "Tema del sistema",
|
"change_application_theme": "Cambia el tema de tu aplicación",
|
||||||
"system_dark_theme_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
|
"dangerous_settings": "Configuraciones peligrosas",
|
||||||
"dangerous_settings": "Configuraciones peligrosas"
|
"reset_config_title": "Restablecer la configuración de la aplicación",
|
||||||
|
"reset_config_description": "Restablecer claves API y usuario root."
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
{
|
{
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"system_dark_theme_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest",
|
|
||||||
"delete_server_description": "See eemaldab teie serveri. Seda ei saa enam juurde pääseda.",
|
|
||||||
"title": "Rakenduse seaded",
|
"title": "Rakenduse seaded",
|
||||||
"system_dark_theme_title": "Süsteemi vaiketeema",
|
"system_theme_mode_title": "Süsteemi vaiketeema",
|
||||||
|
"system_theme_mode_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest",
|
||||||
"dark_theme_title": "Tume teema",
|
"dark_theme_title": "Tume teema",
|
||||||
"dark_theme_description": "Vaheta oma rakenduse teemat",
|
"change_application_theme": "Vaheta oma rakenduse teemat",
|
||||||
"dangerous_settings": "Ohtlikud seaded",
|
"dangerous_settings": "Ohtlikud seaded",
|
||||||
"reset_config_title": "Lähtesta rakenduse konfiguratsioon",
|
"reset_config_title": "Lähtesta rakenduse konfiguratsioon",
|
||||||
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja.",
|
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja."
|
||||||
"delete_server_title": "Kustuta server"
|
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"reboot_after_upgrade": "Taaskäivita pärast värskendust",
|
"reboot_after_upgrade": "Taaskäivita pärast värskendust",
|
||||||
|
|
|
@ -56,15 +56,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Paramètres de l'application",
|
"title": "Paramètres de l'application",
|
||||||
"dark_theme_description": "Changer le thème de l'application",
|
"system_theme_mode_title": "Thème par défaut du système",
|
||||||
"reset_config_title": "Réinitialiser la configuration de l'application",
|
"system_theme_mode_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
|
||||||
"delete_server_title": "Supprimer le serveur",
|
|
||||||
"delete_server_description": "Cela va supprimer votre serveur. Celui-ci ne sera plus accessible.",
|
|
||||||
"dark_theme_title": "Thème sombre",
|
"dark_theme_title": "Thème sombre",
|
||||||
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root.",
|
"change_application_theme": "Changer le thème de l'application",
|
||||||
"system_dark_theme_title": "Thème par défaut du système",
|
"dangerous_settings": "Paramètres dangereux",
|
||||||
"system_dark_theme_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
|
"reset_config_title": "Réinitialiser la configuration de l'application",
|
||||||
"dangerous_settings": "Paramètres dangereux"
|
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root."
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "Clés SSH",
|
"title": "Clés SSH",
|
||||||
|
|
|
@ -81,15 +81,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "הגדרות יישום",
|
"title": "הגדרות יישום",
|
||||||
"system_dark_theme_title": "ערכת העיצוב כברירת המחדל של המערכת",
|
"system_theme_mode_title": "ערכת העיצוב כברירת המחדל של המערכת",
|
||||||
"system_dark_theme_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
|
"system_theme_mode_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
|
||||||
"dark_theme_title": "ערכת עיצוב כהה",
|
"dark_theme_title": "ערכת עיצוב כהה",
|
||||||
"dark_theme_description": "החלפת ערכת העיצוב של המערכת שלך",
|
"change_application_theme": "החלפת ערכת העיצוב של המערכת שלך",
|
||||||
"dangerous_settings": "הגדרות מסוכנות",
|
"dangerous_settings": "הגדרות מסוכנות",
|
||||||
"reset_config_title": "איפוס הגדרות היישומון",
|
"reset_config_title": "איפוס הגדרות היישומון",
|
||||||
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל.",
|
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל."
|
||||||
"delete_server_title": "מחיקת שרת",
|
|
||||||
"delete_server_description": "מסיר את השרת שלך. הוא לא יהיה זמין עוד."
|
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
"create_new_select_heading": "לבחור מה לגבות",
|
"create_new_select_heading": "לבחור מה לגבות",
|
||||||
|
|
|
@ -91,16 +91,14 @@
|
||||||
"bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз."
|
"bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз."
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
|
"title": "Қосымша параметрлері",
|
||||||
|
"system_theme_mode_title": "Системалық қараңғы тақырып",
|
||||||
|
"system_theme_mode_description": "Системалық қараңғы тақырып сипаттамасы",
|
||||||
|
"dark_theme_title": "Қараңғы тақырып",
|
||||||
|
"change_application_theme": "Қараңғы тақырып сипаттамасы",
|
||||||
"dangerous_settings": "Қауіпті параметрлер",
|
"dangerous_settings": "Қауіпті параметрлер",
|
||||||
"reset_config_title": "Конфигурацияны қалпына келтіру",
|
"reset_config_title": "Конфигурацияны қалпына келтіру",
|
||||||
"title": "Қосымша параметрлері",
|
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы."
|
||||||
"system_dark_theme_title": "Системалық қараңғы тақырып",
|
|
||||||
"system_dark_theme_description": "Системалық қараңғы тақырып сипаттамасы",
|
|
||||||
"dark_theme_title": "Қараңғы тақырып",
|
|
||||||
"dark_theme_description": "Қараңғы тақырып сипаттамасы",
|
|
||||||
"delete_server_title": "Серверді жою",
|
|
||||||
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы.",
|
|
||||||
"delete_server_description": "Серверді жою сипаттамасы."
|
|
||||||
},
|
},
|
||||||
"resource_chart": {
|
"resource_chart": {
|
||||||
"month": "Ай",
|
"month": "Ай",
|
||||||
|
|
|
@ -52,16 +52,14 @@
|
||||||
"privacy_policy": "Privātuma politika"
|
"privacy_policy": "Privātuma politika"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"system_dark_theme_title": "Sistēmas noklusējuma dizains",
|
|
||||||
"dark_theme_title": "Tumšs dizains",
|
|
||||||
"title": "Aplikācijas iestatījumi",
|
"title": "Aplikācijas iestatījumi",
|
||||||
"system_dark_theme_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
|
"system_theme_mode_title": "Sistēmas noklusējuma dizains",
|
||||||
"dark_theme_description": "Lietojumprogrammas dizaina pārslēgšana",
|
"system_theme_mode_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
|
||||||
|
"dark_theme_title": "Tumšs dizains",
|
||||||
|
"change_application_theme": "Lietojumprogrammas dizaina pārslēgšana",
|
||||||
"dangerous_settings": "Bīstamie iestatījumi",
|
"dangerous_settings": "Bīstamie iestatījumi",
|
||||||
"reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju",
|
"reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju",
|
||||||
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju.",
|
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju."
|
||||||
"delete_server_title": "Izdzēst serveri",
|
|
||||||
"delete_server_description": "Šis izdzēš jūsu serveri. Tas vairs nebūs pieejams."
|
|
||||||
},
|
},
|
||||||
"locale": "lv",
|
"locale": "lv",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
|
|
|
@ -56,15 +56,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Ustawienia aplikacji",
|
"title": "Ustawienia aplikacji",
|
||||||
|
"system_theme_mode_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu",
|
||||||
|
"system_theme_mode_title": "Domyślny motyw systemowy",
|
||||||
"dark_theme_title": "Ciemny motyw aplikacji",
|
"dark_theme_title": "Ciemny motyw aplikacji",
|
||||||
"dark_theme_description": "Zmień kolor motywu aplikacji",
|
"change_application_theme": "Zmień kolor motywu aplikacji",
|
||||||
|
"dangerous_settings": "Niebezpieczne ustawienia",
|
||||||
"reset_config_title": "Resetowanie",
|
"reset_config_title": "Resetowanie",
|
||||||
"reset_config_description": "Zresetuj klucze API i użytkownika root.",
|
"reset_config_description": "Zresetuj klucze API i użytkownika root."
|
||||||
"delete_server_title": "Usuń serwer",
|
|
||||||
"delete_server_description": "Ta czynność usunie serwer. Po tym będzie niedostępny.",
|
|
||||||
"system_dark_theme_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu",
|
|
||||||
"system_dark_theme_title": "Domyślny motyw systemowy",
|
|
||||||
"dangerous_settings": "Niebezpieczne ustawienia"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "klucze SSH",
|
"title": "klucze SSH",
|
||||||
|
|
|
@ -75,15 +75,15 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Настройки приложения",
|
"title": "Настройки приложения",
|
||||||
|
"system_theme_mode_title": "Системная тема",
|
||||||
|
"system_theme_mode_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
|
||||||
"dark_theme_title": "Тёмная тема",
|
"dark_theme_title": "Тёмная тема",
|
||||||
"dark_theme_description": "Сменить цветовую тему",
|
"change_application_theme": "Сменить цветовую тему",
|
||||||
|
"language": "Язык",
|
||||||
|
"click_to_change_locale": "Нажмите, чтобы открыть список языков",
|
||||||
|
"dangerous_settings": "Опасные настройки",
|
||||||
"reset_config_title": "Сброс настроек",
|
"reset_config_title": "Сброс настроек",
|
||||||
"reset_config_description": "Сбросить API ключи и root пользователя.",
|
"reset_config_description": "Сбросить API ключи и root пользователя."
|
||||||
"delete_server_title": "Удалить сервер",
|
|
||||||
"delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен.",
|
|
||||||
"system_dark_theme_title": "Системная тема",
|
|
||||||
"system_dark_theme_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
|
|
||||||
"dangerous_settings": "Опасные настройки"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH ключи",
|
"title": "SSH ключи",
|
||||||
|
|
|
@ -103,15 +103,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Nastavenia aplikácie",
|
"title": "Nastavenia aplikácie",
|
||||||
|
"system_theme_mode_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému",
|
||||||
|
"system_theme_mode_title": "Systémová predvolená téma",
|
||||||
"dark_theme_title": "Temná téma",
|
"dark_theme_title": "Temná téma",
|
||||||
"dark_theme_description": "Zmeniť tému aplikácie",
|
"change_application_theme": "Zmeniť tému aplikácie",
|
||||||
|
"dangerous_settings": "Nebezpečné nastavenia",
|
||||||
"reset_config_title": "Resetovať nastavenia aplikácie",
|
"reset_config_title": "Resetovať nastavenia aplikácie",
|
||||||
"reset_config_description": "Resetovať kľúče API a užívateľa root.",
|
"reset_config_description": "Resetovať kľúče API a užívateľa root."
|
||||||
"delete_server_title": "Zmazať server",
|
|
||||||
"delete_server_description": "Tým sa odstráni váš server. Už nebude prístupným.",
|
|
||||||
"system_dark_theme_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému",
|
|
||||||
"system_dark_theme_title": "Systémová predvolená téma",
|
|
||||||
"dangerous_settings": "Nebezpečné nastavenia"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "Kľúče SSH",
|
"title": "Kľúče SSH",
|
||||||
|
|
|
@ -53,15 +53,13 @@
|
||||||
"application_version_text": "Različica aplikacije"
|
"application_version_text": "Različica aplikacije"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"dark_theme_title": "Temna tema",
|
|
||||||
"title": "Nastavitve aplikacije",
|
"title": "Nastavitve aplikacije",
|
||||||
"system_dark_theme_title": "Privzeta tema sistema",
|
"system_theme_mode_title": "Privzeta tema sistema",
|
||||||
"system_dark_theme_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
|
"system_theme_mode_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
|
||||||
"dark_theme_description": "Spreminjanje barvne teme",
|
"dark_theme_title": "Temna tema",
|
||||||
|
"change_application_theme": "Spreminjanje barvne teme",
|
||||||
"dangerous_settings": "Nevarne nastavitve",
|
"dangerous_settings": "Nevarne nastavitve",
|
||||||
"reset_config_title": "Ponastavitev konfiguracije aplikacije",
|
"reset_config_title": "Ponastavitev konfiguracije aplikacije"
|
||||||
"delete_server_title": "Brisanje strežnika",
|
|
||||||
"delete_server_description": "To dejanje povzroči izbris strežnika. Nato bo nedosegljiv."
|
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
"page1_title": "Digitalna neodvisnost je na voljo vsem",
|
"page1_title": "Digitalna neodvisnost je na voljo vsem",
|
||||||
|
|
|
@ -47,13 +47,11 @@
|
||||||
"privacy_policy": "นโยบายความเป็นส่วนตัว"
|
"privacy_policy": "นโยบายความเป็นส่วนตัว"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"dark_theme_description": "สลับธีมแอปพลิเคชั่นของคุณ",
|
|
||||||
"delete_server_description": "การกระทำนี้จะลบเซิฟเวอร์ของคุณทิ้งและคุณจะไม่สามารถเข้าถึงมันได้อีก",
|
|
||||||
"title": "การตั้งค่าแอปพลิเคชัน",
|
"title": "การตั้งค่าแอปพลิเคชัน",
|
||||||
"dark_theme_title": "ธีมมืด",
|
"dark_theme_title": "ธีมมืด",
|
||||||
|
"change_application_theme": "สลับธีมแอปพลิเคชั่นของคุณ",
|
||||||
"reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น",
|
"reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น",
|
||||||
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root",
|
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root"
|
||||||
"delete_server_title": "ลบเซิฟเวอร์"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"create": "สร้างกุญแจ SSH",
|
"create": "สร้างกุญแจ SSH",
|
||||||
|
|
|
@ -41,15 +41,14 @@
|
||||||
"locale": "ua",
|
"locale": "ua",
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Налаштування додатка",
|
"title": "Налаштування додатка",
|
||||||
"reset_config_title": "Скинути налаштування",
|
"system_theme_mode_title": "Системна тема за замовчуванням",
|
||||||
|
"system_theme_mode_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
|
||||||
"dark_theme_title": "Темна тема",
|
"dark_theme_title": "Темна тема",
|
||||||
"dark_theme_description": "Змінити тему додатка",
|
"change_application_theme": "Змінити тему додатка",
|
||||||
"reset_config_description": "Скинути API ключі та root користувача.",
|
"language": "Мова",
|
||||||
"delete_server_title": "Видалити сервер",
|
"dangerous_settings": "Небезпечні налаштування",
|
||||||
"delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний.",
|
"reset_config_title": "Скинути налаштування",
|
||||||
"system_dark_theme_title": "Системна тема за замовчуванням",
|
"reset_config_description": "Скинути API ключі та root користувача."
|
||||||
"system_dark_theme_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
|
|
||||||
"dangerous_settings": "Небезпечні налаштування"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?",
|
"delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?",
|
||||||
|
|
|
@ -476,15 +476,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "应用设置",
|
"title": "应用设置",
|
||||||
"system_dark_theme_title": "系统默认主题",
|
"system_theme_mode_title": "系统默认主题",
|
||||||
|
"system_theme_mode_description": "根据系统设置自动使用明亮或暗色主题",
|
||||||
"dark_theme_title": "暗色主题",
|
"dark_theme_title": "暗色主题",
|
||||||
"system_dark_theme_description": "根据系统设置自动使用明亮或暗色主题",
|
"change_application_theme": "切换应用主题",
|
||||||
"dark_theme_description": "切换应用主题",
|
|
||||||
"dangerous_settings": "危险设置",
|
"dangerous_settings": "危险设置",
|
||||||
"reset_config_title": "重置应用配置",
|
"reset_config_title": "重置应用配置",
|
||||||
"delete_server_title": "删除服务器",
|
"delete_server_title": "删除服务器"
|
||||||
"delete_server_description": "这将移除您的服务器。它将不再可以访问。",
|
|
||||||
"reset_config_description": "重置API密钥和root用户。"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH密钥",
|
"title": "SSH密钥",
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_color_utilities/material_color_utilities.dart'
|
||||||
|
as color_utils;
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
|
||||||
|
|
||||||
|
/// A class that many Widgets can interact with to read current app
|
||||||
|
/// configuration, update it, or listen to its changes.
|
||||||
|
///
|
||||||
|
/// AppController uses repo to change persistent data.
|
||||||
|
class AppController with ChangeNotifier {
|
||||||
|
AppController(this._repo);
|
||||||
|
|
||||||
|
/// repo encapsulates retrieval and storage of preferences
|
||||||
|
final PreferencesRepository _repo;
|
||||||
|
|
||||||
|
/// TODO: to be removed or changed
|
||||||
|
late final ApiConfigModel _apiConfigModel = getIt.get<ApiConfigModel>();
|
||||||
|
|
||||||
|
bool _loaded = false;
|
||||||
|
bool get loaded => _loaded;
|
||||||
|
|
||||||
|
// localization
|
||||||
|
late Locale _locale;
|
||||||
|
Locale get locale => _locale;
|
||||||
|
late List<Locale> _supportedLocales;
|
||||||
|
List<Locale> get supportedLocales => _supportedLocales;
|
||||||
|
|
||||||
|
// theme
|
||||||
|
late ThemeData _lightTheme;
|
||||||
|
ThemeData get lightTheme => _lightTheme;
|
||||||
|
late ThemeData _darkTheme;
|
||||||
|
ThemeData get darkTheme => _darkTheme;
|
||||||
|
late color_utils.CorePalette _corePalette;
|
||||||
|
color_utils.CorePalette get corePalette => _corePalette;
|
||||||
|
|
||||||
|
late bool _systemThemeModeActive;
|
||||||
|
bool get systemThemeModeActive => _systemThemeModeActive;
|
||||||
|
|
||||||
|
late bool _darkThemeModeActive;
|
||||||
|
bool get darkThemeModeActive => _darkThemeModeActive;
|
||||||
|
|
||||||
|
ThemeMode get themeMode => systemThemeModeActive
|
||||||
|
? ThemeMode.system
|
||||||
|
: darkThemeModeActive
|
||||||
|
? ThemeMode.dark
|
||||||
|
: ThemeMode.light;
|
||||||
|
// // Make ThemeMode a private variable so it is not updated directly without
|
||||||
|
// // also persisting the changes with the repo..
|
||||||
|
// late ThemeMode _themeMode;
|
||||||
inex
commented
Review
I guess it is no longer needed? I guess it is no longer needed?
|
|||||||
|
// // Allow Widgets to read the user's preferred ThemeMode.
|
||||||
|
// ThemeMode get themeMode => _themeMode;
|
||||||
|
|
||||||
|
late bool _shouldShowOnboarding;
|
||||||
|
bool get shouldShowOnboarding => _shouldShowOnboarding;
|
||||||
|
|
||||||
|
/// Load the user's settings from the SettingsService. It may load from a
|
||||||
|
/// local database or the internet. The controller only knows it can load the
|
||||||
inex
commented
Review
From the internet? From the internet?
|
|||||||
|
/// settings from the service.
|
||||||
|
Future<void> init({
|
||||||
|
// required final AppPreferencesRepository repo,
|
||||||
|
required final ThemeData lightThemeData,
|
||||||
|
required final ThemeData darkThemeData,
|
||||||
|
required final color_utils.CorePalette colorPalette,
|
||||||
|
}) async {
|
||||||
|
// _repo = repo;
|
||||||
|
|
||||||
|
await Future.wait(<Future>[
|
||||||
|
// load locale
|
||||||
|
() async {
|
||||||
|
_supportedLocales = await _repo.getSupportedLocales();
|
||||||
|
|
||||||
|
_locale = await _repo.getActiveLocale();
|
||||||
|
// preset value to other state holders
|
||||||
|
await _apiConfigModel.setLocaleCode(_locale.languageCode);
|
||||||
|
await _repo.setDelegateLocale(_locale);
|
||||||
|
}(),
|
||||||
|
|
||||||
|
// load theme mode && initialize theme
|
||||||
|
() async {
|
||||||
|
_lightTheme = lightThemeData;
|
||||||
|
_darkTheme = darkThemeData;
|
||||||
|
_corePalette = colorPalette;
|
||||||
|
// _themeMode = await _repo.getThemeMode();
|
||||||
|
_darkThemeModeActive = await _repo.getDarkThemeModeFlag();
|
||||||
|
_systemThemeModeActive = await _repo.getSystemThemeModeFlag();
|
||||||
|
}(),
|
||||||
|
|
||||||
|
// load onboarding flag
|
||||||
|
() async {
|
||||||
|
_shouldShowOnboarding = await _repo.getShouldShowOnboarding();
|
||||||
|
}(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
_loaded = true;
|
||||||
|
// Important! Inform listeners a change has occurred.
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRepoReference
|
||||||
|
|
||||||
|
Future<void> setShouldShowOnboarding(final bool newValue) async {
|
||||||
|
// Do not perform any work if new and old flag values are identical
|
||||||
|
if (newValue == shouldShowOnboarding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the flag in memory
|
||||||
|
_shouldShowOnboarding = newValue;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
// Persist the change
|
||||||
|
await _repo.setShouldShowOnboarding(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setSystemThemeModeFlag(final bool newValue) async {
|
||||||
|
// Do not perform any work if new and old ThemeMode are identical
|
||||||
|
if (systemThemeModeActive == newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the new ThemeMode in memory
|
||||||
|
_systemThemeModeActive = newValue;
|
||||||
|
|
||||||
|
// Inform listeners a change has occurred.
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
// Persist the change
|
||||||
|
await _repo.setSystemModeFlag(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setDarkThemeModeFlag(final bool newValue) async {
|
||||||
|
// Do not perform any work if new and old ThemeMode are identical
|
||||||
|
if (darkThemeModeActive == newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the new ThemeMode in memory
|
||||||
|
_darkThemeModeActive = newValue;
|
||||||
|
|
||||||
|
// Inform listeners a change has occurred.
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
// Persist the change
|
||||||
|
await _repo.setDarkThemeModeFlag(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Update and persist the ThemeMode based on the user's selection.
|
||||||
|
// Future<void> setThemeMode(final ThemeMode newThemeMode) async {
|
||||||
|
// // Do not perform any work if new and old ThemeMode are identical
|
||||||
|
// if (newThemeMode == themeMode) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Store the new ThemeMode in memory
|
||||||
|
// _themeMode = newThemeMode;
|
||||||
|
|
||||||
|
// // Inform listeners a change has occurred.
|
||||||
|
// notifyListeners();
|
||||||
|
|
||||||
|
// // Persist the change
|
||||||
|
// await _repo.setThemeMode(newThemeMode);
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> setLocale(final Locale newLocale) async {
|
||||||
|
// Do not perform any work if new and old Locales are identical
|
||||||
|
if (newLocale == _locale) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the new Locale in memory
|
||||||
|
_locale = newLocale;
|
||||||
|
|
||||||
|
/// update locale delegate, which in turn should update deps
|
||||||
|
await _repo.setDelegateLocale(newLocale);
|
||||||
|
|
||||||
|
// Persist the change
|
||||||
|
await _repo.setActiveLocale(newLocale);
|
||||||
|
// Update other locale holders
|
||||||
|
await _apiConfigModel.setLocaleCode(newLocale.languageCode);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_color_utilities/material_color_utilities.dart'
|
||||||
|
as color_utils;
|
||||||
|
import 'package:selfprivacy/config/app_controller/app_controller.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
|
||||||
|
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||||
|
|
||||||
|
class _AppControllerInjector extends InheritedNotifier<AppController> {
|
||||||
|
const _AppControllerInjector({
|
||||||
|
required super.child,
|
||||||
|
required super.notifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class InheritedAppController extends StatefulWidget {
|
||||||
|
const InheritedAppController({
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InheritedAppController> createState() => _InheritedAppControllerState();
|
||||||
|
|
||||||
|
static AppController of(final BuildContext context) => context
|
||||||
|
.dependOnInheritedWidgetOfExactType<_AppControllerInjector>()!
|
||||||
|
.notifier!;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InheritedAppControllerState extends State<InheritedAppController> {
|
||||||
|
// actual state provider
|
||||||
|
late AppController controller;
|
||||||
|
// hold local reference to active repo
|
||||||
|
late PreferencesRepository _repo;
|
||||||
|
|
||||||
|
bool initTriggerred = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
/// update reference on dependency change
|
||||||
|
_repo = InheritedPreferencesRepository.of(context)!;
|
||||||
|
|
||||||
|
if (!initTriggerred) {
|
||||||
|
/// hook controller repo to local reference
|
||||||
|
controller = AppController(_repo);
|
||||||
|
initialize();
|
||||||
|
initTriggerred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
late final ThemeData lightThemeData;
|
||||||
|
late final ThemeData darkThemeData;
|
||||||
|
late final color_utils.CorePalette colorPalette;
|
||||||
|
|
||||||
|
await Future.wait(
|
||||||
|
<Future<void>>[
|
||||||
|
() async {
|
||||||
|
lightThemeData = await AppThemeFactory.create(
|
||||||
|
isDark: false,
|
||||||
|
fallbackColor: BrandColors.primary,
|
||||||
|
);
|
||||||
|
}(),
|
||||||
|
() async {
|
||||||
|
darkThemeData = await AppThemeFactory.create(
|
||||||
|
isDark: true,
|
||||||
|
fallbackColor: BrandColors.primary,
|
||||||
|
);
|
||||||
|
}(),
|
||||||
|
() async {
|
||||||
|
colorPalette = (await AppThemeFactory.getCorePalette()) ??
|
||||||
|
color_utils.CorePalette.of(BrandColors.primary.value);
|
||||||
|
}(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await controller.init(
|
||||||
|
colorPalette: colorPalette,
|
||||||
|
lightThemeData: lightThemeData,
|
||||||
|
darkThemeData: darkThemeData,
|
||||||
|
);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((final _) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => _AppControllerInjector(
|
||||||
|
notifier: controller,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,14 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/connection_status_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||||
|
@ -56,58 +55,46 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) => MultiProvider(
|
||||||
const isDark = false;
|
providers: [
|
||||||
const isAutoDark = true;
|
BlocProvider(
|
||||||
|
create: (final _) => supportSystemCubit,
|
||||||
return MultiProvider(
|
),
|
||||||
providers: [
|
BlocProvider(
|
||||||
BlocProvider(
|
create: (final _) => serverInstallationCubit,
|
||||||
create: (final _) => AppSettingsCubit(
|
lazy: false,
|
||||||
isDarkModeOn: isDark,
|
),
|
||||||
isAutoDarkModeOn: isAutoDark,
|
BlocProvider(
|
||||||
isOnboardingShowing: true,
|
create: (final _) => usersBloc,
|
||||||
)..load(),
|
lazy: false,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => supportSystemCubit,
|
create: (final _) => servicesBloc,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => serverInstallationCubit,
|
create: (final _) => backupsBloc,
|
||||||
lazy: false,
|
),
|
||||||
),
|
BlocProvider(
|
||||||
BlocProvider(
|
create: (final _) => dnsRecordsCubit,
|
||||||
create: (final _) => usersBloc,
|
),
|
||||||
lazy: false,
|
BlocProvider(
|
||||||
),
|
create: (final _) => recoveryKeyBloc,
|
||||||
BlocProvider(
|
),
|
||||||
create: (final _) => servicesBloc,
|
BlocProvider(
|
||||||
),
|
create: (final _) => devicesBloc,
|
||||||
BlocProvider(
|
),
|
||||||
create: (final _) => backupsBloc,
|
BlocProvider(
|
||||||
),
|
create: (final _) => serverJobsBloc,
|
||||||
BlocProvider(
|
),
|
||||||
create: (final _) => dnsRecordsCubit,
|
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||||
),
|
BlocProvider(
|
||||||
BlocProvider(
|
create: (final _) => serverDetailsCubit,
|
||||||
create: (final _) => recoveryKeyBloc,
|
),
|
||||||
),
|
BlocProvider(create: (final _) => volumesBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => devicesBloc,
|
create: (final _) => JobsCubit(),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
],
|
||||||
create: (final _) => serverJobsBloc,
|
child: widget.child,
|
||||||
),
|
);
|
||||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => serverDetailsCubit,
|
|
||||||
),
|
|
||||||
BlocProvider(create: (final _) => volumesBloc),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => JobsCubit(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/console.dart';
|
import 'package:selfprivacy/logic/get_it/console_model.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
|
|
||||||
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
export 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/console.dart';
|
export 'package:selfprivacy/logic/get_it/console_model.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
|
|
||||||
final GetIt getIt = GetIt.instance;
|
final GetIt getIt = GetIt.instance;
|
||||||
|
|
||||||
Future<void> getItSetup() async {
|
Future<void> getItSetup() async {
|
||||||
getIt.registerSingleton<NavigationService>(NavigationService());
|
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||||
|
|
||||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
|
||||||
|
final apiConfigModel = ApiConfigModel();
|
||||||
|
await apiConfigModel.init();
|
||||||
|
getIt.registerSingleton<ApiConfigModel>(apiConfigModel);
|
||||||
|
|
||||||
getIt.registerSingleton<ApiConnectionRepository>(
|
getIt.registerSingleton<ApiConnectionRepository>(
|
||||||
ApiConnectionRepository()..init(),
|
ApiConnectionRepository()..init(),
|
||||||
|
|
|
@ -74,17 +74,20 @@ class HiveConfig {
|
||||||
|
|
||||||
/// Mappings for the different boxes and their keys
|
/// Mappings for the different boxes and their keys
|
||||||
class BNames {
|
class BNames {
|
||||||
/// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing]
|
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
|
||||||
static String appSettingsBox = 'appSettings';
|
static String appSettingsBox = 'appSettings';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isDarkModeOn = 'isDarkModeOn';
|
static String darkThemeModeOn = 'isDarkModeOn';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
|
static String systemThemeModeOn = 'isAutoDarkModeOn';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isOnboardingShowing = 'isOnboardingShowing';
|
static String shouldShowOnboarding = 'isOnboardingShowing';
|
||||||
|
|
||||||
|
/// A string field
|
||||||
|
static String appLocale = 'appLocale';
|
||||||
|
|
||||||
/// Encryption key to decrypt [serverInstallationBox] and [usersBox] box.
|
/// Encryption key to decrypt [serverInstallationBox] and [usersBox] box.
|
||||||
static String serverInstallationEncryptionKey = 'key';
|
static String serverInstallationEncryptionKey = 'key';
|
||||||
|
|
|
@ -3,40 +3,72 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class Localization extends StatelessWidget {
|
class Localization extends StatelessWidget {
|
||||||
const Localization({
|
const Localization({
|
||||||
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
this.child,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget? child;
|
// when adding new locale, add corresponding native language name to mapper
|
||||||
|
// below
|
||||||
|
static const supportedLocales = [
|
||||||
|
Locale('ar'),
|
||||||
|
Locale('az'),
|
||||||
|
Locale('be'),
|
||||||
|
Locale('cs'),
|
||||||
|
Locale('de'),
|
||||||
|
Locale('en'),
|
||||||
|
Locale('es'),
|
||||||
|
Locale('et'),
|
||||||
|
Locale('fr'),
|
||||||
|
Locale('he'),
|
||||||
|
Locale('kk'),
|
||||||
|
Locale('lv'),
|
||||||
|
Locale('mk'),
|
||||||
|
Locale('pl'),
|
||||||
|
Locale('ru'),
|
||||||
|
Locale('sk'),
|
||||||
|
Locale('sl'),
|
||||||
|
Locale('th'),
|
||||||
|
Locale('uk'),
|
||||||
|
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags
|
||||||
|
static final _languageNames = {
|
||||||
|
const Locale('ar'): 'العربية',
|
||||||
|
const Locale('az'): 'Azərbaycan',
|
||||||
|
const Locale('be'): 'беларуская',
|
||||||
|
const Locale('cs'): 'čeština',
|
||||||
|
const Locale('de'): 'Deutsch',
|
||||||
|
const Locale('en'): 'English',
|
||||||
|
const Locale('es'): 'español',
|
||||||
|
const Locale('et'): 'eesti',
|
||||||
|
const Locale('fr'): 'français',
|
||||||
|
const Locale('he'): 'עברית',
|
||||||
|
const Locale('kk'): 'Қазақша',
|
||||||
|
const Locale('lv'): 'latviešu',
|
||||||
|
const Locale('mk'): 'македонски јазик',
|
||||||
|
const Locale('pl'): 'polski',
|
||||||
|
const Locale('ru'): 'русский',
|
||||||
|
const Locale('sk'): 'slovenčina',
|
||||||
|
const Locale('sl'): 'slovenski',
|
||||||
|
const Locale('th'): 'ไทย',
|
||||||
|
const Locale('uk'): 'Українська',
|
||||||
|
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'): '中文',
|
||||||
|
};
|
||||||
|
|
||||||
|
static String getLanguageName(final Locale locale) =>
|
||||||
|
_languageNames[locale] ?? locale.languageCode;
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => EasyLocalization(
|
Widget build(final BuildContext context) => EasyLocalization(
|
||||||
supportedLocales: const [
|
supportedLocales: supportedLocales,
|
||||||
Locale('ar'),
|
|
||||||
Locale('az'),
|
|
||||||
Locale('be'),
|
|
||||||
Locale('cs'),
|
|
||||||
Locale('de'),
|
|
||||||
Locale('en'),
|
|
||||||
Locale('es'),
|
|
||||||
Locale('et'),
|
|
||||||
Locale('fr'),
|
|
||||||
Locale('he'),
|
|
||||||
Locale('kk'),
|
|
||||||
Locale('lv'),
|
|
||||||
Locale('mk'),
|
|
||||||
Locale('pl'),
|
|
||||||
Locale('ru'),
|
|
||||||
Locale('sk'),
|
|
||||||
Locale('sl'),
|
|
||||||
Locale('th'),
|
|
||||||
Locale('uk'),
|
|
||||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
|
|
||||||
],
|
|
||||||
path: 'assets/translations',
|
path: 'assets/translations',
|
||||||
fallbackLocale: const Locale('en'),
|
fallbackLocale: const Locale('en'),
|
||||||
useFallbackTranslations: true,
|
useFallbackTranslations: true,
|
||||||
saveLocale: false,
|
saveLocale: false,
|
||||||
useOnlyLangCode: false,
|
useOnlyLangCode: false,
|
||||||
child: child!,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/// abstraction for manipulation of stored app preferences
|
||||||
|
abstract class PreferencesDataSource {
|
||||||
|
/// should onboarding be shown
|
||||||
|
Future<bool> getOnboardingFlag();
|
||||||
|
|
||||||
|
/// should onboarding be shown
|
||||||
|
Future<void> setOnboardingFlag(final bool newValue);
|
||||||
|
|
||||||
|
// TODO: should probably deprecate the following, instead add the
|
||||||
|
// getThemeMode and setThemeMode methods, which store one value instead of
|
||||||
|
// flags.
|
||||||
|
|
||||||
|
/// should system theme mode be enabled
|
||||||
|
Future<bool?> getSystemThemeModeFlag();
|
||||||
|
|
||||||
|
/// should system theme mode be enabled
|
||||||
|
Future<void> setSystemThemeModeFlag(final bool newValue);
|
||||||
|
|
||||||
|
/// should dark theme be enabled
|
||||||
|
Future<bool?> getDarkThemeModeFlag();
|
||||||
|
|
||||||
|
/// should dark theme be enabled
|
||||||
|
Future<void> setDarkThemeModeFlag(final bool newValue);
|
||||||
|
|
||||||
|
/// locale, as set by user
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// when null, app takes system locale
|
||||||
|
Future<String?> getLocale();
|
||||||
|
|
||||||
|
/// locale, as set by user
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// when null, app takes system locale
|
||||||
|
Future<void> setLocale(final String newLocale);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
|
||||||
|
|
||||||
|
/// app preferences data source hive implementation
|
||||||
|
class PreferencesHiveDataSource implements PreferencesDataSource {
|
||||||
|
final Box _appSettingsBox = Hive.box(BNames.appSettingsBox);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> getOnboardingFlag() async =>
|
||||||
|
_appSettingsBox.get(BNames.shouldShowOnboarding, defaultValue: true);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setOnboardingFlag(final bool newValue) async =>
|
||||||
|
_appSettingsBox.put(BNames.shouldShowOnboarding, newValue);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool?> getSystemThemeModeFlag() async =>
|
||||||
|
_appSettingsBox.get(BNames.systemThemeModeOn);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setSystemThemeModeFlag(final bool newValue) async =>
|
||||||
|
_appSettingsBox.put(BNames.systemThemeModeOn, newValue);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool?> getDarkThemeModeFlag() async =>
|
||||||
|
_appSettingsBox.get(BNames.darkThemeModeOn);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setDarkThemeModeFlag(final bool newValue) async =>
|
||||||
|
_appSettingsBox.put(BNames.darkThemeModeOn, newValue);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getLocale() async => _appSettingsBox.get(BNames.appLocale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLocale(final String newLocale) async =>
|
||||||
|
_appSettingsBox.put(BNames.appLocale, newLocale);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
|
||||||
|
|
||||||
|
class _PreferencesRepositoryInjector extends InheritedWidget {
|
||||||
|
const _PreferencesRepositoryInjector({
|
||||||
|
required this.settingsRepository,
|
||||||
|
required super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PreferencesRepository settingsRepository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(
|
||||||
|
covariant final _PreferencesRepositoryInjector oldWidget,
|
||||||
|
) =>
|
||||||
|
oldWidget.settingsRepository != settingsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates and injects app preferences repository inside widget tree.
|
||||||
|
class InheritedPreferencesRepository extends StatefulWidget {
|
||||||
|
const InheritedPreferencesRepository({
|
||||||
|
required this.child,
|
||||||
|
required this.dataSource,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PreferencesDataSource dataSource;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InheritedPreferencesRepository> createState() =>
|
||||||
|
_InheritedPreferencesRepositoryState();
|
||||||
|
|
||||||
|
static PreferencesRepository? of(final BuildContext context) => context
|
||||||
|
.dependOnInheritedWidgetOfExactType<_PreferencesRepositoryInjector>()
|
||||||
|
?.settingsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InheritedPreferencesRepositoryState
|
||||||
|
extends State<InheritedPreferencesRepository> {
|
||||||
|
late PreferencesRepository repo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
/// recreate repo each time dependencies change
|
||||||
|
repo = PreferencesRepository(
|
||||||
|
dataSource: widget.dataSource,
|
||||||
|
setDelegateLocale: EasyLocalization.of(context)!.setLocale,
|
||||||
|
getDelegateLocale: () => EasyLocalization.of(context)!.locale,
|
||||||
|
getSupportedLocales: () => EasyLocalization.of(context)!.supportedLocales,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => _PreferencesRepositoryInjector(
|
||||||
|
settingsRepository: repo,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
|
||||||
|
|
||||||
|
class PreferencesRepository {
|
||||||
|
const PreferencesRepository({
|
||||||
|
required this.dataSource,
|
||||||
|
required this.getSupportedLocales,
|
||||||
|
required this.getDelegateLocale,
|
||||||
|
required this.setDelegateLocale,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PreferencesDataSource dataSource;
|
||||||
|
|
||||||
|
/// easy localizations don't expose type of localization provider,
|
||||||
|
/// so it needs to be this crutchy (I could've created one more class-wrapper,
|
||||||
|
/// containing needed functions, but perceive it as boilerplate, because we
|
||||||
|
/// don't need additional encapsulation level here)
|
||||||
|
final FutureOr<void> Function(Locale) setDelegateLocale;
|
||||||
|
final FutureOr<List<Locale>> Function() getSupportedLocales;
|
||||||
|
final FutureOr<Locale> Function() getDelegateLocale;
|
||||||
|
|
||||||
|
Future<bool> getSystemThemeModeFlag() async =>
|
||||||
|
(await dataSource.getSystemThemeModeFlag()) ?? true;
|
||||||
|
|
||||||
|
Future<void> setSystemThemeModeFlag(final bool newValue) async =>
|
||||||
|
dataSource.setSystemThemeModeFlag(newValue);
|
||||||
|
|
||||||
|
Future<bool> getDarkThemeModeFlag() async =>
|
||||||
|
(await dataSource.getDarkThemeModeFlag()) ?? false;
|
||||||
|
|
||||||
|
Future<void> setDarkThemeModeFlag(final bool newValue) async =>
|
||||||
|
dataSource.setDarkThemeModeFlag(newValue);
|
||||||
|
|
||||||
|
Future<void> setSystemModeFlag(final bool newValue) async =>
|
||||||
|
dataSource.setSystemThemeModeFlag(newValue);
|
||||||
|
|
||||||
|
// Future<ThemeMode> getThemeMode() async {
|
||||||
|
// final themeMode = await dataSource.getThemeMode()?? ThemeMode.system;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Future<void> setThemeMode(final ThemeMode newThemeMode) =>
|
||||||
|
// dataSource.setThemeMode(newThemeMode);
|
||||||
|
|
||||||
|
Future<List<Locale>> supportedLocales() async => getSupportedLocales();
|
||||||
|
|
||||||
|
Future<Locale> getActiveLocale() async {
|
||||||
|
Locale? chosenLocale;
|
||||||
|
|
||||||
|
final String? storedLocaleCode = await dataSource.getLocale();
|
||||||
|
if (storedLocaleCode != null) {
|
||||||
|
chosenLocale = Locale(storedLocaleCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when it's null fallback on delegate locale
|
||||||
|
chosenLocale ??= await getDelegateLocale();
|
||||||
|
|
||||||
|
return chosenLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setActiveLocale(final Locale newLocale) async {
|
||||||
|
await dataSource.setLocale(newLocale.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// true when we need to show onboarding
|
||||||
|
Future<bool> getShouldShowOnboarding() async =>
|
||||||
|
dataSource.getOnboardingFlag();
|
||||||
|
|
||||||
|
/// true when we need to show onboarding
|
||||||
|
Future<void> setShouldShowOnboarding(final bool newValue) =>
|
||||||
|
dataSource.setOnboardingFlag(newValue);
|
||||||
|
}
|
|
@ -1,18 +1,14 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||||
import 'package:http/io_client.dart';
|
import 'package:http/io_client.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
|
||||||
void _logToAppConsole<T>(final T objectToLog) {
|
void _addConsoleLog(final ConsoleLog message) =>
|
||||||
getIt.get<ConsoleModel>().addMessage(
|
getIt.get<ConsoleModel>().log(message);
|
||||||
Message(
|
|
||||||
text: objectToLog.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequestLoggingLink extends Link {
|
class RequestLoggingLink extends Link {
|
||||||
@override
|
@override
|
||||||
|
@ -20,13 +16,14 @@ class RequestLoggingLink extends Link {
|
||||||
final Request request, [
|
final Request request, [
|
||||||
final NextLink? forward,
|
final NextLink? forward,
|
||||||
]) async* {
|
]) async* {
|
||||||
getIt.get<ConsoleModel>().addMessage(
|
_addConsoleLog(
|
||||||
GraphQlRequestMessage(
|
GraphQlRequestConsoleLog(
|
||||||
operation: request.operation,
|
// context: request.context,
|
||||||
variables: request.variables,
|
operationType: request.type.name,
|
||||||
context: request.context,
|
operation: request.operation,
|
||||||
),
|
variables: request.variables,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
yield* forward!(request);
|
yield* forward!(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,20 +32,26 @@ class ResponseLoggingParser extends ResponseParser {
|
||||||
@override
|
@override
|
||||||
Response parseResponse(final Map<String, dynamic> body) {
|
Response parseResponse(final Map<String, dynamic> body) {
|
||||||
final response = super.parseResponse(body);
|
final response = super.parseResponse(body);
|
||||||
getIt.get<ConsoleModel>().addMessage(
|
_addConsoleLog(
|
||||||
GraphQlResponseMessage(
|
GraphQlResponseConsoleLog(
|
||||||
data: response.data,
|
// context: response.context,
|
||||||
errors: response.errors,
|
data: response.data,
|
||||||
context: response.context,
|
errors: response.errors,
|
||||||
),
|
rawResponse: jsonEncode(response.response),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GraphQLError parseError(final Map<String, dynamic> error) {
|
GraphQLError parseError(final Map<String, dynamic> error) {
|
||||||
final graphQlError = super.parseError(error);
|
final graphQlError = super.parseError(error);
|
||||||
_logToAppConsole(graphQlError);
|
_addConsoleLog(
|
||||||
|
ManualConsoleLog.warning(
|
||||||
|
customTitle: 'GraphQL Error',
|
||||||
|
content: graphQlError.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
return graphQlError;
|
return graphQlError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,8 +122,9 @@ abstract class GraphQLApiMap {
|
||||||
String token = '';
|
String token = '';
|
||||||
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
final serverDetails = getIt<ApiConfigModel>().serverDetails;
|
||||||
if (serverDetails != null) {
|
if (serverDetails != null) {
|
||||||
token = getIt<ApiConfigModel>().serverDetails!.apiToken;
|
token = serverDetails.apiToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -6,7 +7,7 @@ import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
|
||||||
abstract class RestApiMap {
|
abstract class RestApiMap {
|
||||||
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
||||||
|
@ -57,8 +58,8 @@ abstract class RestApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConsoleInterceptor extends InterceptorsWrapper {
|
class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
void addMessage(final Message message) {
|
void addConsoleLog(final ConsoleLog message) {
|
||||||
getIt.get<ConsoleModel>().addMessage(message);
|
getIt.get<ConsoleModel>().log(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -66,12 +67,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final RequestOptions options,
|
final RequestOptions options,
|
||||||
final RequestInterceptorHandler handler,
|
final RequestInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
addMessage(
|
addConsoleLog(
|
||||||
RestApiRequestMessage(
|
RestApiRequestConsoleLog(
|
||||||
method: options.method,
|
|
||||||
data: options.data.toString(),
|
|
||||||
headers: options.headers,
|
|
||||||
uri: options.uri,
|
uri: options.uri,
|
||||||
|
method: options.method,
|
||||||
|
headers: options.headers,
|
||||||
|
data: jsonEncode(options.data),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onRequest(options, handler);
|
return super.onRequest(options, handler);
|
||||||
|
@ -82,12 +83,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final Response response,
|
final Response response,
|
||||||
final ResponseInterceptorHandler handler,
|
final ResponseInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
addMessage(
|
addConsoleLog(
|
||||||
RestApiResponseMessage(
|
RestApiResponseConsoleLog(
|
||||||
|
uri: response.realUri,
|
||||||
method: response.requestOptions.method,
|
method: response.requestOptions.method,
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
data: response.data.toString(),
|
data: jsonEncode(response.data),
|
||||||
uri: response.realUri,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onResponse(
|
return super.onResponse(
|
||||||
|
@ -103,10 +104,13 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
) async {
|
) async {
|
||||||
final Response? response = err.response;
|
final Response? response = err.response;
|
||||||
log(err.toString());
|
log(err.toString());
|
||||||
addMessage(
|
|
||||||
Message.warn(
|
addConsoleLog(
|
||||||
text:
|
ManualConsoleLog.warning(
|
||||||
'response-uri: ${response?.realUri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
|
customTitle: 'RestAPI error',
|
||||||
|
content: '"uri": "${response?.realUri}",\n'
|
||||||
|
'"status_code": ${response?.statusCode},\n'
|
||||||
|
'"response": ${jsonEncode(response)}',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onError(err, handler);
|
return super.onError(err, handler);
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
|
|
||||||
part 'connection_status_event.dart';
|
|
||||||
part 'connection_status_state.dart';
|
|
||||||
|
|
||||||
class ConnectionStatusBloc
|
|
||||||
extends Bloc<ConnectionStatusEvent, ConnectionStatusState> {
|
|
||||||
ConnectionStatusBloc()
|
|
||||||
: super(
|
|
||||||
const ConnectionStatusState(
|
|
||||||
connectionStatus: ConnectionStatus.nonexistent,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
on<ConnectionStatusChanged>((final event, final emit) {
|
|
||||||
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
|
|
||||||
});
|
|
||||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
|
||||||
_apiConnectionStatusSubscription =
|
|
||||||
apiConnectionRepository.connectionStatusStream.listen(
|
|
||||||
(final ConnectionStatus connectionStatus) {
|
|
||||||
add(
|
|
||||||
ConnectionStatusChanged(connectionStatus),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamSubscription? _apiConnectionStatusSubscription;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_apiConnectionStatusSubscription?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
part of 'connection_status_bloc.dart';
|
|
||||||
|
|
||||||
sealed class ConnectionStatusEvent extends Equatable {
|
|
||||||
const ConnectionStatusEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectionStatusChanged extends ConnectionStatusEvent {
|
|
||||||
const ConnectionStatusChanged(this.connectionStatus);
|
|
||||||
|
|
||||||
final ConnectionStatus connectionStatus;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [connectionStatus];
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
part of 'connection_status_bloc.dart';
|
|
||||||
|
|
||||||
class ConnectionStatusState extends Equatable {
|
|
||||||
const ConnectionStatusState({
|
|
||||||
required this.connectionStatus,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ConnectionStatus connectionStatus;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [connectionStatus];
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
|
||||||
|
/// basically, a bus for other blocs to listen to server status updates
|
||||||
inex
commented
Review
Unused as of now, and I'm still not sure if it is a good idea to create dependencies between blocs. Unused as of now, and I'm still not sure if it is a good idea to create dependencies between blocs.
misterfourtytwo
commented
Review
well, i'd prefer an inheritedWidget, which holds this value for dependencies, but also explicitly listens to connectivity and app lifecycle state changes (l mean here when it is suspended, like when app active app changes on android), thus it will hold more meaning and could spare us from some errors. well, i'd prefer an inheritedWidget, which holds this value for dependencies, but also explicitly listens to connectivity and app lifecycle state changes (l mean here when it is suspended, like when app active app changes on android), thus it will hold more meaning and could spare us from some errors.
|
|||||||
|
class ConnectionStatusBloc extends Bloc<ConnectionStatus, ConnectionStatus> {
|
||||||
inex
commented
Review
Does passing the same enum as both Event and State even work? Does passing the same enum as both Event and State even work?
misterfourtytwo
commented
Review
why won't it? why won't it?
you have one place, where events come, and another where state is. also, states are set from inside the bloc only.
|
|||||||
|
ConnectionStatusBloc() : super(ConnectionStatus.nonexistent) {
|
||||||
|
on<ConnectionStatus>(
|
||||||
|
(final newStatus, final emit) => emit(newStatus),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiConnectionStatusSubscription =
|
||||||
|
apiConnectionRepository.connectionStatusStream.listen(
|
||||||
|
(final ConnectionStatus newStatus) => add(newStatus),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiConnectionStatusSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiConnectionStatusSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:material_color_utilities/material_color_utilities.dart'
|
|
||||||
as color_utils;
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
|
||||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
part 'app_settings_state.dart';
|
|
||||||
|
|
||||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
|
||||||
AppSettingsCubit({
|
|
||||||
required final bool isDarkModeOn,
|
|
||||||
required final bool isAutoDarkModeOn,
|
|
||||||
required final bool isOnboardingShowing,
|
|
||||||
}) : super(
|
|
||||||
AppSettingsState(
|
|
||||||
isDarkModeOn: isDarkModeOn,
|
|
||||||
isAutoDarkModeOn: isAutoDarkModeOn,
|
|
||||||
isOnboardingShowing: isOnboardingShowing,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Box box = Hive.box(BNames.appSettingsBox);
|
|
||||||
|
|
||||||
void load() async {
|
|
||||||
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
|
||||||
final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn);
|
|
||||||
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isDarkModeOn: isDarkModeOn,
|
|
||||||
isAutoDarkModeOn: isAutoDarkModeOn,
|
|
||||||
isOnboardingShowing: isOnboardingShowing,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final color_utils.CorePalette? colorPalette =
|
|
||||||
await AppThemeFactory.getCorePalette();
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
corePalette: colorPalette,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateDarkMode({required final bool isDarkModeOn}) {
|
|
||||||
box.put(BNames.isDarkModeOn, isDarkModeOn);
|
|
||||||
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateAutoDarkMode({required final bool isAutoDarkModeOn}) {
|
|
||||||
box.put(BNames.isAutoDarkModeOn, isAutoDarkModeOn);
|
|
||||||
emit(state.copyWith(isAutoDarkModeOn: isAutoDarkModeOn));
|
|
||||||
}
|
|
||||||
|
|
||||||
void turnOffOnboarding({final bool isOnboardingShowing = false}) {
|
|
||||||
box.put(BNames.isOnboardingShowing, isOnboardingShowing);
|
|
||||||
|
|
||||||
emit(state.copyWith(isOnboardingShowing: isOnboardingShowing));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
part of 'app_settings_cubit.dart';
|
|
||||||
|
|
||||||
class AppSettingsState extends Equatable {
|
|
||||||
const AppSettingsState({
|
|
||||||
required this.isDarkModeOn,
|
|
||||||
required this.isAutoDarkModeOn,
|
|
||||||
required this.isOnboardingShowing,
|
|
||||||
this.corePalette,
|
|
||||||
});
|
|
||||||
|
|
||||||
final bool isDarkModeOn;
|
|
||||||
final bool isAutoDarkModeOn;
|
|
||||||
final bool isOnboardingShowing;
|
|
||||||
final color_utils.CorePalette? corePalette;
|
|
||||||
|
|
||||||
AppSettingsState copyWith({
|
|
||||||
final bool? isDarkModeOn,
|
|
||||||
final bool? isAutoDarkModeOn,
|
|
||||||
final bool? isOnboardingShowing,
|
|
||||||
final color_utils.CorePalette? corePalette,
|
|
||||||
}) =>
|
|
||||||
AppSettingsState(
|
|
||||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
|
||||||
isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn,
|
|
||||||
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
|
||||||
corePalette: corePalette ?? this.corePalette,
|
|
||||||
);
|
|
||||||
|
|
||||||
color_utils.CorePalette get corePaletteOrDefault =>
|
|
||||||
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> get props =>
|
|
||||||
[isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette];
|
|
||||||
}
|
|
|
@ -475,7 +475,7 @@ class ServerInstallationRepository {
|
||||||
|
|
||||||
Future<void> deleteServerDetails() async {
|
Future<void> deleteServerDetails() async {
|
||||||
await box.delete(BNames.serverDetails);
|
await box.delete(BNames.serverDetails);
|
||||||
getIt<ApiConfigModel>().init();
|
await getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveServerProviderType(final ServerProviderType type) async {
|
Future<void> saveServerProviderType(final ServerProviderType type) async {
|
||||||
|
@ -501,7 +501,7 @@ class ServerInstallationRepository {
|
||||||
|
|
||||||
Future<void> deleteServerProviderKey() async {
|
Future<void> deleteServerProviderKey() async {
|
||||||
await box.delete(BNames.hetznerKey);
|
await box.delete(BNames.hetznerKey);
|
||||||
getIt<ApiConfigModel>().init();
|
await getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveBackblazeKey(
|
Future<void> saveBackblazeKey(
|
||||||
|
@ -512,7 +512,7 @@ class ServerInstallationRepository {
|
||||||
|
|
||||||
Future<void> deleteBackblazeKey() async {
|
Future<void> deleteBackblazeKey() async {
|
||||||
await box.delete(BNames.backblazeCredential);
|
await box.delete(BNames.backblazeCredential);
|
||||||
getIt<ApiConfigModel>().init();
|
await getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setDnsApiToken(final String key) async {
|
Future<void> setDnsApiToken(final String key) async {
|
||||||
|
@ -521,7 +521,7 @@ class ServerInstallationRepository {
|
||||||
|
|
||||||
Future<void> deleteDnsProviderKey() async {
|
Future<void> deleteDnsProviderKey() async {
|
||||||
await box.delete(BNames.cloudFlareKey);
|
await box.delete(BNames.cloudFlareKey);
|
||||||
getIt<ApiConfigModel>().init();
|
await getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
||||||
|
@ -530,7 +530,7 @@ class ServerInstallationRepository {
|
||||||
|
|
||||||
Future<void> deleteDomain() async {
|
Future<void> deleteDomain() async {
|
||||||
await box.delete(BNames.serverDomain);
|
await box.delete(BNames.serverDomain);
|
||||||
getIt<ApiConfigModel>().init();
|
await getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveIsServerStarted(final bool value) async {
|
Future<void> saveIsServerStarted(final bool value) async {
|
||||||
|
@ -604,6 +604,6 @@ class ServerInstallationRepository {
|
||||||
BNames.hasFinalChecked,
|
BNames.hasFinalChecked,
|
||||||
BNames.isLoading,
|
BNames.isLoading,
|
||||||
]);
|
]);
|
||||||
getIt<ApiConfigModel>().init();
|
await getIt<ApiConfigModel>().init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,10 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
bool get isPrimaryUserFilled => rootUser != null;
|
bool get isPrimaryUserFilled => rootUser != null;
|
||||||
bool get isServerCreated => serverDetails != null;
|
bool get isServerCreated => serverDetails != null;
|
||||||
|
|
||||||
bool get isFullyInitilized => _fulfilementList.every((final el) => el!);
|
bool get isFullyInitialized =>
|
||||||
|
_fulfillmentList.every((final el) => el ?? false);
|
||||||
ServerSetupProgress get progress => ServerSetupProgress
|
ServerSetupProgress get progress => ServerSetupProgress
|
||||||
.values[_fulfilementList.where((final el) => el!).length];
|
.values[_fulfillmentList.where((final el) => el!).length];
|
||||||
|
|
||||||
int get porgressBar {
|
int get porgressBar {
|
||||||
if (progress.index < 6) {
|
if (progress.index < 6) {
|
||||||
|
@ -63,7 +64,7 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<bool?> get _fulfilementList {
|
List<bool?> get _fulfillmentList {
|
||||||
final List<bool> res = [
|
final List<bool> res = [
|
||||||
isServerProviderApiKeyFilled,
|
isServerProviderApiKeyFilled,
|
||||||
isServerTypeFilled,
|
isServerTypeFilled,
|
||||||
|
|
|
@ -88,7 +88,6 @@ class ApiConfigModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_localeCode = null;
|
|
||||||
_serverProviderKey = null;
|
_serverProviderKey = null;
|
||||||
_dnsProvider = null;
|
_dnsProvider = null;
|
||||||
_serverLocation = null;
|
_serverLocation = null;
|
||||||
|
@ -101,7 +100,7 @@ class ApiConfigModel {
|
||||||
_serverProvider = null;
|
_serverProvider = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
Future<void> init() async {
|
||||||
_localeCode = 'en';
|
_localeCode = 'en';
|
||||||
_serverProviderKey = _box.get(BNames.hetznerKey);
|
_serverProviderKey = _box.get(BNames.hetznerKey);
|
||||||
_serverLocation = _box.get(BNames.serverLocation);
|
_serverLocation = _box.get(BNames.serverLocation);
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
|
||||||
|
|
||||||
class ConsoleModel extends ChangeNotifier {
|
|
||||||
final List<Message> _messages = [];
|
|
||||||
|
|
||||||
List<Message> get messages => _messages;
|
|
||||||
|
|
||||||
void addMessage(final Message message) {
|
|
||||||
messages.add(message);
|
|
||||||
notifyListeners();
|
|
||||||
// Make sure we don't have too many messages
|
|
||||||
if (messages.length > 500) {
|
|
||||||
messages.removeAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
|
||||||
|
class ConsoleModel extends ChangeNotifier {
|
||||||
|
/// limit for history, so logs won't affect memory and overflow
|
||||||
|
static const logBufferLimit = 500;
|
||||||
|
|
||||||
|
/// differs from log buffer limit so as to not rearrange memory each time
|
||||||
|
/// we add incoming log
|
||||||
|
static const incomingBufferBreakpoint = 750;
|
||||||
|
|
||||||
|
final List<ConsoleLog> _logs = [];
|
||||||
|
final List<ConsoleLog> _incomingQueue = [];
|
||||||
|
|
||||||
|
bool _paused = false;
|
||||||
|
bool get paused => _paused;
|
||||||
|
List<ConsoleLog> get logs => _logs;
|
||||||
|
|
||||||
|
void log(final ConsoleLog newLog) {
|
||||||
|
if (paused) {
|
||||||
|
_incomingQueue.add(newLog);
|
||||||
|
if (_incomingQueue.length > incomingBufferBreakpoint) {
|
||||||
|
logs.removeRange(0, _incomingQueue.length - logBufferLimit);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logs.add(newLog);
|
||||||
|
_updateQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void play() {
|
||||||
|
_logs.addAll(_incomingQueue);
|
||||||
|
_paused = false;
|
||||||
|
_updateQueue();
|
||||||
|
_incomingQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause() {
|
||||||
|
_paused = true;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// drop logs over the limit and
|
||||||
|
void _updateQueue() {
|
||||||
|
// Make sure we don't have too many
|
||||||
|
if (logs.length > logBufferLimit) {
|
||||||
|
logs.removeRange(0, logs.length - logBufferLimit);
|
||||||
inex
commented
Review
Why not using the same optimization as with Why not using the same optimization as with `_incomingQueue`?
misterfourtytwo
commented
Review
my guess was that buffer of 500 messages was a console feature, so I left it as is.
my guess was that buffer of 500 messages was a console feature, so I left it as is.
also, jumps between 750 and 500 elements in listing user sees are not perceived as stable behavior from user point of view.
`_incomingQueue` is an offscreen buffer, so it doesn't affect what is rendered and we could truncate it lazily, so jumps between 750 and 500 elements should be a ok.
|
|||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:gql/language.dart' as gql;
|
||||||
|
import 'package:graphql/client.dart' as gql_client;
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
enum ConsoleLogSeverity {
|
||||||
|
normal,
|
||||||
|
warning,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base entity for console logs.
|
||||||
|
///
|
||||||
|
/// TODO(misterfourtytwo): should we add?
|
||||||
|
///
|
||||||
|
/// * equality override
|
||||||
|
sealed class ConsoleLog {
|
||||||
|
ConsoleLog({
|
||||||
|
final String? customTitle,
|
||||||
|
this.severity = ConsoleLogSeverity.normal,
|
||||||
|
}) : title = customTitle ??
|
||||||
|
(severity == ConsoleLogSeverity.warning ? 'Error' : 'Log'),
|
||||||
|
time = DateTime.now();
|
||||||
|
|
||||||
|
final DateTime time;
|
||||||
|
final ConsoleLogSeverity severity;
|
||||||
|
bool get isError => severity == ConsoleLogSeverity.warning;
|
||||||
|
|
||||||
|
/// title for both in listing and in dialog
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
/// formatted data to be shown in listing
|
||||||
|
String get content;
|
||||||
|
|
||||||
|
/// data available for copy in dialog
|
||||||
|
String? get shareableData => '{"title":"$title",\n'
|
||||||
|
'"timestamp": "$fullUTCString",\n'
|
||||||
|
'"data":{\n$content\n}'
|
||||||
|
'\n}';
|
||||||
|
|
||||||
|
static final DateFormat _formatter = DateFormat('hh:mm:ss');
|
||||||
|
String get timeString => _formatter.format(time);
|
||||||
|
|
||||||
|
String get fullUTCString => time.toUtc().toIso8601String();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class LogWithRawResponse {
|
||||||
|
String get rawResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// entity for manually created logs, as opposed to automated ones coming
|
||||||
|
/// from requests / responses
|
||||||
|
class ManualConsoleLog extends ConsoleLog {
|
||||||
|
ManualConsoleLog({
|
||||||
|
required this.content,
|
||||||
|
super.customTitle,
|
||||||
|
super.severity,
|
||||||
|
});
|
||||||
|
|
||||||
|
ManualConsoleLog.warning({
|
||||||
|
required this.content,
|
||||||
|
super.customTitle,
|
||||||
|
}) : super(severity: ConsoleLogSeverity.warning);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String content;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestApiRequestConsoleLog extends ConsoleLog {
|
||||||
|
RestApiRequestConsoleLog({
|
||||||
|
this.method,
|
||||||
|
this.uri,
|
||||||
|
this.headers,
|
||||||
|
this.data,
|
||||||
|
super.severity,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? method;
|
||||||
|
final Uri? uri;
|
||||||
|
final Map<String, dynamic>? headers;
|
||||||
|
final String? data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Rest API Request';
|
||||||
|
@override
|
||||||
|
String get content => '"method": "$method",\n'
|
||||||
|
'"uri": "$uri",\n'
|
||||||
|
'"headers": ${jsonEncode(headers)},\n'
|
||||||
|
'"data": $data';
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestApiResponseConsoleLog extends ConsoleLog {
|
||||||
|
RestApiResponseConsoleLog({
|
||||||
|
this.method,
|
||||||
|
this.uri,
|
||||||
|
this.statusCode,
|
||||||
|
this.data,
|
||||||
|
super.severity,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? method;
|
||||||
|
final Uri? uri;
|
||||||
|
final int? statusCode;
|
||||||
|
final String? data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Rest API Response';
|
||||||
|
@override
|
||||||
|
String get content => '"method": "$method",\n'
|
||||||
|
'"status_code": $statusCode,\n'
|
||||||
|
'"uri": "$uri",\n'
|
||||||
|
'"data": $data';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// there is no actual getter for context fields outside of its class
|
||||||
|
/// one can extract unique entries by their type, which implements
|
||||||
|
/// `ContextEntry` class, I'll leave the code here if in the future
|
||||||
|
/// some entries will actually be needed.
|
||||||
|
// extension ContextEncoder on gql_client.Context {
|
||||||
|
// String get encode {
|
||||||
|
// return '""';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
class GraphQlRequestConsoleLog extends ConsoleLog {
|
||||||
|
GraphQlRequestConsoleLog({
|
||||||
|
required this.operationType,
|
||||||
|
required this.operation,
|
||||||
|
required this.variables,
|
||||||
|
// this.context,
|
||||||
|
super.severity,
|
||||||
|
});
|
||||||
|
|
||||||
|
// final gql_client.Context? context;
|
||||||
|
final String operationType;
|
||||||
|
final gql_client.Operation? operation;
|
||||||
|
String get operationDocument =>
|
||||||
|
operation != null ? gql.printNode(operation!.document) : 'null';
|
||||||
|
final Map<String, dynamic>? variables;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'GraphQL Request';
|
||||||
|
@override
|
||||||
|
String get content =>
|
||||||
|
// '"context": ${context?.encode},\n'
|
||||||
|
'"variables": ${jsonEncode(variables)},\n'
|
||||||
|
'"type": "$operationType",\n'
|
||||||
|
'"name": "${operation?.operationName}",\n'
|
||||||
|
'"document": ${jsonEncode(operationDocument)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
class GraphQlResponseConsoleLog extends ConsoleLog
|
||||||
|
implements LogWithRawResponse {
|
||||||
|
GraphQlResponseConsoleLog({
|
||||||
|
required this.rawResponse,
|
||||||
|
// this.context,
|
||||||
|
this.data,
|
||||||
|
this.errors,
|
||||||
|
super.severity,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String rawResponse;
|
||||||
|
// final gql_client.Context? context;
|
||||||
|
final Map<String, dynamic>? data;
|
||||||
|
final List<gql_client.GraphQLError>? errors;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'GraphQL Response';
|
||||||
|
@override
|
||||||
|
String get content =>
|
||||||
|
// '"context": ${context?.encode},\n'
|
||||||
|
'"data": ${jsonEncode(data)},\n'
|
||||||
|
'"errors": $errors';
|
||||||
|
}
|
|
@ -77,46 +77,21 @@ class DigitalOceanLocation {
|
||||||
return emoji;
|
return emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const _townPrefixToCountryMap = {
|
||||||
|
'fra': 'germany',
|
||||||
|
'ams': 'netherlands',
|
||||||
|
'sgp': 'singapore',
|
||||||
|
'lon': 'united_kingdom',
|
||||||
|
'tor': 'canada',
|
||||||
|
'blr': 'india',
|
||||||
|
'syd': 'australia',
|
||||||
|
'nyc': 'united_states',
|
||||||
|
'sfo': 'united_states',
|
||||||
|
};
|
||||||
|
|
||||||
String get countryDisplayKey {
|
String get countryDisplayKey {
|
||||||
String displayKey = 'countries.';
|
final countryName = _townPrefixToCountryMap[slug.substring(0, 3)] ?? slug;
|
||||||
switch (slug.substring(0, 3)) {
|
return 'countries.$countryName';
|
||||||
case 'fra':
|
|
||||||
displayKey += 'germany';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ams':
|
|
||||||
displayKey += 'netherlands';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'sgp':
|
|
||||||
displayKey += 'singapore';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'lon':
|
|
||||||
displayKey += 'united_kingdom';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tor':
|
|
||||||
displayKey += 'canada';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'blr':
|
|
||||||
displayKey += 'india';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'syd':
|
|
||||||
displayKey += 'australia';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'nyc':
|
|
||||||
case 'sfo':
|
|
||||||
displayKey += 'united_states';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
displayKey = slug;
|
|
||||||
}
|
|
||||||
return displayKey;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,10 @@ DigitalOceanVolume _$DigitalOceanVolumeFromJson(Map<String, dynamic> json) =>
|
||||||
DigitalOceanVolume(
|
DigitalOceanVolume(
|
||||||
json['id'] as String,
|
json['id'] as String,
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
json['size_gigabytes'] as int,
|
(json['size_gigabytes'] as num).toInt(),
|
||||||
(json['droplet_ids'] as List<dynamic>?)?.map((e) => e as int).toList(),
|
(json['droplet_ids'] as List<dynamic>?)
|
||||||
|
?.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanVolumeToJson(DigitalOceanVolume instance) =>
|
Map<String, dynamic> _$DigitalOceanVolumeToJson(DigitalOceanVolume instance) =>
|
||||||
|
@ -42,10 +44,10 @@ DigitalOceanServerType _$DigitalOceanServerTypeFromJson(
|
||||||
(json['regions'] as List<dynamic>).map((e) => e as String).toList(),
|
(json['regions'] as List<dynamic>).map((e) => e as String).toList(),
|
||||||
(json['memory'] as num).toDouble(),
|
(json['memory'] as num).toDouble(),
|
||||||
json['description'] as String,
|
json['description'] as String,
|
||||||
json['disk'] as int,
|
(json['disk'] as num).toInt(),
|
||||||
(json['price_monthly'] as num).toDouble(),
|
(json['price_monthly'] as num).toDouble(),
|
||||||
json['slug'] as String,
|
json['slug'] as String,
|
||||||
json['vcpus'] as int,
|
(json['vcpus'] as num).toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanServerTypeToJson(
|
Map<String, dynamic> _$DigitalOceanServerTypeToJson(
|
||||||
|
|
|
@ -24,8 +24,8 @@ CloudflareDnsRecord _$CloudflareDnsRecordFromJson(Map<String, dynamic> json) =>
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
content: json['content'] as String?,
|
content: json['content'] as String?,
|
||||||
zoneName: json['zone_name'] as String,
|
zoneName: json['zone_name'] as String,
|
||||||
ttl: json['ttl'] as int? ?? 3600,
|
ttl: (json['ttl'] as num?)?.toInt() ?? 3600,
|
||||||
priority: json['priority'] as int? ?? 10,
|
priority: (json['priority'] as num?)?.toInt() ?? 10,
|
||||||
id: json['id'] as String?,
|
id: json['id'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ part of 'desec_dns_info.dart';
|
||||||
|
|
||||||
DesecDomain _$DesecDomainFromJson(Map<String, dynamic> json) => DesecDomain(
|
DesecDomain _$DesecDomainFromJson(Map<String, dynamic> json) => DesecDomain(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
minimumTtl: json['minimum_ttl'] as int?,
|
minimumTtl: (json['minimum_ttl'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DesecDomainToJson(DesecDomain instance) =>
|
Map<String, dynamic> _$DesecDomainToJson(DesecDomain instance) =>
|
||||||
|
@ -21,7 +21,7 @@ DesecDnsRecord _$DesecDnsRecordFromJson(Map<String, dynamic> json) =>
|
||||||
DesecDnsRecord(
|
DesecDnsRecord(
|
||||||
subname: json['subname'] as String,
|
subname: json['subname'] as String,
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
ttl: json['ttl'] as int,
|
ttl: (json['ttl'] as num).toInt(),
|
||||||
records:
|
records:
|
||||||
(json['records'] as List<dynamic>).map((e) => e as String).toList(),
|
(json['records'] as List<dynamic>).map((e) => e as String).toList(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ part of 'digital_ocean_dns_info.dart';
|
||||||
DigitalOceanDomain _$DigitalOceanDomainFromJson(Map<String, dynamic> json) =>
|
DigitalOceanDomain _$DigitalOceanDomainFromJson(Map<String, dynamic> json) =>
|
||||||
DigitalOceanDomain(
|
DigitalOceanDomain(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
ttl: json['ttl'] as int?,
|
ttl: (json['ttl'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
||||||
|
@ -21,12 +21,12 @@ Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
||||||
DigitalOceanDnsRecord _$DigitalOceanDnsRecordFromJson(
|
DigitalOceanDnsRecord _$DigitalOceanDnsRecordFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
DigitalOceanDnsRecord(
|
DigitalOceanDnsRecord(
|
||||||
id: json['id'] as int?,
|
id: (json['id'] as num?)?.toInt(),
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
ttl: json['ttl'] as int,
|
ttl: (json['ttl'] as num).toInt(),
|
||||||
data: json['data'] as String,
|
data: json['data'] as String,
|
||||||
priority: json['priority'] as int?,
|
priority: (json['priority'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanDnsRecordToJson(
|
Map<String, dynamic> _$DigitalOceanDnsRecordToJson(
|
||||||
|
|
|
@ -8,7 +8,7 @@ part of 'hetzner_server_info.dart';
|
||||||
|
|
||||||
HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
||||||
HetznerServerInfo(
|
HetznerServerInfo(
|
||||||
json['id'] as int,
|
(json['id'] as num).toInt(),
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
$enumDecode(_$ServerStatusEnumMap, json['status']),
|
$enumDecode(_$ServerStatusEnumMap, json['status']),
|
||||||
DateTime.parse(json['created'] as String),
|
DateTime.parse(json['created'] as String),
|
||||||
|
@ -16,7 +16,9 @@ HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
||||||
json['server_type'] as Map<String, dynamic>),
|
json['server_type'] as Map<String, dynamic>),
|
||||||
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
||||||
HetznerPublicNetInfo.fromJson(json['public_net'] as Map<String, dynamic>),
|
HetznerPublicNetInfo.fromJson(json['public_net'] as Map<String, dynamic>),
|
||||||
(json['volumes'] as List<dynamic>).map((e) => e as int).toList(),
|
(json['volumes'] as List<dynamic>)
|
||||||
|
.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$HetznerServerInfoToJson(HetznerServerInfo instance) =>
|
Map<String, dynamic> _$HetznerServerInfoToJson(HetznerServerInfo instance) =>
|
||||||
|
@ -58,7 +60,7 @@ Map<String, dynamic> _$HetznerPublicNetInfoToJson(
|
||||||
};
|
};
|
||||||
|
|
||||||
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
||||||
json['id'] as int,
|
(json['id'] as num).toInt(),
|
||||||
json['ip'] as String,
|
json['ip'] as String,
|
||||||
json['blocked'] as bool,
|
json['blocked'] as bool,
|
||||||
json['dns_ptr'] as String,
|
json['dns_ptr'] as String,
|
||||||
|
@ -75,9 +77,9 @@ Map<String, dynamic> _$HetznerIp4ToJson(HetznerIp4 instance) =>
|
||||||
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
|
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
HetznerServerTypeInfo(
|
HetznerServerTypeInfo(
|
||||||
json['cores'] as int,
|
(json['cores'] as num).toInt(),
|
||||||
json['memory'] as num,
|
json['memory'] as num,
|
||||||
json['disk'] as int,
|
(json['disk'] as num).toInt(),
|
||||||
(json['prices'] as List<dynamic>)
|
(json['prices'] as List<dynamic>)
|
||||||
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>))
|
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -132,9 +134,9 @@ Map<String, dynamic> _$HetznerLocationToJson(HetznerLocation instance) =>
|
||||||
|
|
||||||
HetznerVolume _$HetznerVolumeFromJson(Map<String, dynamic> json) =>
|
HetznerVolume _$HetznerVolumeFromJson(Map<String, dynamic> json) =>
|
||||||
HetznerVolume(
|
HetznerVolume(
|
||||||
json['id'] as int,
|
(json['id'] as num).toInt(),
|
||||||
json['size'] as int,
|
(json['size'] as num).toInt(),
|
||||||
json['serverId'] as int?,
|
(json['serverId'] as num?)?.toInt(),
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
json['linux_device'] as String?,
|
json['linux_device'] as String?,
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ RecoveryKeyStatus _$RecoveryKeyStatusFromJson(Map<String, dynamic> json) =>
|
||||||
expiration: json['expiration'] == null
|
expiration: json['expiration'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['expiration'] as String),
|
: DateTime.parse(json['expiration'] as String),
|
||||||
usesLeft: json['uses_left'] as int?,
|
usesLeft: (json['uses_left'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$RecoveryKeyStatusToJson(RecoveryKeyStatus instance) =>
|
Map<String, dynamic> _$RecoveryKeyStatusToJson(RecoveryKeyStatus instance) =>
|
||||||
|
|
|
@ -15,7 +15,7 @@ ServerJob _$ServerJobFromJson(Map<String, dynamic> json) => ServerJob(
|
||||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
error: json['error'] as String?,
|
error: json['error'] as String?,
|
||||||
progress: json['progress'] as int?,
|
progress: (json['progress'] as num?)?.toInt(),
|
||||||
result: json['result'] as String?,
|
result: json['result'] as String?,
|
||||||
statusText: json['statusText'] as String?,
|
statusText: json['statusText'] as String?,
|
||||||
finishedAt: json['finishedAt'] == null
|
finishedAt: json['finishedAt'] == null
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import 'package:graphql/client.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
/// TODO(misterfourtytwo): add equality override
|
|
||||||
class Message {
|
|
||||||
Message({this.text, this.severity = MessageSeverity.normal})
|
|
||||||
: time = DateTime.now();
|
|
||||||
Message.warn({this.text})
|
|
||||||
: severity = MessageSeverity.warning,
|
|
||||||
time = DateTime.now();
|
|
||||||
|
|
||||||
final String? text;
|
|
||||||
final DateTime time;
|
|
||||||
final MessageSeverity severity;
|
|
||||||
|
|
||||||
static final DateFormat _formatter = DateFormat('hh:mm');
|
|
||||||
String get timeString => _formatter.format(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MessageSeverity {
|
|
||||||
normal,
|
|
||||||
warning,
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestApiRequestMessage extends Message {
|
|
||||||
RestApiRequestMessage({
|
|
||||||
this.method,
|
|
||||||
this.uri,
|
|
||||||
this.data,
|
|
||||||
this.headers,
|
|
||||||
}) : super(text: 'request-uri: $uri\nheaders: $headers\ndata: $data');
|
|
||||||
|
|
||||||
final String? method;
|
|
||||||
final Uri? uri;
|
|
||||||
final String? data;
|
|
||||||
final Map<String, dynamic>? headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestApiResponseMessage extends Message {
|
|
||||||
RestApiResponseMessage({
|
|
||||||
this.method,
|
|
||||||
this.uri,
|
|
||||||
this.statusCode,
|
|
||||||
this.data,
|
|
||||||
}) : super(text: 'response-uri: $uri\ncode: $statusCode\ndata: $data');
|
|
||||||
|
|
||||||
final String? method;
|
|
||||||
final Uri? uri;
|
|
||||||
final int? statusCode;
|
|
||||||
final String? data;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GraphQlResponseMessage extends Message {
|
|
||||||
GraphQlResponseMessage({
|
|
||||||
this.data,
|
|
||||||
this.errors,
|
|
||||||
this.context,
|
|
||||||
}) : super(text: 'GraphQL Response\ndata: $data');
|
|
||||||
|
|
||||||
final Map<String, dynamic>? data;
|
|
||||||
final List<GraphQLError>? errors;
|
|
||||||
final Context? context;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GraphQlRequestMessage extends Message {
|
|
||||||
GraphQlRequestMessage({
|
|
||||||
this.operation,
|
|
||||||
this.variables,
|
|
||||||
this.context,
|
|
||||||
}) : super(text: 'GraphQL Request\noperation: $operation');
|
|
||||||
|
|
||||||
final Operation? operation;
|
|
||||||
final Map<String, dynamic>? variables;
|
|
||||||
final Context? context;
|
|
||||||
}
|
|
|
@ -27,9 +27,9 @@ class ApiAdapter {
|
||||||
class CloudflareDnsProvider extends DnsProvider {
|
class CloudflareDnsProvider extends DnsProvider {
|
||||||
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
||||||
CloudflareDnsProvider.load(
|
CloudflareDnsProvider.load(
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
|
@ -22,9 +22,9 @@ class ApiAdapter {
|
||||||
class DesecDnsProvider extends DnsProvider {
|
class DesecDnsProvider extends DnsProvider {
|
||||||
DesecDnsProvider() : _adapter = ApiAdapter();
|
DesecDnsProvider() : _adapter = ApiAdapter();
|
||||||
DesecDnsProvider.load(
|
DesecDnsProvider.load(
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
|
@ -22,9 +22,9 @@ class ApiAdapter {
|
||||||
class DigitalOceanDnsProvider extends DnsProvider {
|
class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
||||||
DigitalOceanDnsProvider.load(
|
DigitalOceanDnsProvider.load(
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
|
@ -38,9 +38,9 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
DigitalOceanServerProvider() : _adapter = ApiAdapter();
|
DigitalOceanServerProvider() : _adapter = ApiAdapter();
|
||||||
DigitalOceanServerProvider.load(
|
DigitalOceanServerProvider.load(
|
||||||
final String? location,
|
final String? location,
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
region: location,
|
region: location,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,9 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
HetznerServerProvider() : _adapter = ApiAdapter();
|
HetznerServerProvider() : _adapter = ApiAdapter();
|
||||||
HetznerServerProvider.load(
|
HetznerServerProvider.load(
|
||||||
final String? location,
|
final String? location,
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
region: location,
|
region: location,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
174
lib/main.dart
|
@ -2,28 +2,20 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
import 'package:selfprivacy/config/bloc_config.dart';
|
import 'package:selfprivacy/config/bloc_config.dart';
|
||||||
import 'package:selfprivacy/config/bloc_observer.dart';
|
import 'package:selfprivacy/config/bloc_observer.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/config/localization.dart';
|
import 'package:selfprivacy/config/localization.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_hive_datasource.dart';
|
||||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
|
||||||
import 'package:selfprivacy/ui/pages/errors/failed_to_init_secure_storage.dart';
|
import 'package:selfprivacy/ui/pages/errors/failed_to_init_secure_storage.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
// import 'package:wakelock/wakelock.dart';
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
try {
|
|
||||||
await HiveConfig.init();
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
runApp(
|
|
||||||
FailedToInitSecureStorageScreen(e: e),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
|
@ -34,85 +26,117 @@ void main() async {
|
||||||
// print(e);
|
// print(e);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
await getItSetup();
|
try {
|
||||||
await EasyLocalization.ensureInitialized();
|
await Future.wait(
|
||||||
tz.initializeTimeZones();
|
<Future<void>>[
|
||||||
|
HiveConfig.init(),
|
||||||
|
EasyLocalization.ensureInitialized(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await getItSetup();
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
runApp(
|
||||||
|
FailedToInitSecureStorageScreen(e: e),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final ThemeData lightThemeData = await AppThemeFactory.create(
|
tz.initializeTimeZones();
|
||||||
isDark: false,
|
|
||||||
fallbackColor: BrandColors.primary,
|
|
||||||
);
|
|
||||||
final ThemeData darkThemeData = await AppThemeFactory.create(
|
|
||||||
isDark: true,
|
|
||||||
fallbackColor: BrandColors.primary,
|
|
||||||
);
|
|
||||||
|
|
||||||
Bloc.observer = SimpleBlocObserver();
|
Bloc.observer = SimpleBlocObserver();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
Localization(
|
Localization(
|
||||||
child: SelfprivacyApp(
|
child: InheritedPreferencesRepository(
|
||||||
lightThemeData: lightThemeData,
|
dataSource: PreferencesHiveDataSource(),
|
||||||
darkThemeData: darkThemeData,
|
child: const InheritedAppController(
|
||||||
|
child: AppBuilder(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelfprivacyApp extends StatelessWidget {
|
class AppBuilder extends StatelessWidget {
|
||||||
SelfprivacyApp({
|
const AppBuilder({super.key});
|
||||||
required this.lightThemeData,
|
|
||||||
required this.darkThemeData,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ThemeData lightThemeData;
|
|
||||||
final ThemeData darkThemeData;
|
|
||||||
|
|
||||||
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Localization(
|
Widget build(final BuildContext context) {
|
||||||
child: BlocAndProviderConfig(
|
final appController = InheritedAppController.of(context);
|
||||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
|
||||||
builder: (
|
|
||||||
final BuildContext context,
|
|
||||||
final AppSettingsState appSettings,
|
|
||||||
) {
|
|
||||||
getIt.get<ApiConfigModel>().setLocaleCode(
|
|
||||||
context.locale.languageCode,
|
|
||||||
);
|
|
||||||
return MaterialApp.router(
|
|
||||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
|
||||||
routerDelegate: _appRouter.delegate(),
|
|
||||||
scaffoldMessengerKey:
|
|
||||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
|
||||||
localizationsDelegates: context.localizationDelegates,
|
|
||||||
supportedLocales: context.supportedLocales,
|
|
||||||
locale: context.locale,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
title: 'SelfPrivacy',
|
|
||||||
theme: lightThemeData,
|
|
||||||
darkTheme: darkThemeData,
|
|
||||||
themeMode: appSettings.isAutoDarkModeOn
|
|
||||||
? ThemeMode.system
|
|
||||||
: appSettings.isDarkModeOn
|
|
||||||
? ThemeMode.dark
|
|
||||||
: ThemeMode.light,
|
|
||||||
builder: (final BuildContext context, final Widget? widget) {
|
|
||||||
Widget error =
|
|
||||||
const Center(child: Text('...rendering error...'));
|
|
||||||
if (widget is Scaffold || widget is Navigator) {
|
|
||||||
error = Scaffold(body: error);
|
|
||||||
}
|
|
||||||
ErrorWidget.builder =
|
|
||||||
(final FlutterErrorDetails errorDetails) => error;
|
|
||||||
|
|
||||||
return widget ?? error;
|
if (appController.loaded) {
|
||||||
},
|
return const SelfprivacyApp();
|
||||||
);
|
}
|
||||||
},
|
|
||||||
|
return const SplashScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widget to be shown
|
||||||
|
/// until essential app initialization is completed
|
||||||
|
class SplashScreen extends StatelessWidget {
|
||||||
|
const SplashScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => const ColoredBox(
|
||||||
|
color: Colors.white,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive(
|
||||||
|
valueColor: AlwaysStoppedAnimation(BrandColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SelfprivacyApp extends StatefulWidget {
|
||||||
|
const SelfprivacyApp({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SelfprivacyApp> createState() => _SelfprivacyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelfprivacyAppState extends State<SelfprivacyApp> {
|
||||||
|
final appKey = UniqueKey();
|
||||||
|
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final appController = InheritedAppController.of(context);
|
||||||
|
|
||||||
|
return BlocAndProviderConfig(
|
||||||
|
child: MaterialApp.router(
|
||||||
|
key: appKey,
|
||||||
|
title: 'SelfPrivacy',
|
||||||
|
// routing
|
||||||
|
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||||
|
routerDelegate: _appRouter.delegate(),
|
||||||
|
scaffoldMessengerKey:
|
||||||
|
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||||
|
// localization settings
|
||||||
|
locale: context.locale,
|
||||||
|
supportedLocales: context.supportedLocales,
|
||||||
|
localizationsDelegates: context.localizationDelegates,
|
||||||
|
// theme settings
|
||||||
|
themeMode: appController.themeMode,
|
||||||
|
theme: appController.lightTheme,
|
||||||
|
darkTheme: appController.darkTheme,
|
||||||
|
// other preferences
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
scrollBehavior:
|
||||||
|
const MaterialScrollBehavior().copyWith(scrollbars: false),
|
||||||
|
builder: _builder,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _builder(final BuildContext context, final Widget? widget) {
|
||||||
|
Widget error = const Center(child: Text('...rendering error...'));
|
||||||
|
if (widget is Scaffold || widget is Navigator) {
|
||||||
|
error = Scaffold(body: error);
|
||||||
|
}
|
||||||
|
ErrorWidget.builder = (final FlutterErrorDetails errorDetails) => error;
|
||||||
|
|
||||||
|
return widget ?? error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,11 @@ abstract class AppThemeFactory {
|
||||||
typography: appTypography,
|
typography: appTypography,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
scaffoldBackgroundColor: colorScheme.background,
|
scaffoldBackgroundColor: colorScheme.background,
|
||||||
|
listTileTheme: ListTileThemeData(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return materialThemeData;
|
return materialThemeData;
|
||||||
|
@ -50,7 +55,8 @@ abstract class AppThemeFactory {
|
||||||
static Future<ColorScheme?> _getDynamicColors(final Brightness brightness) {
|
static Future<ColorScheme?> _getDynamicColors(final Brightness brightness) {
|
||||||
try {
|
try {
|
||||||
return DynamicColorPlugin.getCorePalette().then(
|
return DynamicColorPlugin.getCorePalette().then(
|
||||||
(final corePallet) => corePallet?.toColorScheme(brightness: brightness),
|
(final corePallete) =>
|
||||||
|
corePallete?.toColorScheme(brightness: brightness),
|
||||||
);
|
);
|
||||||
} on PlatformException {
|
} on PlatformException {
|
||||||
return Future.value(null);
|
return Future.value(null);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BrandHeader extends StatelessWidget {
|
class BrandHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||||
const BrandHeader({
|
const BrandHeader({
|
||||||
super.key,
|
super.key,
|
||||||
this.title = '',
|
this.title = '',
|
||||||
|
@ -8,6 +8,9 @@ class BrandHeader extends StatelessWidget {
|
||||||
this.onBackButtonPressed,
|
this.onBackButtonPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(52.0);
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final bool hasBackButton;
|
final bool hasBackButton;
|
||||||
final VoidCallback? onBackButtonPressed;
|
final VoidCallback? onBackButtonPressed;
|
||||||
|
|
|
@ -11,15 +11,16 @@ class InfoBox extends StatelessWidget {
|
||||||
final bool isWarning;
|
final bool isWarning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Column(
|
Widget build(final BuildContext context) => Wrap(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
spacing: 8.0,
|
||||||
|
runSpacing: 16.0,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isWarning ? Icons.warning_amber_outlined : Icons.info_outline,
|
isWarning ? Icons.warning_amber_outlined : Icons.info_outline,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
Text(
|
||||||
text,
|
text,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
|
||||||
import 'package:selfprivacy/utils/platform_adapter.dart';
|
|
||||||
|
|
||||||
class LogListItem extends StatelessWidget {
|
|
||||||
const LogListItem({
|
|
||||||
required this.message,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Message message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
final messageItem = message;
|
|
||||||
if (messageItem is RestApiRequestMessage) {
|
|
||||||
return _RestApiRequestMessageItem(message: messageItem);
|
|
||||||
} else if (messageItem is RestApiResponseMessage) {
|
|
||||||
return _RestApiResponseMessageItem(message: messageItem);
|
|
||||||
} else if (messageItem is GraphQlResponseMessage) {
|
|
||||||
return _GraphQlResponseMessageItem(message: messageItem);
|
|
||||||
} else if (messageItem is GraphQlRequestMessage) {
|
|
||||||
return _GraphQlRequestMessageItem(message: messageItem);
|
|
||||||
} else {
|
|
||||||
return _DefaultMessageItem(message: messageItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RestApiRequestMessageItem extends StatelessWidget {
|
|
||||||
const _RestApiRequestMessageItem({required this.message});
|
|
||||||
|
|
||||||
final RestApiRequestMessage message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => ListTile(
|
|
||||||
title: Text(
|
|
||||||
'${message.method}\n${message.uri}',
|
|
||||||
),
|
|
||||||
subtitle: Text(message.timeString),
|
|
||||||
leading: const Icon(Icons.upload_outlined),
|
|
||||||
iconColor: Theme.of(context).colorScheme.secondary,
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final BuildContext context) => AlertDialog(
|
|
||||||
scrollable: true,
|
|
||||||
title: Text(
|
|
||||||
'${message.method}\n${message.uri}',
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Text(message.timeString),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
// Headers is a map of key-value pairs
|
|
||||||
if (message.headers != null) const Text('Headers'),
|
|
||||||
if (message.headers != null)
|
|
||||||
Text(
|
|
||||||
message.headers!.entries
|
|
||||||
.map((final entry) => '${entry.key}: ${entry.value}')
|
|
||||||
.join('\n'),
|
|
||||||
),
|
|
||||||
if (message.data != null && message.data != 'null')
|
|
||||||
const Text('Data'),
|
|
||||||
if (message.data != null && message.data != 'null')
|
|
||||||
Text(message.data!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
// A button to copy the request to the clipboard
|
|
||||||
if (message.text != null)
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
PlatformAdapter.setClipboard(message.text ?? '');
|
|
||||||
},
|
|
||||||
child: Text('console_page.copy'.tr()),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: Text('basis.close'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RestApiResponseMessageItem extends StatelessWidget {
|
|
||||||
const _RestApiResponseMessageItem({required this.message});
|
|
||||||
|
|
||||||
final RestApiResponseMessage message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => ListTile(
|
|
||||||
title: Text(
|
|
||||||
'${message.statusCode} ${message.method}\n${message.uri}',
|
|
||||||
),
|
|
||||||
subtitle: Text(message.timeString),
|
|
||||||
leading: const Icon(Icons.download_outlined),
|
|
||||||
iconColor: Theme.of(context).colorScheme.primary,
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final BuildContext context) => AlertDialog(
|
|
||||||
scrollable: true,
|
|
||||||
title: Text(
|
|
||||||
'${message.statusCode} ${message.method}\n${message.uri}',
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Text(message.timeString),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
// Headers is a map of key-value pairs
|
|
||||||
if (message.data != null && message.data != 'null')
|
|
||||||
const Text('Data'),
|
|
||||||
if (message.data != null && message.data != 'null')
|
|
||||||
Text(message.data!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
// A button to copy the request to the clipboard
|
|
||||||
if (message.text != null)
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
PlatformAdapter.setClipboard(message.text ?? '');
|
|
||||||
},
|
|
||||||
child: Text('console_page.copy'.tr()),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: Text('basis.close'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GraphQlResponseMessageItem extends StatelessWidget {
|
|
||||||
const _GraphQlResponseMessageItem({required this.message});
|
|
||||||
|
|
||||||
final GraphQlResponseMessage message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => ListTile(
|
|
||||||
title: Text(
|
|
||||||
'GraphQL Response at ${message.timeString}',
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
message.data.toString(),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
leading: const Icon(Icons.arrow_circle_down_outlined),
|
|
||||||
iconColor: Theme.of(context).colorScheme.tertiary,
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final BuildContext context) => AlertDialog(
|
|
||||||
scrollable: true,
|
|
||||||
title: Text(
|
|
||||||
'GraphQL Response at ${message.timeString}',
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Text(message.timeString),
|
|
||||||
const Divider(),
|
|
||||||
if (message.data != null) const Text('Data'),
|
|
||||||
// Data is a map of key-value pairs
|
|
||||||
if (message.data != null)
|
|
||||||
Text(
|
|
||||||
message.data!.entries
|
|
||||||
.map((final entry) => '${entry.key}: ${entry.value}')
|
|
||||||
.join('\n'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
if (message.errors != null) const Text('Errors'),
|
|
||||||
if (message.errors != null)
|
|
||||||
Text(
|
|
||||||
message.errors!
|
|
||||||
.map(
|
|
||||||
(final entry) =>
|
|
||||||
'${entry.message} at ${entry.locations}',
|
|
||||||
)
|
|
||||||
.join('\n'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
if (message.context != null) const Text('Context'),
|
|
||||||
if (message.context != null)
|
|
||||||
Text(
|
|
||||||
message.context!.toString(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
// A button to copy the request to the clipboard
|
|
||||||
if (message.text != null)
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
PlatformAdapter.setClipboard(message.text ?? '');
|
|
||||||
},
|
|
||||||
child: Text('console_page.copy'.tr()),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: Text('basis.close'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GraphQlRequestMessageItem extends StatelessWidget {
|
|
||||||
const _GraphQlRequestMessageItem({required this.message});
|
|
||||||
|
|
||||||
final GraphQlRequestMessage message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => ListTile(
|
|
||||||
title: Text(
|
|
||||||
'GraphQL Request at ${message.timeString}',
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
message.operation.toString(),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
leading: const Icon(Icons.arrow_circle_up_outlined),
|
|
||||||
iconColor: Theme.of(context).colorScheme.secondary,
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final BuildContext context) => AlertDialog(
|
|
||||||
scrollable: true,
|
|
||||||
title: Text(
|
|
||||||
'GraphQL Response at ${message.timeString}',
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
children: [
|
|
||||||
Text(message.timeString),
|
|
||||||
const Divider(),
|
|
||||||
if (message.operation != null) const Text('Operation'),
|
|
||||||
// Data is a map of key-value pairs
|
|
||||||
if (message.operation != null)
|
|
||||||
Text(
|
|
||||||
message.operation!.toString(),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
if (message.variables != null) const Text('Variables'),
|
|
||||||
if (message.variables != null)
|
|
||||||
Text(
|
|
||||||
message.variables!.entries
|
|
||||||
.map((final entry) => '${entry.key}: ${entry.value}')
|
|
||||||
.join('\n'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
if (message.context != null) const Text('Context'),
|
|
||||||
if (message.context != null)
|
|
||||||
Text(
|
|
||||||
message.context!.toString(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
// A button to copy the request to the clipboard
|
|
||||||
if (message.text != null)
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
PlatformAdapter.setClipboard(message.text ?? '');
|
|
||||||
},
|
|
||||||
child: Text('console_page.copy'.tr()),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: Text('basis.close'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DefaultMessageItem extends StatelessWidget {
|
|
||||||
const _DefaultMessageItem({required this.message});
|
|
||||||
|
|
||||||
final Message message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
style: DefaultTextStyle.of(context).style,
|
|
||||||
children: <TextSpan>[
|
|
||||||
TextSpan(
|
|
||||||
text: '${message.timeString}: \n',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(text: message.text),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
|
|
||||||
class EmptyPagePlaceholder extends StatelessWidget {
|
class EmptyPagePlaceholder extends StatelessWidget {
|
||||||
|
@ -10,50 +11,72 @@ class EmptyPagePlaceholder extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final bool showReadyCard;
|
||||||
|
final IconData iconData;
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
final IconData iconData;
|
|
||||||
final bool showReadyCard;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => !showReadyCard
|
Widget build(final BuildContext context) => showReadyCard
|
||||||
? _expandedContent(context)
|
? Column(
|
||||||
: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
if (showReadyCard)
|
||||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
const Padding(
|
||||||
child: NotReadyCard(),
|
padding: EdgeInsets.symmetric(
|
||||||
),
|
vertical: 15,
|
||||||
Expanded(
|
horizontal: 15,
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
|
||||||
child: Center(
|
|
||||||
child: _expandedContent(context),
|
|
||||||
),
|
),
|
||||||
|
child: NotReadyCard(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _ContentWidget(
|
||||||
|
iconData: iconData,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
)
|
||||||
|
: _ContentWidget(
|
||||||
|
iconData: iconData,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _expandedContent(final BuildContext context) => Padding(
|
class _ContentWidget extends StatelessWidget {
|
||||||
|
const _ContentWidget({
|
||||||
|
required this.iconData,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData iconData;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
iconData,
|
iconData,
|
||||||
size: 50,
|
size: 50,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const Gap(16),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
description,
|
description,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
|
@ -1,295 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
|
||||||
import 'package:selfprivacy/ui/router/root_destinations.dart';
|
|
||||||
import 'package:selfprivacy/utils/breakpoints.dart';
|
|
||||||
|
|
||||||
class RootScaffoldWithNavigation extends StatelessWidget {
|
|
||||||
const RootScaffoldWithNavigation({
|
|
||||||
required this.child,
|
|
||||||
required this.title,
|
|
||||||
required this.destinations,
|
|
||||||
this.showBottomBar = true,
|
|
||||||
this.showFab = true,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final String title;
|
|
||||||
final bool showBottomBar;
|
|
||||||
final List<RouteDestination> destinations;
|
|
||||||
final bool showFab;
|
|
||||||
|
|
||||||
@override
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: Breakpoints.mediumAndUp.isActive(context)
|
|
||||||
? PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(52),
|
|
||||||
child: _RootAppBar(title: title),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
endDrawer: const SupportDrawer(),
|
|
||||||
endDrawerEnableOpenDragGesture: false,
|
|
||||||
body: Row(
|
|
||||||
children: [
|
|
||||||
if (Breakpoints.medium.isActive(context))
|
|
||||||
_MainScreenNavigationRail(
|
|
||||||
destinations: destinations,
|
|
||||||
showFab: showFab,
|
|
||||||
),
|
|
||||||
if (Breakpoints.large.isActive(context))
|
|
||||||
_MainScreenNavigationDrawer(
|
|
||||||
destinations: destinations,
|
|
||||||
showFab: showFab,
|
|
||||||
),
|
|
||||||
Expanded(child: child),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
bottomNavigationBar: _BottomBar(
|
|
||||||
destinations: destinations,
|
|
||||||
hidden: !(Breakpoints.small.isActive(context) && showBottomBar),
|
|
||||||
key: const Key('bottomBar'),
|
|
||||||
),
|
|
||||||
floatingActionButton:
|
|
||||||
showFab && Breakpoints.small.isActive(context) && showBottomBar
|
|
||||||
? const BrandFab()
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RootAppBar extends StatelessWidget {
|
|
||||||
const _RootAppBar({
|
|
||||||
required this.title,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => AppBar(
|
|
||||||
title: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
transitionBuilder:
|
|
||||||
(final Widget child, final Animation<double> animation) =>
|
|
||||||
SlideTransition(
|
|
||||||
position: animation.drive(
|
|
||||||
Tween<Offset>(
|
|
||||||
begin: const Offset(0.0, 0.2),
|
|
||||||
end: Offset.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: FadeTransition(
|
|
||||||
opacity: animation,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
key: ValueKey<String>(title),
|
|
||||||
width: double.infinity,
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leading: context.router.pageCount > 1
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: () => context.router.maybePop(),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
actions: const [
|
|
||||||
SizedBox.shrink(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainScreenNavigationRail extends StatelessWidget {
|
|
||||||
const _MainScreenNavigationRail({
|
|
||||||
required this.destinations,
|
|
||||||
this.showFab = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<RouteDestination> destinations;
|
|
||||||
final bool showFab;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
int? activeIndex = destinations.indexWhere(
|
|
||||||
(final destination) =>
|
|
||||||
context.router.isRouteActive(destination.route.routeName),
|
|
||||||
);
|
|
||||||
|
|
||||||
final prevActiveIndex = destinations.indexWhere(
|
|
||||||
(final destination) => context.router.stack
|
|
||||||
.any((final route) => route.name == destination.route.routeName),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (activeIndex == -1) {
|
|
||||||
if (prevActiveIndex != -1) {
|
|
||||||
activeIndex = prevActiveIndex;
|
|
||||||
} else {
|
|
||||||
activeIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final isExtended = Breakpoints.large.isActive(context);
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (final context, final constraints) => SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: NavigationRail(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
labelType: isExtended
|
|
||||||
? NavigationRailLabelType.none
|
|
||||||
: NavigationRailLabelType.all,
|
|
||||||
extended: isExtended,
|
|
||||||
leading: showFab
|
|
||||||
? const BrandFab(
|
|
||||||
extended: false,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
groupAlignment: 0.0,
|
|
||||||
destinations: destinations
|
|
||||||
.map(
|
|
||||||
(final destination) => NavigationRailDestination(
|
|
||||||
icon: Icon(destination.icon),
|
|
||||||
label: Text(destination.label),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
selectedIndex: activeIndex,
|
|
||||||
onDestinationSelected: (final index) {
|
|
||||||
context.router.replaceAll([destinations[index].route]);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BottomBar extends StatelessWidget {
|
|
||||||
const _BottomBar({
|
|
||||||
required this.destinations,
|
|
||||||
required this.hidden,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<RouteDestination> destinations;
|
|
||||||
final bool hidden;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
final prevActiveIndex = destinations.indexWhere(
|
|
||||||
(final destination) => context.router.stack
|
|
||||||
.any((final route) => route.name == destination.route.routeName),
|
|
||||||
);
|
|
||||||
|
|
||||||
return AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 500),
|
|
||||||
height: hidden ? 0 : 80,
|
|
||||||
curve: Curves.easeInOutCubicEmphasized,
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
),
|
|
||||||
child: Platform.isIOS
|
|
||||||
? CupertinoTabBar(
|
|
||||||
currentIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex,
|
|
||||||
onTap: (final index) {
|
|
||||||
context.router.replaceAll([destinations[index].route]);
|
|
||||||
},
|
|
||||||
items: destinations
|
|
||||||
.map(
|
|
||||||
(final destination) => BottomNavigationBarItem(
|
|
||||||
icon: Icon(destination.icon),
|
|
||||||
label: destination.label,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
)
|
|
||||||
: NavigationBar(
|
|
||||||
selectedIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex,
|
|
||||||
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
|
||||||
onDestinationSelected: (final index) {
|
|
||||||
context.router.replaceAll([destinations[index].route]);
|
|
||||||
},
|
|
||||||
destinations: destinations
|
|
||||||
.map(
|
|
||||||
(final destination) => NavigationDestination(
|
|
||||||
icon: Icon(destination.icon),
|
|
||||||
label: destination.label,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainScreenNavigationDrawer extends StatelessWidget {
|
|
||||||
const _MainScreenNavigationDrawer({
|
|
||||||
required this.destinations,
|
|
||||||
this.showFab = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<RouteDestination> destinations;
|
|
||||||
final bool showFab;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
int? activeIndex = destinations.indexWhere(
|
|
||||||
(final destination) =>
|
|
||||||
context.router.isRouteActive(destination.route.routeName),
|
|
||||||
);
|
|
||||||
|
|
||||||
final prevActiveIndex = destinations.indexWhere(
|
|
||||||
(final destination) => context.router.stack
|
|
||||||
.any((final route) => route.name == destination.route.routeName),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (activeIndex == -1) {
|
|
||||||
if (prevActiveIndex != -1) {
|
|
||||||
activeIndex = prevActiveIndex;
|
|
||||||
} else {
|
|
||||||
activeIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
width: 296,
|
|
||||||
child: NavigationDrawer(
|
|
||||||
key: const Key('PrimaryNavigationDrawer'),
|
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
selectedIndex: activeIndex,
|
|
||||||
onDestinationSelected: (final index) {
|
|
||||||
context.router.replaceAll([destinations[index].route]);
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(16.0),
|
|
||||||
child: BrandFab(extended: true),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
...destinations.map(
|
|
||||||
(final destination) => NavigationDrawerDestination(
|
|
||||||
icon: Icon(destination.icon),
|
|
||||||
label: Text(destination.label),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
part of 'root_scaffold_with_subroute_selector.dart';
|
||||||
|
|
||||||
|
class _BottomTabBar extends SubrouteSelector {
|
||||||
|
const _BottomTabBar({
|
||||||
|
required super.subroutes,
|
||||||
|
required this.hidden,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool hidden;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final int activeIndex = getActiveIndex(context);
|
||||||
|
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
height: hidden ? 0 : 80,
|
||||||
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
),
|
||||||
|
child: Platform.isIOS
|
||||||
|
? CupertinoTabBar(
|
||||||
|
currentIndex: activeIndex,
|
||||||
|
onTap: openSubpage(context),
|
||||||
|
items: [
|
||||||
|
for (final destination in subroutes)
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(destination.icon),
|
||||||
|
label: destination.label.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: NavigationBar(
|
||||||
|
selectedIndex: activeIndex,
|
||||||
|
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
||||||
|
onDestinationSelected: openSubpage(context),
|
||||||
|
destinations: [
|
||||||
|
for (final destination in subroutes)
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(destination.icon),
|
||||||
|
label: destination.label.tr(),
|
||||||
|
),
|
||||||
|
].toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
part of 'root_scaffold_with_subroute_selector.dart';
|
||||||
|
|
||||||
|
class _NavigationDrawer extends SubrouteSelector {
|
||||||
|
const _NavigationDrawer({
|
||||||
|
required super.subroutes,
|
||||||
|
this.showFab = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool showFab;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
width: 296,
|
||||||
|
child: NavigationDrawer(
|
||||||
|
key: const Key('PrimaryNavigationDrawer'),
|
||||||
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
selectedIndex: getActiveIndex(context),
|
||||||
|
onDestinationSelected: openSubpage(context),
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
child: BrandFab(extended: true),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
for (final destination in subroutes)
|
||||||
|
NavigationDrawerDestination(
|
||||||
|
icon: Icon(destination.icon),
|
||||||
|
label: Text(destination.label.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
part of 'root_scaffold_with_subroute_selector.dart';
|
||||||
|
|
||||||
|
class _NavigationRail extends SubrouteSelector {
|
||||||
|
const _NavigationRail({
|
||||||
|
required super.subroutes,
|
||||||
|
this.showFab = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool showFab;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final isExtended = Breakpoints.large.isActive(context);
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (final context, final constraints) => SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: NavigationRail(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
labelType: isExtended
|
||||||
|
? NavigationRailLabelType.none
|
||||||
|
: NavigationRailLabelType.all,
|
||||||
|
extended: isExtended,
|
||||||
|
leading: showFab
|
||||||
|
? const BrandFab(
|
||||||
|
extended: false,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
groupAlignment: 0.0,
|
||||||
|
destinations: [
|
||||||
|
for (final destination in subroutes)
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(destination.icon),
|
||||||
|
label: Text(destination.label.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selectedIndex: getActiveIndex(context),
|
||||||
|
onDestinationSelected: openSubpage(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
part of 'root_scaffold_with_subroute_selector.dart';
|
||||||
|
|
||||||
|
class _RootAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
const _RootAppBar({
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(52);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => AppBar(
|
||||||
|
title: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
transitionBuilder:
|
||||||
|
(final Widget child, final Animation<double> animation) =>
|
||||||
|
SlideTransition(
|
||||||
|
position: animation.drive(
|
||||||
|
Tween<Offset>(
|
||||||
|
begin: const Offset(0.0, 0.2),
|
||||||
|
end: Offset.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
key: ValueKey<String>(title),
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: context.router.pageCount > 1
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => context.router.maybePop(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
actions: const [SizedBox.shrink()],
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
||||||
|
import 'package:selfprivacy/ui/router/root_destinations.dart';
|
||||||
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
|
|
||||||
|
part 'bottom_tab_bar.dart';
|
||||||
|
part 'navigation_drawer.dart';
|
||||||
|
part 'navigation_rail.dart';
|
||||||
|
part 'root_app_bar.dart';
|
||||||
|
part 'subroute_selector.dart';
|
||||||
|
|
||||||
|
class RootScaffoldWithSubrouteSelector extends StatelessWidget {
|
||||||
|
const RootScaffoldWithSubrouteSelector({
|
||||||
|
required this.child,
|
||||||
|
required this.destinations,
|
||||||
|
this.showBottomBar = true,
|
||||||
|
this.showFab = true,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final bool showBottomBar;
|
||||||
|
final List<RouteDestination> destinations;
|
||||||
|
final bool showFab;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Scaffold(
|
||||||
|
appBar: Breakpoints.mediumAndUp.isActive(context)
|
||||||
|
? _RootAppBar(
|
||||||
|
title: getRouteTitle(context.router.current.name).tr(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
endDrawer: const SupportDrawer(),
|
||||||
|
endDrawerEnableOpenDragGesture: false,
|
||||||
|
body: Row(
|
||||||
|
children: [
|
||||||
|
if (Breakpoints.medium.isActive(context))
|
||||||
|
_NavigationRail(
|
||||||
|
subroutes: destinations,
|
||||||
|
showFab: showFab,
|
||||||
|
)
|
||||||
|
else if (Breakpoints.large.isActive(context))
|
||||||
|
_NavigationDrawer(
|
||||||
|
subroutes: destinations,
|
||||||
|
showFab: showFab,
|
||||||
|
),
|
||||||
|
Expanded(child: child),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomNavigationBar: _BottomTabBar(
|
||||||
|
key: const ValueKey('bottomBar'),
|
||||||
|
subroutes: destinations,
|
||||||
|
hidden: !(Breakpoints.small.isActive(context) && showBottomBar),
|
||||||
|
),
|
||||||
|
floatingActionButton:
|
||||||
|
showFab && Breakpoints.small.isActive(context) && showBottomBar
|
||||||
|
? const BrandFab()
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
part of 'root_scaffold_with_subroute_selector.dart';
|
||||||
|
|
||||||
|
abstract class SubrouteSelector extends StatelessWidget {
|
||||||
|
const SubrouteSelector({
|
||||||
|
required this.subroutes,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<RouteDestination> subroutes;
|
||||||
|
|
||||||
|
int getActiveIndex(final BuildContext context) {
|
||||||
|
int activeIndex = subroutes.indexWhere(
|
||||||
|
(final destination) =>
|
||||||
|
context.router.isRouteActive(destination.route.routeName),
|
||||||
|
);
|
||||||
|
|
||||||
|
final prevActiveIndex = subroutes.indexWhere(
|
||||||
|
(final destination) => context.router.stack.any(
|
||||||
|
(final route) => route.name == destination.route.routeName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeIndex == -1) {
|
||||||
|
activeIndex = prevActiveIndex != -1 ? prevActiveIndex : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueSetter<int> openSubpage(final BuildContext context) => (final index) {
|
||||||
|
context.router.replaceAll([subroutes[index].route]);
|
||||||
|
};
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ class NewDeviceScreen extends StatelessWidget {
|
||||||
|
|
||||||
class _KeyDisplay extends StatelessWidget {
|
class _KeyDisplay extends StatelessWidget {
|
||||||
const _KeyDisplay({required this.newDeviceKey});
|
const _KeyDisplay({required this.newDeviceKey});
|
||||||
|
|
||||||
final String newDeviceKey;
|
final String newDeviceKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -47,7 +48,7 @@ class _KeyDisplay extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
SelectableText(
|
||||||
newDeviceKey,
|
newDeviceKey,
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:io';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
|
import 'package:selfprivacy/config/localization.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
||||||
|
part 'language_picker.dart';
|
||||||
|
part 'reset_app_button.dart';
|
||||||
|
part 'theme_picker.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class AppSettingsPage extends StatefulWidget {
|
class AppSettingsPage extends StatefulWidget {
|
||||||
|
@ -16,82 +23,36 @@ class AppSettingsPage extends StatefulWidget {
|
||||||
|
|
||||||
class _AppSettingsPageState extends State<AppSettingsPage> {
|
class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||||
final bool isDarkModeOn =
|
hasBackButton: true,
|
||||||
context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
hasFlashButton: false,
|
||||||
|
bodyPadding: const EdgeInsets.symmetric(
|
||||||
final bool isSystemDarkModeOn =
|
horizontal: 12,
|
||||||
context.watch<AppSettingsCubit>().state.isAutoDarkModeOn;
|
vertical: 16,
|
||||||
|
|
||||||
return BrandHeroScreen(
|
|
||||||
hasBackButton: true,
|
|
||||||
hasFlashButton: false,
|
|
||||||
bodyPadding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
heroTitle: 'application_settings.title'.tr(),
|
|
||||||
children: [
|
|
||||||
SwitchListTile.adaptive(
|
|
||||||
title: Text('application_settings.system_dark_theme_title'.tr()),
|
|
||||||
subtitle:
|
|
||||||
Text('application_settings.system_dark_theme_description'.tr()),
|
|
||||||
value: isSystemDarkModeOn,
|
|
||||||
onChanged: (final value) => context
|
|
||||||
.read<AppSettingsCubit>()
|
|
||||||
.updateAutoDarkMode(isAutoDarkModeOn: !isSystemDarkModeOn),
|
|
||||||
),
|
),
|
||||||
SwitchListTile.adaptive(
|
heroTitle: 'application_settings.title'.tr(),
|
||||||
title: Text('application_settings.dark_theme_title'.tr()),
|
children: [
|
||||||
subtitle: Text('application_settings.dark_theme_description'.tr()),
|
_ThemePicker(
|
||||||
value: Theme.of(context).brightness == Brightness.dark,
|
key: ValueKey('theme_picker'.tr()),
|
||||||
onChanged: isSystemDarkModeOn
|
|
||||||
? null
|
|
||||||
: (final value) => context
|
|
||||||
.read<AppSettingsCubit>()
|
|
||||||
.updateDarkMode(isDarkModeOn: !isDarkModeOn),
|
|
||||||
),
|
|
||||||
const Divider(height: 0),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Text(
|
|
||||||
'application_settings.dangerous_settings'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
const Divider(height: 5, thickness: 0),
|
||||||
const _ResetAppTile(),
|
_LanguagePicker(
|
||||||
],
|
key: ValueKey('language_picker'.tr()),
|
||||||
);
|
),
|
||||||
}
|
const Divider(height: 5, thickness: 0),
|
||||||
}
|
const Gap(4),
|
||||||
|
Padding(
|
||||||
class _ResetAppTile extends StatelessWidget {
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
const _ResetAppTile();
|
child: Text(
|
||||||
|
'application_settings.dangerous_settings'.tr(),
|
||||||
@override
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
inex
commented
Review
This should be a This should be a `labelLarge`, looks broken as a `titleLarge`.
|
|||||||
Widget build(final BuildContext context) => ListTile(
|
color: Theme.of(context).colorScheme.error,
|
||||||
title: Text('application_settings.reset_config_title'.tr()),
|
),
|
||||||
subtitle: Text('application_settings.reset_config_description'.tr()),
|
|
||||||
onTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final _) => AlertDialog(
|
|
||||||
title: Text('modals.are_you_sure'.tr()),
|
|
||||||
content: Text('modals.purge_all_keys'.tr()),
|
|
||||||
actions: [
|
|
||||||
DialogActionButton(
|
|
||||||
text: 'modals.purge_all_keys_confirm'.tr(),
|
|
||||||
isRed: true,
|
|
||||||
onPressed: () {
|
|
||||||
context.read<ServerInstallationCubit>().clearAppConfig();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DialogActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
_ResetAppTile(
|
||||||
|
key: ValueKey('reset_app'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
@ -60,17 +61,14 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
|
||||||
title: Text('developer_settings.reset_onboarding'.tr()),
|
title: Text('developer_settings.reset_onboarding'.tr()),
|
||||||
subtitle:
|
subtitle:
|
||||||
Text('developer_settings.reset_onboarding_description'.tr()),
|
Text('developer_settings.reset_onboarding_description'.tr()),
|
||||||
enabled:
|
enabled: !InheritedAppController.of(context).shouldShowOnboarding,
|
||||||
!context.watch<AppSettingsCubit>().state.isOnboardingShowing,
|
onTap: () => InheritedAppController.of(context)
|
||||||
onTap: () => context
|
.setShouldShowOnboarding(true),
|
||||||
.read<AppSettingsCubit>()
|
|
||||||
.turnOffOnboarding(isOnboardingShowing: true),
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('storage.start_migration_button'.tr()),
|
title: Text('storage.start_migration_button'.tr()),
|
||||||
subtitle: Text('storage.data_migration_notice'.tr()),
|
subtitle: Text('storage.data_migration_notice'.tr()),
|
||||||
enabled:
|
enabled: InheritedAppController.of(context).shouldShowOnboarding,
|
||||||
!context.watch<AppSettingsCubit>().state.isOnboardingShowing,
|
|
||||||
onTap: () => context.pushRoute(
|
onTap: () => context.pushRoute(
|
||||||
ServicesMigrationRoute(
|
ServicesMigrationRoute(
|
||||||
diskStatus: context.read<VolumesBloc>().state.diskStatus,
|
diskStatus: context.read<VolumesBloc>().state.diskStatus,
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
part of 'app_settings.dart';
|
||||||
|
|
||||||
|
class _LanguagePicker extends StatelessWidget {
|
||||||
|
const _LanguagePicker({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => ListTile(
|
||||||
|
title: Text(
|
||||||
|
'application_settings.language'.tr(),
|
||||||
|
),
|
||||||
|
subtitle: Text('application_settings.click_to_change_locale'.tr()),
|
||||||
|
trailing: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Text(
|
||||||
|
Localization.getLanguageName(context.locale),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
inex
commented
Review
This is too big This is too big
|
|||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
final appController = InheritedAppController.of(context);
|
||||||
|
final Locale? newLocale = await showDialog<Locale?>(
|
||||||
|
context: context,
|
||||||
|
builder: (final context) => const _LanguagePickerDialog(),
|
||||||
|
routeSettings: _LanguagePickerDialog.routeSettings,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newLocale != null) {
|
||||||
|
await appController.setLocale(newLocale);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LanguagePickerDialog extends StatelessWidget {
|
||||||
|
const _LanguagePickerDialog();
|
||||||
|
static const routeSettings = RouteSettings(name: 'LanguagePickerDialog');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => SimpleDialog(
|
||||||
|
title: Text('application_settings.language'.tr()),
|
||||||
|
children: [
|
||||||
|
for (final locale
|
||||||
inex
commented
Review
Probably add a "System default" value to reset our overwrite? Probably add a "System default" value to reset our overwrite?
|
|||||||
|
in InheritedAppController.of(context).supportedLocales)
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
Localization.getLanguageName(locale),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: locale == context.locale
|
||||||
|
? FontWeight.w800
|
||||||
inex
commented
Review
How about using a radio button, not just the bold text? How about using a radio button, not just the bold text?
|
|||||||
|
: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop(locale);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
part of 'app_settings.dart';
|
||||||
|
|
||||||
|
class _ResetAppTile extends StatelessWidget {
|
||||||
|
const _ResetAppTile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => ListTile(
|
||||||
|
title: Text('application_settings.reset_config_title'.tr()),
|
||||||
|
subtitle: Text('application_settings.reset_config_description'.tr()),
|
||||||
|
onTap: () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (final context) => const _ResetAppDialog(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ResetAppDialog extends StatelessWidget {
|
||||||
|
const _ResetAppDialog();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => AlertDialog(
|
||||||
|
title: Text('modals.are_you_sure'.tr()),
|
||||||
|
content: Text('modals.purge_all_keys'.tr()),
|
||||||
|
actions: [
|
||||||
|
DialogActionButton(
|
||||||
|
text: 'modals.purge_all_keys_confirm'.tr(),
|
||||||
|
isRed: true,
|
||||||
|
onPressed: () {
|
||||||
|
context.read<ServerInstallationCubit>().clearAppConfig();
|
||||||
|
|
||||||
|
context.router.maybePop([
|
||||||
|
const RootRoute(),
|
||||||
|
]);
|
||||||
|
context.resetLocale();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DialogActionButton(
|
||||||
|
text: 'basis.cancel'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
part of 'app_settings.dart';
|
||||||
|
|
||||||
|
class _ThemePicker extends StatelessWidget {
|
||||||
|
const _ThemePicker({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final appController = InheritedAppController.of(context);
|
||||||
|
// final themeMode = appController.themeMode;
|
||||||
|
// final bool isSystemThemeModeEnabled = themeMode == ThemeMode.system;
|
||||||
|
// final bool isDarkModeOn = themeMode == ThemeMode.dark;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
title: Text('application_settings.system_theme_mode_title'.tr()),
|
||||||
|
subtitle:
|
||||||
|
Text('application_settings.system_theme_mode_description'.tr()),
|
||||||
|
value: appController.systemThemeModeActive,
|
||||||
|
onChanged: appController.setSystemThemeModeFlag,
|
||||||
|
// onChanged: (final newValue) => appController.setThemeMode(
|
||||||
inex
commented
Review
Why is it still there, commented out? Why is it still there, commented out?
|
|||||||
|
// newValue
|
||||||
|
// ? ThemeMode.system
|
||||||
|
// : (isDarkModeOn ? ThemeMode.dark : ThemeMode.light),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
title: Text('application_settings.dark_theme_title'.tr()),
|
||||||
|
subtitle: Text('application_settings.change_application_theme'.tr()),
|
||||||
|
value: appController.darkThemeModeActive,
|
||||||
|
onChanged: appController.systemThemeModeActive
|
||||||
|
? null
|
||||||
|
: appController.setDarkThemeModeFlag,
|
||||||
|
// onChanged: isSystemThemeModeEnabled
|
||||||
|
// ? null
|
||||||
|
// : (final newValue) => appController.setThemeMode(
|
||||||
|
// newValue ? ThemeMode.dark : ThemeMode.light,
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/log_list_tile.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ConsolePage extends StatefulWidget {
|
|
||||||
const ConsolePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ConsolePage> createState() => _ConsolePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConsolePageState extends State<ConsolePage> {
|
|
||||||
bool paused = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
getIt<ConsoleModel>().addListener(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
getIt<ConsoleModel>().removeListener(update);
|
|
||||||
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() {
|
|
||||||
/// listener update could come at any time, like when widget is already
|
|
||||||
/// unmounted or during frame build, adding as postframe callback ensures
|
|
||||||
/// that element is marked for rebuild
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((final _) {
|
|
||||||
if (!paused && mounted) {
|
|
||||||
setState(() => {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void togglePause() {
|
|
||||||
paused ^= true;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => SafeArea(
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text('console_page.title'.tr()),
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
paused ? Icons.play_arrow_outlined : Icons.pause_outlined,
|
|
||||||
),
|
|
||||||
onPressed: togglePause,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: FutureBuilder(
|
|
||||||
future: getIt.allReady(),
|
|
||||||
builder: (
|
|
||||||
final BuildContext context,
|
|
||||||
final AsyncSnapshot<void> snapshot,
|
|
||||||
) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
final List<Message> messages =
|
|
||||||
getIt.get<ConsoleModel>().messages;
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
reverse: true,
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: [
|
|
||||||
const Gap(20),
|
|
||||||
...messages.reversed.map(
|
|
||||||
(final message) => LogListItem(
|
|
||||||
key: ValueKey(message),
|
|
||||||
message: message,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text('console_page.waiting'.tr()),
|
|
||||||
const Gap(16),
|
|
||||||
const CircularProgressIndicator.adaptive(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
import 'package:selfprivacy/utils/platform_adapter.dart';
|
||||||
|
|
||||||
|
extension on ConsoleLog {
|
||||||
|
List<Widget> unwrapContent(final BuildContext context) => switch (this) {
|
||||||
|
(final RestApiRequestConsoleLog log) => [
|
||||||
|
if (log.method != null) _KeyValueRow('method', log.method),
|
||||||
|
if (log.uri != null) _KeyValueRow('uri', '${log.uri}'),
|
||||||
|
|
||||||
|
// headers bloc
|
||||||
|
if (log.headers?.isNotEmpty ?? false) ...[
|
||||||
inex
commented
Review
Rest API requests dialogs expose the API token, and we don't want that (as this console is usually used to send us screenshots/copies of request data) Rest API requests dialogs expose the API token, and we don't want that (as this console is usually used to send us screenshots/copies of request data)
misterfourtytwo
commented
Review
so you propose to remove headers section or to hide it? so you propose to remove headers section or to hide it?
inex
commented
Review
Not necessary, just censoring the Authorization header should be enough. Not necessary, just censoring the Authorization header should be enough.
|
|||||||
|
const _SectionRow('console_page.headers'),
|
||||||
|
for (final entry in log.headers!.entries)
|
||||||
|
_KeyValueRow(entry.key, '${entry.value}'),
|
||||||
|
],
|
||||||
|
|
||||||
|
// data
|
||||||
|
const _SectionRow('console_page.data'),
|
||||||
|
_DataRow('${log.data}'),
|
||||||
|
],
|
||||||
|
(final RestApiResponseConsoleLog log) => [
|
||||||
|
if (log.method != null) _KeyValueRow('method', '${log.method}'),
|
||||||
|
if (log.uri != null) _KeyValueRow('uri', '${log.uri}'),
|
||||||
|
if (log.statusCode != null)
|
||||||
|
_KeyValueRow('statusCode', '${log.statusCode}'),
|
||||||
|
|
||||||
|
// data
|
||||||
|
const _SectionRow('console_page.response_data'),
|
||||||
|
_DataRow('${log.data}'),
|
||||||
|
],
|
||||||
|
(final GraphQlRequestConsoleLog log) => [
|
||||||
|
// // context
|
||||||
|
// if (log.context != null) ...[
|
||||||
|
// const _SectionRow('console_page.context'),
|
||||||
|
// _DataRow('${log.context}'),
|
||||||
|
// ],
|
||||||
|
|
||||||
|
const _SectionRow('console_page.operation'),
|
||||||
|
if (log.operation != null) ...[
|
||||||
|
_KeyValueRow(
|
||||||
|
'console_page.operation_type'.tr(),
|
||||||
|
log.operationType,
|
||||||
|
),
|
||||||
|
_KeyValueRow(
|
||||||
|
'console_page.operation_name'.tr(),
|
||||||
|
log.operation?.operationName,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
// data
|
||||||
|
_DataRow(log.operationDocument),
|
||||||
|
],
|
||||||
|
// preset variables
|
||||||
|
if (log.variables?.isNotEmpty ?? false) ...[
|
||||||
|
const _SectionRow('console_page.variables'),
|
||||||
|
for (final entry in log.variables!.entries)
|
||||||
|
_KeyValueRow(entry.key, '${entry.value}'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
(final GraphQlResponseConsoleLog log) => [
|
||||||
|
// // context
|
||||||
|
// const _SectionRow('console_page.context'),
|
||||||
|
// _DataRow('${log.context}'),
|
||||||
|
// data
|
||||||
|
if (log.data != null) ...[
|
||||||
|
const _SectionRow('console_page.data'),
|
||||||
|
for (final entry in log.data!.entries)
|
||||||
|
_KeyValueRow(entry.key, '${entry.value}'),
|
||||||
|
],
|
||||||
|
// errors
|
||||||
|
if (log.errors?.isNotEmpty ?? false) ...[
|
||||||
|
const _SectionRow('console_page.errors'),
|
||||||
|
for (final entry in log.errors!) ...[
|
||||||
|
_KeyValueRow(
|
||||||
|
'${'console_page.error_message'.tr()}: ',
|
||||||
|
entry.message,
|
||||||
|
),
|
||||||
|
_KeyValueRow(
|
||||||
|
'${'console_page.error_path'.tr()}: ',
|
||||||
|
'${entry.path}',
|
||||||
|
),
|
||||||
|
if (entry.locations?.isNotEmpty ?? false)
|
||||||
|
_KeyValueRow(
|
||||||
|
'${'console_page.error_locations'.tr()}: ',
|
||||||
|
'${entry.locations}',
|
||||||
|
),
|
||||||
|
if (entry.extensions?.isNotEmpty ?? false)
|
||||||
|
_KeyValueRow(
|
||||||
|
'${'console_page.error_extensions'.tr()}: ',
|
||||||
|
'${entry.extensions}',
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
(final ManualConsoleLog log) => [
|
||||||
|
_DataRow(log.content),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dialog with detailed log content
|
||||||
|
class ConsoleItemDialog extends StatelessWidget {
|
||||||
|
const ConsoleItemDialog({
|
||||||
|
required this.log,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ConsoleLog log;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => AlertDialog(
|
||||||
|
scrollable: true,
|
||||||
|
title: Text(log.title),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: SelectableText.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: <TextSpan>[
|
||||||
|
TextSpan(
|
||||||
|
text: '${'console_page.logged_at'.tr()}: ',
|
||||||
|
style: const TextStyle(),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '${log.timeString} (${log.fullUTCString})',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontFeatures: [FontFeature.tabularFigures()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
...log.unwrapContent(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
if (log is LogWithRawResponse)
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => PlatformAdapter.setClipboard(
|
||||||
|
(log as LogWithRawResponse).rawResponse,
|
||||||
|
),
|
||||||
|
child: Text('console_page.copy_raw'.tr()),
|
||||||
|
),
|
||||||
|
// A button to copy the request to the clipboard
|
||||||
|
if (log.shareableData?.isNotEmpty ?? false)
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => PlatformAdapter.setClipboard(log.shareableData!),
|
||||||
|
child: Text('console_page.copy'.tr()),
|
||||||
|
),
|
||||||
|
// close dialog
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: Text('basis.close'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// different sections delimiter with `title`
|
||||||
|
class _SectionRow extends StatelessWidget {
|
||||||
|
const _SectionRow(this.title);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outlineVariant,
|
||||||
|
width: 2.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SelectableText(
|
||||||
|
title.tr(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// data row with a {key: value} pair
|
||||||
|
class _KeyValueRow extends StatelessWidget {
|
||||||
|
const _KeyValueRow(this.title, this.value);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String? value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: SelectableText.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: <TextSpan>[
|
||||||
|
TextSpan(
|
||||||
|
text: '$title: ',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(text: value ?? ''),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// data row with only text
|
||||||
|
class _DataRow extends StatelessWidget {
|
||||||
|
const _DataRow(this.data);
|
||||||
|
|
||||||
|
final String? data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: SelectableText(
|
||||||
|
data ?? 'null',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/more/console/console_log_item_dialog.dart';
|
||||||
|
|
||||||
|
extension on ConsoleLog {
|
||||||
|
Color resolveColor(final BuildContext context) => isError
|
||||||
|
? Theme.of(context).colorScheme.error
|
||||||
|
: switch (this) {
|
||||||
|
(final RestApiRequestConsoleLog _) =>
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
(final RestApiResponseConsoleLog _) =>
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
|
(final GraphQlRequestConsoleLog _) =>
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
(final GraphQlResponseConsoleLog _) =>
|
||||||
|
Theme.of(context).colorScheme.tertiary,
|
||||||
|
(final ManualConsoleLog _) => Theme.of(context).colorScheme.tertiary,
|
||||||
|
};
|
||||||
|
|
||||||
|
IconData resolveIcon() => switch (this) {
|
||||||
|
(final RestApiRequestConsoleLog _) => Icons.upload_outlined,
|
||||||
|
(final RestApiResponseConsoleLog _) => Icons.download_outlined,
|
||||||
|
(final GraphQlRequestConsoleLog _) => Icons.arrow_circle_up_outlined,
|
||||||
|
(final GraphQlResponseConsoleLog _) => Icons.arrow_circle_down_outlined,
|
||||||
|
(final ManualConsoleLog _) => Icons.read_more_outlined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleLogItemWidget extends StatelessWidget {
|
||||||
|
const ConsoleLogItemWidget({
|
||||||
|
required this.log,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ConsoleLog log;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: <TextSpan>[
|
||||||
|
TextSpan(
|
||||||
|
text: '${log.timeString}: ',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFeatures: [FontFeature.tabularFigures()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: log.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
log.content,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
leading: Icon(log.resolveIcon()),
|
||||||
|
iconColor: log.resolveColor(context),
|
||||||
|
onTap: () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (final BuildContext context) =>
|
||||||
|
ConsoleItemDialog(log: log),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/more/console/console_log_item_widget.dart';
|
||||||
|
|
||||||
|
/// listing with 500 latest app operations.
|
||||||
|
@RoutePage()
|
||||||
|
class ConsolePage extends StatefulWidget {
|
||||||
|
const ConsolePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ConsolePage> createState() => _ConsolePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsolePageState extends State<ConsolePage> {
|
||||||
|
ConsoleModel get console => getIt<ConsoleModel>();
|
||||||
|
|
||||||
|
/// should freeze logs state to properly read logs
|
||||||
|
late final Future<void> future;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
future = getIt.allReady();
|
||||||
|
console.addListener(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
console.removeListener(update);
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() {
|
||||||
|
/// listener update could come at any time, like when widget is already
|
||||||
|
/// unmounted or during frame build, adding as postframe callback ensures
|
||||||
|
/// that element is marked for rebuild
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((final _) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => SafeArea(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('console_page.title'.tr()),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
console.paused
|
||||||
|
? Icons.play_arrow_outlined
|
||||||
|
: Icons.pause_outlined,
|
||||||
|
),
|
||||||
|
onPressed: console.paused ? console.play : console.pause,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Scrollbar(
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: future,
|
||||||
|
builder: (
|
||||||
|
final BuildContext context,
|
||||||
|
final AsyncSnapshot<void> snapshot,
|
||||||
|
) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
final List<ConsoleLog> logs = console.logs;
|
||||||
|
|
||||||
|
return logs.isEmpty
|
||||||
|
? const _ConsoleViewEmpty()
|
||||||
|
: _ConsoleViewLoaded(logs: logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const _ConsoleViewLoading();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsoleViewLoading extends StatelessWidget {
|
||||||
|
const _ConsoleViewLoading();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text('console_page.waiting'.tr()),
|
||||||
|
const Gap(16),
|
||||||
|
const Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsoleViewEmpty extends StatelessWidget {
|
||||||
|
const _ConsoleViewEmpty();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: Text('console_page.historyEmpty'.tr()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsoleViewLoaded extends StatelessWidget {
|
||||||
|
const _ConsoleViewLoaded({required this.logs});
|
||||||
|
|
||||||
|
final List<ConsoleLog> logs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => ListView.separated(
|
||||||
|
primary: true,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
itemCount: logs.length,
|
||||||
|
itemBuilder: (final BuildContext context, final int index) {
|
||||||
|
final log = logs[logs.length - 1 - index];
|
||||||
|
|
||||||
|
return ConsoleLogItemWidget(
|
||||||
|
key: ValueKey(log),
|
||||||
|
log: log,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (final context, final _) => const Divider(),
|
||||||
|
);
|
||||||
|
}
|
|
@ -21,11 +21,8 @@ class MorePage extends StatelessWidget {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: Breakpoints.small.isActive(context)
|
appBar: Breakpoints.small.isActive(context)
|
||||||
? PreferredSize(
|
? BrandHeader(
|
||||||
preferredSize: const Size.fromHeight(52),
|
title: 'basis.more'.tr(),
|
||||||
child: BrandHeader(
|
|
||||||
title: 'basis.more'.tr(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/views/views.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/views/views.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
),
|
),
|
||||||
OnboardingSecondView(
|
OnboardingSecondView(
|
||||||
onProceed: () {
|
onProceed: () {
|
||||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
InheritedAppController.of(context)
|
||||||
|
.setShouldShowOnboarding(false);
|
||||||
context.router.replaceAll([
|
context.router.replaceAll([
|
||||||
const RootRoute(),
|
const RootRoute(),
|
||||||
const InitializingRoute(),
|
const InitializingRoute(),
|
||||||
|
|
|
@ -65,11 +65,8 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: Breakpoints.small.isActive(context)
|
appBar: Breakpoints.small.isActive(context)
|
||||||
? PreferredSize(
|
? BrandHeader(
|
||||||
preferredSize: const Size.fromHeight(52),
|
title: 'basis.providers_title'.tr(),
|
||||||
child: BrandHeader(
|
|
||||||
title: 'basis.providers_title'.tr(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/root_scaffold_with_navigation.dart';
|
import 'package:selfprivacy/ui/layouts/root_scaffold_with_subroute_selector/root_scaffold_with_subroute_selector.dart';
|
||||||
import 'package:selfprivacy/ui/router/root_destinations.dart';
|
import 'package:selfprivacy/ui/router/root_destinations.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
||||||
|
@ -19,31 +18,31 @@ class RootPage extends StatefulWidget implements AutoRouteWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
||||||
bool shouldUseSplitView() => false;
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
if (InheritedAppController.of(context).shouldShowOnboarding) {
|
||||||
|
context.router.replace(const OnboardingRoute());
|
||||||
|
}
|
||||||
|
|
||||||
final destinations = rootDestinations;
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||||
is ServerInstallationFinished;
|
is ServerInstallationFinished;
|
||||||
|
|
||||||
if (context.read<AppSettingsCubit>().state.isOnboardingShowing) {
|
|
||||||
context.router.replace(const OnboardingRoute());
|
|
||||||
}
|
|
||||||
|
|
||||||
return AutoRouter(
|
return AutoRouter(
|
||||||
builder: (final context, final child) {
|
builder: (final context, final child) {
|
||||||
final currentDestinationIndex = destinations.indexWhere(
|
final currentDestinationIndex = rootDestinations.indexWhere(
|
||||||
(final destination) =>
|
(final destination) =>
|
||||||
context.router.isRouteActive(destination.route.routeName),
|
context.router.isRouteActive(destination.route.routeName),
|
||||||
);
|
);
|
||||||
final isOtherRouterActive =
|
final isOtherRouterActive =
|
||||||
context.router.root.current.name != RootRoute.name;
|
context.router.root.current.name != RootRoute.name;
|
||||||
final routeName = getRouteTitle(context.router.current.name).tr();
|
|
||||||
return RootScaffoldWithNavigation(
|
return RootScaffoldWithSubrouteSelector(
|
||||||
title: routeName,
|
destinations: rootDestinations,
|
||||||
destinations: destinations,
|
|
||||||
showBottomBar:
|
showBottomBar:
|
||||||
!(currentDestinationIndex == -1 && !isOtherRouterActive),
|
!(currentDestinationIndex == -1 && !isOtherRouterActive),
|
||||||
showFab: isReady,
|
showFab: isReady,
|
||||||
|
@ -53,99 +52,3 @@ class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainScreenNavigationRail extends StatelessWidget {
|
|
||||||
const MainScreenNavigationRail({
|
|
||||||
required this.destinations,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<RouteDestination> destinations;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
int? activeIndex = destinations.indexWhere(
|
|
||||||
(final destination) =>
|
|
||||||
context.router.isRouteActive(destination.route.routeName),
|
|
||||||
);
|
|
||||||
if (activeIndex == -1) {
|
|
||||||
activeIndex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
width: 72,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (final context, final constraints) => SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: NavigationRail(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
labelType: NavigationRailLabelType.all,
|
|
||||||
destinations: destinations
|
|
||||||
.map(
|
|
||||||
(final destination) => NavigationRailDestination(
|
|
||||||
icon: Icon(destination.icon),
|
|
||||||
label: Text(destination.label),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
selectedIndex: activeIndex,
|
|
||||||
onDestinationSelected: (final index) {
|
|
||||||
context.router.replaceAll([destinations[index].route]);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MainScreenNavigationDrawer extends StatelessWidget {
|
|
||||||
const MainScreenNavigationDrawer({
|
|
||||||
required this.destinations,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<RouteDestination> destinations;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
int? activeIndex = destinations.indexWhere(
|
|
||||||
(final destination) =>
|
|
||||||
context.router.isRouteActive(destination.route.routeName),
|
|
||||||
);
|
|
||||||
if (activeIndex == -1) {
|
|
||||||
activeIndex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
width: 296,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (final context, final constraints) => NavigationDrawer(
|
|
||||||
key: const Key('PrimaryNavigationDrawer'),
|
|
||||||
selectedIndex: activeIndex,
|
|
||||||
onDestinationSelected: (final index) {
|
|
||||||
context.router.replaceAll([destinations[index].route]);
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 18),
|
|
||||||
...destinations.map(
|
|
||||||
(final destination) => NavigationDrawerDestination(
|
|
||||||
icon: Icon(destination.icon),
|
|
||||||
label: Text(destination.label),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,11 +37,8 @@ class _ServicesPageState extends State<ServicesPage> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: Breakpoints.small.isActive(context)
|
appBar: Breakpoints.small.isActive(context)
|
||||||
? PreferredSize(
|
? BrandHeader(
|
||||||
preferredSize: const Size.fromHeight(52),
|
title: 'basis.services'.tr(),
|
||||||
child: BrandHeader(
|
|
||||||
title: 'basis.services'.tr(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
body: !isReady
|
body: !isReady
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
import 'package:selfprivacy/illustrations/stray_deer.dart';
|
import 'package:selfprivacy/illustrations/stray_deer.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/price.dart';
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
|
@ -205,10 +205,8 @@ class SelectTypePage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
painter: StrayDeerPainter(
|
painter: StrayDeerPainter(
|
||||||
colorScheme: Theme.of(context).colorScheme,
|
colorScheme: Theme.of(context).colorScheme,
|
||||||
colorPalette: context
|
colorPalette:
|
||||||
.read<AppSettingsCubit>()
|
InheritedAppController.of(context).corePalette,
|
||||||
.state
|
|
||||||
.corePaletteOrDefault,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -14,17 +14,16 @@ class NewUserPage extends StatelessWidget {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (final BuildContext context) {
|
create: (final BuildContext context) {
|
||||||
final jobCubit = context.read<JobsCubit>();
|
final jobCubit = context.read<JobsCubit>();
|
||||||
final jobState = jobCubit.state;
|
|
||||||
final users = <User>[];
|
// final jobsState = jobCubit.state;
|
||||||
users.addAll(context.read<UsersBloc>().state.users);
|
// final users = <User>[
|
||||||
if (jobState is JobsStateWithJobs) {
|
// ...context.read<UsersBloc>().state.users,
|
||||||
final jobs = jobState.clientJobList;
|
// if (jobsState is JobsStateWithJobs)
|
||||||
for (final job in jobs) {
|
// ...jobsState.clientJobList
|
||||||
if (job is CreateUserJob) {
|
// .whereType<CreateUserJob>()
|
||||||
users.add(job.user);
|
// .map((final job) => job.user),
|
||||||
}
|
// ];
|
||||||
NaiJi
commented
Review
If we don't need the block of code, why not just delete it though If we don't need the block of code, why not just delete it though
misterfourtytwo
commented
Review
it's simplified logic for filtering jobs with creation of the same(by name) user, which should be added later, but out of scope of changes now it's simplified logic for filtering jobs with creation of the same(by name) user, which should be added later, but out of scope of changes now
|
|||||||
}
|
|
||||||
}
|
|
||||||
return UserFormCubit(
|
return UserFormCubit(
|
||||||
jobsCubit: jobCubit,
|
jobsCubit: jobCubit,
|
||||||
fieldFactory: FieldCubitFactory(context),
|
fieldFactory: FieldCubitFactory(context),
|
||||||
|
|
|
@ -129,11 +129,8 @@ class UsersPage extends StatelessWidget {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: Breakpoints.small.isActive(context)
|
appBar: Breakpoints.small.isActive(context)
|
||||||
? PreferredSize(
|
? BrandHeader(
|
||||||
preferredSize: const Size.fromHeight(52),
|
title: 'basis.users'.tr(),
|
||||||
child: BrandHeader(
|
|
||||||
title: 'basis.users'.tr(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
body: child,
|
body: child,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
@ -18,29 +17,29 @@ class RouteDestination {
|
||||||
final String title;
|
final String title;
|
||||||
}
|
}
|
||||||
|
|
||||||
final rootDestinations = [
|
const List<RouteDestination> rootDestinations = [
|
||||||
RouteDestination(
|
RouteDestination(
|
||||||
route: const ProvidersRoute(),
|
route: ProvidersRoute(),
|
||||||
icon: BrandIcons.server,
|
icon: BrandIcons.server,
|
||||||
label: 'basis.providers'.tr(),
|
label: 'basis.providers',
|
||||||
title: 'basis.providers_title'.tr(),
|
title: 'basis.providers_title',
|
||||||
),
|
),
|
||||||
RouteDestination(
|
RouteDestination(
|
||||||
route: const ServicesRoute(),
|
route: ServicesRoute(),
|
||||||
icon: BrandIcons.box,
|
icon: BrandIcons.box,
|
||||||
label: 'basis.services'.tr(),
|
label: 'basis.services',
|
||||||
title: 'basis.services'.tr(),
|
title: 'basis.services',
|
||||||
),
|
),
|
||||||
RouteDestination(
|
RouteDestination(
|
||||||
route: const UsersRoute(),
|
route: UsersRoute(),
|
||||||
icon: BrandIcons.users,
|
icon: BrandIcons.users,
|
||||||
label: 'basis.users'.tr(),
|
label: 'basis.users',
|
||||||
title: 'basis.users'.tr(),
|
title: 'basis.users',
|
||||||
),
|
),
|
||||||
RouteDestination(
|
RouteDestination(
|
||||||
route: const MoreRoute(),
|
route: MoreRoute(),
|
||||||
icon: Icons.menu_rounded,
|
icon: Icons.menu_rounded,
|
||||||
label: 'basis.more'.tr(),
|
label: 'basis.more',
|
||||||
title: 'basis.more'.tr(),
|
title: 'basis.more',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
|
@ -10,7 +10,7 @@ import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/about_application.dart';
|
import 'package:selfprivacy/ui/pages/more/about_application.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/app_settings/app_settings.dart';
|
import 'package:selfprivacy/ui/pages/more/app_settings/app_settings.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/app_settings/developer_settings.dart';
|
import 'package:selfprivacy/ui/pages/more/app_settings/developer_settings.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/console.dart';
|
import 'package:selfprivacy/ui/pages/more/console/console_page.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/more.dart';
|
import 'package:selfprivacy/ui/pages/more/more.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
||||||
|
@ -53,6 +53,7 @@ class RootRouter extends _$RootRouter {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RouteType get defaultRouteType => const RouteType.material();
|
RouteType get defaultRouteType => const RouteType.material();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final List<AutoRoute> routes = [
|
final List<AutoRoute> routes = [
|
||||||
AutoRoute(page: OnboardingRoute.page),
|
AutoRoute(page: OnboardingRoute.page),
|
||||||
|
|
|
@ -9,7 +9,7 @@ import connectivity_plus
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
import dynamic_color
|
import dynamic_color
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import package_info
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
@ -19,7 +19,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|
|
@ -9,7 +9,7 @@ PODS:
|
||||||
- flutter_secure_storage_macos (6.1.1):
|
- flutter_secure_storage_macos (6.1.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- package_info (0.0.1):
|
- package_info_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
@ -27,7 +27,7 @@ DEPENDENCIES:
|
||||||
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
|
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
|
||||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`)
|
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
@ -47,8 +47,8 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
package_info:
|
package_info_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
|
@ -58,16 +58,16 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
|
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
|
||||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
||||||
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
||||||
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2
|
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
||||||
|
|
||||||
PODFILE CHECKSUM: b0cc1fdf1eda0fefb5163971bbf18550427d02c4
|
PODFILE CHECKSUM: b0cc1fdf1eda0fefb5163971bbf18550427d02c4
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.15.1
|
||||||
|
|
|
@ -202,7 +202,7 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0920;
|
LastSwiftUpdateCheck = 0920;
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
33CC10EC2044A3C60003C045 = {
|
33CC10EC2044A3C60003C045 = {
|
||||||
|
@ -419,6 +419,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = 46723VZHWZ;
|
DEVELOPMENT_TEAM = 46723VZHWZ;
|
||||||
|
@ -550,9 +551,10 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = 46723VZHWZ;
|
DEVELOPMENT_TEAM = 7SWL2X7X4N;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SelfPrivacy;
|
INFOPLIST_KEY_CFBundleDisplayName = SelfPrivacy;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
|
@ -562,6 +564,7 @@
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
MARKETING_VERSION = 0.8.0;
|
MARKETING_VERSION = 0.8.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.misterfourtytwo.selfprivacy;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -575,6 +578,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = 46723VZHWZ;
|
DEVELOPMENT_TEAM = 46723VZHWZ;
|
||||||
|
|
Translations directly to the JSON files might cause conflicts with Weblate. It is better to use weblate.selfprivacy.org in the future if you want to change a translation.
Changing
en.json
is fine, as this is a source for all translations.I guess it is about already existing keys, like, for when ill want to add translation for a new key, it will be ok?