mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-23 09:16:54 +00:00
Merge branch 'master' into inex/april-refactor
# Conflicts: # lib/config/get_it_config.dart # lib/config/hive_config.dart # lib/logic/api_maps/graphql_maps/graphql_api_map.dart # lib/logic/cubit/server_installation/server_installation_repository.dart # lib/logic/cubit/server_installation/server_installation_state.dart # lib/logic/get_it/api_config.dart
This commit is contained in:
commit
4ce7b0bcdb
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -40,3 +40,9 @@ app.*.symbols
|
|||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Flatpak
|
||||
.flatpak-builder/
|
||||
flatpak-build/
|
||||
flatpak-repo/
|
||||
*.flatpak
|
||||
|
|
53
.vscode/launch.json
vendored
Normal file
53
.vscode/launch.json
vendored
Normal file
|
@ -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 localPropertiesFile = rootProject.file('local.properties')
|
||||
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')
|
||||
if (flutterVersionCode == null) {
|
||||
|
@ -21,14 +24,9 @@ if (flutterVersionName == null) {
|
|||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
namespace 'org.selfprivacy.app'
|
||||
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
namespace 'org.selfprivacy.app'
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
sourceSets {
|
||||
|
@ -43,13 +41,16 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "org.selfprivacy.app"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
|
@ -57,31 +58,33 @@ android {
|
|||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
|
||||
flavorDimensions "default"
|
||||
productFlavors {
|
||||
fdroid {
|
||||
applicationId "pro.kherel.selfprivacy"
|
||||
}
|
||||
production {
|
||||
applicationIdSuffix ""
|
||||
profile {
|
||||
|
||||
}
|
||||
nightly {
|
||||
applicationIdSuffix ".nightly"
|
||||
versionCode project.getVersionCode()
|
||||
versionName "nightly-" + project.getVersionCode()
|
||||
release {
|
||||
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
flavorDimensions = ["default"]
|
||||
}
|
||||
|
||||
|
||||
flavorDimensions "default"
|
||||
productFlavors {
|
||||
fdroid {
|
||||
dimension 'default'
|
||||
applicationId "pro.kherel.selfprivacy"
|
||||
}
|
||||
production {
|
||||
applicationIdSuffix ""
|
||||
dimension 'default'
|
||||
}
|
||||
nightly {
|
||||
dimension 'default'
|
||||
applicationIdSuffix ".nightly"
|
||||
versionCode project.getVersionCode()
|
||||
versionName "nightly-" + project.getVersionCode()
|
||||
|
@ -93,6 +96,5 @@ flutter {
|
|||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
dependencies {}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.9.21'
|
||||
ext.getVersionCode = { ->
|
||||
ext.getVersionCode = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
|
@ -13,15 +12,6 @@ buildscript {
|
|||
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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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")
|
||||
def properties = new Properties()
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.9.21" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
|
||||
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
|
||||
|
||||
```sh
|
||||
cat /etc/nixos/userdata/tokens.json
|
||||
sp-print-api-token
|
||||
```
|
||||
|
||||
This file will have a similar construction:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "token_to_copy",
|
||||
"name": "device_name",
|
||||
"date": "date"
|
||||
}
|
||||
```
|
||||
|
||||
Copy the token from the file and paste it in the next window.
|
||||
Copy the token from the terminal and paste it in the next window.
|
||||
|
|
|
@ -333,14 +333,12 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"title": "إعدادات التطبيق",
|
||||
"system_dark_theme_title": "الوضع الافتراضي للنظام",
|
||||
"system_dark_theme_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
|
||||
"system_theme_mode_title": "الوضع الافتراضي للنظام",
|
||||
"system_theme_mode_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
|
||||
"dark_theme_title": "الوضع الداكن",
|
||||
"change_application_theme": "قم بتبديل وضع التطبيق",
|
||||
"dangerous_settings": "إعدادات خطرة",
|
||||
"reset_config_title": "قم بإعادة ضبط إعدادات التطبيق",
|
||||
"delete_server_title": "قم بحذف الخادم",
|
||||
"delete_server_description": "سيزيل هذا الخادم الخاص بك، حيث أنه لن تتمكن من الوصول إليه بعد ذلك.",
|
||||
"dark_theme_description": "قم بتبديل وضع التطبيق",
|
||||
"reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز."
|
||||
},
|
||||
"ssh": {
|
||||
|
|
|
@ -54,15 +54,15 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"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",
|
||||
"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_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"
|
||||
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın."
|
||||
|
||||
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH açarları",
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"connect_to_server_provider": "Аўтарызавацца ў ",
|
||||
"connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер",
|
||||
"steps": {
|
||||
"nixos_installation": "Ўстаноўка NixOS",
|
||||
"nixos_installation": "Ўсталёўка NixOS",
|
||||
"hosting": "Хостынг",
|
||||
"server_type": "Тып сервера",
|
||||
"dns_provider": "DNS правайдэр",
|
||||
|
@ -59,7 +59,7 @@
|
|||
"domain": "Дамен",
|
||||
"master_account": "Майстар акаўнт",
|
||||
"server": "Сервер",
|
||||
"dns_setup": "Устаноўка DNS",
|
||||
"dns_setup": "Усталёўка DNS",
|
||||
"server_reboot": "Перазагрузка сервера",
|
||||
"final_checks": "Фінальныя праверкі"
|
||||
},
|
||||
|
@ -100,7 +100,7 @@
|
|||
"modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен",
|
||||
"modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу",
|
||||
"fallback_select_provider_console": "Доступ да кансолі хостынгу.",
|
||||
"provider_connected_description": "Сувязь устаноўлена. Увядзіце свой токен з доступам да {}:",
|
||||
"provider_connected_description": "Сувязь наладжана. Увядзіце свой токен з доступам да {}:",
|
||||
"choose_server": "Выберыце сервер",
|
||||
"no_servers": "На вашым акаўнце няма даступных сэрвэраў.",
|
||||
"modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.",
|
||||
|
@ -114,7 +114,7 @@
|
|||
"authorize_new_device": "Аўтарызаваць новую прыладу",
|
||||
"access_granted_on": "Доступ выдадзены {}",
|
||||
"tip": "Націсніце на прыладу, каб адклікаць доступ.",
|
||||
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыкладанне SelfPrivacy."
|
||||
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыладу SelfPrivacy."
|
||||
},
|
||||
"add_new_device_screen": {
|
||||
"description": "Увядзіце гэты ключ на новай прыладзе:",
|
||||
|
@ -127,7 +127,7 @@
|
|||
"revoke_device_alert": {
|
||||
"header": "Адклікаць доступ?",
|
||||
"yes": "Адклікаць",
|
||||
"no": "Адменіць",
|
||||
"no": "Адхіліць",
|
||||
"description": "Прылада {} больш не зможа кіраваць серверам."
|
||||
}
|
||||
},
|
||||
|
@ -143,7 +143,7 @@
|
|||
"later": "Прапусціць і наладзіць потым",
|
||||
"no_data": "Няма дадзеных",
|
||||
"services": "Сэрвісы",
|
||||
"users": "Ужыткоўнікі",
|
||||
"users": "Карыстальнікі",
|
||||
"more": "Дадаткова",
|
||||
"got_it": "Зразумеў",
|
||||
"settings": "Налады",
|
||||
|
@ -234,7 +234,7 @@
|
|||
},
|
||||
"more_page": {
|
||||
"configuration_wizard": "Майстар наладкі",
|
||||
"onboarding": "Прівітанне",
|
||||
"onboarding": "Прывітанне",
|
||||
"create_ssh_key": "SSH ключы адміністратара"
|
||||
},
|
||||
"about_application_page": {
|
||||
|
@ -244,16 +244,16 @@
|
|||
"privacy_policy": "Палітыка прыватнасці"
|
||||
},
|
||||
"application_settings": {
|
||||
"reset_config_description": "Скінуць API ключы i суперкарыстальніка.",
|
||||
"delete_server_description": "Дзеянне прывядзе да выдалення сервера. Пасля гэтага ён будзе недаступны.",
|
||||
"title": "Налады праграмы",
|
||||
"system_theme_mode_title": "Сістэмная тэма па-змаўчанні",
|
||||
"system_theme_mode_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
|
||||
"dark_theme_title": "Цёмная тэма",
|
||||
"dark_theme_description": "Змяніць каляровую тэму",
|
||||
"change_application_theme": "Змяніць каляровую тэму",
|
||||
"language": "Мова",
|
||||
"click_to_change_locale": "Націсніце, каб адчыніць меню выбару мовы",
|
||||
"dangerous_settings": "Небяспечныя налады",
|
||||
"reset_config_title": "Скід налад",
|
||||
"delete_server_title": "Выдаліць сервер",
|
||||
"system_dark_theme_title": "Сістэмная тэма па-змаўчанні",
|
||||
"system_dark_theme_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
|
||||
"dangerous_settings": "Небяспечныя наладкі"
|
||||
"reset_config_description": "Скінуць API ключы i суперкарыстальніка."
|
||||
},
|
||||
"ssh": {
|
||||
"root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.",
|
||||
|
|
|
@ -54,15 +54,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"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",
|
||||
"change_application_theme": "Přepnutí tématu aplikace",
|
||||
"dangerous_settings": "Nebezpečná nastavení",
|
||||
"reset_config_title": "Obnovení konfigurace aplikace",
|
||||
"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í"
|
||||
"reset_config_description": "Obnovení klíčů API a uživatele root."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "Klíče SSH",
|
||||
|
|
|
@ -57,15 +57,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"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_description": "Ihr Anwendungsdesign wechseln",
|
||||
"change_application_theme": "Ihr Anwendungsdesign wechseln",
|
||||
"dangerous_settings": "Gefährliche Einstellungen",
|
||||
"reset_config_title": "Anwendungseinstellungen 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"
|
||||
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH Schlüssel",
|
||||
|
|
|
@ -47,7 +47,29 @@
|
|||
"console_page": {
|
||||
"title": "Console",
|
||||
"waiting": "Waiting for initialization…",
|
||||
"copy": "Copy"
|
||||
"copy": "Copy",
|
||||
"copy_raw": "Raw response",
|
||||
"history_empty": "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": {
|
||||
"title": "About & support",
|
||||
|
@ -75,10 +97,12 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"title": "Application settings",
|
||||
"system_dark_theme_title": "System default theme",
|
||||
"system_dark_theme_description": "Use light or dark theme depending on system settings",
|
||||
"system_theme_mode_title": "System default theme",
|
||||
"system_theme_mode_description": "Use light or dark theme depending on system settings",
|
||||
"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",
|
||||
"reset_config_title": "Reset application config",
|
||||
"reset_config_description": "Resets API keys and root user."
|
||||
|
@ -565,6 +589,8 @@
|
|||
"upgrade_success": "Server upgrade started",
|
||||
"upgrade_failed": "Failed to upgrade server",
|
||||
"upgrade_server": "Upgrade server",
|
||||
"collect_nix_garbage": "Collect system garbage",
|
||||
"collect_nix_garbage_failed": "Failed to collect system garbage",
|
||||
"reboot_server": "Reboot server",
|
||||
"create_ssh_key": "Create SSH key for {}",
|
||||
"delete_ssh_key": "Delete SSH key for {}",
|
||||
|
@ -606,5 +632,16 @@
|
|||
"reset_onboarding": "Reset onboarding switch",
|
||||
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
|
||||
"cubit_statuses": "Cubit loading statuses"
|
||||
},
|
||||
"countries": {
|
||||
"germany": "Germany",
|
||||
"netherlands": "Netherlands",
|
||||
"singapore": "Singapore",
|
||||
"united_kingdom": "United Kingdom",
|
||||
"canada": "Canada",
|
||||
"india": "India",
|
||||
"australia": "Australia",
|
||||
"united_states": "United States",
|
||||
"finland": "Finland"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,16 +39,14 @@
|
|||
"test": "es-test",
|
||||
"locale": "es",
|
||||
"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",
|
||||
"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",
|
||||
"system_dark_theme_title": "Tema del sistema",
|
||||
"system_dark_theme_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
|
||||
"dangerous_settings": "Configuraciones peligrosas"
|
||||
"change_application_theme": "Cambia el tema de tu aplicación",
|
||||
"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": {
|
||||
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
{
|
||||
"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",
|
||||
"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_description": "Vaheta oma rakenduse teemat",
|
||||
"change_application_theme": "Vaheta oma rakenduse teemat",
|
||||
"dangerous_settings": "Ohtlikud seaded",
|
||||
"reset_config_title": "Lähtesta rakenduse konfiguratsioon",
|
||||
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja.",
|
||||
"delete_server_title": "Kustuta server"
|
||||
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja."
|
||||
},
|
||||
"server": {
|
||||
"reboot_after_upgrade": "Taaskäivita pärast värskendust",
|
||||
|
|
|
@ -56,15 +56,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"title": "Paramètres de l'application",
|
||||
"dark_theme_description": "Changer le thème de l'application",
|
||||
"reset_config_title": "Réinitialiser la configuration de l'application",
|
||||
"delete_server_title": "Supprimer le serveur",
|
||||
"delete_server_description": "Cela va supprimer votre serveur. Celui-ci ne sera plus accessible.",
|
||||
"system_theme_mode_title": "Thème par défaut du système",
|
||||
"system_theme_mode_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
|
||||
"dark_theme_title": "Thème sombre",
|
||||
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root.",
|
||||
"system_dark_theme_title": "Thème par défaut du système",
|
||||
"system_dark_theme_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
|
||||
"dangerous_settings": "Paramètres dangereux"
|
||||
"change_application_theme": "Changer le thème de l'application",
|
||||
"dangerous_settings": "Paramètres dangereux",
|
||||
"reset_config_title": "Réinitialiser la configuration de l'application",
|
||||
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "Clés SSH",
|
||||
|
|
|
@ -81,15 +81,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"title": "הגדרות יישום",
|
||||
"system_dark_theme_title": "ערכת העיצוב כברירת המחדל של המערכת",
|
||||
"system_dark_theme_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
|
||||
"system_theme_mode_title": "ערכת העיצוב כברירת המחדל של המערכת",
|
||||
"system_theme_mode_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
|
||||
"dark_theme_title": "ערכת עיצוב כהה",
|
||||
"dark_theme_description": "החלפת ערכת העיצוב של המערכת שלך",
|
||||
"change_application_theme": "החלפת ערכת העיצוב של המערכת שלך",
|
||||
"dangerous_settings": "הגדרות מסוכנות",
|
||||
"reset_config_title": "איפוס הגדרות היישומון",
|
||||
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל.",
|
||||
"delete_server_title": "מחיקת שרת",
|
||||
"delete_server_description": "מסיר את השרת שלך. הוא לא יהיה זמין עוד."
|
||||
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל."
|
||||
},
|
||||
"backup": {
|
||||
"create_new_select_heading": "לבחור מה לגבות",
|
||||
|
|
|
@ -91,16 +91,14 @@
|
|||
"bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз."
|
||||
},
|
||||
"application_settings": {
|
||||
"title": "Қосымша параметрлері",
|
||||
"system_theme_mode_title": "Системалық қараңғы тақырып",
|
||||
"system_theme_mode_description": "Системалық қараңғы тақырып сипаттамасы",
|
||||
"dark_theme_title": "Қараңғы тақырып",
|
||||
"change_application_theme": "Қараңғы тақырып сипаттамасы",
|
||||
"dangerous_settings": "Қауіпті параметрлер",
|
||||
"reset_config_title": "Конфигурацияны қалпына келтіру",
|
||||
"title": "Қосымша параметрлері",
|
||||
"system_dark_theme_title": "Системалық қараңғы тақырып",
|
||||
"system_dark_theme_description": "Системалық қараңғы тақырып сипаттамасы",
|
||||
"dark_theme_title": "Қараңғы тақырып",
|
||||
"dark_theme_description": "Қараңғы тақырып сипаттамасы",
|
||||
"delete_server_title": "Серверді жою",
|
||||
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы.",
|
||||
"delete_server_description": "Серверді жою сипаттамасы."
|
||||
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы."
|
||||
},
|
||||
"resource_chart": {
|
||||
"month": "Ай",
|
||||
|
|
|
@ -52,16 +52,14 @@
|
|||
"privacy_policy": "Privātuma politika"
|
||||
},
|
||||
"application_settings": {
|
||||
"system_dark_theme_title": "Sistēmas noklusējuma dizains",
|
||||
"dark_theme_title": "Tumšs dizains",
|
||||
"title": "Aplikācijas iestatījumi",
|
||||
"system_dark_theme_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
|
||||
"dark_theme_description": "Lietojumprogrammas dizaina pārslēgšana",
|
||||
"system_theme_mode_title": "Sistēmas noklusējuma dizains",
|
||||
"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",
|
||||
"reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju",
|
||||
"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."
|
||||
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju."
|
||||
},
|
||||
"locale": "lv",
|
||||
"ssh": {
|
||||
|
|
|
@ -56,15 +56,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"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_description": "Zmień kolor motywu aplikacji",
|
||||
"change_application_theme": "Zmień kolor motywu aplikacji",
|
||||
"dangerous_settings": "Niebezpieczne ustawienia",
|
||||
"reset_config_title": "Resetowanie",
|
||||
"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"
|
||||
"reset_config_description": "Zresetuj klucze API i użytkownika root."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "klucze SSH",
|
||||
|
|
|
@ -75,15 +75,15 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"title": "Настройки приложения",
|
||||
"system_theme_mode_title": "Системная тема",
|
||||
"system_theme_mode_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
|
||||
"dark_theme_title": "Тёмная тема",
|
||||
"dark_theme_description": "Сменить цветовую тему",
|
||||
"change_application_theme": "Сменить цветовую тему",
|
||||
"language": "Язык",
|
||||
"click_to_change_locale": "Нажмите, чтобы открыть список языков",
|
||||
"dangerous_settings": "Опасные настройки",
|
||||
"reset_config_title": "Сброс настроек",
|
||||
"reset_config_description": "Сбросить API ключи и root пользователя.",
|
||||
"delete_server_title": "Удалить сервер",
|
||||
"delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен.",
|
||||
"system_dark_theme_title": "Системная тема",
|
||||
"system_dark_theme_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
|
||||
"dangerous_settings": "Опасные настройки"
|
||||
"reset_config_description": "Сбросить API ключи и root пользователя."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH ключи",
|
||||
|
|
|
@ -103,15 +103,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"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_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_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"
|
||||
"reset_config_description": "Resetovať kľúče API a užívateľa root."
|
||||
},
|
||||
"ssh": {
|
||||
"title": "Kľúče SSH",
|
||||
|
|
|
@ -53,15 +53,13 @@
|
|||
"application_version_text": "Različica aplikacije"
|
||||
},
|
||||
"application_settings": {
|
||||
"dark_theme_title": "Temna tema",
|
||||
"title": "Nastavitve aplikacije",
|
||||
"system_dark_theme_title": "Privzeta tema sistema",
|
||||
"system_dark_theme_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
|
||||
"dark_theme_description": "Spreminjanje barvne teme",
|
||||
"system_theme_mode_title": "Privzeta tema sistema",
|
||||
"system_theme_mode_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
|
||||
"dark_theme_title": "Temna tema",
|
||||
"change_application_theme": "Spreminjanje barvne teme",
|
||||
"dangerous_settings": "Nevarne nastavitve",
|
||||
"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."
|
||||
"reset_config_title": "Ponastavitev konfiguracije aplikacije"
|
||||
},
|
||||
"onboarding": {
|
||||
"page1_title": "Digitalna neodvisnost je na voljo vsem",
|
||||
|
|
|
@ -47,13 +47,11 @@
|
|||
"privacy_policy": "นโยบายความเป็นส่วนตัว"
|
||||
},
|
||||
"application_settings": {
|
||||
"dark_theme_description": "สลับธีมแอปพลิเคชั่นของคุณ",
|
||||
"delete_server_description": "การกระทำนี้จะลบเซิฟเวอร์ของคุณทิ้งและคุณจะไม่สามารถเข้าถึงมันได้อีก",
|
||||
"title": "การตั้งค่าแอปพลิเคชัน",
|
||||
"dark_theme_title": "ธีมมืด",
|
||||
"change_application_theme": "สลับธีมแอปพลิเคชั่นของคุณ",
|
||||
"reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น",
|
||||
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root",
|
||||
"delete_server_title": "ลบเซิฟเวอร์"
|
||||
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root"
|
||||
},
|
||||
"ssh": {
|
||||
"create": "สร้างกุญแจ SSH",
|
||||
|
|
|
@ -41,15 +41,14 @@
|
|||
"locale": "ua",
|
||||
"application_settings": {
|
||||
"title": "Налаштування додатка",
|
||||
"reset_config_title": "Скинути налаштування",
|
||||
"system_theme_mode_title": "Системна тема за замовчуванням",
|
||||
"system_theme_mode_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
|
||||
"dark_theme_title": "Темна тема",
|
||||
"dark_theme_description": "Змінити тему додатка",
|
||||
"reset_config_description": "Скинути API ключі та root користувача.",
|
||||
"delete_server_title": "Видалити сервер",
|
||||
"delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний.",
|
||||
"system_dark_theme_title": "Системна тема за замовчуванням",
|
||||
"system_dark_theme_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
|
||||
"dangerous_settings": "Небезпечні налаштування"
|
||||
"change_application_theme": "Змінити тему додатка",
|
||||
"language": "Мова",
|
||||
"dangerous_settings": "Небезпечні налаштування",
|
||||
"reset_config_title": "Скинути налаштування",
|
||||
"reset_config_description": "Скинути API ключі та root користувача."
|
||||
},
|
||||
"ssh": {
|
||||
"delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?",
|
||||
|
|
|
@ -476,15 +476,13 @@
|
|||
},
|
||||
"application_settings": {
|
||||
"title": "应用设置",
|
||||
"system_dark_theme_title": "系统默认主题",
|
||||
"system_theme_mode_title": "系统默认主题",
|
||||
"system_theme_mode_description": "根据系统设置自动使用明亮或暗色主题",
|
||||
"dark_theme_title": "暗色主题",
|
||||
"system_dark_theme_description": "根据系统设置自动使用明亮或暗色主题",
|
||||
"dark_theme_description": "切换应用主题",
|
||||
"change_application_theme": "切换应用主题",
|
||||
"dangerous_settings": "危险设置",
|
||||
"reset_config_title": "重置应用配置",
|
||||
"delete_server_title": "删除服务器",
|
||||
"delete_server_description": "这将移除您的服务器。它将不再可以访问。",
|
||||
"reset_config_description": "重置API密钥和root用户。"
|
||||
"delete_server_title": "删除服务器"
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH密钥",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
app-id: org.selfprivacy.app
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '22.08'
|
||||
runtime-version: '23.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: selfprivacy
|
||||
finish-args:
|
||||
|
@ -11,6 +11,7 @@ finish-args:
|
|||
- "--share=network"
|
||||
- "--own-name=org.selfprivacy.app"
|
||||
- "--device=dri"
|
||||
- "--talk-name=org.freedesktop.secrets"
|
||||
modules:
|
||||
- name: selfprivacy
|
||||
buildsystem: simple
|
||||
|
@ -35,7 +36,7 @@ modules:
|
|||
sources:
|
||||
- type: git
|
||||
url: https://gitlab.gnome.org/GNOME/libsecret.git
|
||||
tag: 0.20.5
|
||||
tag: 0.21.4
|
||||
- name: libjsoncpp
|
||||
buildsystem: meson
|
||||
config-opts:
|
||||
|
|
175
lib/config/app_controller/app_controller.dart
Normal file
175
lib/config/app_controller/app_controller.dart
Normal file
|
@ -0,0 +1,175 @@
|
|||
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/localization.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;
|
||||
|
||||
late bool _shouldShowOnboarding;
|
||||
bool get shouldShowOnboarding => _shouldShowOnboarding;
|
||||
|
||||
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 = [
|
||||
Localization.systemLocale,
|
||||
...await _repo.getSupportedLocales(),
|
||||
];
|
||||
|
||||
_locale = await _repo.getActiveLocale();
|
||||
if (_locale != Localization.systemLocale) {
|
||||
// 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;
|
||||
_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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (newLocale == Localization.systemLocale) {
|
||||
return resetLocale();
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
Future<void> resetLocale() async {
|
||||
/// update locale delegate, which in turn should update deps
|
||||
await _repo.resetDelegateLocale();
|
||||
|
||||
// Persist the change
|
||||
await _repo.resetActiveLocale();
|
||||
// Update other locale holders
|
||||
await _apiConfigModel.resetLocaleCode();
|
||||
}
|
||||
}
|
106
lib/config/app_controller/inherited_app_controller.dart
Normal file
106
lib/config/app_controller/inherited_app_controller.dart
Normal file
|
@ -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_bloc/flutter_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/recovery_key/recovery_key_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/users/users_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/dns_records/dns_records_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
|
||||
Widget build(final BuildContext context) {
|
||||
const isDark = false;
|
||||
const isAutoDark = true;
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (final _) => AppSettingsCubit(
|
||||
isDarkModeOn: isDark,
|
||||
isAutoDarkModeOn: isAutoDark,
|
||||
isOnboardingShowing: true,
|
||||
)..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => supportSystemCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => usersBloc,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => devicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverJobsBloc,
|
||||
),
|
||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => serverDetailsCubit,
|
||||
),
|
||||
BlocProvider(create: (final _) => volumesBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => JobsCubit(),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
Widget build(final BuildContext context) => MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (final _) => supportSystemCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => usersBloc,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => devicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverJobsBloc,
|
||||
),
|
||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => serverDetailsCubit,
|
||||
),
|
||||
BlocProvider(create: (final _) => volumesBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => JobsCubit(),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
import 'package:get_it/get_it.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/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/resources_model.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/console.dart';
|
||||
export 'package:selfprivacy/logic/get_it/console_model.dart';
|
||||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
||||
Future<void> getItSetup() async {
|
||||
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||
|
||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||
getIt.registerSingleton<ResourcesModel>(ResourcesModel()..init());
|
||||
getIt.registerSingleton<WizardDataModel>(WizardDataModel()..init());
|
||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||
|
||||
final apiConfigModel = ApiConfigModel();
|
||||
await apiConfigModel.init();
|
||||
getIt.registerSingleton<ApiConfigModel>(apiConfigModel);
|
||||
|
||||
getIt.registerSingleton<ApiConnectionRepository>(
|
||||
ApiConnectionRepository()..init(),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||
|
@ -36,121 +36,136 @@ class HiveConfig {
|
|||
|
||||
await Hive.openBox(BNames.appSettingsBox);
|
||||
|
||||
final HiveAesCipher cipher = HiveAesCipher(
|
||||
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
||||
);
|
||||
try {
|
||||
final HiveAesCipher cipher = HiveAesCipher(
|
||||
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
||||
);
|
||||
|
||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||
await Hive.openBox(BNames.resourcesBox, encryptionCipher: cipher);
|
||||
await Hive.openBox(BNames.wizardDataBox, encryptionCipher: cipher);
|
||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||
await Hive.openBox(BNames.resourcesBox, encryptionCipher: cipher);
|
||||
await Hive.openBox(BNames.wizardDataBox, encryptionCipher: cipher);
|
||||
|
||||
final Box resourcesBox = Hive.box(BNames.resourcesBox);
|
||||
if (resourcesBox.isEmpty) {
|
||||
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||
final Box resourcesBox = Hive.box(BNames.resourcesBox);
|
||||
if (resourcesBox.isEmpty) {
|
||||
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||
|
||||
final String? serverProviderKey =
|
||||
serverInstallationBox.get(BNames.hetznerKey);
|
||||
final String? serverLocation =
|
||||
serverInstallationBox.get(BNames.serverLocation);
|
||||
final String? dnsProviderKey =
|
||||
serverInstallationBox.get(BNames.cloudFlareKey);
|
||||
final BackupsCredential? backblazeCredential =
|
||||
serverInstallationBox.get(BNames.backblazeCredential);
|
||||
final ServerDomain? serverDomain =
|
||||
serverInstallationBox.get(BNames.serverDomain);
|
||||
final ServerHostingDetails? serverDetails =
|
||||
serverInstallationBox.get(BNames.serverDetails);
|
||||
final BackblazeBucket? backblazeBucket =
|
||||
serverInstallationBox.get(BNames.backblazeBucket);
|
||||
final String? serverType =
|
||||
serverInstallationBox.get(BNames.serverTypeIdentifier);
|
||||
final ServerProviderType? serverProvider =
|
||||
serverInstallationBox.get(BNames.serverProvider);
|
||||
final DnsProviderType? dnsProvider =
|
||||
serverInstallationBox.get(BNames.dnsProvider);
|
||||
final String? serverProviderKey =
|
||||
serverInstallationBox.get(BNames.hetznerKey);
|
||||
final String? serverLocation =
|
||||
serverInstallationBox.get(BNames.serverLocation);
|
||||
final String? dnsProviderKey =
|
||||
serverInstallationBox.get(BNames.cloudFlareKey);
|
||||
final BackupsCredential? backblazeCredential =
|
||||
serverInstallationBox.get(BNames.backblazeCredential);
|
||||
final ServerDomain? serverDomain =
|
||||
serverInstallationBox.get(BNames.serverDomain);
|
||||
final ServerHostingDetails? serverDetails =
|
||||
serverInstallationBox.get(BNames.serverDetails);
|
||||
final BackblazeBucket? backblazeBucket =
|
||||
serverInstallationBox.get(BNames.backblazeBucket);
|
||||
final String? serverType =
|
||||
serverInstallationBox.get(BNames.serverTypeIdentifier);
|
||||
final ServerProviderType? serverProvider =
|
||||
serverInstallationBox.get(BNames.serverProvider);
|
||||
final DnsProviderType? dnsProvider =
|
||||
serverInstallationBox.get(BNames.dnsProvider);
|
||||
|
||||
if (serverProviderKey != null &&
|
||||
(serverProvider != null ||
|
||||
(serverDetails != null &&
|
||||
serverDetails.provider != ServerProviderType.unknown))) {
|
||||
final ServerProviderCredential serverProviderCredential =
|
||||
ServerProviderCredential(
|
||||
tokenId: null,
|
||||
token: serverProviderKey,
|
||||
provider: serverProvider ?? serverDetails!.provider,
|
||||
associatedServerIds: serverDetails != null ? [serverDetails.id] : [],
|
||||
);
|
||||
if (serverProviderKey != null &&
|
||||
(serverProvider != null ||
|
||||
(serverDetails != null &&
|
||||
serverDetails.provider != ServerProviderType.unknown))) {
|
||||
final ServerProviderCredential serverProviderCredential =
|
||||
ServerProviderCredential(
|
||||
tokenId: null,
|
||||
token: serverProviderKey,
|
||||
provider: serverProvider ?? serverDetails!.provider,
|
||||
associatedServerIds: serverDetails != null ? [serverDetails.id] : [],
|
||||
);
|
||||
|
||||
await resourcesBox
|
||||
.put(BNames.serverProviderTokens, [serverProviderCredential]);
|
||||
}
|
||||
await resourcesBox
|
||||
.put(BNames.serverProviderTokens, [serverProviderCredential]);
|
||||
}
|
||||
|
||||
if (dnsProviderKey != null &&
|
||||
(dnsProvider != null ||
|
||||
(serverDomain != null &&
|
||||
serverDomain.provider != DnsProviderType.unknown))) {
|
||||
final DnsProviderCredential dnsProviderCredential =
|
||||
DnsProviderCredential(
|
||||
tokenId: null,
|
||||
token: dnsProviderKey,
|
||||
provider: dnsProvider ?? serverDomain!.provider,
|
||||
associatedDomainNames:
|
||||
serverDomain != null ? [serverDomain.domainName] : [],
|
||||
);
|
||||
if (dnsProviderKey != null &&
|
||||
(dnsProvider != null ||
|
||||
(serverDomain != null &&
|
||||
serverDomain.provider != DnsProviderType.unknown))) {
|
||||
final DnsProviderCredential dnsProviderCredential =
|
||||
DnsProviderCredential(
|
||||
tokenId: null,
|
||||
token: dnsProviderKey,
|
||||
provider: dnsProvider ?? serverDomain!.provider,
|
||||
associatedDomainNames:
|
||||
serverDomain != null ? [serverDomain.domainName] : [],
|
||||
);
|
||||
|
||||
await resourcesBox
|
||||
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
|
||||
}
|
||||
await resourcesBox
|
||||
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
|
||||
}
|
||||
|
||||
if (backblazeCredential != null) {
|
||||
await resourcesBox
|
||||
.put(BNames.backupsProviderTokens, [backblazeCredential]);
|
||||
}
|
||||
if (backblazeCredential != null) {
|
||||
await resourcesBox
|
||||
.put(BNames.backupsProviderTokens, [backblazeCredential]);
|
||||
}
|
||||
|
||||
if (backblazeBucket != null) {
|
||||
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
|
||||
}
|
||||
if (backblazeBucket != null) {
|
||||
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
|
||||
}
|
||||
|
||||
if (serverDetails != null && serverDomain != null) {
|
||||
await resourcesBox.put(BNames.servers, [
|
||||
Server(
|
||||
domain: serverDomain,
|
||||
hostingDetails: serverDetails.copyWith(
|
||||
serverLocation: serverLocation,
|
||||
serverType: serverType,
|
||||
if (serverDetails != null && serverDomain != null) {
|
||||
await resourcesBox.put(BNames.servers, [
|
||||
Server(
|
||||
domain: serverDomain,
|
||||
hostingDetails: serverDetails.copyWith(
|
||||
serverLocation: serverLocation,
|
||||
serverType: serverType,
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
]);
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
print('HiveConfig: Error while opening boxes: $e');
|
||||
rethrow;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Future<Uint8List> getEncryptedKey(final String encKey) async {
|
||||
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
||||
final bool hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
||||
if (!hasEncryptionKey) {
|
||||
final List<int> key = Hive.generateSecureKey();
|
||||
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
||||
}
|
||||
try {
|
||||
final bool hasEncryptionKey =
|
||||
await secureStorage.containsKey(key: encKey);
|
||||
if (!hasEncryptionKey) {
|
||||
final List<int> key = Hive.generateSecureKey();
|
||||
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
||||
}
|
||||
|
||||
final String? string = await secureStorage.read(key: encKey);
|
||||
return base64Url.decode(string!);
|
||||
final String? string = await secureStorage.read(key: encKey);
|
||||
return base64Url.decode(string!);
|
||||
} on PlatformException catch (e) {
|
||||
print('HiveConfig: Error while getting encryption key: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mappings for the different boxes and their keys
|
||||
class BNames {
|
||||
/// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing]
|
||||
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
|
||||
static String appSettingsBox = 'appSettings';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isDarkModeOn = 'isDarkModeOn';
|
||||
static String darkThemeModeOn = 'isDarkModeOn';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
|
||||
static String systemThemeModeOn = 'isAutoDarkModeOn';
|
||||
|
||||
/// 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] box.
|
||||
static String serverInstallationEncryptionKey = 'key';
|
||||
|
|
|
@ -3,40 +3,76 @@ import 'package:flutter/material.dart';
|
|||
|
||||
class Localization extends StatelessWidget {
|
||||
const Localization({
|
||||
required this.child,
|
||||
super.key,
|
||||
this.child,
|
||||
});
|
||||
|
||||
final Widget? child;
|
||||
/// value for resetting locale in settings to system default
|
||||
static const systemLocale = Locale('system');
|
||||
|
||||
// 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 = {
|
||||
systemLocale: 'System default',
|
||||
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
|
||||
Widget build(final BuildContext context) => EasyLocalization(
|
||||
supportedLocales: const [
|
||||
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'),
|
||||
],
|
||||
supportedLocales: supportedLocales,
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: const Locale('en'),
|
||||
useFallbackTranslations: true,
|
||||
saveLocale: false,
|
||||
useOnlyLangCode: false,
|
||||
child: child!,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/// 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
|
||||
Future<void> setLocale(final String? newLocale);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
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 => newLocale == null
|
||||
? _appSettingsBox.delete(BNames.appLocale)
|
||||
: _appSettingsBox.put(BNames.appLocale, newLocale);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
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,
|
||||
resetDelegateLocale: EasyLocalization.of(context)!.resetLocale,
|
||||
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/localization.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,
|
||||
required this.resetDelegateLocale,
|
||||
});
|
||||
|
||||
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<void> Function() resetDelegateLocale;
|
||||
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<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 ??= Localization.systemLocale;
|
||||
|
||||
return chosenLocale;
|
||||
}
|
||||
|
||||
Future<void> setActiveLocale(final Locale newLocale) async {
|
||||
await dataSource.setLocale(newLocale.toString());
|
||||
}
|
||||
|
||||
Future<void> resetActiveLocale() async {
|
||||
await dataSource.setLocale(null);
|
||||
}
|
||||
|
||||
/// 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,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
|
@ -5,15 +6,10 @@ import 'package:http/io_client.dart';
|
|||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
||||
import 'package:selfprivacy/logic/get_it/resources_model.dart';
|
||||
import 'package:selfprivacy/logic/models/message.dart';
|
||||
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||
|
||||
void _logToAppConsole<T>(final T objectToLog) {
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
Message(
|
||||
text: objectToLog.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
void _addConsoleLog(final ConsoleLog message) =>
|
||||
getIt.get<ConsoleModel>().log(message);
|
||||
|
||||
class RequestLoggingLink extends Link {
|
||||
@override
|
||||
|
@ -21,13 +17,14 @@ class RequestLoggingLink extends Link {
|
|||
final Request request, [
|
||||
final NextLink? forward,
|
||||
]) async* {
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
GraphQlRequestMessage(
|
||||
operation: request.operation,
|
||||
variables: request.variables,
|
||||
context: request.context,
|
||||
),
|
||||
);
|
||||
_addConsoleLog(
|
||||
GraphQlRequestConsoleLog(
|
||||
// context: request.context,
|
||||
operationType: request.type.name,
|
||||
operation: request.operation,
|
||||
variables: request.variables,
|
||||
),
|
||||
);
|
||||
yield* forward!(request);
|
||||
}
|
||||
}
|
||||
|
@ -36,20 +33,26 @@ class ResponseLoggingParser extends ResponseParser {
|
|||
@override
|
||||
Response parseResponse(final Map<String, dynamic> body) {
|
||||
final response = super.parseResponse(body);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
GraphQlResponseMessage(
|
||||
data: response.data,
|
||||
errors: response.errors,
|
||||
context: response.context,
|
||||
),
|
||||
);
|
||||
_addConsoleLog(
|
||||
GraphQlResponseConsoleLog(
|
||||
// context: response.context,
|
||||
data: response.data,
|
||||
errors: response.errors,
|
||||
rawResponse: jsonEncode(response.response),
|
||||
),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
@override
|
||||
GraphQLError parseError(final Map<String, dynamic> error) {
|
||||
final graphQlError = super.parseError(error);
|
||||
_logToAppConsole(graphQlError);
|
||||
_addConsoleLog(
|
||||
ManualConsoleLog.warning(
|
||||
customTitle: 'GraphQL Error',
|
||||
content: graphQlError.toString(),
|
||||
),
|
||||
);
|
||||
return graphQlError;
|
||||
}
|
||||
}
|
||||
|
@ -114,14 +117,15 @@ abstract class GraphQLApiMap {
|
|||
);
|
||||
}
|
||||
|
||||
String get _locale => getIt.get<ApiConfigModel>().localeCode ?? 'en';
|
||||
String get _locale => getIt.get<ApiConfigModel>().localeCode;
|
||||
|
||||
String get _token {
|
||||
String token = '';
|
||||
final serverDetails = getIt<ResourcesModel>().serverDetails;
|
||||
if (serverDetails != null) {
|
||||
token = getIt<ResourcesModel>().serverDetails!.apiToken;
|
||||
token = serverDetails.apiToken;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
|
|
|
@ -443,6 +443,7 @@ type SystemMutations {
|
|||
runSystemUpgrade: GenericJobMutationReturn!
|
||||
rebootSystem: GenericMutationReturn!
|
||||
pullRepositoryChanges: GenericMutationReturn!
|
||||
nixCollectGarbage: GenericJobMutationReturn!
|
||||
}
|
||||
|
||||
type SystemProviderInfo {
|
||||
|
|
|
@ -79,6 +79,17 @@ mutation RunSystemUpgrade {
|
|||
}
|
||||
}
|
||||
|
||||
mutation NixCollectGarbage {
|
||||
system {
|
||||
nixCollectGarbage {
|
||||
...basicMutationReturnFields
|
||||
job {
|
||||
...basicApiJobsFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation RunSystemUpgradeFallback {
|
||||
system {
|
||||
runSystemUpgrade {
|
||||
|
|
|
@ -7043,6 +7043,663 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade<TRes>
|
|||
CopyWith$Fragment$basicApiJobsFields.stub(_res);
|
||||
}
|
||||
|
||||
class Mutation$NixCollectGarbage {
|
||||
Mutation$NixCollectGarbage({
|
||||
required this.system,
|
||||
this.$__typename = 'Mutation',
|
||||
});
|
||||
|
||||
factory Mutation$NixCollectGarbage.fromJson(Map<String, dynamic> json) {
|
||||
final l$system = json['system'];
|
||||
final l$$__typename = json['__typename'];
|
||||
return Mutation$NixCollectGarbage(
|
||||
system: Mutation$NixCollectGarbage$system.fromJson(
|
||||
(l$system as Map<String, dynamic>)),
|
||||
$__typename: (l$$__typename as String),
|
||||
);
|
||||
}
|
||||
|
||||
final Mutation$NixCollectGarbage$system system;
|
||||
|
||||
final String $__typename;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _resultData = <String, dynamic>{};
|
||||
final l$system = system;
|
||||
_resultData['system'] = l$system.toJson();
|
||||
final l$$__typename = $__typename;
|
||||
_resultData['__typename'] = l$$__typename;
|
||||
return _resultData;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final l$system = system;
|
||||
final l$$__typename = $__typename;
|
||||
return Object.hashAll([
|
||||
l$system,
|
||||
l$$__typename,
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (!(other is Mutation$NixCollectGarbage) ||
|
||||
runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
final l$system = system;
|
||||
final lOther$system = other.system;
|
||||
if (l$system != lOther$system) {
|
||||
return false;
|
||||
}
|
||||
final l$$__typename = $__typename;
|
||||
final lOther$$__typename = other.$__typename;
|
||||
if (l$$__typename != lOther$$__typename) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
extension UtilityExtension$Mutation$NixCollectGarbage
|
||||
on Mutation$NixCollectGarbage {
|
||||
CopyWith$Mutation$NixCollectGarbage<Mutation$NixCollectGarbage>
|
||||
get copyWith => CopyWith$Mutation$NixCollectGarbage(
|
||||
this,
|
||||
(i) => i,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class CopyWith$Mutation$NixCollectGarbage<TRes> {
|
||||
factory CopyWith$Mutation$NixCollectGarbage(
|
||||
Mutation$NixCollectGarbage instance,
|
||||
TRes Function(Mutation$NixCollectGarbage) then,
|
||||
) = _CopyWithImpl$Mutation$NixCollectGarbage;
|
||||
|
||||
factory CopyWith$Mutation$NixCollectGarbage.stub(TRes res) =
|
||||
_CopyWithStubImpl$Mutation$NixCollectGarbage;
|
||||
|
||||
TRes call({
|
||||
Mutation$NixCollectGarbage$system? system,
|
||||
String? $__typename,
|
||||
});
|
||||
CopyWith$Mutation$NixCollectGarbage$system<TRes> get system;
|
||||
}
|
||||
|
||||
class _CopyWithImpl$Mutation$NixCollectGarbage<TRes>
|
||||
implements CopyWith$Mutation$NixCollectGarbage<TRes> {
|
||||
_CopyWithImpl$Mutation$NixCollectGarbage(
|
||||
this._instance,
|
||||
this._then,
|
||||
);
|
||||
|
||||
final Mutation$NixCollectGarbage _instance;
|
||||
|
||||
final TRes Function(Mutation$NixCollectGarbage) _then;
|
||||
|
||||
static const _undefined = <dynamic, dynamic>{};
|
||||
|
||||
TRes call({
|
||||
Object? system = _undefined,
|
||||
Object? $__typename = _undefined,
|
||||
}) =>
|
||||
_then(Mutation$NixCollectGarbage(
|
||||
system: system == _undefined || system == null
|
||||
? _instance.system
|
||||
: (system as Mutation$NixCollectGarbage$system),
|
||||
$__typename: $__typename == _undefined || $__typename == null
|
||||
? _instance.$__typename
|
||||
: ($__typename as String),
|
||||
));
|
||||
|
||||
CopyWith$Mutation$NixCollectGarbage$system<TRes> get system {
|
||||
final local$system = _instance.system;
|
||||
return CopyWith$Mutation$NixCollectGarbage$system(
|
||||
local$system, (e) => call(system: e));
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyWithStubImpl$Mutation$NixCollectGarbage<TRes>
|
||||
implements CopyWith$Mutation$NixCollectGarbage<TRes> {
|
||||
_CopyWithStubImpl$Mutation$NixCollectGarbage(this._res);
|
||||
|
||||
TRes _res;
|
||||
|
||||
call({
|
||||
Mutation$NixCollectGarbage$system? system,
|
||||
String? $__typename,
|
||||
}) =>
|
||||
_res;
|
||||
|
||||
CopyWith$Mutation$NixCollectGarbage$system<TRes> get system =>
|
||||
CopyWith$Mutation$NixCollectGarbage$system.stub(_res);
|
||||
}
|
||||
|
||||
const documentNodeMutationNixCollectGarbage = DocumentNode(definitions: [
|
||||
OperationDefinitionNode(
|
||||
type: OperationType.mutation,
|
||||
name: NameNode(value: 'NixCollectGarbage'),
|
||||
variableDefinitions: [],
|
||||
directives: [],
|
||||
selectionSet: SelectionSetNode(selections: [
|
||||
FieldNode(
|
||||
name: NameNode(value: 'system'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: SelectionSetNode(selections: [
|
||||
FieldNode(
|
||||
name: NameNode(value: 'nixCollectGarbage'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: SelectionSetNode(selections: [
|
||||
FragmentSpreadNode(
|
||||
name: NameNode(value: 'basicMutationReturnFields'),
|
||||
directives: [],
|
||||
),
|
||||
FieldNode(
|
||||
name: NameNode(value: 'job'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: SelectionSetNode(selections: [
|
||||
FragmentSpreadNode(
|
||||
name: NameNode(value: 'basicApiJobsFields'),
|
||||
directives: [],
|
||||
),
|
||||
FieldNode(
|
||||
name: NameNode(value: '__typename'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: null,
|
||||
),
|
||||
]),
|
||||
),
|
||||
FieldNode(
|
||||
name: NameNode(value: '__typename'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: null,
|
||||
),
|
||||
]),
|
||||
),
|
||||
FieldNode(
|
||||
name: NameNode(value: '__typename'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: null,
|
||||
),
|
||||
]),
|
||||
),
|
||||
FieldNode(
|
||||
name: NameNode(value: '__typename'),
|
||||
alias: null,
|
||||
arguments: [],
|
||||
directives: [],
|
||||
selectionSet: null,
|
||||
),
|
||||
]),
|
||||
),
|
||||
fragmentDefinitionbasicMutationReturnFields,
|
||||
fragmentDefinitionbasicApiJobsFields,
|
||||
]);
|
||||
Mutation$NixCollectGarbage _parserFn$Mutation$NixCollectGarbage(
|
||||
Map<String, dynamic> data) =>
|
||||
Mutation$NixCollectGarbage.fromJson(data);
|
||||
typedef OnMutationCompleted$Mutation$NixCollectGarbage = FutureOr<void>
|
||||
Function(
|
||||
Map<String, dynamic>?,
|
||||
Mutation$NixCollectGarbage?,
|
||||
);
|
||||
|
||||
class Options$Mutation$NixCollectGarbage
|
||||
extends graphql.MutationOptions<Mutation$NixCollectGarbage> {
|
||||
Options$Mutation$NixCollectGarbage({
|
||||
String? operationName,
|
||||
graphql.FetchPolicy? fetchPolicy,
|
||||
graphql.ErrorPolicy? errorPolicy,
|
||||
graphql.CacheRereadPolicy? cacheRereadPolicy,
|
||||
Object? optimisticResult,
|
||||
Mutation$NixCollectGarbage? typedOptimisticResult,
|
||||
graphql.Context? context,
|
||||
OnMutationCompleted$Mutation$NixCollectGarbage? onCompleted,
|
||||
graphql.OnMutationUpdate<Mutation$NixCollectGarbage>? update,
|
||||
graphql.OnError? onError,
|
||||
}) : onCompletedWithParsed = onCompleted,
|
||||
super(
|
||||
operationName: operationName,
|
||||
fetchPolicy: fetchPolicy,
|
||||
errorPolicy: errorPolicy,
|
||||
cacheRereadPolicy: cacheRereadPolicy,
|
||||
optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
|
||||
context: context,
|
||||
onCompleted: onCompleted == null
|
||||
? null
|
||||
: (data) => onCompleted(
|
||||
data,
|
||||
data == null
|
||||
? null
|
||||
: _parserFn$Mutation$NixCollectGarbage(data),
|
||||
),
|
||||
update: update,
|
||||
onError: onError,
|
||||
document: documentNodeMutationNixCollectGarbage,
|
||||
parserFn: _parserFn$Mutation$NixCollectGarbage,
|
||||
);
|
||||
|
||||
final OnMutationCompleted$Mutation$NixCollectGarbage? onCompletedWithParsed;
|
||||
|
||||
@override
|
||||
List<Object?> get properties => [
|
||||
...super.onCompleted == null
|
||||
? super.properties
|
||||
: super.properties.where((property) => property != onCompleted),
|
||||
onCompletedWithParsed,
|
||||
];
|
||||
}
|
||||
|
||||
class WatchOptions$Mutation$NixCollectGarbage
|
||||
extends graphql.WatchQueryOptions<Mutation$NixCollectGarbage> {
|
||||
WatchOptions$Mutation$NixCollectGarbage({
|
||||
String? operationName,
|
||||
graphql.FetchPolicy? fetchPolicy,
|
||||
graphql.ErrorPolicy? errorPolicy,
|
||||
graphql.CacheRereadPolicy? cacheRereadPolicy,
|
||||
Object? optimisticResult,
|
||||
Mutation$NixCollectGarbage? typedOptimisticResult,
|
||||
graphql.Context? context,
|
||||
Duration? pollInterval,
|
||||
bool? eagerlyFetchResults,
|
||||
bool carryForwardDataOnException = true,
|
||||
bool fetchResults = false,
|
||||
}) : super(
|
||||
operationName: operationName,
|
||||
fetchPolicy: fetchPolicy,
|
||||
errorPolicy: errorPolicy,
|
||||
cacheRereadPolicy: cacheRereadPolicy,
|
||||
optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
|
||||
context: context,
|
||||
document: documentNodeMutationNixCollectGarbage,
|
||||
pollInterval: pollInterval,
|
||||
eagerlyFetchResults: eagerlyFetchResults,
|
||||
carryForwardDataOnException: carryForwardDataOnException,
|
||||
fetchResults: fetchResults,
|
||||
parserFn: _parserFn$Mutation$NixCollectGarbage,
|
||||
);
|
||||
}
|
||||
|
||||
extension ClientExtension$Mutation$NixCollectGarbage on graphql.GraphQLClient {
|
||||
Future<graphql.QueryResult<Mutation$NixCollectGarbage>>
|
||||
mutate$NixCollectGarbage(
|
||||
[Options$Mutation$NixCollectGarbage? options]) async =>
|
||||
await this.mutate(options ?? Options$Mutation$NixCollectGarbage());
|
||||
graphql.ObservableQuery<
|
||||
Mutation$NixCollectGarbage> watchMutation$NixCollectGarbage(
|
||||
[WatchOptions$Mutation$NixCollectGarbage? options]) =>
|
||||
this.watchMutation(options ?? WatchOptions$Mutation$NixCollectGarbage());
|
||||
}
|
||||
|
||||
class Mutation$NixCollectGarbage$system {
|
||||
Mutation$NixCollectGarbage$system({
|
||||
required this.nixCollectGarbage,
|
||||
this.$__typename = 'SystemMutations',
|
||||
});
|
||||
|
||||
factory Mutation$NixCollectGarbage$system.fromJson(
|
||||
Map<String, dynamic> json) {
|
||||
final l$nixCollectGarbage = json['nixCollectGarbage'];
|
||||
final l$$__typename = json['__typename'];
|
||||
return Mutation$NixCollectGarbage$system(
|
||||
nixCollectGarbage:
|
||||
Mutation$NixCollectGarbage$system$nixCollectGarbage.fromJson(
|
||||
(l$nixCollectGarbage as Map<String, dynamic>)),
|
||||
$__typename: (l$$__typename as String),
|
||||
);
|
||||
}
|
||||
|
||||
final Mutation$NixCollectGarbage$system$nixCollectGarbage nixCollectGarbage;
|
||||
|
||||
final String $__typename;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _resultData = <String, dynamic>{};
|
||||
final l$nixCollectGarbage = nixCollectGarbage;
|
||||
_resultData['nixCollectGarbage'] = l$nixCollectGarbage.toJson();
|
||||
final l$$__typename = $__typename;
|
||||
_resultData['__typename'] = l$$__typename;
|
||||
return _resultData;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final l$nixCollectGarbage = nixCollectGarbage;
|
||||
final l$$__typename = $__typename;
|
||||
return Object.hashAll([
|
||||
l$nixCollectGarbage,
|
||||
l$$__typename,
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (!(other is Mutation$NixCollectGarbage$system) ||
|
||||
runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
final l$nixCollectGarbage = nixCollectGarbage;
|
||||
final lOther$nixCollectGarbage = other.nixCollectGarbage;
|
||||
if (l$nixCollectGarbage != lOther$nixCollectGarbage) {
|
||||
return false;
|
||||
}
|
||||
final l$$__typename = $__typename;
|
||||
final lOther$$__typename = other.$__typename;
|
||||
if (l$$__typename != lOther$$__typename) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
extension UtilityExtension$Mutation$NixCollectGarbage$system
|
||||
on Mutation$NixCollectGarbage$system {
|
||||
CopyWith$Mutation$NixCollectGarbage$system<Mutation$NixCollectGarbage$system>
|
||||
get copyWith => CopyWith$Mutation$NixCollectGarbage$system(
|
||||
this,
|
||||
(i) => i,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class CopyWith$Mutation$NixCollectGarbage$system<TRes> {
|
||||
factory CopyWith$Mutation$NixCollectGarbage$system(
|
||||
Mutation$NixCollectGarbage$system instance,
|
||||
TRes Function(Mutation$NixCollectGarbage$system) then,
|
||||
) = _CopyWithImpl$Mutation$NixCollectGarbage$system;
|
||||
|
||||
factory CopyWith$Mutation$NixCollectGarbage$system.stub(TRes res) =
|
||||
_CopyWithStubImpl$Mutation$NixCollectGarbage$system;
|
||||
|
||||
TRes call({
|
||||
Mutation$NixCollectGarbage$system$nixCollectGarbage? nixCollectGarbage,
|
||||
String? $__typename,
|
||||
});
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
|
||||
get nixCollectGarbage;
|
||||
}
|
||||
|
||||
class _CopyWithImpl$Mutation$NixCollectGarbage$system<TRes>
|
||||
implements CopyWith$Mutation$NixCollectGarbage$system<TRes> {
|
||||
_CopyWithImpl$Mutation$NixCollectGarbage$system(
|
||||
this._instance,
|
||||
this._then,
|
||||
);
|
||||
|
||||
final Mutation$NixCollectGarbage$system _instance;
|
||||
|
||||
final TRes Function(Mutation$NixCollectGarbage$system) _then;
|
||||
|
||||
static const _undefined = <dynamic, dynamic>{};
|
||||
|
||||
TRes call({
|
||||
Object? nixCollectGarbage = _undefined,
|
||||
Object? $__typename = _undefined,
|
||||
}) =>
|
||||
_then(Mutation$NixCollectGarbage$system(
|
||||
nixCollectGarbage:
|
||||
nixCollectGarbage == _undefined || nixCollectGarbage == null
|
||||
? _instance.nixCollectGarbage
|
||||
: (nixCollectGarbage
|
||||
as Mutation$NixCollectGarbage$system$nixCollectGarbage),
|
||||
$__typename: $__typename == _undefined || $__typename == null
|
||||
? _instance.$__typename
|
||||
: ($__typename as String),
|
||||
));
|
||||
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
|
||||
get nixCollectGarbage {
|
||||
final local$nixCollectGarbage = _instance.nixCollectGarbage;
|
||||
return CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
local$nixCollectGarbage, (e) => call(nixCollectGarbage: e));
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyWithStubImpl$Mutation$NixCollectGarbage$system<TRes>
|
||||
implements CopyWith$Mutation$NixCollectGarbage$system<TRes> {
|
||||
_CopyWithStubImpl$Mutation$NixCollectGarbage$system(this._res);
|
||||
|
||||
TRes _res;
|
||||
|
||||
call({
|
||||
Mutation$NixCollectGarbage$system$nixCollectGarbage? nixCollectGarbage,
|
||||
String? $__typename,
|
||||
}) =>
|
||||
_res;
|
||||
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
|
||||
get nixCollectGarbage =>
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage.stub(
|
||||
_res);
|
||||
}
|
||||
|
||||
class Mutation$NixCollectGarbage$system$nixCollectGarbage
|
||||
implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn {
|
||||
Mutation$NixCollectGarbage$system$nixCollectGarbage({
|
||||
required this.code,
|
||||
required this.message,
|
||||
required this.success,
|
||||
this.$__typename = 'GenericJobMutationReturn',
|
||||
this.job,
|
||||
});
|
||||
|
||||
factory Mutation$NixCollectGarbage$system$nixCollectGarbage.fromJson(
|
||||
Map<String, dynamic> json) {
|
||||
final l$code = json['code'];
|
||||
final l$message = json['message'];
|
||||
final l$success = json['success'];
|
||||
final l$$__typename = json['__typename'];
|
||||
final l$job = json['job'];
|
||||
return Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
code: (l$code as int),
|
||||
message: (l$message as String),
|
||||
success: (l$success as bool),
|
||||
$__typename: (l$$__typename as String),
|
||||
job: l$job == null
|
||||
? null
|
||||
: Fragment$basicApiJobsFields.fromJson(
|
||||
(l$job as Map<String, dynamic>)),
|
||||
);
|
||||
}
|
||||
|
||||
final int code;
|
||||
|
||||
final String message;
|
||||
|
||||
final bool success;
|
||||
|
||||
final String $__typename;
|
||||
|
||||
final Fragment$basicApiJobsFields? job;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _resultData = <String, dynamic>{};
|
||||
final l$code = code;
|
||||
_resultData['code'] = l$code;
|
||||
final l$message = message;
|
||||
_resultData['message'] = l$message;
|
||||
final l$success = success;
|
||||
_resultData['success'] = l$success;
|
||||
final l$$__typename = $__typename;
|
||||
_resultData['__typename'] = l$$__typename;
|
||||
final l$job = job;
|
||||
_resultData['job'] = l$job?.toJson();
|
||||
return _resultData;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final l$code = code;
|
||||
final l$message = message;
|
||||
final l$success = success;
|
||||
final l$$__typename = $__typename;
|
||||
final l$job = job;
|
||||
return Object.hashAll([
|
||||
l$code,
|
||||
l$message,
|
||||
l$success,
|
||||
l$$__typename,
|
||||
l$job,
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (!(other is Mutation$NixCollectGarbage$system$nixCollectGarbage) ||
|
||||
runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
final l$code = code;
|
||||
final lOther$code = other.code;
|
||||
if (l$code != lOther$code) {
|
||||
return false;
|
||||
}
|
||||
final l$message = message;
|
||||
final lOther$message = other.message;
|
||||
if (l$message != lOther$message) {
|
||||
return false;
|
||||
}
|
||||
final l$success = success;
|
||||
final lOther$success = other.success;
|
||||
if (l$success != lOther$success) {
|
||||
return false;
|
||||
}
|
||||
final l$$__typename = $__typename;
|
||||
final lOther$$__typename = other.$__typename;
|
||||
if (l$$__typename != lOther$$__typename) {
|
||||
return false;
|
||||
}
|
||||
final l$job = job;
|
||||
final lOther$job = other.job;
|
||||
if (l$job != lOther$job) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
extension UtilityExtension$Mutation$NixCollectGarbage$system$nixCollectGarbage
|
||||
on Mutation$NixCollectGarbage$system$nixCollectGarbage {
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<
|
||||
Mutation$NixCollectGarbage$system$nixCollectGarbage>
|
||||
get copyWith =>
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
this,
|
||||
(i) => i,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<
|
||||
TRes> {
|
||||
factory CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
Mutation$NixCollectGarbage$system$nixCollectGarbage instance,
|
||||
TRes Function(Mutation$NixCollectGarbage$system$nixCollectGarbage) then,
|
||||
) = _CopyWithImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage;
|
||||
|
||||
factory CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage.stub(
|
||||
TRes res) =
|
||||
_CopyWithStubImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage;
|
||||
|
||||
TRes call({
|
||||
int? code,
|
||||
String? message,
|
||||
bool? success,
|
||||
String? $__typename,
|
||||
Fragment$basicApiJobsFields? job,
|
||||
});
|
||||
CopyWith$Fragment$basicApiJobsFields<TRes> get job;
|
||||
}
|
||||
|
||||
class _CopyWithImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
|
||||
implements
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes> {
|
||||
_CopyWithImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
this._instance,
|
||||
this._then,
|
||||
);
|
||||
|
||||
final Mutation$NixCollectGarbage$system$nixCollectGarbage _instance;
|
||||
|
||||
final TRes Function(Mutation$NixCollectGarbage$system$nixCollectGarbage)
|
||||
_then;
|
||||
|
||||
static const _undefined = <dynamic, dynamic>{};
|
||||
|
||||
TRes call({
|
||||
Object? code = _undefined,
|
||||
Object? message = _undefined,
|
||||
Object? success = _undefined,
|
||||
Object? $__typename = _undefined,
|
||||
Object? job = _undefined,
|
||||
}) =>
|
||||
_then(Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
code:
|
||||
code == _undefined || code == null ? _instance.code : (code as int),
|
||||
message: message == _undefined || message == null
|
||||
? _instance.message
|
||||
: (message as String),
|
||||
success: success == _undefined || success == null
|
||||
? _instance.success
|
||||
: (success as bool),
|
||||
$__typename: $__typename == _undefined || $__typename == null
|
||||
? _instance.$__typename
|
||||
: ($__typename as String),
|
||||
job: job == _undefined
|
||||
? _instance.job
|
||||
: (job as Fragment$basicApiJobsFields?),
|
||||
));
|
||||
|
||||
CopyWith$Fragment$basicApiJobsFields<TRes> get job {
|
||||
final local$job = _instance.job;
|
||||
return local$job == null
|
||||
? CopyWith$Fragment$basicApiJobsFields.stub(_then(_instance))
|
||||
: CopyWith$Fragment$basicApiJobsFields(local$job, (e) => call(job: e));
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyWithStubImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage<
|
||||
TRes>
|
||||
implements
|
||||
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes> {
|
||||
_CopyWithStubImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage(
|
||||
this._res);
|
||||
|
||||
TRes _res;
|
||||
|
||||
call({
|
||||
int? code,
|
||||
String? message,
|
||||
bool? success,
|
||||
String? $__typename,
|
||||
Fragment$basicApiJobsFields? job,
|
||||
}) =>
|
||||
_res;
|
||||
|
||||
CopyWith$Fragment$basicApiJobsFields<TRes> get job =>
|
||||
CopyWith$Fragment$basicApiJobsFields.stub(_res);
|
||||
}
|
||||
|
||||
class Mutation$RunSystemUpgradeFallback {
|
||||
Mutation$RunSystemUpgradeFallback({
|
||||
required this.system,
|
||||
|
|
|
@ -144,4 +144,38 @@ mixin ServerActionsApi on GraphQLApiMap {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<GenericResult<ServerJob?>> collectNixGarbage() async {
|
||||
try {
|
||||
final GraphQLClient client = await getClient();
|
||||
final result = await client.mutate$NixCollectGarbage();
|
||||
if (result.hasException) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
data: null,
|
||||
);
|
||||
} else if (result.parsedData!.system.nixCollectGarbage.success &&
|
||||
result.parsedData!.system.nixCollectGarbage.job != null) {
|
||||
return GenericResult(
|
||||
success: true,
|
||||
data: ServerJob.fromGraphQL(
|
||||
result.parsedData!.system.nixCollectGarbage.job!,
|
||||
),
|
||||
message: result.parsedData!.system.nixCollectGarbage.message,
|
||||
);
|
||||
} else {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: result.parsedData!.system.nixCollectGarbage.message,
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return GenericResult(
|
||||
success: false,
|
||||
message: e.toString(),
|
||||
data: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
|
@ -6,7 +7,7 @@ import 'package:dio/dio.dart';
|
|||
import 'package:dio/io.dart';
|
||||
import 'package:pretty_dio_logger/pretty_dio_logger.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 {
|
||||
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
||||
|
@ -57,8 +58,8 @@ abstract class RestApiMap {
|
|||
}
|
||||
|
||||
class ConsoleInterceptor extends InterceptorsWrapper {
|
||||
void addMessage(final Message message) {
|
||||
getIt.get<ConsoleModel>().addMessage(message);
|
||||
void addConsoleLog(final ConsoleLog message) {
|
||||
getIt.get<ConsoleModel>().log(message);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -66,12 +67,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
final RequestOptions options,
|
||||
final RequestInterceptorHandler handler,
|
||||
) async {
|
||||
addMessage(
|
||||
RestApiRequestMessage(
|
||||
method: options.method,
|
||||
data: options.data.toString(),
|
||||
headers: options.headers,
|
||||
addConsoleLog(
|
||||
RestApiRequestConsoleLog(
|
||||
uri: options.uri,
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
data: jsonEncode(options.data),
|
||||
),
|
||||
);
|
||||
return super.onRequest(options, handler);
|
||||
|
@ -82,12 +83,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
final Response response,
|
||||
final ResponseInterceptorHandler handler,
|
||||
) async {
|
||||
addMessage(
|
||||
RestApiResponseMessage(
|
||||
addConsoleLog(
|
||||
RestApiResponseConsoleLog(
|
||||
uri: response.realUri,
|
||||
method: response.requestOptions.method,
|
||||
statusCode: response.statusCode,
|
||||
data: response.data.toString(),
|
||||
uri: response.realUri,
|
||||
data: jsonEncode(response.data),
|
||||
),
|
||||
);
|
||||
return super.onResponse(
|
||||
|
@ -103,10 +104,13 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
) async {
|
||||
final Response? response = err.response;
|
||||
log(err.toString());
|
||||
addMessage(
|
||||
Message.warn(
|
||||
text:
|
||||
'response-uri: ${response?.realUri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
|
||||
|
||||
addConsoleLog(
|
||||
ManualConsoleLog.warning(
|
||||
customTitle: 'RestAPI error',
|
||||
content: '"uri": "${response?.realUri}",\n'
|
||||
'"status_code": ${response?.statusCode},\n'
|
||||
'"response": ${jsonEncode(response)}',
|
||||
),
|
||||
);
|
||||
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];
|
||||
}
|
27
lib/logic/bloc/connection_status_bloc.dart
Normal file
27
lib/logic/bloc/connection_status_bloc.dart
Normal file
|
@ -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
|
||||
class ConnectionStatusBloc extends Bloc<ConnectionStatus, ConnectionStatus> {
|
||||
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];
|
||||
}
|
|
@ -178,6 +178,45 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> collectNixGarbage() async {
|
||||
if (state is JobsStateEmpty) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[CollectNixGarbageJob(status: JobStatusEnum.running)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
final result =
|
||||
await getIt<ApiConnectionRepository>().api.collectNixGarbage();
|
||||
if (result.success && result.data != null) {
|
||||
emit(
|
||||
JobsStateLoading(
|
||||
[CollectNixGarbageJob(status: JobStatusEnum.finished)],
|
||||
result.data!.uid,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
} else if (result.success) {
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[CollectNixGarbageJob(status: JobStatusEnum.finished)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
JobsStateFinished(
|
||||
[CollectNixGarbageJob(status: JobStatusEnum.error)],
|
||||
null,
|
||||
const [],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> acknowledgeFinished() async {
|
||||
if (state is! JobsStateFinished) {
|
||||
return;
|
||||
|
|
|
@ -49,7 +49,8 @@ abstract class ServerInstallationState extends Equatable {
|
|||
bool get isPrimaryUserFilled => rootUser != null;
|
||||
bool get isServerCreated => serverDetails != null;
|
||||
|
||||
bool get isFullyInitialized => _fulfillmentList.every((final el) => el!);
|
||||
bool get isFullyInitialized =>
|
||||
_fulfillmentList.every((final el) => el ?? false);
|
||||
ServerSetupProgress get progress => ServerSetupProgress
|
||||
.values[_fulfillmentList.where((final el) => el!).length];
|
||||
|
||||
|
|
|
@ -7,21 +7,22 @@ class ApiConfigModel {
|
|||
|
||||
String? get localeCode => _localeCode;
|
||||
|
||||
static const localeCodeFallback = 'en';
|
||||
String? _localeCode;
|
||||
|
||||
Future<void> setLocaleCode(final String value) async {
|
||||
_localeCode = value;
|
||||
}
|
||||
String get localeCode => _localeCode ?? localeCodeFallback;
|
||||
Future<void> setLocaleCode(final String value) async => _localeCode = value;
|
||||
Future<void> resetLocaleCode() async => _localeCode = null;
|
||||
|
||||
Future<void> setBackblazeBucket(final BackblazeBucket value) async {
|
||||
await _box.put(BNames.backblazeBucket, value);
|
||||
}
|
||||
|
||||
// TODO: Remove it
|
||||
void clear() {
|
||||
_localeCode = null;
|
||||
}
|
||||
|
||||
// TODO: Remove it
|
||||
void init() {
|
||||
_localeCode = 'en';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
51
lib/logic/get_it/console_model.dart
Normal file
51
lib/logic/get_it/console_model.dart
Normal file
|
@ -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);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
184
lib/logic/models/console_log.dart
Normal file
184
lib/logic/models/console_log.dart
Normal file
|
@ -0,0 +1,184 @@
|
|||
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.
|
||||
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,
|
||||
});
|
||||
|
||||
/// headers thath should not be included into clipboard buffer, as opposed to
|
||||
/// `[[ConsoleLogItemDialog]]` `_KeyValueRow.hideList` which filters values,
|
||||
/// that should be accessible from UI, but hidden in screenshots
|
||||
static const blacklistedHeaders = ['Authorization'];
|
||||
|
||||
final String? method;
|
||||
final Uri? uri;
|
||||
final Map<String, dynamic>? headers;
|
||||
final String? data;
|
||||
|
||||
@override
|
||||
String get title => 'Rest API Request';
|
||||
|
||||
Map<String, dynamic> get filteredHeaders => Map.fromEntries(
|
||||
headers?.entries.where(
|
||||
(final entry) => !blacklistedHeaders.contains(entry.key),
|
||||
) ??
|
||||
const [],
|
||||
);
|
||||
|
||||
@override
|
||||
String get content => '"method": "$method",\n'
|
||||
'"uri": "$uri",\n'
|
||||
'"headers": ${jsonEncode(filteredHeaders)},\n' // censor header to not expose API keys
|
||||
'"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';
|
||||
}
|
|
@ -62,6 +62,36 @@ class UpgradeServerJob extends ClientJob {
|
|||
);
|
||||
}
|
||||
|
||||
class CollectNixGarbageJob extends ClientJob {
|
||||
CollectNixGarbageJob({
|
||||
super.status,
|
||||
super.message,
|
||||
super.id,
|
||||
}) : super(title: 'jobs.collect_nix_garbage'.tr());
|
||||
|
||||
@override
|
||||
bool canAddTo(final List<ClientJob> jobs) =>
|
||||
!jobs.any((final job) => job is CollectNixGarbageJob);
|
||||
|
||||
@override
|
||||
Future<(bool, String)> execute() async {
|
||||
final result =
|
||||
await getIt<ApiConnectionRepository>().api.collectNixGarbage();
|
||||
return (result.success, result.message ?? '');
|
||||
}
|
||||
|
||||
@override
|
||||
CollectNixGarbageJob copyWithNewStatus({
|
||||
required final JobStatusEnum status,
|
||||
final String? message,
|
||||
}) =>
|
||||
CollectNixGarbageJob(
|
||||
status: status,
|
||||
message: message,
|
||||
id: id,
|
||||
);
|
||||
}
|
||||
|
||||
class RebootServerJob extends ClientJob {
|
||||
RebootServerJob({
|
||||
super.status,
|
||||
|
|
|
@ -65,14 +65,34 @@ class DigitalOceanLocation {
|
|||
emoji = '🇮🇳';
|
||||
break;
|
||||
|
||||
case 'syd':
|
||||
emoji = '🇦🇺';
|
||||
break;
|
||||
|
||||
case 'nyc':
|
||||
case 'sfo':
|
||||
emoji = '🇺🇸';
|
||||
break;
|
||||
}
|
||||
|
||||
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 {
|
||||
final countryName = _townPrefixToCountryMap[slug.substring(0, 3)] ?? slug;
|
||||
return 'countries.$countryName';
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
|
|
|
@ -10,8 +10,10 @@ DigitalOceanVolume _$DigitalOceanVolumeFromJson(Map<String, dynamic> json) =>
|
|||
DigitalOceanVolume(
|
||||
json['id'] as String,
|
||||
json['name'] as String,
|
||||
json['size_gigabytes'] as int,
|
||||
(json['droplet_ids'] as List<dynamic>?)?.map((e) => e as int).toList(),
|
||||
(json['size_gigabytes'] as num).toInt(),
|
||||
(json['droplet_ids'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DigitalOceanVolumeToJson(DigitalOceanVolume instance) =>
|
||||
|
@ -42,10 +44,10 @@ DigitalOceanServerType _$DigitalOceanServerTypeFromJson(
|
|||
(json['regions'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
(json['memory'] as num).toDouble(),
|
||||
json['description'] as String,
|
||||
json['disk'] as int,
|
||||
(json['disk'] as num).toInt(),
|
||||
(json['price_monthly'] as num).toDouble(),
|
||||
json['slug'] as String,
|
||||
json['vcpus'] as int,
|
||||
(json['vcpus'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DigitalOceanServerTypeToJson(
|
||||
|
|
|
@ -24,8 +24,8 @@ CloudflareDnsRecord _$CloudflareDnsRecordFromJson(Map<String, dynamic> json) =>
|
|||
name: json['name'] as String?,
|
||||
content: json['content'] as String?,
|
||||
zoneName: json['zone_name'] as String,
|
||||
ttl: json['ttl'] as int? ?? 3600,
|
||||
priority: json['priority'] as int? ?? 10,
|
||||
ttl: (json['ttl'] as num?)?.toInt() ?? 3600,
|
||||
priority: (json['priority'] as num?)?.toInt() ?? 10,
|
||||
id: json['id'] as String?,
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ part of 'desec_dns_info.dart';
|
|||
|
||||
DesecDomain _$DesecDomainFromJson(Map<String, dynamic> json) => DesecDomain(
|
||||
name: json['name'] as String,
|
||||
minimumTtl: json['minimum_ttl'] as int?,
|
||||
minimumTtl: (json['minimum_ttl'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DesecDomainToJson(DesecDomain instance) =>
|
||||
|
@ -21,7 +21,7 @@ DesecDnsRecord _$DesecDnsRecordFromJson(Map<String, dynamic> json) =>
|
|||
DesecDnsRecord(
|
||||
subname: json['subname'] as String,
|
||||
type: json['type'] as String,
|
||||
ttl: json['ttl'] as int,
|
||||
ttl: (json['ttl'] as num).toInt(),
|
||||
records:
|
||||
(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(
|
||||
name: json['name'] as String,
|
||||
ttl: json['ttl'] as int?,
|
||||
ttl: (json['ttl'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
||||
|
@ -21,12 +21,12 @@ Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
|||
DigitalOceanDnsRecord _$DigitalOceanDnsRecordFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
DigitalOceanDnsRecord(
|
||||
id: json['id'] as int?,
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
name: json['name'] as String,
|
||||
type: json['type'] as String,
|
||||
ttl: json['ttl'] as int,
|
||||
ttl: (json['ttl'] as num).toInt(),
|
||||
data: json['data'] as String,
|
||||
priority: json['priority'] as int?,
|
||||
priority: (json['priority'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DigitalOceanDnsRecordToJson(
|
||||
|
|
|
@ -155,6 +155,27 @@ class HetznerLocation {
|
|||
}
|
||||
return emoji;
|
||||
}
|
||||
|
||||
String get countryDisplayKey {
|
||||
String displayKey = 'countries.';
|
||||
switch (country.substring(0, 2)) {
|
||||
case 'DE':
|
||||
displayKey += 'germany';
|
||||
break;
|
||||
|
||||
case 'FI':
|
||||
displayKey += 'finland';
|
||||
break;
|
||||
|
||||
case 'US':
|
||||
displayKey += 'united_states';
|
||||
break;
|
||||
|
||||
default:
|
||||
displayKey = country;
|
||||
}
|
||||
return displayKey;
|
||||
}
|
||||
}
|
||||
|
||||
/// A Volume is a highly-available, scalable, and SSD-based block storage for Servers.
|
||||
|
|
|
@ -8,7 +8,7 @@ part of 'hetzner_server_info.dart';
|
|||
|
||||
HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
||||
HetznerServerInfo(
|
||||
json['id'] as int,
|
||||
(json['id'] as num).toInt(),
|
||||
json['name'] as String,
|
||||
$enumDecode(_$ServerStatusEnumMap, json['status']),
|
||||
DateTime.parse(json['created'] as String),
|
||||
|
@ -16,7 +16,9 @@ HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
|||
json['server_type'] as Map<String, dynamic>),
|
||||
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
||||
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) =>
|
||||
|
@ -58,7 +60,7 @@ Map<String, dynamic> _$HetznerPublicNetInfoToJson(
|
|||
};
|
||||
|
||||
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
||||
json['id'] as int,
|
||||
(json['id'] as num).toInt(),
|
||||
json['ip'] as String,
|
||||
json['blocked'] as bool,
|
||||
json['dns_ptr'] as String,
|
||||
|
@ -75,9 +77,9 @@ Map<String, dynamic> _$HetznerIp4ToJson(HetznerIp4 instance) =>
|
|||
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
HetznerServerTypeInfo(
|
||||
json['cores'] as int,
|
||||
(json['cores'] as num).toInt(),
|
||||
json['memory'] as num,
|
||||
json['disk'] as int,
|
||||
(json['disk'] as num).toInt(),
|
||||
(json['prices'] as List<dynamic>)
|
||||
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
|
@ -132,9 +134,9 @@ Map<String, dynamic> _$HetznerLocationToJson(HetznerLocation instance) =>
|
|||
|
||||
HetznerVolume _$HetznerVolumeFromJson(Map<String, dynamic> json) =>
|
||||
HetznerVolume(
|
||||
json['id'] as int,
|
||||
json['size'] as int,
|
||||
json['serverId'] as int?,
|
||||
(json['id'] as num).toInt(),
|
||||
(json['size'] as num).toInt(),
|
||||
(json['serverId'] as num?)?.toInt(),
|
||||
json['name'] as String,
|
||||
json['linux_device'] as String?,
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ RecoveryKeyStatus _$RecoveryKeyStatusFromJson(Map<String, dynamic> json) =>
|
|||
expiration: json['expiration'] == null
|
||||
? null
|
||||
: 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) =>
|
||||
|
|
|
@ -15,7 +15,7 @@ ServerJob _$ServerJobFromJson(Map<String, dynamic> json) => ServerJob(
|
|||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
error: json['error'] as String?,
|
||||
progress: json['progress'] as int?,
|
||||
progress: (json['progress'] as num?)?.toInt(),
|
||||
result: json['result'] as String?,
|
||||
statusText: json['statusText'] as String?,
|
||||
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;
|
||||
}
|
|
@ -2,12 +2,14 @@ class ServerProviderLocation {
|
|||
ServerProviderLocation({
|
||||
required this.title,
|
||||
required this.identifier,
|
||||
required this.countryDisplayKey,
|
||||
this.description,
|
||||
this.flag = '',
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String identifier;
|
||||
final String countryDisplayKey;
|
||||
final String? description;
|
||||
final String flag;
|
||||
}
|
||||
|
|
|
@ -27,9 +27,9 @@ class ApiAdapter {
|
|||
class CloudflareDnsProvider extends DnsProvider {
|
||||
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
||||
CloudflareDnsProvider.load(
|
||||
final bool isAuthotized,
|
||||
final bool isAuthorized,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthotized,
|
||||
isWithToken: isAuthorized,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
|
|
|
@ -22,9 +22,9 @@ class ApiAdapter {
|
|||
class DesecDnsProvider extends DnsProvider {
|
||||
DesecDnsProvider() : _adapter = ApiAdapter();
|
||||
DesecDnsProvider.load(
|
||||
final bool isAuthotized,
|
||||
final bool isAuthorized,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthotized,
|
||||
isWithToken: isAuthorized,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
|
|
|
@ -22,9 +22,9 @@ class ApiAdapter {
|
|||
class DigitalOceanDnsProvider extends DnsProvider {
|
||||
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
||||
DigitalOceanDnsProvider.load(
|
||||
final bool isAuthotized,
|
||||
final bool isAuthorized,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthotized,
|
||||
isWithToken: isAuthorized,
|
||||
);
|
||||
|
||||
ApiAdapter _adapter;
|
||||
|
|
|
@ -38,9 +38,9 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
DigitalOceanServerProvider() : _adapter = ApiAdapter();
|
||||
DigitalOceanServerProvider.load(
|
||||
final String? location,
|
||||
final bool isAuthotized,
|
||||
final bool isAuthorized,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthotized,
|
||||
isWithToken: isAuthorized,
|
||||
region: location,
|
||||
);
|
||||
|
||||
|
@ -438,6 +438,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
|||
description: rawLocation.name,
|
||||
flag: rawLocation.flag,
|
||||
identifier: rawLocation.slug,
|
||||
countryDisplayKey: rawLocation.countryDisplayKey,
|
||||
);
|
||||
} catch (e) {
|
||||
continue;
|
||||
|
|
|
@ -38,9 +38,9 @@ class HetznerServerProvider extends ServerProvider {
|
|||
HetznerServerProvider() : _adapter = ApiAdapter();
|
||||
HetznerServerProvider.load(
|
||||
final String? location,
|
||||
final bool isAuthotized,
|
||||
final bool isAuthorized,
|
||||
) : _adapter = ApiAdapter(
|
||||
isWithToken: isAuthotized,
|
||||
isWithToken: isAuthorized,
|
||||
region: location,
|
||||
);
|
||||
|
||||
|
@ -156,6 +156,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
description: server.location.description,
|
||||
flag: server.location.flag,
|
||||
identifier: server.location.name,
|
||||
countryDisplayKey: server.location.countryDisplayKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -456,6 +457,7 @@ class HetznerServerProvider extends ServerProvider {
|
|||
description: rawLocation.description,
|
||||
flag: rawLocation.flag,
|
||||
identifier: rawLocation.name,
|
||||
countryDisplayKey: rawLocation.countryDisplayKey,
|
||||
);
|
||||
} catch (e) {
|
||||
continue;
|
||||
|
|
170
lib/main.dart
170
lib/main.dart
|
@ -1,21 +1,21 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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_observer.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/config/localization.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_hive_datasource.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/router/router.dart';
|
||||
// import 'package:wakelock/wakelock.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await HiveConfig.init();
|
||||
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
// try {
|
||||
|
@ -26,85 +26,117 @@ void main() async {
|
|||
// print(e);
|
||||
// }
|
||||
|
||||
await getItSetup();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
tz.initializeTimeZones();
|
||||
try {
|
||||
await Future.wait(
|
||||
<Future<void>>[
|
||||
HiveConfig.init(),
|
||||
EasyLocalization.ensureInitialized(),
|
||||
],
|
||||
);
|
||||
await getItSetup();
|
||||
} on PlatformException catch (e) {
|
||||
runApp(
|
||||
FailedToInitSecureStorageScreen(e: e),
|
||||
);
|
||||
}
|
||||
|
||||
final ThemeData lightThemeData = await AppThemeFactory.create(
|
||||
isDark: false,
|
||||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
final ThemeData darkThemeData = await AppThemeFactory.create(
|
||||
isDark: true,
|
||||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
tz.initializeTimeZones();
|
||||
|
||||
Bloc.observer = SimpleBlocObserver();
|
||||
|
||||
runApp(
|
||||
Localization(
|
||||
child: SelfprivacyApp(
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
child: InheritedPreferencesRepository(
|
||||
dataSource: PreferencesHiveDataSource(),
|
||||
child: const InheritedAppController(
|
||||
child: AppBuilder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class SelfprivacyApp extends StatelessWidget {
|
||||
SelfprivacyApp({
|
||||
required this.lightThemeData,
|
||||
required this.darkThemeData,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ThemeData lightThemeData;
|
||||
final ThemeData darkThemeData;
|
||||
|
||||
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||
class AppBuilder extends StatelessWidget {
|
||||
const AppBuilder({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Localization(
|
||||
child: BlocAndProviderConfig(
|
||||
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;
|
||||
Widget build(final BuildContext context) {
|
||||
final appController = InheritedAppController.of(context);
|
||||
|
||||
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,
|
||||
useMaterial3: true,
|
||||
scaffoldBackgroundColor: colorScheme.background,
|
||||
listTileTheme: ListTileThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return materialThemeData;
|
||||
|
@ -50,7 +55,8 @@ abstract class AppThemeFactory {
|
|||
static Future<ColorScheme?> _getDynamicColors(final Brightness brightness) {
|
||||
try {
|
||||
return DynamicColorPlugin.getCorePalette().then(
|
||||
(final corePallet) => corePallet?.toColorScheme(brightness: brightness),
|
||||
(final corePallete) =>
|
||||
corePallete?.toColorScheme(brightness: brightness),
|
||||
);
|
||||
} on PlatformException {
|
||||
return Future.value(null);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class BrandHeader extends StatelessWidget {
|
||||
class BrandHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
const BrandHeader({
|
||||
super.key,
|
||||
this.title = '',
|
||||
|
@ -8,6 +8,9 @@ class BrandHeader extends StatelessWidget {
|
|||
this.onBackButtonPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(52.0);
|
||||
|
||||
final String title;
|
||||
final bool hasBackButton;
|
||||
final VoidCallback? onBackButtonPressed;
|
||||
|
|
|
@ -11,15 +11,16 @@ class InfoBox extends StatelessWidget {
|
|||
final bool isWarning;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
Widget build(final BuildContext context) => Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 16.0,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isWarning ? Icons.warning_amber_outlined : Icons.info_outline,
|
||||
size: 24,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
|
@ -63,7 +64,7 @@ class JobsContent extends StatelessWidget {
|
|||
context.read<ServerInstallationCubit>().state;
|
||||
if (state is JobsStateEmpty) {
|
||||
widgets = [
|
||||
const SizedBox(height: 80),
|
||||
const Gap(80),
|
||||
Center(
|
||||
child: Text(
|
||||
'jobs.empty'.tr(),
|
||||
|
@ -75,12 +76,12 @@ class JobsContent extends StatelessWidget {
|
|||
if (installationState is ServerInstallationFinished) {
|
||||
widgets = [
|
||||
...widgets,
|
||||
const SizedBox(height: 80),
|
||||
const Gap(80),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().upgradeServer(),
|
||||
text: 'jobs.upgrade_server'.tr(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Gap(10),
|
||||
BrandButton.text(
|
||||
title: 'jobs.reboot_server'.tr(),
|
||||
onPressed: () {
|
||||
|
@ -189,7 +190,7 @@ class JobsContent extends StatelessWidget {
|
|||
style:
|
||||
Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
LinearProgressIndicator(
|
||||
value: rebuildJob?.progress == null
|
||||
? 0.0
|
||||
|
@ -206,7 +207,7 @@ class JobsContent extends StatelessWidget {
|
|||
minHeight: 7.0,
|
||||
borderRadius: BorderRadius.circular(7.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
if (rebuildJob?.error != null ||
|
||||
rebuildJob?.result != null ||
|
||||
rebuildJob?.statusText != null)
|
||||
|
@ -282,7 +283,7 @@ class JobsContent extends StatelessWidget {
|
|||
(final job) => job.uid == state.rebuildJobUid,
|
||||
);
|
||||
if (rebuildJob == null) {
|
||||
return const SizedBox();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
|
@ -322,7 +323,7 @@ class JobsContent extends StatelessWidget {
|
|||
rebuildJob.description,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
LinearProgressIndicator(
|
||||
value: rebuildJob.progress == null
|
||||
? 0.0
|
||||
|
@ -339,7 +340,7 @@ class JobsContent extends StatelessWidget {
|
|||
minHeight: 7.0,
|
||||
borderRadius: BorderRadius.circular(7.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
if (rebuildJob.error != null ||
|
||||
rebuildJob.result != null ||
|
||||
rebuildJob.statusText != null)
|
||||
|
@ -360,7 +361,7 @@ class JobsContent extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Gap(16),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().acknowledgeFinished(),
|
||||
text: 'basis.done'.tr(),
|
||||
|
@ -403,7 +404,7 @@ class JobsContent extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Gap(8),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
|
@ -423,7 +424,7 @@ class JobsContent extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Gap(16),
|
||||
BrandButton.rised(
|
||||
onPressed: hasBlockingJobs
|
||||
? null
|
||||
|
@ -436,18 +437,18 @@ class JobsContent extends StatelessWidget {
|
|||
controller: controller,
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const Gap(16),
|
||||
Center(
|
||||
child: Text(
|
||||
'jobs.title'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Gap(20),
|
||||
...widgets,
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
if (serverJobs.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -489,7 +490,7 @@ class JobsContent extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Gap(24),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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:gap/gap.dart';
|
||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||
|
||||
class EmptyPagePlaceholder extends StatelessWidget {
|
||||
|
@ -10,50 +11,72 @@ class EmptyPagePlaceholder extends StatelessWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
final bool showReadyCard;
|
||||
final IconData iconData;
|
||||
final String title;
|
||||
final String description;
|
||||
final IconData iconData;
|
||||
final bool showReadyCard;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => !showReadyCard
|
||||
? _expandedContent(context)
|
||||
: Column(
|
||||
Widget build(final BuildContext context) => showReadyCard
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: NotReadyCard(),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Center(
|
||||
child: _expandedContent(context),
|
||||
if (showReadyCard)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
horizontal: 15,
|
||||
),
|
||||
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),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
size: 50,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Gap(16),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Gap(8),
|
||||
Text(
|
||||
description,
|
||||
textAlign: TextAlign.center,
|
||||
|
|
|
@ -127,7 +127,9 @@ class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
|
|||
Widget build(final BuildContext context) {
|
||||
final isMobile =
|
||||
widget.ignoreBreakpoints ? true : Breakpoints.small.isActive(context);
|
||||
final isJobsListEmpty = context.watch<JobsCubit>().state is JobsStateEmpty;
|
||||
final isJobsListEmpty = widget.hasFlashButton
|
||||
? context.watch<JobsCubit>().state is JobsStateEmpty
|
||||
: true;
|
||||
return SliverAppBar(
|
||||
expandedHeight:
|
||||
widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height,
|
||||
|
|
|
@ -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]);
|
||||
};
|
||||
}
|
|
@ -38,8 +38,14 @@ class BackupDetailsPage extends StatelessWidget {
|
|||
: StateType.uninitialized;
|
||||
final bool preventActions = backupsState.preventActions;
|
||||
final List<Backup> backups = backupsState.backups;
|
||||
final List<Service> services =
|
||||
context.watch<ServicesBloc>().state.servicesThatCanBeBackedUp;
|
||||
final List<Service> services = context
|
||||
.watch<ServicesBloc>()
|
||||
.state
|
||||
.servicesThatCanBeBackedUp
|
||||
.where(
|
||||
(final service) => service.isEnabled,
|
||||
)
|
||||
.toList();
|
||||
final Duration? autobackupPeriod = backupsState.autobackupPeriod;
|
||||
final List<ServerJob> backupJobs = context
|
||||
.watch<ServerJobsBloc>()
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
|
||||
|
@ -103,6 +104,29 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
|||
...widget.services.map(
|
||||
(final Service service) {
|
||||
final bool busy = busyServices.contains(service.id);
|
||||
final List<Widget> descriptionWidgets = [];
|
||||
if (busy) {
|
||||
descriptionWidgets.add(Text('backup.service_busy'.tr()));
|
||||
} else {
|
||||
descriptionWidgets.add(
|
||||
Text(
|
||||
'service_page.uses'.tr(
|
||||
namedArgs: {
|
||||
'usage': service.storageUsage.used.toString(),
|
||||
'volume': context
|
||||
.read<VolumesBloc>()
|
||||
.state
|
||||
.getVolume(service.storageUsage.volume ?? '')
|
||||
.displayName,
|
||||
},
|
||||
),
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
);
|
||||
descriptionWidgets.add(
|
||||
Text(service.backupDescription),
|
||||
);
|
||||
}
|
||||
return CheckboxListTile.adaptive(
|
||||
onChanged: !busy
|
||||
? (final bool? value) {
|
||||
|
@ -122,8 +146,9 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
|||
title: Text(
|
||||
service.displayName,
|
||||
),
|
||||
subtitle: Text(
|
||||
busy ? 'backup.service_busy'.tr() : service.backupDescription,
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: descriptionWidgets,
|
||||
),
|
||||
secondary: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
|
|
|
@ -39,6 +39,7 @@ class NewDeviceScreen extends StatelessWidget {
|
|||
|
||||
class _KeyDisplay extends StatelessWidget {
|
||||
const _KeyDisplay({required this.newDeviceKey});
|
||||
|
||||
final String newDeviceKey;
|
||||
|
||||
@override
|
||||
|
@ -47,7 +48,7 @@ class _KeyDisplay extends StatelessWidget {
|
|||
children: [
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
SelectableText(
|
||||
newDeviceKey,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 24,
|
||||
|
|
71
lib/ui/pages/errors/failed_to_init_secure_storage.dart
Normal file
71
lib/ui/pages/errors/failed_to_init_secure_storage.dart
Normal file
|
@ -0,0 +1,71 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/pages/more/about_application.dart';
|
||||
|
||||
class FailedToInitSecureStorageScreen extends StatelessWidget {
|
||||
const FailedToInitSecureStorageScreen({
|
||||
required this.e,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final PlatformException e;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => MaterialApp(
|
||||
home: BrandHeroScreen(
|
||||
heroIcon: Icons.error_outline,
|
||||
heroTitle: 'Failed to initialize secure storage',
|
||||
hasBackButton: false,
|
||||
children: [
|
||||
const Text(
|
||||
'SelfPrivacy requires a secure storage provided by your operating system to encrypt sensitive data, but it failed to initialize.',
|
||||
),
|
||||
if (Platform.isLinux)
|
||||
const Text(
|
||||
'Please make sure that the libsecret library is installed.',
|
||||
),
|
||||
const Gap(16),
|
||||
Text('Error: ${e.message}'),
|
||||
const Gap(16),
|
||||
const Divider(),
|
||||
const Gap(16),
|
||||
const LinkListTile(
|
||||
title: 'Our website',
|
||||
subtitle: 'selfprivacy.org',
|
||||
uri: 'https://selfprivacy.org/',
|
||||
icon: Icons.language_outlined,
|
||||
),
|
||||
const LinkListTile(
|
||||
title: 'Documentation',
|
||||
subtitle: 'selfprivacy.org/docs',
|
||||
uri: 'https://selfprivacy.org/docs/',
|
||||
icon: Icons.library_books_outlined,
|
||||
),
|
||||
const LinkListTile(
|
||||
title: 'Privacy Policy',
|
||||
subtitle: 'selfprivacy.org/privacy-policy',
|
||||
uri: 'https://selfprivacy.org/privacy-policy/',
|
||||
icon: Icons.policy_outlined,
|
||||
),
|
||||
const LinkListTile(
|
||||
title: 'Matrix support chat',
|
||||
subtitle: '#chat:selfprivacy.org',
|
||||
uri: 'https://matrix.to/#/#chat:selfprivacy.org',
|
||||
icon: Icons.question_answer_outlined,
|
||||
longPressText: '#chat:selfprivacy.org',
|
||||
),
|
||||
const LinkListTile(
|
||||
title: 'Telegram support chat',
|
||||
subtitle: '@selfprivacy_chat',
|
||||
uri: 'https://t.me/selfprivacy_chat',
|
||||
icon: Icons.question_answer_outlined,
|
||||
longPressText: '@selfprivacy_chat',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
|
@ -3,7 +3,7 @@ import 'dart:io';
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.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/ui/components/list_tiles/section_title.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue