mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-09-19 09:57:50 +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
|
# Obfuscation related
|
||||||
app.*.map.json
|
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 localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
|
@ -6,10 +13,6 @@ if (localPropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
|
@ -21,14 +24,9 @@ if (flutterVersionName == null) {
|
||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'org.selfprivacy.app'
|
namespace 'org.selfprivacy.app'
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -44,12 +42,15 @@ android {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'InvalidPackage'
|
disable 'InvalidPackage'
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId "org.selfprivacy.app"
|
applicationId "org.selfprivacy.app"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
|
@ -58,30 +59,32 @@ android {
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "default"
|
buildTypes {
|
||||||
productFlavors {
|
debug {
|
||||||
fdroid {
|
|
||||||
applicationId "pro.kherel.selfprivacy"
|
|
||||||
}
|
}
|
||||||
production {
|
profile {
|
||||||
applicationIdSuffix ""
|
|
||||||
}
|
}
|
||||||
nightly {
|
release {
|
||||||
applicationIdSuffix ".nightly"
|
|
||||||
versionCode project.getVersionCode()
|
|
||||||
versionName "nightly-" + project.getVersionCode()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buildFeatures {
|
||||||
|
flavorDimensions = ["default"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
flavorDimensions "default"
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
fdroid {
|
fdroid {
|
||||||
|
dimension 'default'
|
||||||
applicationId "pro.kherel.selfprivacy"
|
applicationId "pro.kherel.selfprivacy"
|
||||||
}
|
}
|
||||||
production {
|
production {
|
||||||
applicationIdSuffix ""
|
dimension 'default'
|
||||||
}
|
}
|
||||||
nightly {
|
nightly {
|
||||||
|
dimension 'default'
|
||||||
applicationIdSuffix ".nightly"
|
applicationIdSuffix ".nightly"
|
||||||
versionCode project.getVersionCode()
|
versionCode project.getVersionCode()
|
||||||
versionName "nightly-" + project.getVersionCode()
|
versionName "nightly-" + project.getVersionCode()
|
||||||
|
@ -93,6 +96,5 @@ flutter {
|
||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {}
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.9.21'
|
ext.getVersionCode = { ->
|
||||||
ext.getVersionCode = { ->
|
|
||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
|
@ -13,15 +12,6 @@ buildscript {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx4G
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.bundle.enableUncompressedNativeLibs=false
|
android.bundle.enableUncompressedNativeLibs=false
|
||||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||||
|
|
|
@ -1,11 +1,25 @@
|
||||||
include ':app'
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
def properties = new Properties()
|
|
||||||
|
|
||||||
assert localPropertiesFile.exists()
|
repositories {
|
||||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
plugins {
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
id "com.android.application" version "7.3.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.9.21" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
|
@ -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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file 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
|
```sh
|
||||||
cat /etc/nixos/userdata/tokens.json
|
sp-print-api-token
|
||||||
```
|
```
|
||||||
|
|
||||||
This file will have a similar construction:
|
Copy the token from the terminal and paste it in the next window.
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"tokens": [
|
|
||||||
{
|
|
||||||
"token": "token_to_copy",
|
|
||||||
"name": "device_name",
|
|
||||||
"date": "date"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the token from the file and paste it in the next window.
|
|
||||||
|
|
|
@ -333,14 +333,12 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "إعدادات التطبيق",
|
"title": "إعدادات التطبيق",
|
||||||
"system_dark_theme_title": "الوضع الافتراضي للنظام",
|
"system_theme_mode_title": "الوضع الافتراضي للنظام",
|
||||||
"system_dark_theme_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
|
"system_theme_mode_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
|
||||||
"dark_theme_title": "الوضع الداكن",
|
"dark_theme_title": "الوضع الداكن",
|
||||||
|
"change_application_theme": "قم بتبديل وضع التطبيق",
|
||||||
"dangerous_settings": "إعدادات خطرة",
|
"dangerous_settings": "إعدادات خطرة",
|
||||||
"reset_config_title": "قم بإعادة ضبط إعدادات التطبيق",
|
"reset_config_title": "قم بإعادة ضبط إعدادات التطبيق",
|
||||||
"delete_server_title": "قم بحذف الخادم",
|
|
||||||
"delete_server_description": "سيزيل هذا الخادم الخاص بك، حيث أنه لن تتمكن من الوصول إليه بعد ذلك.",
|
|
||||||
"dark_theme_description": "قم بتبديل وضع التطبيق",
|
|
||||||
"reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز."
|
"reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز."
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
|
|
|
@ -54,15 +54,15 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Tətbiq parametrləri",
|
"title": "Tətbiq parametrləri",
|
||||||
|
"system_theme_mode_title": "Defolt sistem mövzusu",
|
||||||
|
"system_theme_mode_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin",
|
||||||
"dark_theme_title": "Qaranlıq mövzu",
|
"dark_theme_title": "Qaranlıq mövzu",
|
||||||
|
"change_application_theme": "Rəng mövzusunu dəyişdirin",
|
||||||
|
"dangerous_settings": "Təhlükəli Parametrlər",
|
||||||
"reset_config_title": "Tətbiq Sıfırlayın",
|
"reset_config_title": "Tətbiq Sıfırlayın",
|
||||||
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın.",
|
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın."
|
||||||
"delete_server_title": "Serveri silin",
|
|
||||||
"dark_theme_description": "Rəng mövzusunu dəyişdirin",
|
|
||||||
"delete_server_description": "Əməliyyat serveri siləcək. Bundan sonra o, əlçatmaz olacaq.",
|
|
||||||
"system_dark_theme_title": "Defolt sistem mövzusu",
|
|
||||||
"system_dark_theme_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin",
|
|
||||||
"dangerous_settings": "Təhlükəli Parametrlər"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH açarları",
|
"title": "SSH açarları",
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"connect_to_server_provider": "Аўтарызавацца ў ",
|
"connect_to_server_provider": "Аўтарызавацца ў ",
|
||||||
"connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер",
|
"connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер",
|
||||||
"steps": {
|
"steps": {
|
||||||
"nixos_installation": "Ўстаноўка NixOS",
|
"nixos_installation": "Ўсталёўка NixOS",
|
||||||
"hosting": "Хостынг",
|
"hosting": "Хостынг",
|
||||||
"server_type": "Тып сервера",
|
"server_type": "Тып сервера",
|
||||||
"dns_provider": "DNS правайдэр",
|
"dns_provider": "DNS правайдэр",
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
"domain": "Дамен",
|
"domain": "Дамен",
|
||||||
"master_account": "Майстар акаўнт",
|
"master_account": "Майстар акаўнт",
|
||||||
"server": "Сервер",
|
"server": "Сервер",
|
||||||
"dns_setup": "Устаноўка DNS",
|
"dns_setup": "Усталёўка DNS",
|
||||||
"server_reboot": "Перазагрузка сервера",
|
"server_reboot": "Перазагрузка сервера",
|
||||||
"final_checks": "Фінальныя праверкі"
|
"final_checks": "Фінальныя праверкі"
|
||||||
},
|
},
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
"modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен",
|
"modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен",
|
||||||
"modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу",
|
"modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу",
|
||||||
"fallback_select_provider_console": "Доступ да кансолі хостынгу.",
|
"fallback_select_provider_console": "Доступ да кансолі хостынгу.",
|
||||||
"provider_connected_description": "Сувязь устаноўлена. Увядзіце свой токен з доступам да {}:",
|
"provider_connected_description": "Сувязь наладжана. Увядзіце свой токен з доступам да {}:",
|
||||||
"choose_server": "Выберыце сервер",
|
"choose_server": "Выберыце сервер",
|
||||||
"no_servers": "На вашым акаўнце няма даступных сэрвэраў.",
|
"no_servers": "На вашым акаўнце няма даступных сэрвэраў.",
|
||||||
"modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.",
|
"modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.",
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
"authorize_new_device": "Аўтарызаваць новую прыладу",
|
"authorize_new_device": "Аўтарызаваць новую прыладу",
|
||||||
"access_granted_on": "Доступ выдадзены {}",
|
"access_granted_on": "Доступ выдадзены {}",
|
||||||
"tip": "Націсніце на прыладу, каб адклікаць доступ.",
|
"tip": "Націсніце на прыладу, каб адклікаць доступ.",
|
||||||
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыкладанне SelfPrivacy."
|
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыладу SelfPrivacy."
|
||||||
},
|
},
|
||||||
"add_new_device_screen": {
|
"add_new_device_screen": {
|
||||||
"description": "Увядзіце гэты ключ на новай прыладзе:",
|
"description": "Увядзіце гэты ключ на новай прыладзе:",
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
"revoke_device_alert": {
|
"revoke_device_alert": {
|
||||||
"header": "Адклікаць доступ?",
|
"header": "Адклікаць доступ?",
|
||||||
"yes": "Адклікаць",
|
"yes": "Адклікаць",
|
||||||
"no": "Адменіць",
|
"no": "Адхіліць",
|
||||||
"description": "Прылада {} больш не зможа кіраваць серверам."
|
"description": "Прылада {} больш не зможа кіраваць серверам."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
"later": "Прапусціць і наладзіць потым",
|
"later": "Прапусціць і наладзіць потым",
|
||||||
"no_data": "Няма дадзеных",
|
"no_data": "Няма дадзеных",
|
||||||
"services": "Сэрвісы",
|
"services": "Сэрвісы",
|
||||||
"users": "Ужыткоўнікі",
|
"users": "Карыстальнікі",
|
||||||
"more": "Дадаткова",
|
"more": "Дадаткова",
|
||||||
"got_it": "Зразумеў",
|
"got_it": "Зразумеў",
|
||||||
"settings": "Налады",
|
"settings": "Налады",
|
||||||
|
@ -234,7 +234,7 @@
|
||||||
},
|
},
|
||||||
"more_page": {
|
"more_page": {
|
||||||
"configuration_wizard": "Майстар наладкі",
|
"configuration_wizard": "Майстар наладкі",
|
||||||
"onboarding": "Прівітанне",
|
"onboarding": "Прывітанне",
|
||||||
"create_ssh_key": "SSH ключы адміністратара"
|
"create_ssh_key": "SSH ключы адміністратара"
|
||||||
},
|
},
|
||||||
"about_application_page": {
|
"about_application_page": {
|
||||||
|
@ -244,16 +244,16 @@
|
||||||
"privacy_policy": "Палітыка прыватнасці"
|
"privacy_policy": "Палітыка прыватнасці"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"reset_config_description": "Скінуць API ключы i суперкарыстальніка.",
|
|
||||||
"delete_server_description": "Дзеянне прывядзе да выдалення сервера. Пасля гэтага ён будзе недаступны.",
|
|
||||||
"title": "Налады праграмы",
|
"title": "Налады праграмы",
|
||||||
|
"system_theme_mode_title": "Сістэмная тэма па-змаўчанні",
|
||||||
|
"system_theme_mode_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
|
||||||
"dark_theme_title": "Цёмная тэма",
|
"dark_theme_title": "Цёмная тэма",
|
||||||
"dark_theme_description": "Змяніць каляровую тэму",
|
"change_application_theme": "Змяніць каляровую тэму",
|
||||||
|
"language": "Мова",
|
||||||
|
"click_to_change_locale": "Націсніце, каб адчыніць меню выбару мовы",
|
||||||
|
"dangerous_settings": "Небяспечныя налады",
|
||||||
"reset_config_title": "Скід налад",
|
"reset_config_title": "Скід налад",
|
||||||
"delete_server_title": "Выдаліць сервер",
|
"reset_config_description": "Скінуць API ключы i суперкарыстальніка."
|
||||||
"system_dark_theme_title": "Сістэмная тэма па-змаўчанні",
|
|
||||||
"system_dark_theme_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
|
|
||||||
"dangerous_settings": "Небяспечныя наладкі"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.",
|
"root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.",
|
||||||
|
|
|
@ -54,15 +54,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Nastavení aplikace",
|
"title": "Nastavení aplikace",
|
||||||
|
"system_theme_mode_title": "Výchozí téma systému",
|
||||||
|
"system_theme_mode_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému",
|
||||||
"dark_theme_title": "Tmavé téma",
|
"dark_theme_title": "Tmavé téma",
|
||||||
|
"change_application_theme": "Přepnutí tématu aplikace",
|
||||||
|
"dangerous_settings": "Nebezpečná nastavení",
|
||||||
"reset_config_title": "Obnovení konfigurace aplikace",
|
"reset_config_title": "Obnovení konfigurace aplikace",
|
||||||
"reset_config_description": "Obnovení klíčů API a uživatele root.",
|
"reset_config_description": "Obnovení klíčů API a uživatele root."
|
||||||
"delete_server_title": "Odstranit server",
|
|
||||||
"dark_theme_description": "Přepnutí tématu aplikace",
|
|
||||||
"delete_server_description": "Tím odstraníte svůj server. Nebude již přístupný.",
|
|
||||||
"system_dark_theme_title": "Výchozí téma systému",
|
|
||||||
"system_dark_theme_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému",
|
|
||||||
"dangerous_settings": "Nebezpečná nastavení"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "Klíče SSH",
|
"title": "Klíče SSH",
|
||||||
|
|
|
@ -57,15 +57,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Anwendungseinstellungen",
|
"title": "Anwendungseinstellungen",
|
||||||
|
"system_theme_mode_title": "Standard-Systemthema",
|
||||||
|
"system_theme_mode_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema",
|
||||||
"dark_theme_title": "Dunkles Thema",
|
"dark_theme_title": "Dunkles Thema",
|
||||||
"dark_theme_description": "Ihr Anwendungsdesign wechseln",
|
"change_application_theme": "Ihr Anwendungsdesign wechseln",
|
||||||
|
"dangerous_settings": "Gefährliche Einstellungen",
|
||||||
"reset_config_title": "Anwendungseinstellungen zurücksetzen",
|
"reset_config_title": "Anwendungseinstellungen zurücksetzen",
|
||||||
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen.",
|
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen."
|
||||||
"delete_server_title": "Server löschen",
|
|
||||||
"delete_server_description": "Das wird Ihren Server löschen. Es wird nicht mehr zugänglich sein.",
|
|
||||||
"system_dark_theme_title": "Standard-Systemthema",
|
|
||||||
"system_dark_theme_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema",
|
|
||||||
"dangerous_settings": "Gefährliche Einstellungen"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH Schlüssel",
|
"title": "SSH Schlüssel",
|
||||||
|
|
|
@ -47,7 +47,29 @@
|
||||||
"console_page": {
|
"console_page": {
|
||||||
"title": "Console",
|
"title": "Console",
|
||||||
"waiting": "Waiting for initialization…",
|
"waiting": "Waiting for initialization…",
|
||||||
"copy": "Copy"
|
"copy": "Copy",
|
||||||
|
"copy_raw": "Raw response",
|
||||||
|
"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": {
|
"about_application_page": {
|
||||||
"title": "About & support",
|
"title": "About & support",
|
||||||
|
@ -75,10 +97,12 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Application settings",
|
"title": "Application settings",
|
||||||
"system_dark_theme_title": "System default theme",
|
"system_theme_mode_title": "System default theme",
|
||||||
"system_dark_theme_description": "Use light or dark theme depending on system settings",
|
"system_theme_mode_description": "Use light or dark theme depending on system settings",
|
||||||
"dark_theme_title": "Dark theme",
|
"dark_theme_title": "Dark theme",
|
||||||
"dark_theme_description": "Switch your application theme",
|
"change_application_theme": "Switch your application theme",
|
||||||
|
"language": "Language",
|
||||||
|
"click_to_change_locale": "Click to open language list",
|
||||||
"dangerous_settings": "Dangerous settings",
|
"dangerous_settings": "Dangerous settings",
|
||||||
"reset_config_title": "Reset application config",
|
"reset_config_title": "Reset application config",
|
||||||
"reset_config_description": "Resets API keys and root user."
|
"reset_config_description": "Resets API keys and root user."
|
||||||
|
@ -565,6 +589,8 @@
|
||||||
"upgrade_success": "Server upgrade started",
|
"upgrade_success": "Server upgrade started",
|
||||||
"upgrade_failed": "Failed to upgrade server",
|
"upgrade_failed": "Failed to upgrade server",
|
||||||
"upgrade_server": "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",
|
"reboot_server": "Reboot server",
|
||||||
"create_ssh_key": "Create SSH key for {}",
|
"create_ssh_key": "Create SSH key for {}",
|
||||||
"delete_ssh_key": "Delete SSH key for {}",
|
"delete_ssh_key": "Delete SSH key for {}",
|
||||||
|
@ -606,5 +632,16 @@
|
||||||
"reset_onboarding": "Reset onboarding switch",
|
"reset_onboarding": "Reset onboarding switch",
|
||||||
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
|
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
|
||||||
"cubit_statuses": "Cubit loading statuses"
|
"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",
|
"test": "es-test",
|
||||||
"locale": "es",
|
"locale": "es",
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"reset_config_title": "Restablecer la configuración de la aplicación",
|
|
||||||
"dark_theme_description": "Cambia el tema de tu aplicación",
|
|
||||||
"reset_config_description": "Restablecer claves API y usuario root.",
|
|
||||||
"delete_server_title": "Eliminar servidor",
|
|
||||||
"delete_server_description": "Esto elimina su servidor. Ya no será accesible.",
|
|
||||||
"title": "Ajustes de la aplicación",
|
"title": "Ajustes de la aplicación",
|
||||||
|
"system_theme_mode_title": "Tema del sistema",
|
||||||
|
"system_theme_mode_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
|
||||||
"dark_theme_title": "Tema oscuro",
|
"dark_theme_title": "Tema oscuro",
|
||||||
"system_dark_theme_title": "Tema del sistema",
|
"change_application_theme": "Cambia el tema de tu aplicación",
|
||||||
"system_dark_theme_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
|
"dangerous_settings": "Configuraciones peligrosas",
|
||||||
"dangerous_settings": "Configuraciones peligrosas"
|
"reset_config_title": "Restablecer la configuración de la aplicación",
|
||||||
|
"reset_config_description": "Restablecer claves API y usuario root."
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
{
|
{
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"system_dark_theme_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest",
|
|
||||||
"delete_server_description": "See eemaldab teie serveri. Seda ei saa enam juurde pääseda.",
|
|
||||||
"title": "Rakenduse seaded",
|
"title": "Rakenduse seaded",
|
||||||
"system_dark_theme_title": "Süsteemi vaiketeema",
|
"system_theme_mode_title": "Süsteemi vaiketeema",
|
||||||
|
"system_theme_mode_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest",
|
||||||
"dark_theme_title": "Tume teema",
|
"dark_theme_title": "Tume teema",
|
||||||
"dark_theme_description": "Vaheta oma rakenduse teemat",
|
"change_application_theme": "Vaheta oma rakenduse teemat",
|
||||||
"dangerous_settings": "Ohtlikud seaded",
|
"dangerous_settings": "Ohtlikud seaded",
|
||||||
"reset_config_title": "Lähtesta rakenduse konfiguratsioon",
|
"reset_config_title": "Lähtesta rakenduse konfiguratsioon",
|
||||||
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja.",
|
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja."
|
||||||
"delete_server_title": "Kustuta server"
|
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"reboot_after_upgrade": "Taaskäivita pärast värskendust",
|
"reboot_after_upgrade": "Taaskäivita pärast värskendust",
|
||||||
|
|
|
@ -56,15 +56,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Paramètres de l'application",
|
"title": "Paramètres de l'application",
|
||||||
"dark_theme_description": "Changer le thème de l'application",
|
"system_theme_mode_title": "Thème par défaut du système",
|
||||||
"reset_config_title": "Réinitialiser la configuration de l'application",
|
"system_theme_mode_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
|
||||||
"delete_server_title": "Supprimer le serveur",
|
|
||||||
"delete_server_description": "Cela va supprimer votre serveur. Celui-ci ne sera plus accessible.",
|
|
||||||
"dark_theme_title": "Thème sombre",
|
"dark_theme_title": "Thème sombre",
|
||||||
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root.",
|
"change_application_theme": "Changer le thème de l'application",
|
||||||
"system_dark_theme_title": "Thème par défaut du système",
|
"dangerous_settings": "Paramètres dangereux",
|
||||||
"system_dark_theme_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
|
"reset_config_title": "Réinitialiser la configuration de l'application",
|
||||||
"dangerous_settings": "Paramètres dangereux"
|
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root."
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "Clés SSH",
|
"title": "Clés SSH",
|
||||||
|
|
|
@ -81,15 +81,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "הגדרות יישום",
|
"title": "הגדרות יישום",
|
||||||
"system_dark_theme_title": "ערכת העיצוב כברירת המחדל של המערכת",
|
"system_theme_mode_title": "ערכת העיצוב כברירת המחדל של המערכת",
|
||||||
"system_dark_theme_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
|
"system_theme_mode_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
|
||||||
"dark_theme_title": "ערכת עיצוב כהה",
|
"dark_theme_title": "ערכת עיצוב כהה",
|
||||||
"dark_theme_description": "החלפת ערכת העיצוב של המערכת שלך",
|
"change_application_theme": "החלפת ערכת העיצוב של המערכת שלך",
|
||||||
"dangerous_settings": "הגדרות מסוכנות",
|
"dangerous_settings": "הגדרות מסוכנות",
|
||||||
"reset_config_title": "איפוס הגדרות היישומון",
|
"reset_config_title": "איפוס הגדרות היישומון",
|
||||||
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל.",
|
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל."
|
||||||
"delete_server_title": "מחיקת שרת",
|
|
||||||
"delete_server_description": "מסיר את השרת שלך. הוא לא יהיה זמין עוד."
|
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
"create_new_select_heading": "לבחור מה לגבות",
|
"create_new_select_heading": "לבחור מה לגבות",
|
||||||
|
|
|
@ -91,16 +91,14 @@
|
||||||
"bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз."
|
"bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз."
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
|
"title": "Қосымша параметрлері",
|
||||||
|
"system_theme_mode_title": "Системалық қараңғы тақырып",
|
||||||
|
"system_theme_mode_description": "Системалық қараңғы тақырып сипаттамасы",
|
||||||
|
"dark_theme_title": "Қараңғы тақырып",
|
||||||
|
"change_application_theme": "Қараңғы тақырып сипаттамасы",
|
||||||
"dangerous_settings": "Қауіпті параметрлер",
|
"dangerous_settings": "Қауіпті параметрлер",
|
||||||
"reset_config_title": "Конфигурацияны қалпына келтіру",
|
"reset_config_title": "Конфигурацияны қалпына келтіру",
|
||||||
"title": "Қосымша параметрлері",
|
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы."
|
||||||
"system_dark_theme_title": "Системалық қараңғы тақырып",
|
|
||||||
"system_dark_theme_description": "Системалық қараңғы тақырып сипаттамасы",
|
|
||||||
"dark_theme_title": "Қараңғы тақырып",
|
|
||||||
"dark_theme_description": "Қараңғы тақырып сипаттамасы",
|
|
||||||
"delete_server_title": "Серверді жою",
|
|
||||||
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы.",
|
|
||||||
"delete_server_description": "Серверді жою сипаттамасы."
|
|
||||||
},
|
},
|
||||||
"resource_chart": {
|
"resource_chart": {
|
||||||
"month": "Ай",
|
"month": "Ай",
|
||||||
|
|
|
@ -52,16 +52,14 @@
|
||||||
"privacy_policy": "Privātuma politika"
|
"privacy_policy": "Privātuma politika"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"system_dark_theme_title": "Sistēmas noklusējuma dizains",
|
|
||||||
"dark_theme_title": "Tumšs dizains",
|
|
||||||
"title": "Aplikācijas iestatījumi",
|
"title": "Aplikācijas iestatījumi",
|
||||||
"system_dark_theme_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
|
"system_theme_mode_title": "Sistēmas noklusējuma dizains",
|
||||||
"dark_theme_description": "Lietojumprogrammas dizaina pārslēgšana",
|
"system_theme_mode_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
|
||||||
|
"dark_theme_title": "Tumšs dizains",
|
||||||
|
"change_application_theme": "Lietojumprogrammas dizaina pārslēgšana",
|
||||||
"dangerous_settings": "Bīstamie iestatījumi",
|
"dangerous_settings": "Bīstamie iestatījumi",
|
||||||
"reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju",
|
"reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju",
|
||||||
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju.",
|
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju."
|
||||||
"delete_server_title": "Izdzēst serveri",
|
|
||||||
"delete_server_description": "Šis izdzēš jūsu serveri. Tas vairs nebūs pieejams."
|
|
||||||
},
|
},
|
||||||
"locale": "lv",
|
"locale": "lv",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
|
|
|
@ -56,15 +56,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Ustawienia aplikacji",
|
"title": "Ustawienia aplikacji",
|
||||||
|
"system_theme_mode_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu",
|
||||||
|
"system_theme_mode_title": "Domyślny motyw systemowy",
|
||||||
"dark_theme_title": "Ciemny motyw aplikacji",
|
"dark_theme_title": "Ciemny motyw aplikacji",
|
||||||
"dark_theme_description": "Zmień kolor motywu aplikacji",
|
"change_application_theme": "Zmień kolor motywu aplikacji",
|
||||||
|
"dangerous_settings": "Niebezpieczne ustawienia",
|
||||||
"reset_config_title": "Resetowanie",
|
"reset_config_title": "Resetowanie",
|
||||||
"reset_config_description": "Zresetuj klucze API i użytkownika root.",
|
"reset_config_description": "Zresetuj klucze API i użytkownika root."
|
||||||
"delete_server_title": "Usuń serwer",
|
|
||||||
"delete_server_description": "Ta czynność usunie serwer. Po tym będzie niedostępny.",
|
|
||||||
"system_dark_theme_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu",
|
|
||||||
"system_dark_theme_title": "Domyślny motyw systemowy",
|
|
||||||
"dangerous_settings": "Niebezpieczne ustawienia"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "klucze SSH",
|
"title": "klucze SSH",
|
||||||
|
|
|
@ -75,15 +75,15 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Настройки приложения",
|
"title": "Настройки приложения",
|
||||||
|
"system_theme_mode_title": "Системная тема",
|
||||||
|
"system_theme_mode_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
|
||||||
"dark_theme_title": "Тёмная тема",
|
"dark_theme_title": "Тёмная тема",
|
||||||
"dark_theme_description": "Сменить цветовую тему",
|
"change_application_theme": "Сменить цветовую тему",
|
||||||
|
"language": "Язык",
|
||||||
|
"click_to_change_locale": "Нажмите, чтобы открыть список языков",
|
||||||
|
"dangerous_settings": "Опасные настройки",
|
||||||
"reset_config_title": "Сброс настроек",
|
"reset_config_title": "Сброс настроек",
|
||||||
"reset_config_description": "Сбросить API ключи и root пользователя.",
|
"reset_config_description": "Сбросить API ключи и root пользователя."
|
||||||
"delete_server_title": "Удалить сервер",
|
|
||||||
"delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен.",
|
|
||||||
"system_dark_theme_title": "Системная тема",
|
|
||||||
"system_dark_theme_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
|
|
||||||
"dangerous_settings": "Опасные настройки"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH ключи",
|
"title": "SSH ключи",
|
||||||
|
|
|
@ -103,15 +103,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Nastavenia aplikácie",
|
"title": "Nastavenia aplikácie",
|
||||||
|
"system_theme_mode_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému",
|
||||||
|
"system_theme_mode_title": "Systémová predvolená téma",
|
||||||
"dark_theme_title": "Temná téma",
|
"dark_theme_title": "Temná téma",
|
||||||
"dark_theme_description": "Zmeniť tému aplikácie",
|
"change_application_theme": "Zmeniť tému aplikácie",
|
||||||
|
"dangerous_settings": "Nebezpečné nastavenia",
|
||||||
"reset_config_title": "Resetovať nastavenia aplikácie",
|
"reset_config_title": "Resetovať nastavenia aplikácie",
|
||||||
"reset_config_description": "Resetovať kľúče API a užívateľa root.",
|
"reset_config_description": "Resetovať kľúče API a užívateľa root."
|
||||||
"delete_server_title": "Zmazať server",
|
|
||||||
"delete_server_description": "Tým sa odstráni váš server. Už nebude prístupným.",
|
|
||||||
"system_dark_theme_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému",
|
|
||||||
"system_dark_theme_title": "Systémová predvolená téma",
|
|
||||||
"dangerous_settings": "Nebezpečné nastavenia"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "Kľúče SSH",
|
"title": "Kľúče SSH",
|
||||||
|
|
|
@ -53,15 +53,13 @@
|
||||||
"application_version_text": "Različica aplikacije"
|
"application_version_text": "Različica aplikacije"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"dark_theme_title": "Temna tema",
|
|
||||||
"title": "Nastavitve aplikacije",
|
"title": "Nastavitve aplikacije",
|
||||||
"system_dark_theme_title": "Privzeta tema sistema",
|
"system_theme_mode_title": "Privzeta tema sistema",
|
||||||
"system_dark_theme_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
|
"system_theme_mode_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
|
||||||
"dark_theme_description": "Spreminjanje barvne teme",
|
"dark_theme_title": "Temna tema",
|
||||||
|
"change_application_theme": "Spreminjanje barvne teme",
|
||||||
"dangerous_settings": "Nevarne nastavitve",
|
"dangerous_settings": "Nevarne nastavitve",
|
||||||
"reset_config_title": "Ponastavitev konfiguracije aplikacije",
|
"reset_config_title": "Ponastavitev konfiguracije aplikacije"
|
||||||
"delete_server_title": "Brisanje strežnika",
|
|
||||||
"delete_server_description": "To dejanje povzroči izbris strežnika. Nato bo nedosegljiv."
|
|
||||||
},
|
},
|
||||||
"onboarding": {
|
"onboarding": {
|
||||||
"page1_title": "Digitalna neodvisnost je na voljo vsem",
|
"page1_title": "Digitalna neodvisnost je na voljo vsem",
|
||||||
|
|
|
@ -47,13 +47,11 @@
|
||||||
"privacy_policy": "นโยบายความเป็นส่วนตัว"
|
"privacy_policy": "นโยบายความเป็นส่วนตัว"
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"dark_theme_description": "สลับธีมแอปพลิเคชั่นของคุณ",
|
|
||||||
"delete_server_description": "การกระทำนี้จะลบเซิฟเวอร์ของคุณทิ้งและคุณจะไม่สามารถเข้าถึงมันได้อีก",
|
|
||||||
"title": "การตั้งค่าแอปพลิเคชัน",
|
"title": "การตั้งค่าแอปพลิเคชัน",
|
||||||
"dark_theme_title": "ธีมมืด",
|
"dark_theme_title": "ธีมมืด",
|
||||||
|
"change_application_theme": "สลับธีมแอปพลิเคชั่นของคุณ",
|
||||||
"reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น",
|
"reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น",
|
||||||
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root",
|
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root"
|
||||||
"delete_server_title": "ลบเซิฟเวอร์"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"create": "สร้างกุญแจ SSH",
|
"create": "สร้างกุญแจ SSH",
|
||||||
|
|
|
@ -41,15 +41,14 @@
|
||||||
"locale": "ua",
|
"locale": "ua",
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Налаштування додатка",
|
"title": "Налаштування додатка",
|
||||||
"reset_config_title": "Скинути налаштування",
|
"system_theme_mode_title": "Системна тема за замовчуванням",
|
||||||
|
"system_theme_mode_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
|
||||||
"dark_theme_title": "Темна тема",
|
"dark_theme_title": "Темна тема",
|
||||||
"dark_theme_description": "Змінити тему додатка",
|
"change_application_theme": "Змінити тему додатка",
|
||||||
"reset_config_description": "Скинути API ключі та root користувача.",
|
"language": "Мова",
|
||||||
"delete_server_title": "Видалити сервер",
|
"dangerous_settings": "Небезпечні налаштування",
|
||||||
"delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний.",
|
"reset_config_title": "Скинути налаштування",
|
||||||
"system_dark_theme_title": "Системна тема за замовчуванням",
|
"reset_config_description": "Скинути API ключі та root користувача."
|
||||||
"system_dark_theme_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
|
|
||||||
"dangerous_settings": "Небезпечні налаштування"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?",
|
"delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?",
|
||||||
|
|
|
@ -476,15 +476,13 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "应用设置",
|
"title": "应用设置",
|
||||||
"system_dark_theme_title": "系统默认主题",
|
"system_theme_mode_title": "系统默认主题",
|
||||||
|
"system_theme_mode_description": "根据系统设置自动使用明亮或暗色主题",
|
||||||
"dark_theme_title": "暗色主题",
|
"dark_theme_title": "暗色主题",
|
||||||
"system_dark_theme_description": "根据系统设置自动使用明亮或暗色主题",
|
"change_application_theme": "切换应用主题",
|
||||||
"dark_theme_description": "切换应用主题",
|
|
||||||
"dangerous_settings": "危险设置",
|
"dangerous_settings": "危险设置",
|
||||||
"reset_config_title": "重置应用配置",
|
"reset_config_title": "重置应用配置",
|
||||||
"delete_server_title": "删除服务器",
|
"delete_server_title": "删除服务器"
|
||||||
"delete_server_description": "这将移除您的服务器。它将不再可以访问。",
|
|
||||||
"reset_config_description": "重置API密钥和root用户。"
|
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH密钥",
|
"title": "SSH密钥",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
app-id: org.selfprivacy.app
|
app-id: org.selfprivacy.app
|
||||||
runtime: org.freedesktop.Platform
|
runtime: org.freedesktop.Platform
|
||||||
runtime-version: '22.08'
|
runtime-version: '23.08'
|
||||||
sdk: org.freedesktop.Sdk
|
sdk: org.freedesktop.Sdk
|
||||||
command: selfprivacy
|
command: selfprivacy
|
||||||
finish-args:
|
finish-args:
|
||||||
|
@ -11,6 +11,7 @@ finish-args:
|
||||||
- "--share=network"
|
- "--share=network"
|
||||||
- "--own-name=org.selfprivacy.app"
|
- "--own-name=org.selfprivacy.app"
|
||||||
- "--device=dri"
|
- "--device=dri"
|
||||||
|
- "--talk-name=org.freedesktop.secrets"
|
||||||
modules:
|
modules:
|
||||||
- name: selfprivacy
|
- name: selfprivacy
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
|
@ -35,7 +36,7 @@ modules:
|
||||||
sources:
|
sources:
|
||||||
- type: git
|
- type: git
|
||||||
url: https://gitlab.gnome.org/GNOME/libsecret.git
|
url: https://gitlab.gnome.org/GNOME/libsecret.git
|
||||||
tag: 0.20.5
|
tag: 0.21.4
|
||||||
- name: libjsoncpp
|
- name: libjsoncpp
|
||||||
buildsystem: meson
|
buildsystem: meson
|
||||||
config-opts:
|
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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/connection_status_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||||
|
@ -56,58 +55,46 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) => MultiProvider(
|
||||||
const isDark = false;
|
providers: [
|
||||||
const isAutoDark = true;
|
BlocProvider(
|
||||||
|
create: (final _) => supportSystemCubit,
|
||||||
return MultiProvider(
|
),
|
||||||
providers: [
|
BlocProvider(
|
||||||
BlocProvider(
|
create: (final _) => serverInstallationCubit,
|
||||||
create: (final _) => AppSettingsCubit(
|
lazy: false,
|
||||||
isDarkModeOn: isDark,
|
),
|
||||||
isAutoDarkModeOn: isAutoDark,
|
BlocProvider(
|
||||||
isOnboardingShowing: true,
|
create: (final _) => usersBloc,
|
||||||
)..load(),
|
lazy: false,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => supportSystemCubit,
|
create: (final _) => servicesBloc,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => serverInstallationCubit,
|
create: (final _) => backupsBloc,
|
||||||
lazy: false,
|
),
|
||||||
),
|
BlocProvider(
|
||||||
BlocProvider(
|
create: (final _) => dnsRecordsCubit,
|
||||||
create: (final _) => usersBloc,
|
),
|
||||||
lazy: false,
|
BlocProvider(
|
||||||
),
|
create: (final _) => recoveryKeyBloc,
|
||||||
BlocProvider(
|
),
|
||||||
create: (final _) => servicesBloc,
|
BlocProvider(
|
||||||
),
|
create: (final _) => devicesBloc,
|
||||||
BlocProvider(
|
),
|
||||||
create: (final _) => backupsBloc,
|
BlocProvider(
|
||||||
),
|
create: (final _) => serverJobsBloc,
|
||||||
BlocProvider(
|
),
|
||||||
create: (final _) => dnsRecordsCubit,
|
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||||
),
|
BlocProvider(
|
||||||
BlocProvider(
|
create: (final _) => serverDetailsCubit,
|
||||||
create: (final _) => recoveryKeyBloc,
|
),
|
||||||
),
|
BlocProvider(create: (final _) => volumesBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => devicesBloc,
|
create: (final _) => JobsCubit(),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
],
|
||||||
create: (final _) => serverJobsBloc,
|
child: widget.child,
|
||||||
),
|
);
|
||||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => serverDetailsCubit,
|
|
||||||
),
|
|
||||||
BlocProvider(create: (final _) => volumesBloc),
|
|
||||||
BlocProvider(
|
|
||||||
create: (final _) => JobsCubit(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
import 'package:selfprivacy/logic/get_it/api_config.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/console.dart';
|
import 'package:selfprivacy/logic/get_it/console_model.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
import 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/resources_model.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_config.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/console.dart';
|
export 'package:selfprivacy/logic/get_it/console_model.dart';
|
||||||
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
export 'package:selfprivacy/logic/get_it/navigation.dart';
|
||||||
|
|
||||||
final GetIt getIt = GetIt.instance;
|
final GetIt getIt = GetIt.instance;
|
||||||
|
|
||||||
Future<void> getItSetup() async {
|
Future<void> getItSetup() async {
|
||||||
getIt.registerSingleton<NavigationService>(NavigationService());
|
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||||
|
|
||||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||||
getIt.registerSingleton<ResourcesModel>(ResourcesModel()..init());
|
getIt.registerSingleton<ResourcesModel>(ResourcesModel()..init());
|
||||||
getIt.registerSingleton<WizardDataModel>(WizardDataModel()..init());
|
getIt.registerSingleton<WizardDataModel>(WizardDataModel()..init());
|
||||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
|
||||||
|
final apiConfigModel = ApiConfigModel();
|
||||||
|
await apiConfigModel.init();
|
||||||
|
getIt.registerSingleton<ApiConfigModel>(apiConfigModel);
|
||||||
|
|
||||||
getIt.registerSingleton<ApiConnectionRepository>(
|
getIt.registerSingleton<ApiConnectionRepository>(
|
||||||
ApiConnectionRepository()..init(),
|
ApiConnectionRepository()..init(),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
|
@ -36,121 +36,136 @@ class HiveConfig {
|
||||||
|
|
||||||
await Hive.openBox(BNames.appSettingsBox);
|
await Hive.openBox(BNames.appSettingsBox);
|
||||||
|
|
||||||
final HiveAesCipher cipher = HiveAesCipher(
|
try {
|
||||||
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
final HiveAesCipher cipher = HiveAesCipher(
|
||||||
);
|
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
||||||
|
);
|
||||||
|
|
||||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||||
await Hive.openBox(BNames.resourcesBox, encryptionCipher: cipher);
|
await Hive.openBox(BNames.resourcesBox, encryptionCipher: cipher);
|
||||||
await Hive.openBox(BNames.wizardDataBox, encryptionCipher: cipher);
|
await Hive.openBox(BNames.wizardDataBox, encryptionCipher: cipher);
|
||||||
|
|
||||||
final Box resourcesBox = Hive.box(BNames.resourcesBox);
|
final Box resourcesBox = Hive.box(BNames.resourcesBox);
|
||||||
if (resourcesBox.isEmpty) {
|
if (resourcesBox.isEmpty) {
|
||||||
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||||
|
|
||||||
final String? serverProviderKey =
|
final String? serverProviderKey =
|
||||||
serverInstallationBox.get(BNames.hetznerKey);
|
serverInstallationBox.get(BNames.hetznerKey);
|
||||||
final String? serverLocation =
|
final String? serverLocation =
|
||||||
serverInstallationBox.get(BNames.serverLocation);
|
serverInstallationBox.get(BNames.serverLocation);
|
||||||
final String? dnsProviderKey =
|
final String? dnsProviderKey =
|
||||||
serverInstallationBox.get(BNames.cloudFlareKey);
|
serverInstallationBox.get(BNames.cloudFlareKey);
|
||||||
final BackupsCredential? backblazeCredential =
|
final BackupsCredential? backblazeCredential =
|
||||||
serverInstallationBox.get(BNames.backblazeCredential);
|
serverInstallationBox.get(BNames.backblazeCredential);
|
||||||
final ServerDomain? serverDomain =
|
final ServerDomain? serverDomain =
|
||||||
serverInstallationBox.get(BNames.serverDomain);
|
serverInstallationBox.get(BNames.serverDomain);
|
||||||
final ServerHostingDetails? serverDetails =
|
final ServerHostingDetails? serverDetails =
|
||||||
serverInstallationBox.get(BNames.serverDetails);
|
serverInstallationBox.get(BNames.serverDetails);
|
||||||
final BackblazeBucket? backblazeBucket =
|
final BackblazeBucket? backblazeBucket =
|
||||||
serverInstallationBox.get(BNames.backblazeBucket);
|
serverInstallationBox.get(BNames.backblazeBucket);
|
||||||
final String? serverType =
|
final String? serverType =
|
||||||
serverInstallationBox.get(BNames.serverTypeIdentifier);
|
serverInstallationBox.get(BNames.serverTypeIdentifier);
|
||||||
final ServerProviderType? serverProvider =
|
final ServerProviderType? serverProvider =
|
||||||
serverInstallationBox.get(BNames.serverProvider);
|
serverInstallationBox.get(BNames.serverProvider);
|
||||||
final DnsProviderType? dnsProvider =
|
final DnsProviderType? dnsProvider =
|
||||||
serverInstallationBox.get(BNames.dnsProvider);
|
serverInstallationBox.get(BNames.dnsProvider);
|
||||||
|
|
||||||
if (serverProviderKey != null &&
|
if (serverProviderKey != null &&
|
||||||
(serverProvider != null ||
|
(serverProvider != null ||
|
||||||
(serverDetails != null &&
|
(serverDetails != null &&
|
||||||
serverDetails.provider != ServerProviderType.unknown))) {
|
serverDetails.provider != ServerProviderType.unknown))) {
|
||||||
final ServerProviderCredential serverProviderCredential =
|
final ServerProviderCredential serverProviderCredential =
|
||||||
ServerProviderCredential(
|
ServerProviderCredential(
|
||||||
tokenId: null,
|
tokenId: null,
|
||||||
token: serverProviderKey,
|
token: serverProviderKey,
|
||||||
provider: serverProvider ?? serverDetails!.provider,
|
provider: serverProvider ?? serverDetails!.provider,
|
||||||
associatedServerIds: serverDetails != null ? [serverDetails.id] : [],
|
associatedServerIds: serverDetails != null ? [serverDetails.id] : [],
|
||||||
);
|
);
|
||||||
|
|
||||||
await resourcesBox
|
await resourcesBox
|
||||||
.put(BNames.serverProviderTokens, [serverProviderCredential]);
|
.put(BNames.serverProviderTokens, [serverProviderCredential]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dnsProviderKey != null &&
|
if (dnsProviderKey != null &&
|
||||||
(dnsProvider != null ||
|
(dnsProvider != null ||
|
||||||
(serverDomain != null &&
|
(serverDomain != null &&
|
||||||
serverDomain.provider != DnsProviderType.unknown))) {
|
serverDomain.provider != DnsProviderType.unknown))) {
|
||||||
final DnsProviderCredential dnsProviderCredential =
|
final DnsProviderCredential dnsProviderCredential =
|
||||||
DnsProviderCredential(
|
DnsProviderCredential(
|
||||||
tokenId: null,
|
tokenId: null,
|
||||||
token: dnsProviderKey,
|
token: dnsProviderKey,
|
||||||
provider: dnsProvider ?? serverDomain!.provider,
|
provider: dnsProvider ?? serverDomain!.provider,
|
||||||
associatedDomainNames:
|
associatedDomainNames:
|
||||||
serverDomain != null ? [serverDomain.domainName] : [],
|
serverDomain != null ? [serverDomain.domainName] : [],
|
||||||
);
|
);
|
||||||
|
|
||||||
await resourcesBox
|
await resourcesBox
|
||||||
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
|
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backblazeCredential != null) {
|
if (backblazeCredential != null) {
|
||||||
await resourcesBox
|
await resourcesBox
|
||||||
.put(BNames.backupsProviderTokens, [backblazeCredential]);
|
.put(BNames.backupsProviderTokens, [backblazeCredential]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backblazeBucket != null) {
|
if (backblazeBucket != null) {
|
||||||
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
|
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDetails != null && serverDomain != null) {
|
if (serverDetails != null && serverDomain != null) {
|
||||||
await resourcesBox.put(BNames.servers, [
|
await resourcesBox.put(BNames.servers, [
|
||||||
Server(
|
Server(
|
||||||
domain: serverDomain,
|
domain: serverDomain,
|
||||||
hostingDetails: serverDetails.copyWith(
|
hostingDetails: serverDetails.copyWith(
|
||||||
serverLocation: serverLocation,
|
serverLocation: serverLocation,
|
||||||
serverType: serverType,
|
serverType: serverType,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
]);
|
||||||
]);
|
}
|
||||||
}
|
}
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
print('HiveConfig: Error while opening boxes: $e');
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Uint8List> getEncryptedKey(final String encKey) async {
|
static Future<Uint8List> getEncryptedKey(final String encKey) async {
|
||||||
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
||||||
final bool hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
try {
|
||||||
if (!hasEncryptionKey) {
|
final bool hasEncryptionKey =
|
||||||
final List<int> key = Hive.generateSecureKey();
|
await secureStorage.containsKey(key: encKey);
|
||||||
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
if (!hasEncryptionKey) {
|
||||||
}
|
final List<int> key = Hive.generateSecureKey();
|
||||||
|
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
||||||
|
}
|
||||||
|
|
||||||
final String? string = await secureStorage.read(key: encKey);
|
final String? string = await secureStorage.read(key: encKey);
|
||||||
return base64Url.decode(string!);
|
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
|
/// Mappings for the different boxes and their keys
|
||||||
class BNames {
|
class BNames {
|
||||||
/// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing]
|
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
|
||||||
static String appSettingsBox = 'appSettings';
|
static String appSettingsBox = 'appSettings';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isDarkModeOn = 'isDarkModeOn';
|
static String darkThemeModeOn = 'isDarkModeOn';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
|
static String systemThemeModeOn = 'isAutoDarkModeOn';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isOnboardingShowing = 'isOnboardingShowing';
|
static String shouldShowOnboarding = 'isOnboardingShowing';
|
||||||
|
|
||||||
|
/// A string field
|
||||||
|
static String appLocale = 'appLocale';
|
||||||
|
|
||||||
/// Encryption key to decrypt [serverInstallationBox] box.
|
/// Encryption key to decrypt [serverInstallationBox] box.
|
||||||
static String serverInstallationEncryptionKey = 'key';
|
static String serverInstallationEncryptionKey = 'key';
|
||||||
|
|
|
@ -3,40 +3,76 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class Localization extends StatelessWidget {
|
class Localization extends StatelessWidget {
|
||||||
const Localization({
|
const Localization({
|
||||||
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
this.child,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget? child;
|
/// 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
|
@override
|
||||||
Widget build(final BuildContext context) => EasyLocalization(
|
Widget build(final BuildContext context) => EasyLocalization(
|
||||||
supportedLocales: const [
|
supportedLocales: supportedLocales,
|
||||||
Locale('ar'),
|
|
||||||
Locale('az'),
|
|
||||||
Locale('be'),
|
|
||||||
Locale('cs'),
|
|
||||||
Locale('de'),
|
|
||||||
Locale('en'),
|
|
||||||
Locale('es'),
|
|
||||||
Locale('et'),
|
|
||||||
Locale('fr'),
|
|
||||||
Locale('he'),
|
|
||||||
Locale('kk'),
|
|
||||||
Locale('lv'),
|
|
||||||
Locale('mk'),
|
|
||||||
Locale('pl'),
|
|
||||||
Locale('ru'),
|
|
||||||
Locale('sk'),
|
|
||||||
Locale('sl'),
|
|
||||||
Locale('th'),
|
|
||||||
Locale('uk'),
|
|
||||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
|
|
||||||
],
|
|
||||||
path: 'assets/translations',
|
path: 'assets/translations',
|
||||||
fallbackLocale: const Locale('en'),
|
fallbackLocale: const Locale('en'),
|
||||||
useFallbackTranslations: true,
|
useFallbackTranslations: true,
|
||||||
saveLocale: false,
|
saveLocale: false,
|
||||||
useOnlyLangCode: false,
|
useOnlyLangCode: false,
|
||||||
child: child!,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,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 'dart:io';
|
||||||
|
|
||||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
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/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
||||||
import 'package:selfprivacy/logic/get_it/resources_model.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) {
|
void _addConsoleLog(final ConsoleLog message) =>
|
||||||
getIt.get<ConsoleModel>().addMessage(
|
getIt.get<ConsoleModel>().log(message);
|
||||||
Message(
|
|
||||||
text: objectToLog.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequestLoggingLink extends Link {
|
class RequestLoggingLink extends Link {
|
||||||
@override
|
@override
|
||||||
|
@ -21,13 +17,14 @@ class RequestLoggingLink extends Link {
|
||||||
final Request request, [
|
final Request request, [
|
||||||
final NextLink? forward,
|
final NextLink? forward,
|
||||||
]) async* {
|
]) async* {
|
||||||
getIt.get<ConsoleModel>().addMessage(
|
_addConsoleLog(
|
||||||
GraphQlRequestMessage(
|
GraphQlRequestConsoleLog(
|
||||||
operation: request.operation,
|
// context: request.context,
|
||||||
variables: request.variables,
|
operationType: request.type.name,
|
||||||
context: request.context,
|
operation: request.operation,
|
||||||
),
|
variables: request.variables,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
yield* forward!(request);
|
yield* forward!(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,20 +33,26 @@ class ResponseLoggingParser extends ResponseParser {
|
||||||
@override
|
@override
|
||||||
Response parseResponse(final Map<String, dynamic> body) {
|
Response parseResponse(final Map<String, dynamic> body) {
|
||||||
final response = super.parseResponse(body);
|
final response = super.parseResponse(body);
|
||||||
getIt.get<ConsoleModel>().addMessage(
|
_addConsoleLog(
|
||||||
GraphQlResponseMessage(
|
GraphQlResponseConsoleLog(
|
||||||
data: response.data,
|
// context: response.context,
|
||||||
errors: response.errors,
|
data: response.data,
|
||||||
context: response.context,
|
errors: response.errors,
|
||||||
),
|
rawResponse: jsonEncode(response.response),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GraphQLError parseError(final Map<String, dynamic> error) {
|
GraphQLError parseError(final Map<String, dynamic> error) {
|
||||||
final graphQlError = super.parseError(error);
|
final graphQlError = super.parseError(error);
|
||||||
_logToAppConsole(graphQlError);
|
_addConsoleLog(
|
||||||
|
ManualConsoleLog.warning(
|
||||||
|
customTitle: 'GraphQL Error',
|
||||||
|
content: graphQlError.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
return graphQlError;
|
return graphQlError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 get _token {
|
||||||
String token = '';
|
String token = '';
|
||||||
final serverDetails = getIt<ResourcesModel>().serverDetails;
|
final serverDetails = getIt<ResourcesModel>().serverDetails;
|
||||||
if (serverDetails != null) {
|
if (serverDetails != null) {
|
||||||
token = getIt<ResourcesModel>().serverDetails!.apiToken;
|
token = serverDetails.apiToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -443,6 +443,7 @@ type SystemMutations {
|
||||||
runSystemUpgrade: GenericJobMutationReturn!
|
runSystemUpgrade: GenericJobMutationReturn!
|
||||||
rebootSystem: GenericMutationReturn!
|
rebootSystem: GenericMutationReturn!
|
||||||
pullRepositoryChanges: GenericMutationReturn!
|
pullRepositoryChanges: GenericMutationReturn!
|
||||||
|
nixCollectGarbage: GenericJobMutationReturn!
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemProviderInfo {
|
type SystemProviderInfo {
|
||||||
|
|
|
@ -79,6 +79,17 @@ mutation RunSystemUpgrade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation NixCollectGarbage {
|
||||||
|
system {
|
||||||
|
nixCollectGarbage {
|
||||||
|
...basicMutationReturnFields
|
||||||
|
job {
|
||||||
|
...basicApiJobsFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation RunSystemUpgradeFallback {
|
mutation RunSystemUpgradeFallback {
|
||||||
system {
|
system {
|
||||||
runSystemUpgrade {
|
runSystemUpgrade {
|
||||||
|
|
|
@ -7043,6 +7043,663 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade<TRes>
|
||||||
CopyWith$Fragment$basicApiJobsFields.stub(_res);
|
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 {
|
class Mutation$RunSystemUpgradeFallback {
|
||||||
Mutation$RunSystemUpgradeFallback({
|
Mutation$RunSystemUpgradeFallback({
|
||||||
required this.system,
|
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:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -6,7 +7,7 @@ import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/message.dart';
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
|
||||||
abstract class RestApiMap {
|
abstract class RestApiMap {
|
||||||
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
||||||
|
@ -57,8 +58,8 @@ abstract class RestApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConsoleInterceptor extends InterceptorsWrapper {
|
class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
void addMessage(final Message message) {
|
void addConsoleLog(final ConsoleLog message) {
|
||||||
getIt.get<ConsoleModel>().addMessage(message);
|
getIt.get<ConsoleModel>().log(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -66,12 +67,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final RequestOptions options,
|
final RequestOptions options,
|
||||||
final RequestInterceptorHandler handler,
|
final RequestInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
addMessage(
|
addConsoleLog(
|
||||||
RestApiRequestMessage(
|
RestApiRequestConsoleLog(
|
||||||
method: options.method,
|
|
||||||
data: options.data.toString(),
|
|
||||||
headers: options.headers,
|
|
||||||
uri: options.uri,
|
uri: options.uri,
|
||||||
|
method: options.method,
|
||||||
|
headers: options.headers,
|
||||||
|
data: jsonEncode(options.data),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onRequest(options, handler);
|
return super.onRequest(options, handler);
|
||||||
|
@ -82,12 +83,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final Response response,
|
final Response response,
|
||||||
final ResponseInterceptorHandler handler,
|
final ResponseInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
addMessage(
|
addConsoleLog(
|
||||||
RestApiResponseMessage(
|
RestApiResponseConsoleLog(
|
||||||
|
uri: response.realUri,
|
||||||
method: response.requestOptions.method,
|
method: response.requestOptions.method,
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
data: response.data.toString(),
|
data: jsonEncode(response.data),
|
||||||
uri: response.realUri,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onResponse(
|
return super.onResponse(
|
||||||
|
@ -103,10 +104,13 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
) async {
|
) async {
|
||||||
final Response? response = err.response;
|
final Response? response = err.response;
|
||||||
log(err.toString());
|
log(err.toString());
|
||||||
addMessage(
|
|
||||||
Message.warn(
|
addConsoleLog(
|
||||||
text:
|
ManualConsoleLog.warning(
|
||||||
'response-uri: ${response?.realUri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
|
customTitle: 'RestAPI error',
|
||||||
|
content: '"uri": "${response?.realUri}",\n'
|
||||||
|
'"status_code": ${response?.statusCode},\n'
|
||||||
|
'"response": ${jsonEncode(response)}',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onError(err, handler);
|
return super.onError(err, handler);
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
|
|
||||||
part 'connection_status_event.dart';
|
|
||||||
part 'connection_status_state.dart';
|
|
||||||
|
|
||||||
class ConnectionStatusBloc
|
|
||||||
extends Bloc<ConnectionStatusEvent, ConnectionStatusState> {
|
|
||||||
ConnectionStatusBloc()
|
|
||||||
: super(
|
|
||||||
const ConnectionStatusState(
|
|
||||||
connectionStatus: ConnectionStatus.nonexistent,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
on<ConnectionStatusChanged>((final event, final emit) {
|
|
||||||
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
|
|
||||||
});
|
|
||||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
|
||||||
_apiConnectionStatusSubscription =
|
|
||||||
apiConnectionRepository.connectionStatusStream.listen(
|
|
||||||
(final ConnectionStatus connectionStatus) {
|
|
||||||
add(
|
|
||||||
ConnectionStatusChanged(connectionStatus),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamSubscription? _apiConnectionStatusSubscription;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_apiConnectionStatusSubscription?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
part of 'connection_status_bloc.dart';
|
|
||||||
|
|
||||||
sealed class ConnectionStatusEvent extends Equatable {
|
|
||||||
const ConnectionStatusEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectionStatusChanged extends ConnectionStatusEvent {
|
|
||||||
const ConnectionStatusChanged(this.connectionStatus);
|
|
||||||
|
|
||||||
final ConnectionStatus connectionStatus;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [connectionStatus];
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
part of 'connection_status_bloc.dart';
|
|
||||||
|
|
||||||
class ConnectionStatusState extends Equatable {
|
|
||||||
const ConnectionStatusState({
|
|
||||||
required this.connectionStatus,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ConnectionStatus connectionStatus;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [connectionStatus];
|
|
||||||
}
|
|
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 {
|
Future<void> acknowledgeFinished() async {
|
||||||
if (state is! JobsStateFinished) {
|
if (state is! JobsStateFinished) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -49,7 +49,8 @@ abstract class ServerInstallationState extends Equatable {
|
||||||
bool get isPrimaryUserFilled => rootUser != null;
|
bool get isPrimaryUserFilled => rootUser != null;
|
||||||
bool get isServerCreated => serverDetails != null;
|
bool get isServerCreated => serverDetails != null;
|
||||||
|
|
||||||
bool get isFullyInitialized => _fulfillmentList.every((final el) => el!);
|
bool get isFullyInitialized =>
|
||||||
|
_fulfillmentList.every((final el) => el ?? false);
|
||||||
ServerSetupProgress get progress => ServerSetupProgress
|
ServerSetupProgress get progress => ServerSetupProgress
|
||||||
.values[_fulfillmentList.where((final el) => el!).length];
|
.values[_fulfillmentList.where((final el) => el!).length];
|
||||||
|
|
||||||
|
|
|
@ -7,21 +7,22 @@ class ApiConfigModel {
|
||||||
|
|
||||||
String? get localeCode => _localeCode;
|
String? get localeCode => _localeCode;
|
||||||
|
|
||||||
|
static const localeCodeFallback = 'en';
|
||||||
String? _localeCode;
|
String? _localeCode;
|
||||||
|
|
||||||
Future<void> setLocaleCode(final String value) async {
|
String get localeCode => _localeCode ?? localeCodeFallback;
|
||||||
_localeCode = value;
|
Future<void> setLocaleCode(final String value) async => _localeCode = value;
|
||||||
}
|
Future<void> resetLocaleCode() async => _localeCode = null;
|
||||||
|
|
||||||
Future<void> setBackblazeBucket(final BackblazeBucket value) async {
|
Future<void> setBackblazeBucket(final BackblazeBucket value) async {
|
||||||
await _box.put(BNames.backblazeBucket, value);
|
await _box.put(BNames.backblazeBucket, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove it
|
||||||
void clear() {
|
void clear() {
|
||||||
_localeCode = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove it
|
||||||
void init() {
|
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 {
|
class RebootServerJob extends ClientJob {
|
||||||
RebootServerJob({
|
RebootServerJob({
|
||||||
super.status,
|
super.status,
|
||||||
|
|
|
@ -65,14 +65,34 @@ class DigitalOceanLocation {
|
||||||
emoji = '🇮🇳';
|
emoji = '🇮🇳';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'syd':
|
||||||
|
emoji = '🇦🇺';
|
||||||
|
break;
|
||||||
|
|
||||||
case 'nyc':
|
case 'nyc':
|
||||||
case 'sfo':
|
case 'sfo':
|
||||||
emoji = '🇺🇸';
|
emoji = '🇺🇸';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return emoji;
|
return emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const _townPrefixToCountryMap = {
|
||||||
|
'fra': 'germany',
|
||||||
|
'ams': 'netherlands',
|
||||||
|
'sgp': 'singapore',
|
||||||
|
'lon': 'united_kingdom',
|
||||||
|
'tor': 'canada',
|
||||||
|
'blr': 'india',
|
||||||
|
'syd': 'australia',
|
||||||
|
'nyc': 'united_states',
|
||||||
|
'sfo': 'united_states',
|
||||||
|
};
|
||||||
|
|
||||||
|
String get countryDisplayKey {
|
||||||
|
final countryName = _townPrefixToCountryMap[slug.substring(0, 3)] ?? slug;
|
||||||
|
return 'countries.$countryName';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
|
@ -10,8 +10,10 @@ DigitalOceanVolume _$DigitalOceanVolumeFromJson(Map<String, dynamic> json) =>
|
||||||
DigitalOceanVolume(
|
DigitalOceanVolume(
|
||||||
json['id'] as String,
|
json['id'] as String,
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
json['size_gigabytes'] as int,
|
(json['size_gigabytes'] as num).toInt(),
|
||||||
(json['droplet_ids'] as List<dynamic>?)?.map((e) => e as int).toList(),
|
(json['droplet_ids'] as List<dynamic>?)
|
||||||
|
?.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanVolumeToJson(DigitalOceanVolume instance) =>
|
Map<String, dynamic> _$DigitalOceanVolumeToJson(DigitalOceanVolume instance) =>
|
||||||
|
@ -42,10 +44,10 @@ DigitalOceanServerType _$DigitalOceanServerTypeFromJson(
|
||||||
(json['regions'] as List<dynamic>).map((e) => e as String).toList(),
|
(json['regions'] as List<dynamic>).map((e) => e as String).toList(),
|
||||||
(json['memory'] as num).toDouble(),
|
(json['memory'] as num).toDouble(),
|
||||||
json['description'] as String,
|
json['description'] as String,
|
||||||
json['disk'] as int,
|
(json['disk'] as num).toInt(),
|
||||||
(json['price_monthly'] as num).toDouble(),
|
(json['price_monthly'] as num).toDouble(),
|
||||||
json['slug'] as String,
|
json['slug'] as String,
|
||||||
json['vcpus'] as int,
|
(json['vcpus'] as num).toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanServerTypeToJson(
|
Map<String, dynamic> _$DigitalOceanServerTypeToJson(
|
||||||
|
|
|
@ -24,8 +24,8 @@ CloudflareDnsRecord _$CloudflareDnsRecordFromJson(Map<String, dynamic> json) =>
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
content: json['content'] as String?,
|
content: json['content'] as String?,
|
||||||
zoneName: json['zone_name'] as String,
|
zoneName: json['zone_name'] as String,
|
||||||
ttl: json['ttl'] as int? ?? 3600,
|
ttl: (json['ttl'] as num?)?.toInt() ?? 3600,
|
||||||
priority: json['priority'] as int? ?? 10,
|
priority: (json['priority'] as num?)?.toInt() ?? 10,
|
||||||
id: json['id'] as String?,
|
id: json['id'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ part of 'desec_dns_info.dart';
|
||||||
|
|
||||||
DesecDomain _$DesecDomainFromJson(Map<String, dynamic> json) => DesecDomain(
|
DesecDomain _$DesecDomainFromJson(Map<String, dynamic> json) => DesecDomain(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
minimumTtl: json['minimum_ttl'] as int?,
|
minimumTtl: (json['minimum_ttl'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DesecDomainToJson(DesecDomain instance) =>
|
Map<String, dynamic> _$DesecDomainToJson(DesecDomain instance) =>
|
||||||
|
@ -21,7 +21,7 @@ DesecDnsRecord _$DesecDnsRecordFromJson(Map<String, dynamic> json) =>
|
||||||
DesecDnsRecord(
|
DesecDnsRecord(
|
||||||
subname: json['subname'] as String,
|
subname: json['subname'] as String,
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
ttl: json['ttl'] as int,
|
ttl: (json['ttl'] as num).toInt(),
|
||||||
records:
|
records:
|
||||||
(json['records'] as List<dynamic>).map((e) => e as String).toList(),
|
(json['records'] as List<dynamic>).map((e) => e as String).toList(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ part of 'digital_ocean_dns_info.dart';
|
||||||
DigitalOceanDomain _$DigitalOceanDomainFromJson(Map<String, dynamic> json) =>
|
DigitalOceanDomain _$DigitalOceanDomainFromJson(Map<String, dynamic> json) =>
|
||||||
DigitalOceanDomain(
|
DigitalOceanDomain(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
ttl: json['ttl'] as int?,
|
ttl: (json['ttl'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
||||||
|
@ -21,12 +21,12 @@ Map<String, dynamic> _$DigitalOceanDomainToJson(DigitalOceanDomain instance) =>
|
||||||
DigitalOceanDnsRecord _$DigitalOceanDnsRecordFromJson(
|
DigitalOceanDnsRecord _$DigitalOceanDnsRecordFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
DigitalOceanDnsRecord(
|
DigitalOceanDnsRecord(
|
||||||
id: json['id'] as int?,
|
id: (json['id'] as num?)?.toInt(),
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
ttl: json['ttl'] as int,
|
ttl: (json['ttl'] as num).toInt(),
|
||||||
data: json['data'] as String,
|
data: json['data'] as String,
|
||||||
priority: json['priority'] as int?,
|
priority: (json['priority'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$DigitalOceanDnsRecordToJson(
|
Map<String, dynamic> _$DigitalOceanDnsRecordToJson(
|
||||||
|
|
|
@ -155,6 +155,27 @@ class HetznerLocation {
|
||||||
}
|
}
|
||||||
return emoji;
|
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.
|
/// 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 _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
||||||
HetznerServerInfo(
|
HetznerServerInfo(
|
||||||
json['id'] as int,
|
(json['id'] as num).toInt(),
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
$enumDecode(_$ServerStatusEnumMap, json['status']),
|
$enumDecode(_$ServerStatusEnumMap, json['status']),
|
||||||
DateTime.parse(json['created'] as String),
|
DateTime.parse(json['created'] as String),
|
||||||
|
@ -16,7 +16,9 @@ HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) =>
|
||||||
json['server_type'] as Map<String, dynamic>),
|
json['server_type'] as Map<String, dynamic>),
|
||||||
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
|
||||||
HetznerPublicNetInfo.fromJson(json['public_net'] as Map<String, dynamic>),
|
HetznerPublicNetInfo.fromJson(json['public_net'] as Map<String, dynamic>),
|
||||||
(json['volumes'] as List<dynamic>).map((e) => e as int).toList(),
|
(json['volumes'] as List<dynamic>)
|
||||||
|
.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$HetznerServerInfoToJson(HetznerServerInfo instance) =>
|
Map<String, dynamic> _$HetznerServerInfoToJson(HetznerServerInfo instance) =>
|
||||||
|
@ -58,7 +60,7 @@ Map<String, dynamic> _$HetznerPublicNetInfoToJson(
|
||||||
};
|
};
|
||||||
|
|
||||||
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
HetznerIp4 _$HetznerIp4FromJson(Map<String, dynamic> json) => HetznerIp4(
|
||||||
json['id'] as int,
|
(json['id'] as num).toInt(),
|
||||||
json['ip'] as String,
|
json['ip'] as String,
|
||||||
json['blocked'] as bool,
|
json['blocked'] as bool,
|
||||||
json['dns_ptr'] as String,
|
json['dns_ptr'] as String,
|
||||||
|
@ -75,9 +77,9 @@ Map<String, dynamic> _$HetznerIp4ToJson(HetznerIp4 instance) =>
|
||||||
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
|
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
HetznerServerTypeInfo(
|
HetznerServerTypeInfo(
|
||||||
json['cores'] as int,
|
(json['cores'] as num).toInt(),
|
||||||
json['memory'] as num,
|
json['memory'] as num,
|
||||||
json['disk'] as int,
|
(json['disk'] as num).toInt(),
|
||||||
(json['prices'] as List<dynamic>)
|
(json['prices'] as List<dynamic>)
|
||||||
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>))
|
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -132,9 +134,9 @@ Map<String, dynamic> _$HetznerLocationToJson(HetznerLocation instance) =>
|
||||||
|
|
||||||
HetznerVolume _$HetznerVolumeFromJson(Map<String, dynamic> json) =>
|
HetznerVolume _$HetznerVolumeFromJson(Map<String, dynamic> json) =>
|
||||||
HetznerVolume(
|
HetznerVolume(
|
||||||
json['id'] as int,
|
(json['id'] as num).toInt(),
|
||||||
json['size'] as int,
|
(json['size'] as num).toInt(),
|
||||||
json['serverId'] as int?,
|
(json['serverId'] as num?)?.toInt(),
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
json['linux_device'] as String?,
|
json['linux_device'] as String?,
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ RecoveryKeyStatus _$RecoveryKeyStatusFromJson(Map<String, dynamic> json) =>
|
||||||
expiration: json['expiration'] == null
|
expiration: json['expiration'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['expiration'] as String),
|
: DateTime.parse(json['expiration'] as String),
|
||||||
usesLeft: json['uses_left'] as int?,
|
usesLeft: (json['uses_left'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$RecoveryKeyStatusToJson(RecoveryKeyStatus instance) =>
|
Map<String, dynamic> _$RecoveryKeyStatusToJson(RecoveryKeyStatus instance) =>
|
||||||
|
|
|
@ -15,7 +15,7 @@ ServerJob _$ServerJobFromJson(Map<String, dynamic> json) => ServerJob(
|
||||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
error: json['error'] as String?,
|
error: json['error'] as String?,
|
||||||
progress: json['progress'] as int?,
|
progress: (json['progress'] as num?)?.toInt(),
|
||||||
result: json['result'] as String?,
|
result: json['result'] as String?,
|
||||||
statusText: json['statusText'] as String?,
|
statusText: json['statusText'] as String?,
|
||||||
finishedAt: json['finishedAt'] == null
|
finishedAt: json['finishedAt'] == null
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import 'package:graphql/client.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
/// TODO(misterfourtytwo): add equality override
|
|
||||||
class Message {
|
|
||||||
Message({this.text, this.severity = MessageSeverity.normal})
|
|
||||||
: time = DateTime.now();
|
|
||||||
Message.warn({this.text})
|
|
||||||
: severity = MessageSeverity.warning,
|
|
||||||
time = DateTime.now();
|
|
||||||
|
|
||||||
final String? text;
|
|
||||||
final DateTime time;
|
|
||||||
final MessageSeverity severity;
|
|
||||||
|
|
||||||
static final DateFormat _formatter = DateFormat('hh:mm');
|
|
||||||
String get timeString => _formatter.format(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MessageSeverity {
|
|
||||||
normal,
|
|
||||||
warning,
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestApiRequestMessage extends Message {
|
|
||||||
RestApiRequestMessage({
|
|
||||||
this.method,
|
|
||||||
this.uri,
|
|
||||||
this.data,
|
|
||||||
this.headers,
|
|
||||||
}) : super(text: 'request-uri: $uri\nheaders: $headers\ndata: $data');
|
|
||||||
|
|
||||||
final String? method;
|
|
||||||
final Uri? uri;
|
|
||||||
final String? data;
|
|
||||||
final Map<String, dynamic>? headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestApiResponseMessage extends Message {
|
|
||||||
RestApiResponseMessage({
|
|
||||||
this.method,
|
|
||||||
this.uri,
|
|
||||||
this.statusCode,
|
|
||||||
this.data,
|
|
||||||
}) : super(text: 'response-uri: $uri\ncode: $statusCode\ndata: $data');
|
|
||||||
|
|
||||||
final String? method;
|
|
||||||
final Uri? uri;
|
|
||||||
final int? statusCode;
|
|
||||||
final String? data;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GraphQlResponseMessage extends Message {
|
|
||||||
GraphQlResponseMessage({
|
|
||||||
this.data,
|
|
||||||
this.errors,
|
|
||||||
this.context,
|
|
||||||
}) : super(text: 'GraphQL Response\ndata: $data');
|
|
||||||
|
|
||||||
final Map<String, dynamic>? data;
|
|
||||||
final List<GraphQLError>? errors;
|
|
||||||
final Context? context;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GraphQlRequestMessage extends Message {
|
|
||||||
GraphQlRequestMessage({
|
|
||||||
this.operation,
|
|
||||||
this.variables,
|
|
||||||
this.context,
|
|
||||||
}) : super(text: 'GraphQL Request\noperation: $operation');
|
|
||||||
|
|
||||||
final Operation? operation;
|
|
||||||
final Map<String, dynamic>? variables;
|
|
||||||
final Context? context;
|
|
||||||
}
|
|
|
@ -2,12 +2,14 @@ class ServerProviderLocation {
|
||||||
ServerProviderLocation({
|
ServerProviderLocation({
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.identifier,
|
required this.identifier,
|
||||||
|
required this.countryDisplayKey,
|
||||||
this.description,
|
this.description,
|
||||||
this.flag = '',
|
this.flag = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String identifier;
|
final String identifier;
|
||||||
|
final String countryDisplayKey;
|
||||||
final String? description;
|
final String? description;
|
||||||
final String flag;
|
final String flag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,9 @@ class ApiAdapter {
|
||||||
class CloudflareDnsProvider extends DnsProvider {
|
class CloudflareDnsProvider extends DnsProvider {
|
||||||
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
CloudflareDnsProvider() : _adapter = ApiAdapter();
|
||||||
CloudflareDnsProvider.load(
|
CloudflareDnsProvider.load(
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
|
@ -22,9 +22,9 @@ class ApiAdapter {
|
||||||
class DesecDnsProvider extends DnsProvider {
|
class DesecDnsProvider extends DnsProvider {
|
||||||
DesecDnsProvider() : _adapter = ApiAdapter();
|
DesecDnsProvider() : _adapter = ApiAdapter();
|
||||||
DesecDnsProvider.load(
|
DesecDnsProvider.load(
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
|
@ -22,9 +22,9 @@ class ApiAdapter {
|
||||||
class DigitalOceanDnsProvider extends DnsProvider {
|
class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
DigitalOceanDnsProvider() : _adapter = ApiAdapter();
|
||||||
DigitalOceanDnsProvider.load(
|
DigitalOceanDnsProvider.load(
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
|
@ -38,9 +38,9 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
DigitalOceanServerProvider() : _adapter = ApiAdapter();
|
DigitalOceanServerProvider() : _adapter = ApiAdapter();
|
||||||
DigitalOceanServerProvider.load(
|
DigitalOceanServerProvider.load(
|
||||||
final String? location,
|
final String? location,
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
region: location,
|
region: location,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -438,6 +438,7 @@ class DigitalOceanServerProvider extends ServerProvider {
|
||||||
description: rawLocation.name,
|
description: rawLocation.name,
|
||||||
flag: rawLocation.flag,
|
flag: rawLocation.flag,
|
||||||
identifier: rawLocation.slug,
|
identifier: rawLocation.slug,
|
||||||
|
countryDisplayKey: rawLocation.countryDisplayKey,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -38,9 +38,9 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
HetznerServerProvider() : _adapter = ApiAdapter();
|
HetznerServerProvider() : _adapter = ApiAdapter();
|
||||||
HetznerServerProvider.load(
|
HetznerServerProvider.load(
|
||||||
final String? location,
|
final String? location,
|
||||||
final bool isAuthotized,
|
final bool isAuthorized,
|
||||||
) : _adapter = ApiAdapter(
|
) : _adapter = ApiAdapter(
|
||||||
isWithToken: isAuthotized,
|
isWithToken: isAuthorized,
|
||||||
region: location,
|
region: location,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -156,6 +156,7 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
description: server.location.description,
|
description: server.location.description,
|
||||||
flag: server.location.flag,
|
flag: server.location.flag,
|
||||||
identifier: server.location.name,
|
identifier: server.location.name,
|
||||||
|
countryDisplayKey: server.location.countryDisplayKey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -456,6 +457,7 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
description: rawLocation.description,
|
description: rawLocation.description,
|
||||||
flag: rawLocation.flag,
|
flag: rawLocation.flag,
|
||||||
identifier: rawLocation.name,
|
identifier: rawLocation.name,
|
||||||
|
countryDisplayKey: rawLocation.countryDisplayKey,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
continue;
|
continue;
|
||||||
|
|
170
lib/main.dart
170
lib/main.dart
|
@ -1,21 +1,21 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||||
import 'package:selfprivacy/config/bloc_config.dart';
|
import 'package:selfprivacy/config/bloc_config.dart';
|
||||||
import 'package:selfprivacy/config/bloc_observer.dart';
|
import 'package:selfprivacy/config/bloc_observer.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/config/localization.dart';
|
import 'package:selfprivacy/config/localization.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_hive_datasource.dart';
|
||||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/errors/failed_to_init_secure_storage.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
// import 'package:wakelock/wakelock.dart';
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await HiveConfig.init();
|
|
||||||
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
|
@ -26,85 +26,117 @@ void main() async {
|
||||||
// print(e);
|
// print(e);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
await getItSetup();
|
try {
|
||||||
await EasyLocalization.ensureInitialized();
|
await Future.wait(
|
||||||
tz.initializeTimeZones();
|
<Future<void>>[
|
||||||
|
HiveConfig.init(),
|
||||||
|
EasyLocalization.ensureInitialized(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await getItSetup();
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
runApp(
|
||||||
|
FailedToInitSecureStorageScreen(e: e),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final ThemeData lightThemeData = await AppThemeFactory.create(
|
tz.initializeTimeZones();
|
||||||
isDark: false,
|
|
||||||
fallbackColor: BrandColors.primary,
|
|
||||||
);
|
|
||||||
final ThemeData darkThemeData = await AppThemeFactory.create(
|
|
||||||
isDark: true,
|
|
||||||
fallbackColor: BrandColors.primary,
|
|
||||||
);
|
|
||||||
|
|
||||||
Bloc.observer = SimpleBlocObserver();
|
Bloc.observer = SimpleBlocObserver();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
Localization(
|
Localization(
|
||||||
child: SelfprivacyApp(
|
child: InheritedPreferencesRepository(
|
||||||
lightThemeData: lightThemeData,
|
dataSource: PreferencesHiveDataSource(),
|
||||||
darkThemeData: darkThemeData,
|
child: const InheritedAppController(
|
||||||
|
child: AppBuilder(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelfprivacyApp extends StatelessWidget {
|
class AppBuilder extends StatelessWidget {
|
||||||
SelfprivacyApp({
|
const AppBuilder({super.key});
|
||||||
required this.lightThemeData,
|
|
||||||
required this.darkThemeData,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ThemeData lightThemeData;
|
|
||||||
final ThemeData darkThemeData;
|
|
||||||
|
|
||||||
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Localization(
|
Widget build(final BuildContext context) {
|
||||||
child: BlocAndProviderConfig(
|
final appController = InheritedAppController.of(context);
|
||||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
|
||||||
builder: (
|
|
||||||
final BuildContext context,
|
|
||||||
final AppSettingsState appSettings,
|
|
||||||
) {
|
|
||||||
getIt.get<ApiConfigModel>().setLocaleCode(
|
|
||||||
context.locale.languageCode,
|
|
||||||
);
|
|
||||||
return MaterialApp.router(
|
|
||||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
|
||||||
routerDelegate: _appRouter.delegate(),
|
|
||||||
scaffoldMessengerKey:
|
|
||||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
|
||||||
localizationsDelegates: context.localizationDelegates,
|
|
||||||
supportedLocales: context.supportedLocales,
|
|
||||||
locale: context.locale,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
title: 'SelfPrivacy',
|
|
||||||
theme: lightThemeData,
|
|
||||||
darkTheme: darkThemeData,
|
|
||||||
themeMode: appSettings.isAutoDarkModeOn
|
|
||||||
? ThemeMode.system
|
|
||||||
: appSettings.isDarkModeOn
|
|
||||||
? ThemeMode.dark
|
|
||||||
: ThemeMode.light,
|
|
||||||
builder: (final BuildContext context, final Widget? widget) {
|
|
||||||
Widget error =
|
|
||||||
const Center(child: Text('...rendering error...'));
|
|
||||||
if (widget is Scaffold || widget is Navigator) {
|
|
||||||
error = Scaffold(body: error);
|
|
||||||
}
|
|
||||||
ErrorWidget.builder =
|
|
||||||
(final FlutterErrorDetails errorDetails) => error;
|
|
||||||
|
|
||||||
return widget ?? error;
|
if (appController.loaded) {
|
||||||
},
|
return const SelfprivacyApp();
|
||||||
);
|
}
|
||||||
},
|
|
||||||
|
return const SplashScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widget to be shown
|
||||||
|
/// until essential app initialization is completed
|
||||||
|
class SplashScreen extends StatelessWidget {
|
||||||
|
const SplashScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => const ColoredBox(
|
||||||
|
color: Colors.white,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive(
|
||||||
|
valueColor: AlwaysStoppedAnimation(BrandColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SelfprivacyApp extends StatefulWidget {
|
||||||
|
const SelfprivacyApp({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SelfprivacyApp> createState() => _SelfprivacyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelfprivacyAppState extends State<SelfprivacyApp> {
|
||||||
|
final appKey = UniqueKey();
|
||||||
|
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final appController = InheritedAppController.of(context);
|
||||||
|
|
||||||
|
return BlocAndProviderConfig(
|
||||||
|
child: MaterialApp.router(
|
||||||
|
key: appKey,
|
||||||
|
title: 'SelfPrivacy',
|
||||||
|
// routing
|
||||||
|
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||||
|
routerDelegate: _appRouter.delegate(),
|
||||||
|
scaffoldMessengerKey:
|
||||||
|
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||||
|
// localization settings
|
||||||
|
locale: context.locale,
|
||||||
|
supportedLocales: context.supportedLocales,
|
||||||
|
localizationsDelegates: context.localizationDelegates,
|
||||||
|
// theme settings
|
||||||
|
themeMode: appController.themeMode,
|
||||||
|
theme: appController.lightTheme,
|
||||||
|
darkTheme: appController.darkTheme,
|
||||||
|
// other preferences
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
scrollBehavior:
|
||||||
|
const MaterialScrollBehavior().copyWith(scrollbars: false),
|
||||||
|
builder: _builder,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _builder(final BuildContext context, final Widget? widget) {
|
||||||
|
Widget error = const Center(child: Text('...rendering error...'));
|
||||||
|
if (widget is Scaffold || widget is Navigator) {
|
||||||
|
error = Scaffold(body: error);
|
||||||
|
}
|
||||||
|
ErrorWidget.builder = (final FlutterErrorDetails errorDetails) => error;
|
||||||
|
|
||||||
|
return widget ?? error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,11 @@ abstract class AppThemeFactory {
|
||||||
typography: appTypography,
|
typography: appTypography,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
scaffoldBackgroundColor: colorScheme.background,
|
scaffoldBackgroundColor: colorScheme.background,
|
||||||
|
listTileTheme: ListTileThemeData(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return materialThemeData;
|
return materialThemeData;
|
||||||
|
@ -50,7 +55,8 @@ abstract class AppThemeFactory {
|
||||||
static Future<ColorScheme?> _getDynamicColors(final Brightness brightness) {
|
static Future<ColorScheme?> _getDynamicColors(final Brightness brightness) {
|
||||||
try {
|
try {
|
||||||
return DynamicColorPlugin.getCorePalette().then(
|
return DynamicColorPlugin.getCorePalette().then(
|
||||||
(final corePallet) => corePallet?.toColorScheme(brightness: brightness),
|
(final corePallete) =>
|
||||||
|
corePallete?.toColorScheme(brightness: brightness),
|
||||||
);
|
);
|
||||||
} on PlatformException {
|
} on PlatformException {
|
||||||
return Future.value(null);
|
return Future.value(null);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BrandHeader extends StatelessWidget {
|
class BrandHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||||
const BrandHeader({
|
const BrandHeader({
|
||||||
super.key,
|
super.key,
|
||||||
this.title = '',
|
this.title = '',
|
||||||
|
@ -8,6 +8,9 @@ class BrandHeader extends StatelessWidget {
|
||||||
this.onBackButtonPressed,
|
this.onBackButtonPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(52.0);
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final bool hasBackButton;
|
final bool hasBackButton;
|
||||||
final VoidCallback? onBackButtonPressed;
|
final VoidCallback? onBackButtonPressed;
|
||||||
|
|
|
@ -11,15 +11,16 @@ class InfoBox extends StatelessWidget {
|
||||||
final bool isWarning;
|
final bool isWarning;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Column(
|
Widget build(final BuildContext context) => Wrap(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
spacing: 8.0,
|
||||||
|
runSpacing: 16.0,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isWarning ? Icons.warning_amber_outlined : Icons.info_outline,
|
isWarning ? Icons.warning_amber_outlined : Icons.info_outline,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
Text(
|
||||||
text,
|
text,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
|
@ -63,7 +64,7 @@ class JobsContent extends StatelessWidget {
|
||||||
context.read<ServerInstallationCubit>().state;
|
context.read<ServerInstallationCubit>().state;
|
||||||
if (state is JobsStateEmpty) {
|
if (state is JobsStateEmpty) {
|
||||||
widgets = [
|
widgets = [
|
||||||
const SizedBox(height: 80),
|
const Gap(80),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'jobs.empty'.tr(),
|
'jobs.empty'.tr(),
|
||||||
|
@ -75,12 +76,12 @@ class JobsContent extends StatelessWidget {
|
||||||
if (installationState is ServerInstallationFinished) {
|
if (installationState is ServerInstallationFinished) {
|
||||||
widgets = [
|
widgets = [
|
||||||
...widgets,
|
...widgets,
|
||||||
const SizedBox(height: 80),
|
const Gap(80),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () => context.read<JobsCubit>().upgradeServer(),
|
onPressed: () => context.read<JobsCubit>().upgradeServer(),
|
||||||
text: 'jobs.upgrade_server'.tr(),
|
text: 'jobs.upgrade_server'.tr(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const Gap(10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
title: 'jobs.reboot_server'.tr(),
|
title: 'jobs.reboot_server'.tr(),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -189,7 +190,7 @@ class JobsContent extends StatelessWidget {
|
||||||
style:
|
style:
|
||||||
Theme.of(context).textTheme.labelSmall,
|
Theme.of(context).textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
value: rebuildJob?.progress == null
|
value: rebuildJob?.progress == null
|
||||||
? 0.0
|
? 0.0
|
||||||
|
@ -206,7 +207,7 @@ class JobsContent extends StatelessWidget {
|
||||||
minHeight: 7.0,
|
minHeight: 7.0,
|
||||||
borderRadius: BorderRadius.circular(7.0),
|
borderRadius: BorderRadius.circular(7.0),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
if (rebuildJob?.error != null ||
|
if (rebuildJob?.error != null ||
|
||||||
rebuildJob?.result != null ||
|
rebuildJob?.result != null ||
|
||||||
rebuildJob?.statusText != null)
|
rebuildJob?.statusText != null)
|
||||||
|
@ -282,7 +283,7 @@ class JobsContent extends StatelessWidget {
|
||||||
(final job) => job.uid == state.rebuildJobUid,
|
(final job) => job.uid == state.rebuildJobUid,
|
||||||
);
|
);
|
||||||
if (rebuildJob == null) {
|
if (rebuildJob == null) {
|
||||||
return const SizedBox();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -322,7 +323,7 @@ class JobsContent extends StatelessWidget {
|
||||||
rebuildJob.description,
|
rebuildJob.description,
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
value: rebuildJob.progress == null
|
value: rebuildJob.progress == null
|
||||||
? 0.0
|
? 0.0
|
||||||
|
@ -339,7 +340,7 @@ class JobsContent extends StatelessWidget {
|
||||||
minHeight: 7.0,
|
minHeight: 7.0,
|
||||||
borderRadius: BorderRadius.circular(7.0),
|
borderRadius: BorderRadius.circular(7.0),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
if (rebuildJob.error != null ||
|
if (rebuildJob.error != null ||
|
||||||
rebuildJob.result != null ||
|
rebuildJob.result != null ||
|
||||||
rebuildJob.statusText != null)
|
rebuildJob.statusText != null)
|
||||||
|
@ -360,7 +361,7 @@ class JobsContent extends StatelessWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const Gap(16),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () => context.read<JobsCubit>().acknowledgeFinished(),
|
onPressed: () => context.read<JobsCubit>().acknowledgeFinished(),
|
||||||
text: 'basis.done'.tr(),
|
text: 'basis.done'.tr(),
|
||||||
|
@ -403,7 +404,7 @@ class JobsContent extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const Gap(8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
@ -423,7 +424,7 @@ class JobsContent extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const Gap(16),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: hasBlockingJobs
|
onPressed: hasBlockingJobs
|
||||||
? null
|
? null
|
||||||
|
@ -436,18 +437,18 @@ class JobsContent extends StatelessWidget {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
padding: paddingH15V0,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const Gap(16),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'jobs.title'.tr(),
|
'jobs.title'.tr(),
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const Gap(20),
|
||||||
...widgets,
|
...widgets,
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
const Divider(height: 0),
|
const Divider(height: 0),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
if (serverJobs.isNotEmpty)
|
if (serverJobs.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
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:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
|
|
||||||
class EmptyPagePlaceholder extends StatelessWidget {
|
class EmptyPagePlaceholder extends StatelessWidget {
|
||||||
|
@ -10,50 +11,72 @@ class EmptyPagePlaceholder extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final bool showReadyCard;
|
||||||
|
final IconData iconData;
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
final IconData iconData;
|
|
||||||
final bool showReadyCard;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => !showReadyCard
|
Widget build(final BuildContext context) => showReadyCard
|
||||||
? _expandedContent(context)
|
? Column(
|
||||||
: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
if (showReadyCard)
|
||||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
const Padding(
|
||||||
child: NotReadyCard(),
|
padding: EdgeInsets.symmetric(
|
||||||
),
|
vertical: 15,
|
||||||
Expanded(
|
horizontal: 15,
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
|
||||||
child: Center(
|
|
||||||
child: _expandedContent(context),
|
|
||||||
),
|
),
|
||||||
|
child: NotReadyCard(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _ContentWidget(
|
||||||
|
iconData: iconData,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
)
|
||||||
|
: _ContentWidget(
|
||||||
|
iconData: iconData,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _expandedContent(final BuildContext context) => Padding(
|
class _ContentWidget extends StatelessWidget {
|
||||||
|
const _ContentWidget({
|
||||||
|
required this.iconData,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData iconData;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
iconData,
|
iconData,
|
||||||
size: 50,
|
size: 50,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const Gap(16),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
description,
|
description,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
|
@ -127,7 +127,9 @@ class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final isMobile =
|
final isMobile =
|
||||||
widget.ignoreBreakpoints ? true : Breakpoints.small.isActive(context);
|
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(
|
return SliverAppBar(
|
||||||
expandedHeight:
|
expandedHeight:
|
||||||
widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height,
|
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;
|
: StateType.uninitialized;
|
||||||
final bool preventActions = backupsState.preventActions;
|
final bool preventActions = backupsState.preventActions;
|
||||||
final List<Backup> backups = backupsState.backups;
|
final List<Backup> backups = backupsState.backups;
|
||||||
final List<Service> services =
|
final List<Service> services = context
|
||||||
context.watch<ServicesBloc>().state.servicesThatCanBeBackedUp;
|
.watch<ServicesBloc>()
|
||||||
|
.state
|
||||||
|
.servicesThatCanBeBackedUp
|
||||||
|
.where(
|
||||||
|
(final service) => service.isEnabled,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
final Duration? autobackupPeriod = backupsState.autobackupPeriod;
|
final Duration? autobackupPeriod = backupsState.autobackupPeriod;
|
||||||
final List<ServerJob> backupJobs = context
|
final List<ServerJob> backupJobs = context
|
||||||
.watch<ServerJobsBloc>()
|
.watch<ServerJobsBloc>()
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.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/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/json/server_job.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
|
||||||
|
@ -103,6 +104,29 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
||||||
...widget.services.map(
|
...widget.services.map(
|
||||||
(final Service service) {
|
(final Service service) {
|
||||||
final bool busy = busyServices.contains(service.id);
|
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(
|
return CheckboxListTile.adaptive(
|
||||||
onChanged: !busy
|
onChanged: !busy
|
||||||
? (final bool? value) {
|
? (final bool? value) {
|
||||||
|
@ -122,8 +146,9 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
|
||||||
title: Text(
|
title: Text(
|
||||||
service.displayName,
|
service.displayName,
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Column(
|
||||||
busy ? 'backup.service_busy'.tr() : service.backupDescription,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: descriptionWidgets,
|
||||||
),
|
),
|
||||||
secondary: SvgPicture.string(
|
secondary: SvgPicture.string(
|
||||||
service.svgIcon,
|
service.svgIcon,
|
||||||
|
|
|
@ -39,6 +39,7 @@ class NewDeviceScreen extends StatelessWidget {
|
||||||
|
|
||||||
class _KeyDisplay extends StatelessWidget {
|
class _KeyDisplay extends StatelessWidget {
|
||||||
const _KeyDisplay({required this.newDeviceKey});
|
const _KeyDisplay({required this.newDeviceKey});
|
||||||
|
|
||||||
final String newDeviceKey;
|
final String newDeviceKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -47,7 +48,7 @@ class _KeyDisplay extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
SelectableText(
|
||||||
newDeviceKey,
|
newDeviceKey,
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
|
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:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue