mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-06 16:14:15 +00:00
chore: Merge master into digital-ocean-dns
This commit is contained in:
commit
755ac1d5c0
|
@ -14,3 +14,6 @@ max_line_length = 150
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 4
|
||||||
|
|
|
@ -29,16 +29,16 @@ linter:
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
avoid_print: false # Uncomment to disable the `avoid_print` rule
|
avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
|
||||||
always_use_package_imports: true
|
|
||||||
no_adjacent_strings_in_list: true
|
|
||||||
unnecessary_statements: true
|
|
||||||
always_declare_return_types: true
|
always_declare_return_types: true
|
||||||
always_put_required_named_parameters_first: true
|
|
||||||
always_put_control_body_on_new_line: true
|
always_put_control_body_on_new_line: true
|
||||||
|
always_put_required_named_parameters_first: true
|
||||||
|
always_use_package_imports: true
|
||||||
avoid_escaping_inner_quotes: true
|
avoid_escaping_inner_quotes: true
|
||||||
avoid_setters_without_getters: true
|
avoid_setters_without_getters: true
|
||||||
|
collection_methods_unrelated_type: true
|
||||||
|
combinators_ordering: true
|
||||||
eol_at_end_of_file: true
|
eol_at_end_of_file: true
|
||||||
|
no_adjacent_strings_in_list: true
|
||||||
prefer_constructors_over_static_methods: true
|
prefer_constructors_over_static_methods: true
|
||||||
prefer_expression_function_bodies: true
|
prefer_expression_function_bodies: true
|
||||||
prefer_final_in_for_each: true
|
prefer_final_in_for_each: true
|
||||||
|
@ -48,12 +48,18 @@ linter:
|
||||||
prefer_if_elements_to_conditional_expressions: true
|
prefer_if_elements_to_conditional_expressions: true
|
||||||
prefer_mixin: true
|
prefer_mixin: true
|
||||||
prefer_null_aware_method_calls: true
|
prefer_null_aware_method_calls: true
|
||||||
|
prefer_single_quotes: true
|
||||||
require_trailing_commas: true
|
require_trailing_commas: true
|
||||||
sized_box_shrink_expand: true
|
sized_box_shrink_expand: true
|
||||||
sort_constructors_first: true
|
sort_constructors_first: true
|
||||||
|
unawaited_futures: true
|
||||||
unnecessary_await_in_return: true
|
unnecessary_await_in_return: true
|
||||||
|
unnecessary_null_aware_operator_on_extension_on_nullable: true
|
||||||
unnecessary_null_checks: true
|
unnecessary_null_checks: true
|
||||||
unnecessary_parenthesis: true
|
unnecessary_parenthesis: true
|
||||||
|
unnecessary_statements: true
|
||||||
|
unnecessary_to_list_in_spreads: true
|
||||||
|
unreachable_from_main: true
|
||||||
use_enums: true
|
use_enums: true
|
||||||
use_if_null_to_convert_nulls_to_bools: true
|
use_if_null_to_convert_nulls_to_bools: true
|
||||||
use_is_even_rather_than_modulo: true
|
use_is_even_rather_than_modulo: true
|
||||||
|
@ -61,6 +67,7 @@ linter:
|
||||||
use_named_constants: true
|
use_named_constants: true
|
||||||
use_setters_to_change_properties: true
|
use_setters_to_change_properties: true
|
||||||
use_string_buffers: true
|
use_string_buffers: true
|
||||||
|
use_string_in_part_of_directives: true
|
||||||
use_super_parameters: true
|
use_super_parameters: true
|
||||||
use_to_and_as_if_applicable: true
|
use_to_and_as_if_applicable: true
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"test": "en-test",
|
"test": "en-test",
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
"basis": {
|
"basis": {
|
||||||
|
"app_name": "SelfPrivacy",
|
||||||
"providers": "Providers",
|
"providers": "Providers",
|
||||||
"providers_title": "Your Data Center",
|
"providers_title": "Your Data Center",
|
||||||
"select": "Select",
|
"select": "Select",
|
||||||
|
@ -46,7 +47,8 @@
|
||||||
},
|
},
|
||||||
"console_page": {
|
"console_page": {
|
||||||
"title": "Console",
|
"title": "Console",
|
||||||
"waiting": "Waiting for initialization…"
|
"waiting": "Waiting for initialization…",
|
||||||
|
"copy": "Copy"
|
||||||
},
|
},
|
||||||
"about_us_page": {
|
"about_us_page": {
|
||||||
"title": "About us"
|
"title": "About us"
|
||||||
|
@ -59,8 +61,11 @@
|
||||||
},
|
},
|
||||||
"application_settings": {
|
"application_settings": {
|
||||||
"title": "Application settings",
|
"title": "Application settings",
|
||||||
|
"system_dark_theme_title": "System default theme",
|
||||||
|
"system_dark_theme_description": "Use light or dark theme depending on system settings",
|
||||||
"dark_theme_title": "Dark theme",
|
"dark_theme_title": "Dark theme",
|
||||||
"dark_theme_description": "Switch your application theme",
|
"dark_theme_description": "Switch your application theme",
|
||||||
|
"dangerous_settings": "Dangerous settings",
|
||||||
"reset_config_title": "Reset application config",
|
"reset_config_title": "Reset application config",
|
||||||
"reset_config_description": "Reset api keys and root user",
|
"reset_config_description": "Reset api keys and root user",
|
||||||
"delete_server_title": "Delete server",
|
"delete_server_title": "Delete server",
|
||||||
|
@ -251,6 +256,7 @@
|
||||||
"subtitle": "Private VPN server"
|
"subtitle": "Private VPN server"
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
|
"details_title": "User details",
|
||||||
"add_new_user": "Add a first user",
|
"add_new_user": "Add a first user",
|
||||||
"new_user": "New user",
|
"new_user": "New user",
|
||||||
"delete_user": "Delete user",
|
"delete_user": "Delete user",
|
||||||
|
@ -335,7 +341,20 @@
|
||||||
"create_master_account": "Create master account",
|
"create_master_account": "Create master account",
|
||||||
"enter_username_and_password": "Enter username and strong password",
|
"enter_username_and_password": "Enter username and strong password",
|
||||||
"finish": "Everything is initialized",
|
"finish": "Everything is initialized",
|
||||||
"checks": "Checks have been completed \n{} out of {}"
|
"checks": "Checks have been completed \n{} out of {}",
|
||||||
|
"steps": {
|
||||||
|
"hosting": "Hosting",
|
||||||
|
"server_type": "Server type",
|
||||||
|
"dns_provider": "DNS provider",
|
||||||
|
"backups_provider": "Backups",
|
||||||
|
"domain": "Domain",
|
||||||
|
"master_account": "Master account",
|
||||||
|
"server": "Server",
|
||||||
|
"dns_setup": "DNS setup",
|
||||||
|
"nixos_installation": "NixOS installation",
|
||||||
|
"server_reboot": "Server reboot",
|
||||||
|
"final_checks": "Final checks"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"recovering": {
|
"recovering": {
|
||||||
"generic_error": "Operation failed, please try again.",
|
"generic_error": "Operation failed, please try again.",
|
||||||
|
@ -478,5 +497,19 @@
|
||||||
"root_name": "Cannot be 'root'",
|
"root_name": "Cannot be 'root'",
|
||||||
"length_not_equal": "Length is [], should be {}",
|
"length_not_equal": "Length is [], should be {}",
|
||||||
"length_longer": "Length is [], should be shorter than or equal to {}"
|
"length_longer": "Length is [], should be shorter than or equal to {}"
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"title": "SelfPrivacy Support"
|
||||||
|
},
|
||||||
|
"developer_settings": {
|
||||||
|
"title": "Developer settings",
|
||||||
|
"subtitle": "These settings are for debugging only. Don't change them unless you know what you're doing.",
|
||||||
|
"server_setup": "Server setup",
|
||||||
|
"use_staging_acme": "Use staging ACME server",
|
||||||
|
"use_staging_acme_description": "Rebuild your app to change this value.",
|
||||||
|
"routing": "App routing",
|
||||||
|
"reset_onboarding": "Reset onboarding switch",
|
||||||
|
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
|
||||||
|
"cubit_statuses": "Cubit loading statuses"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart';
|
||||||
|
|
||||||
|
@ -23,7 +24,9 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
const isDark = false;
|
const isDark = false;
|
||||||
|
const isAutoDark = true;
|
||||||
final serverInstallationCubit = ServerInstallationCubit()..load();
|
final serverInstallationCubit = ServerInstallationCubit()..load();
|
||||||
|
final supportSystemCubit = SupportSystemCubit();
|
||||||
final usersCubit = UsersCubit(serverInstallationCubit);
|
final usersCubit = UsersCubit(serverInstallationCubit);
|
||||||
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
||||||
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
||||||
|
@ -41,9 +44,13 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => AppSettingsCubit(
|
create: (final _) => AppSettingsCubit(
|
||||||
isDarkModeOn: isDark,
|
isDarkModeOn: isDark,
|
||||||
|
isAutoDarkModeOn: isAutoDark,
|
||||||
isOnboardingShowing: true,
|
isOnboardingShowing: true,
|
||||||
)..load(),
|
)..load(),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (final _) => supportSystemCubit,
|
||||||
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => serverInstallationCubit,
|
create: (final _) => serverInstallationCubit,
|
||||||
lazy: false,
|
lazy: false,
|
||||||
|
|
|
@ -2,53 +2,16 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BrandColors {
|
class BrandColors {
|
||||||
static const Color blue = Color(0xFF093CEF);
|
static const Color blue = Color(0xFF093CEF);
|
||||||
static const Color white = Colors.white;
|
|
||||||
static const Color black = Colors.black;
|
|
||||||
|
|
||||||
static const Color gray1 = Color(0xFF555555);
|
|
||||||
static const Color gray2 = Color(0xFF7C7C7C);
|
|
||||||
static const Color gray3 = Color(0xFFFAFAFA);
|
|
||||||
static const Color gray4 = Color(0xFFDDDDDD);
|
|
||||||
static const Color gray5 = Color(0xFFEDEEF1);
|
|
||||||
static Color gray6 = const Color(0xFF181818).withOpacity(0.7);
|
|
||||||
static const Color grey7 = Color(0xFFABABAB);
|
|
||||||
|
|
||||||
static const Color red1 = Color(0xFFFA0E0E);
|
|
||||||
static const Color red2 = Color(0xFFE65527);
|
|
||||||
|
|
||||||
static const Color green1 = Color(0xFF00AF54);
|
|
||||||
|
|
||||||
static const Color green2 = Color(0xFF0F8849);
|
|
||||||
|
|
||||||
static Color get navBackgroundLight => white.withOpacity(0.8);
|
|
||||||
static Color get navBackgroundDark => black.withOpacity(0.8);
|
|
||||||
|
|
||||||
static const List<Color> uninitializedGradientColors = [
|
static const List<Color> uninitializedGradientColors = [
|
||||||
Color(0xFF555555),
|
Color(0xFF555555),
|
||||||
Color(0xFFABABAB),
|
Color(0xFFABABAB),
|
||||||
];
|
];
|
||||||
static const List<Color> stableGradientColors = [
|
|
||||||
Color(0xFF093CEF),
|
|
||||||
Color(0xFF14A1CB),
|
|
||||||
];
|
|
||||||
|
|
||||||
static const List<Color> progressGradientColors = [
|
|
||||||
Color(0xFF093CEF),
|
|
||||||
Color(0xFF14A1CB),
|
|
||||||
];
|
|
||||||
static const List<Color> warningGradientColors = [
|
static const List<Color> warningGradientColors = [
|
||||||
Color(0xFFEF4E09),
|
Color(0xFFEF4E09),
|
||||||
Color(0xFFEFD135),
|
Color(0xFFEFD135),
|
||||||
];
|
];
|
||||||
|
|
||||||
static const Color primary = blue;
|
static const Color primary = blue;
|
||||||
static const Color headlineColor = black;
|
|
||||||
static const Color inactive = gray2;
|
|
||||||
static const Color scaffoldBackground = gray3;
|
|
||||||
static const Color inputInactive = gray4;
|
|
||||||
|
|
||||||
static const Color textColor1 = black;
|
|
||||||
static const Color textColor2 = gray1;
|
|
||||||
static const Color dividerColor = gray5;
|
|
||||||
static const Color warning = red1;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@ class HiveConfig {
|
||||||
final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated);
|
final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated);
|
||||||
if (deprecatedUsers.isNotEmpty) {
|
if (deprecatedUsers.isNotEmpty) {
|
||||||
final Box<User> users = Hive.box<User>(BNames.usersBox);
|
final Box<User> users = Hive.box<User>(BNames.usersBox);
|
||||||
users.addAll(deprecatedUsers.values.toList());
|
await users.addAll(deprecatedUsers.values.toList());
|
||||||
deprecatedUsers.clear();
|
await deprecatedUsers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||||
|
@ -63,6 +63,9 @@ class BNames {
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isDarkModeOn = 'isDarkModeOn';
|
static String isDarkModeOn = 'isDarkModeOn';
|
||||||
|
|
||||||
|
/// A boolean field of [appSettingsBox] box.
|
||||||
|
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String isOnboardingShowing = 'isOnboardingShowing';
|
static String isOnboardingShowing = 'isOnboardingShowing';
|
||||||
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
|
||||||
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
|
|
||||||
const TextStyle defaultTextStyle = TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
color: BrandColors.textColor1,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle headline1Style = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 40,
|
|
||||||
fontWeight: NamedFontWeight.extraBold,
|
|
||||||
color: BrandColors.headlineColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle headline2Style = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: NamedFontWeight.extraBold,
|
|
||||||
color: BrandColors.headlineColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle onboardingTitle = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 30,
|
|
||||||
fontWeight: NamedFontWeight.extraBold,
|
|
||||||
color: BrandColors.headlineColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle headline3Style = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: NamedFontWeight.extraBold,
|
|
||||||
color: BrandColors.headlineColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle headline4Style = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: NamedFontWeight.medium,
|
|
||||||
color: BrandColors.headlineColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle headline4UnderlinedStyle = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: NamedFontWeight.medium,
|
|
||||||
color: BrandColors.headlineColor,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle headline5Style = defaultTextStyle.copyWith(
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: NamedFontWeight.medium,
|
|
||||||
color: BrandColors.headlineColor.withOpacity(0.8),
|
|
||||||
);
|
|
||||||
|
|
||||||
const TextStyle body1Style = defaultTextStyle;
|
|
||||||
final TextStyle body2Style = defaultTextStyle.copyWith(
|
|
||||||
color: BrandColors.textColor2,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle buttonTitleText = defaultTextStyle.copyWith(
|
|
||||||
color: BrandColors.white,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle mediumStyle =
|
|
||||||
defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
|
|
||||||
|
|
||||||
final TextStyle smallStyle =
|
|
||||||
defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
|
|
||||||
|
|
||||||
const TextStyle progressTextStyleLight = TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: BrandColors.textColor1,
|
|
||||||
height: 1.7,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextStyle progressTextStyleDark = progressTextStyleLight.copyWith(
|
|
||||||
color: BrandColors.white,
|
|
||||||
);
|
|
|
@ -20,7 +20,13 @@ class RequestLoggingLink extends Link {
|
||||||
final Request request, [
|
final Request request, [
|
||||||
final NextLink? forward,
|
final NextLink? forward,
|
||||||
]) async* {
|
]) async* {
|
||||||
_logToAppConsole(request);
|
getIt.get<ConsoleModel>().addMessage(
|
||||||
|
GraphQlRequestMessage(
|
||||||
|
operation: request.operation,
|
||||||
|
variables: request.variables,
|
||||||
|
context: request.context,
|
||||||
|
),
|
||||||
|
);
|
||||||
yield* forward!(request);
|
yield* forward!(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +35,13 @@ 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);
|
||||||
_logToAppConsole(response);
|
getIt.get<ConsoleModel>().addMessage(
|
||||||
|
GraphQlResponseMessage(
|
||||||
|
data: response.data,
|
||||||
|
errors: response.errors,
|
||||||
|
context: response.context,
|
||||||
|
),
|
||||||
|
);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,9 +65,11 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final RequestInterceptorHandler handler,
|
final RequestInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
addMessage(
|
addMessage(
|
||||||
Message(
|
RestApiRequestMessage(
|
||||||
text:
|
method: options.method,
|
||||||
'request-uri: ${options.uri}\nheaders: ${options.headers}\ndata: ${options.data}',
|
data: options.data.toString(),
|
||||||
|
headers: options.headers,
|
||||||
|
uri: options.uri,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onRequest(options, handler);
|
return super.onRequest(options, handler);
|
||||||
|
@ -79,9 +81,11 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final ResponseInterceptorHandler handler,
|
final ResponseInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
addMessage(
|
addMessage(
|
||||||
Message(
|
RestApiResponseMessage(
|
||||||
text:
|
method: response.requestOptions.method,
|
||||||
'response-uri: ${response.realUri}\ncode: ${response.statusCode}\ndata: ${response.toString()}\n',
|
statusCode: response.statusCode,
|
||||||
|
data: response.data.toString(),
|
||||||
|
uri: response.realUri,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return super.onResponse(
|
return super.onResponse(
|
||||||
|
|
|
@ -15,10 +15,12 @@ part 'app_settings_state.dart';
|
||||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||||
AppSettingsCubit({
|
AppSettingsCubit({
|
||||||
required final bool isDarkModeOn,
|
required final bool isDarkModeOn,
|
||||||
|
required final bool isAutoDarkModeOn,
|
||||||
required final bool isOnboardingShowing,
|
required final bool isOnboardingShowing,
|
||||||
}) : super(
|
}) : super(
|
||||||
AppSettingsState(
|
AppSettingsState(
|
||||||
isDarkModeOn: isDarkModeOn,
|
isDarkModeOn: isDarkModeOn,
|
||||||
|
isAutoDarkModeOn: isAutoDarkModeOn,
|
||||||
isOnboardingShowing: isOnboardingShowing,
|
isOnboardingShowing: isOnboardingShowing,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -27,10 +29,12 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||||
|
|
||||||
void load() async {
|
void load() async {
|
||||||
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||||
|
final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn);
|
||||||
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isDarkModeOn: isDarkModeOn,
|
isDarkModeOn: isDarkModeOn,
|
||||||
|
isAutoDarkModeOn: isAutoDarkModeOn,
|
||||||
isOnboardingShowing: isOnboardingShowing,
|
isOnboardingShowing: isOnboardingShowing,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -49,9 +53,14 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||||
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
||||||
}
|
}
|
||||||
|
|
||||||
void turnOffOnboarding() {
|
void updateAutoDarkMode({required final bool isAutoDarkModeOn}) {
|
||||||
box.put(BNames.isOnboardingShowing, false);
|
box.put(BNames.isAutoDarkModeOn, isAutoDarkModeOn);
|
||||||
|
emit(state.copyWith(isAutoDarkModeOn: isAutoDarkModeOn));
|
||||||
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isOnboardingShowing: false));
|
void turnOffOnboarding({final bool isOnboardingShowing = false}) {
|
||||||
|
box.put(BNames.isOnboardingShowing, isOnboardingShowing);
|
||||||
|
|
||||||
|
emit(state.copyWith(isOnboardingShowing: isOnboardingShowing));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,25 @@ part of 'app_settings_cubit.dart';
|
||||||
class AppSettingsState extends Equatable {
|
class AppSettingsState extends Equatable {
|
||||||
const AppSettingsState({
|
const AppSettingsState({
|
||||||
required this.isDarkModeOn,
|
required this.isDarkModeOn,
|
||||||
|
required this.isAutoDarkModeOn,
|
||||||
required this.isOnboardingShowing,
|
required this.isOnboardingShowing,
|
||||||
this.corePalette,
|
this.corePalette,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isDarkModeOn;
|
final bool isDarkModeOn;
|
||||||
|
final bool isAutoDarkModeOn;
|
||||||
final bool isOnboardingShowing;
|
final bool isOnboardingShowing;
|
||||||
final color_utils.CorePalette? corePalette;
|
final color_utils.CorePalette? corePalette;
|
||||||
|
|
||||||
AppSettingsState copyWith({
|
AppSettingsState copyWith({
|
||||||
final bool? isDarkModeOn,
|
final bool? isDarkModeOn,
|
||||||
|
final bool? isAutoDarkModeOn,
|
||||||
final bool? isOnboardingShowing,
|
final bool? isOnboardingShowing,
|
||||||
final color_utils.CorePalette? corePalette,
|
final color_utils.CorePalette? corePalette,
|
||||||
}) =>
|
}) =>
|
||||||
AppSettingsState(
|
AppSettingsState(
|
||||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||||
|
isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn,
|
||||||
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
||||||
corePalette: corePalette ?? this.corePalette,
|
corePalette: corePalette ?? this.corePalette,
|
||||||
);
|
);
|
||||||
|
@ -26,5 +30,6 @@ class AppSettingsState extends Equatable {
|
||||||
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
|
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<dynamic> get props => [isDarkModeOn, isOnboardingShowing, corePalette];
|
List<dynamic> get props =>
|
||||||
|
[isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ class ApiDevicesCubit
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void load() async {
|
void load() async {
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||||
_refetch();
|
_refetch();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
|
|
|
@ -22,9 +22,6 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() => super.close();
|
|
||||||
|
|
||||||
Future<void> saveDomain() async {
|
Future<void> saveDomain() async {
|
||||||
assert(state is Loaded, 'wrong state');
|
assert(state is Loaded, 'wrong state');
|
||||||
final String domainName = (state as Loaded).domain;
|
final String domainName = (state as Loaded).domain;
|
||||||
|
|
|
@ -36,10 +36,6 @@ class UserFormCubit extends FormCubit {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> onSubmit() {
|
FutureOr<void> onSubmit() {
|
||||||
print('onSubmit');
|
|
||||||
print('initialUser: $initialUser');
|
|
||||||
print('login: ${login.state.value}');
|
|
||||||
print('password: ${password.state.value}');
|
|
||||||
if (initialUser == null) {
|
if (initialUser == null) {
|
||||||
final User user = User(
|
final User user = User(
|
||||||
login: login.state.value,
|
login: login.state.value,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
|
@ -20,7 +22,7 @@ class ApiProviderVolumeCubit
|
||||||
@override
|
@override
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||||
_refetch();
|
unawaited(_refetch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ class ApiProviderVolumeCubit
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
||||||
_refetch();
|
unawaited(_refetch());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refetch() async {
|
Future<void> _refetch() async {
|
||||||
|
@ -56,14 +58,14 @@ class ApiProviderVolumeCubit
|
||||||
await ApiController.currentVolumeProviderApiFactory!
|
await ApiController.currentVolumeProviderApiFactory!
|
||||||
.getVolumeProvider()
|
.getVolumeProvider()
|
||||||
.attachVolume(volume.providerVolume!, server.id);
|
.attachVolume(volume.providerVolume!, server.id);
|
||||||
refresh();
|
unawaited(refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> detachVolume(final DiskVolume volume) async {
|
Future<void> detachVolume(final DiskVolume volume) async {
|
||||||
await ApiController.currentVolumeProviderApiFactory!
|
await ApiController.currentVolumeProviderApiFactory!
|
||||||
.getVolumeProvider()
|
.getVolumeProvider()
|
||||||
.detachVolume(volume.providerVolume!);
|
.detachVolume(volume.providerVolume!);
|
||||||
refresh();
|
unawaited(refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> resizeVolume(
|
Future<bool> resizeVolume(
|
||||||
|
@ -125,14 +127,14 @@ class ApiProviderVolumeCubit
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
|
||||||
await ServerApi().mountVolume(volume!.name);
|
await ServerApi().mountVolume(volume!.name);
|
||||||
refresh();
|
unawaited(refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteVolume(final DiskVolume volume) async {
|
Future<void> deleteVolume(final DiskVolume volume) async {
|
||||||
await ApiController.currentVolumeProviderApiFactory!
|
await ApiController.currentVolumeProviderApiFactory!
|
||||||
.getVolumeProvider()
|
.getVolumeProvider()
|
||||||
.deleteVolume(volume.providerVolume!);
|
.deleteVolume(volume.providerVolume!);
|
||||||
refresh();
|
unawaited(refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
@ -14,21 +16,21 @@ class RecoveryKeyCubit
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void load() async {
|
void load() async {
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
||||||
} else {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
status: status,
|
|
||||||
loadingStatus: LoadingStatus.success,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: status,
|
||||||
|
loadingStatus: LoadingStatus.success,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
// } else {
|
||||||
|
// emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RecoveryKeyStatus?> _getRecoveryKeyStatus() async {
|
Future<RecoveryKeyStatus?> _getRecoveryKeyStatus() async {
|
||||||
|
@ -60,7 +62,7 @@ class RecoveryKeyCubit
|
||||||
final APIGenericResult<String> response =
|
final APIGenericResult<String> response =
|
||||||
await api.generateRecoveryToken(expirationDate, numberOfUses);
|
await api.generateRecoveryToken(expirationDate, numberOfUses);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
refresh();
|
unawaited(refresh());
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw GenerationError(response.message ?? 'Unknown error');
|
throw GenerationError(response.message ?? 'Unknown error');
|
||||||
|
|
|
@ -215,7 +215,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
|
|
||||||
void setDnsApiToken(final String dnsApiToken) async {
|
void setDnsApiToken(final String dnsApiToken) async {
|
||||||
if (state is ServerInstallationRecovery) {
|
if (state is ServerInstallationRecovery) {
|
||||||
setAndValidateCloudflareToken(dnsApiToken);
|
await setAndValidateCloudflareToken(dnsApiToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await repository.setDnsApiToken(dnsApiToken);
|
await repository.setDnsApiToken(dnsApiToken);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:basic_utils/basic_utils.dart';
|
import 'package:basic_utils/basic_utils.dart';
|
||||||
|
@ -276,7 +277,7 @@ class ServerInstallationRepository {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await saveServerDetails(serverDetails);
|
await saveServerDetails(serverDetails);
|
||||||
onSuccess(serverDetails);
|
unawaited(onSuccess(serverDetails));
|
||||||
},
|
},
|
||||||
cancelButtonOnPressed: onCancel,
|
cancelButtonOnPressed: onCancel,
|
||||||
);
|
);
|
||||||
|
@ -326,15 +327,15 @@ class ServerInstallationRepository {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await saveServerDetails(serverDetails);
|
await saveServerDetails(serverDetails);
|
||||||
onSuccess(serverDetails);
|
unawaited(onSuccess(serverDetails));
|
||||||
},
|
},
|
||||||
cancelButtonOnPressed: onCancel,
|
cancelButtonOnPressed: onCancel,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveServerDetails(createServerResult.data!);
|
await saveServerDetails(createServerResult.data!);
|
||||||
onSuccess(createServerResult.data!);
|
unawaited(onSuccess(createServerResult.data!));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
showInstallationErrorPopUp();
|
showInstallationErrorPopUp();
|
||||||
|
|
|
@ -24,7 +24,7 @@ class ApiServerVolumeCubit
|
||||||
@override
|
@override
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||||
reload();
|
unawaited(reload());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
reload();
|
unawaited(reload());
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
|
@ -62,7 +62,7 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
reload();
|
unawaited(reload());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveService(
|
Future<void> moveService(
|
||||||
|
|
19
lib/logic/cubit/support_system/support_system_cubit.dart
Normal file
19
lib/logic/cubit/support_system/support_system_cubit.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
part 'support_system_state.dart';
|
||||||
|
|
||||||
|
class SupportSystemCubit extends Cubit<SupportSystemState> {
|
||||||
|
SupportSystemCubit() : super(const SupportSystemState('about'));
|
||||||
|
|
||||||
|
void showArticle({
|
||||||
|
required final String article,
|
||||||
|
final BuildContext? context,
|
||||||
|
}) {
|
||||||
|
emit(SupportSystemState(article));
|
||||||
|
if (context != null) {
|
||||||
|
Scaffold.of(context).openEndDrawer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
lib/logic/cubit/support_system/support_system_state.dart
Normal file
12
lib/logic/cubit/support_system/support_system_state.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
part of 'support_system_cubit.dart';
|
||||||
|
|
||||||
|
class SupportSystemState extends Equatable {
|
||||||
|
const SupportSystemState(
|
||||||
|
this.currentArticle,
|
||||||
|
);
|
||||||
|
|
||||||
|
final String currentArticle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [currentArticle];
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
@ -39,7 +41,7 @@ class UsersCubit extends ServerInstallationDependendCubit<UsersState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh();
|
unawaited(refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
|
||||||
|
|
||||||
class NavigationService {
|
class NavigationService {
|
||||||
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
|
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
|
||||||
GlobalKey<ScaffoldMessengerState>();
|
GlobalKey<ScaffoldMessengerState>();
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
NavigatorState? get navigator => navigatorKey.currentState;
|
|
||||||
|
|
||||||
void showPopUpDialog(final AlertDialog dialog) {
|
void showPopUpDialog(final AlertDialog dialog) {
|
||||||
final BuildContext context = navigatorKey.currentState!.overlay!.context;
|
final BuildContext? context = navigatorKey.currentContext;
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
showSnackBar(
|
||||||
|
'Could not show dialog. This should not happen, please report this.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -21,8 +24,7 @@ class NavigationService {
|
||||||
void showSnackBar(final String text) {
|
void showSnackBar(final String text) {
|
||||||
final ScaffoldMessengerState state = scaffoldMessengerKey.currentState!;
|
final ScaffoldMessengerState state = scaffoldMessengerKey.currentState!;
|
||||||
final SnackBar snack = SnackBar(
|
final SnackBar snack = SnackBar(
|
||||||
backgroundColor: BrandColors.black.withOpacity(0.8),
|
content: Text(text),
|
||||||
content: Text(text, style: buttonTitleText),
|
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
state.showSnackBar(snack);
|
state.showSnackBar(snack);
|
||||||
|
|
|
@ -1,20 +1,74 @@
|
||||||
|
import 'package:graphql/client.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
final DateFormat formatter = DateFormat('hh:mm');
|
final DateFormat formatter = DateFormat('hh:mm');
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
Message({this.text, this.type = MessageType.normal}) : time = DateTime.now();
|
Message({this.text, this.severity = MessageSeverity.normal})
|
||||||
|
: time = DateTime.now();
|
||||||
Message.warn({this.text})
|
Message.warn({this.text})
|
||||||
: type = MessageType.warning,
|
: severity = MessageSeverity.warning,
|
||||||
time = DateTime.now();
|
time = DateTime.now();
|
||||||
|
|
||||||
final String? text;
|
final String? text;
|
||||||
final DateTime time;
|
final DateTime time;
|
||||||
final MessageType type;
|
final MessageSeverity severity;
|
||||||
String get timeString => formatter.format(time);
|
String get timeString => formatter.format(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MessageType {
|
enum MessageSeverity {
|
||||||
normal,
|
normal,
|
||||||
warning,
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
|
||||||
|
@ -20,7 +18,7 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await HiveConfig.init();
|
await HiveConfig.init();
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/// Wakelock support for Linux
|
/// Wakelock support for Linux
|
||||||
|
@ -43,21 +41,20 @@ void main() async {
|
||||||
fallbackColor: BrandColors.primary,
|
fallbackColor: BrandColors.primary,
|
||||||
);
|
);
|
||||||
|
|
||||||
BlocOverrides.runZoned(
|
Bloc.observer = SimpleBlocObserver();
|
||||||
() => runApp(
|
|
||||||
Localization(
|
runApp(
|
||||||
child: MyApp(
|
Localization(
|
||||||
lightThemeData: lightThemeData,
|
child: SelfprivacyApp(
|
||||||
darkThemeData: darkThemeData,
|
lightThemeData: lightThemeData,
|
||||||
),
|
darkThemeData: darkThemeData,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
blocObserver: SimpleBlocObserver(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class SelfprivacyApp extends StatelessWidget {
|
||||||
const MyApp({
|
SelfprivacyApp({
|
||||||
required this.lightThemeData,
|
required this.lightThemeData,
|
||||||
required this.darkThemeData,
|
required this.darkThemeData,
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -66,42 +63,42 @@ class MyApp extends StatelessWidget {
|
||||||
final ThemeData lightThemeData;
|
final ThemeData lightThemeData;
|
||||||
final ThemeData darkThemeData;
|
final ThemeData darkThemeData;
|
||||||
|
|
||||||
|
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Localization(
|
Widget build(final BuildContext context) => Localization(
|
||||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
child: BlocAndProviderConfig(
|
||||||
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||||
child: BlocAndProviderConfig(
|
builder: (
|
||||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
final BuildContext context,
|
||||||
builder: (
|
final AppSettingsState appSettings,
|
||||||
final BuildContext context,
|
) =>
|
||||||
final AppSettingsState appSettings,
|
MaterialApp.router(
|
||||||
) =>
|
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||||
MaterialApp(
|
routerDelegate: _appRouter.delegate(),
|
||||||
scaffoldMessengerKey:
|
scaffoldMessengerKey:
|
||||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||||
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
localizationsDelegates: context.localizationDelegates,
|
supportedLocales: context.supportedLocales,
|
||||||
supportedLocales: context.supportedLocales,
|
locale: context.locale,
|
||||||
locale: context.locale,
|
debugShowCheckedModeBanner: false,
|
||||||
debugShowCheckedModeBanner: false,
|
title: 'SelfPrivacy',
|
||||||
title: 'SelfPrivacy',
|
theme: lightThemeData,
|
||||||
theme: lightThemeData,
|
darkTheme: darkThemeData,
|
||||||
darkTheme: darkThemeData,
|
themeMode: appSettings.isAutoDarkModeOn
|
||||||
themeMode:
|
? ThemeMode.system
|
||||||
appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
|
: appSettings.isDarkModeOn
|
||||||
home: appSettings.isOnboardingShowing
|
? ThemeMode.dark
|
||||||
? const OnboardingPage(nextPage: InitializingPage())
|
: ThemeMode.light,
|
||||||
: const RootPage(),
|
builder: (final BuildContext context, final Widget? widget) {
|
||||||
builder: (final BuildContext context, final Widget? widget) {
|
Widget error = const Text('...rendering error...');
|
||||||
Widget error = const Text('...rendering error...');
|
if (widget is Scaffold || widget is Navigator) {
|
||||||
if (widget is Scaffold || widget is Navigator) {
|
error = Scaffold(body: Center(child: error));
|
||||||
error = Scaffold(body: Center(child: error));
|
}
|
||||||
}
|
ErrorWidget.builder =
|
||||||
ErrorWidget.builder =
|
(final FlutterErrorDetails errorDetails) => error;
|
||||||
(final FlutterErrorDetails errorDetails) => error;
|
return widget!;
|
||||||
return widget!;
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class BrandAlert extends AlertDialog {
|
|
||||||
BrandAlert({
|
|
||||||
super.key,
|
|
||||||
final String? title,
|
|
||||||
final String? contentText,
|
|
||||||
super.actions,
|
|
||||||
}) : super(
|
|
||||||
title: title != null ? Text(title) : null,
|
|
||||||
content: title != null ? Text(contentText!) : null,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
|
|
||||||
class BrandBottomSheet extends StatelessWidget {
|
|
||||||
const BrandBottomSheet({
|
|
||||||
required this.child,
|
|
||||||
super.key,
|
|
||||||
this.isExpended = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final bool isExpended;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
final double mainHeight = MediaQuery.of(context).size.height -
|
|
||||||
MediaQuery.of(context).padding.top -
|
|
||||||
300;
|
|
||||||
late Widget innerWidget;
|
|
||||||
if (isExpended) {
|
|
||||||
innerWidget = Scaffold(
|
|
||||||
body: child,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final ThemeData themeData = Theme.of(context);
|
|
||||||
|
|
||||||
innerWidget = Material(
|
|
||||||
color: themeData.scaffoldBackgroundColor,
|
|
||||||
child: IntrinsicHeight(child: child),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
height: 4,
|
|
||||||
width: 30,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
color: BrandColors.gray4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(maxHeight: mainHeight),
|
|
||||||
child: innerWidget,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class BrandCards {
|
|
||||||
static Widget big({required final Widget child}) => _BrandCard(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 15,
|
|
||||||
),
|
|
||||||
shadow: bigShadow,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
static Widget small({required final Widget child}) => _BrandCard(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 15,
|
|
||||||
vertical: 10,
|
|
||||||
),
|
|
||||||
shadow: bigShadow,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BrandCard extends StatelessWidget {
|
|
||||||
const _BrandCard({
|
|
||||||
required this.child,
|
|
||||||
required this.padding,
|
|
||||||
required this.shadow,
|
|
||||||
required this.borderRadius,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final EdgeInsets padding;
|
|
||||||
final List<BoxShadow> shadow;
|
|
||||||
final BorderRadius borderRadius;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: borderRadius,
|
|
||||||
boxShadow: shadow,
|
|
||||||
),
|
|
||||||
padding: padding,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<BoxShadow> bigShadow = [
|
|
||||||
BoxShadow(
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
blurRadius: 8,
|
|
||||||
color: Colors.black.withOpacity(.08),
|
|
||||||
)
|
|
||||||
];
|
|
|
@ -25,5 +25,8 @@ class BrandHeader extends StatelessWidget {
|
||||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
actions: const [
|
||||||
|
SizedBox.shrink(),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
|
||||||
import 'package:selfprivacy/ui/helpers/widget_size.dart';
|
|
||||||
|
|
||||||
class BrandHeroScreen extends StatelessWidget {
|
|
||||||
const BrandHeroScreen({
|
|
||||||
required this.children,
|
|
||||||
super.key,
|
|
||||||
this.hasBackButton = true,
|
|
||||||
this.hasFlashButton = true,
|
|
||||||
this.heroIcon,
|
|
||||||
this.heroIconWidget,
|
|
||||||
this.heroTitle = '',
|
|
||||||
this.heroSubtitle,
|
|
||||||
this.onBackButtonPressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<Widget> children;
|
|
||||||
final bool hasBackButton;
|
|
||||||
final bool hasFlashButton;
|
|
||||||
final IconData? heroIcon;
|
|
||||||
final Widget? heroIconWidget;
|
|
||||||
final String heroTitle;
|
|
||||||
final String? heroSubtitle;
|
|
||||||
final VoidCallback? onBackButtonPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
final Widget heroIconWidget = this.heroIconWidget ??
|
|
||||||
Icon(
|
|
||||||
heroIcon ?? Icons.help,
|
|
||||||
size: 48.0,
|
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
|
||||||
);
|
|
||||||
final bool hasHeroIcon = heroIcon != null || this.heroIconWidget != null;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
floatingActionButton: hasFlashButton ? const BrandFab() : null,
|
|
||||||
body: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
HeroSliverAppBar(
|
|
||||||
heroTitle: heroTitle,
|
|
||||||
hasHeroIcon: hasHeroIcon,
|
|
||||||
hasBackButton: hasBackButton,
|
|
||||||
onBackButtonPressed: onBackButtonPressed,
|
|
||||||
heroIconWidget: heroIconWidget,
|
|
||||||
),
|
|
||||||
if (heroSubtitle != null)
|
|
||||||
SliverPadding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
vertical: 4.0,
|
|
||||||
),
|
|
||||||
sliver: SliverList(
|
|
||||||
delegate: SliverChildListDelegate([
|
|
||||||
Text(
|
|
||||||
heroSubtitle!,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
|
||||||
),
|
|
||||||
textAlign: hasHeroIcon ? TextAlign.center : TextAlign.start,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverPadding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
sliver: SliverList(
|
|
||||||
delegate: SliverChildListDelegate(children),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HeroSliverAppBar extends StatefulWidget {
|
|
||||||
const HeroSliverAppBar({
|
|
||||||
required this.heroTitle,
|
|
||||||
required this.hasHeroIcon,
|
|
||||||
required this.hasBackButton,
|
|
||||||
required this.onBackButtonPressed,
|
|
||||||
required this.heroIconWidget,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String heroTitle;
|
|
||||||
final bool hasHeroIcon;
|
|
||||||
final bool hasBackButton;
|
|
||||||
final VoidCallback? onBackButtonPressed;
|
|
||||||
final Widget heroIconWidget;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<HeroSliverAppBar> createState() => _HeroSliverAppBarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
|
|
||||||
Size _size = Size.zero;
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => SliverAppBar(
|
|
||||||
expandedHeight:
|
|
||||||
widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height,
|
|
||||||
primary: true,
|
|
||||||
pinned: true,
|
|
||||||
stretch: true,
|
|
||||||
leading: widget.hasBackButton
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: widget.onBackButtonPressed ??
|
|
||||||
() => Navigator.of(context).pop(),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
|
||||||
title: LayoutBuilder(
|
|
||||||
builder: (final context, final constraints) => SizedBox(
|
|
||||||
width: constraints.maxWidth - 72.0,
|
|
||||||
child: WidgetSize(
|
|
||||||
onChange: (final Size size) => setState(() => _size = size),
|
|
||||||
child: Text(
|
|
||||||
widget.heroTitle,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
expandedTitleScale: 1.2,
|
|
||||||
centerTitle: true,
|
|
||||||
collapseMode: CollapseMode.pin,
|
|
||||||
titlePadding: const EdgeInsets.only(
|
|
||||||
bottom: 12.0,
|
|
||||||
top: 16.0,
|
|
||||||
),
|
|
||||||
background: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 72.0),
|
|
||||||
if (widget.hasHeroIcon) widget.heroIconWidget,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -27,14 +27,14 @@ class BrandLinearIndicator extends StatelessWidget {
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: AnimatedSlide(
|
child: AnimatedSlide(
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
offset: Offset(
|
offset: Offset(
|
||||||
-(1 - value),
|
-(1 - value),
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
width: constraints.maxWidth,
|
width: constraints.maxWidth,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: color,
|
||||||
|
|
|
@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter/services.dart' show rootBundle;
|
import 'package:flutter/services.dart' show rootBundle;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class BrandMarkdown extends StatefulWidget {
|
class BrandMarkdown extends StatefulWidget {
|
||||||
|
@ -37,24 +35,7 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
final MarkdownStyleSheet markdown = MarkdownStyleSheet();
|
||||||
final MarkdownStyleSheet markdown = MarkdownStyleSheet(
|
|
||||||
p: defaultTextStyle.copyWith(
|
|
||||||
color: isDark ? BrandColors.white : null,
|
|
||||||
),
|
|
||||||
h1: headline1Style.copyWith(
|
|
||||||
color: isDark ? BrandColors.white : null,
|
|
||||||
),
|
|
||||||
h2: headline2Style.copyWith(
|
|
||||||
color: isDark ? BrandColors.white : null,
|
|
||||||
),
|
|
||||||
h3: headline3Style.copyWith(
|
|
||||||
color: isDark ? BrandColors.white : null,
|
|
||||||
),
|
|
||||||
h4: headline4Style.copyWith(
|
|
||||||
color: isDark ? BrandColors.white : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return MarkdownBody(
|
return MarkdownBody(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
styleSheet: markdown,
|
styleSheet: markdown,
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
|
|
||||||
// TODO: Delete this file.
|
|
||||||
|
|
||||||
class BrandRadio extends StatelessWidget {
|
|
||||||
const BrandRadio({
|
|
||||||
required this.isChecked,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final bool isChecked;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Container(
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: _getBorder(),
|
|
||||||
),
|
|
||||||
child: isChecked
|
|
||||||
? Container(
|
|
||||||
height: 10,
|
|
||||||
width: 10,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: BrandColors.primary,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
BoxBorder? _getBorder() => Border.all(
|
|
||||||
color: isChecked ? BrandColors.primary : BrandColors.gray1,
|
|
||||||
width: 2,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class BrandSwitch extends StatelessWidget {
|
|
||||||
const BrandSwitch({
|
|
||||||
required this.onChanged,
|
|
||||||
required this.value,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ValueChanged<bool> onChanged;
|
|
||||||
final bool value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Switch(
|
|
||||||
activeColor: Theme.of(context).colorScheme.primary,
|
|
||||||
value: value,
|
|
||||||
onChanged: onChanged,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
|
||||||
|
|
||||||
class BrandTabBar extends StatefulWidget {
|
|
||||||
const BrandTabBar({super.key, this.controller});
|
|
||||||
|
|
||||||
final TabController? controller;
|
|
||||||
@override
|
|
||||||
State<BrandTabBar> createState() => _BrandTabBarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BrandTabBarState extends State<BrandTabBar> {
|
|
||||||
int? currentIndex;
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
currentIndex = widget.controller!.index;
|
|
||||||
widget.controller!.addListener(_listener);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listener() {
|
|
||||||
if (currentIndex != widget.controller!.index) {
|
|
||||||
setState(() {
|
|
||||||
currentIndex = widget.controller!.index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
widget.controller ?? widget.controller!.removeListener(_listener);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => NavigationBar(
|
|
||||||
destinations: [
|
|
||||||
_getIconButton('basis.providers'.tr(), BrandIcons.server, 0),
|
|
||||||
_getIconButton('basis.services'.tr(), BrandIcons.box, 1),
|
|
||||||
_getIconButton('basis.users'.tr(), BrandIcons.users, 2),
|
|
||||||
_getIconButton('basis.more'.tr(), Icons.menu_rounded, 3),
|
|
||||||
],
|
|
||||||
onDestinationSelected: (final index) {
|
|
||||||
widget.controller!.animateTo(index);
|
|
||||||
},
|
|
||||||
selectedIndex: currentIndex ?? 0,
|
|
||||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
|
||||||
);
|
|
||||||
|
|
||||||
NavigationDestination _getIconButton(
|
|
||||||
final String label,
|
|
||||||
final IconData iconData,
|
|
||||||
final int index,
|
|
||||||
) =>
|
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(iconData),
|
|
||||||
label: label,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,238 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
|
||||||
export 'package:selfprivacy/utils/extensions/text_extensions.dart';
|
|
||||||
|
|
||||||
// TODO: Delete this file
|
|
||||||
|
|
||||||
enum TextType {
|
|
||||||
h1, // right now only at onboarding and opened providers
|
|
||||||
h2, // cards titles
|
|
||||||
h3, // titles in about page
|
|
||||||
h4, // caption
|
|
||||||
h5, // Table data
|
|
||||||
body1, // normal
|
|
||||||
body2, // with opacity
|
|
||||||
medium,
|
|
||||||
small,
|
|
||||||
onboardingTitle,
|
|
||||||
buttonTitleText, // risen button title text,
|
|
||||||
h4Underlined,
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrandText extends StatelessWidget {
|
|
||||||
factory BrandText.h4(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextAlign? textAlign,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.h4,
|
|
||||||
style: style,
|
|
||||||
softWrap: true,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: textAlign,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory BrandText.onboardingTitle(
|
|
||||||
final String text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.onboardingTitle,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
factory BrandText.h3(
|
|
||||||
final String text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextAlign? textAlign,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.h3,
|
|
||||||
style: style,
|
|
||||||
textAlign: textAlign,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory BrandText.h4Underlined(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextAlign? textAlign,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.h4Underlined,
|
|
||||||
style: style,
|
|
||||||
softWrap: true,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: textAlign,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory BrandText.h1(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextOverflow? overflow,
|
|
||||||
final bool? softWrap,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.h1,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
factory BrandText.h2(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextAlign? textAlign,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.h2,
|
|
||||||
style: style,
|
|
||||||
textAlign: textAlign,
|
|
||||||
);
|
|
||||||
factory BrandText.body1(final String? text, {final TextStyle? style}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.body1,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
factory BrandText.small(final String text, {final TextStyle? style}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.small,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
factory BrandText.body2(final String? text, {final TextStyle? style}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.body2,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
factory BrandText.buttonTitleText(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.buttonTitleText,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory BrandText.h5(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextAlign? textAlign,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.h5,
|
|
||||||
style: style,
|
|
||||||
textAlign: textAlign,
|
|
||||||
);
|
|
||||||
factory BrandText.medium(
|
|
||||||
final String? text, {
|
|
||||||
final TextStyle? style,
|
|
||||||
final TextAlign? textAlign,
|
|
||||||
}) =>
|
|
||||||
BrandText(
|
|
||||||
text,
|
|
||||||
type: TextType.medium,
|
|
||||||
style: style,
|
|
||||||
textAlign: textAlign,
|
|
||||||
);
|
|
||||||
const BrandText(
|
|
||||||
this.text, {
|
|
||||||
required this.type,
|
|
||||||
super.key,
|
|
||||||
this.style,
|
|
||||||
this.overflow,
|
|
||||||
this.softWrap,
|
|
||||||
this.textAlign,
|
|
||||||
this.maxLines,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String? text;
|
|
||||||
final TextStyle? style;
|
|
||||||
final TextType type;
|
|
||||||
final TextOverflow? overflow;
|
|
||||||
final bool? softWrap;
|
|
||||||
final TextAlign? textAlign;
|
|
||||||
final int? maxLines;
|
|
||||||
@override
|
|
||||||
Text build(final BuildContext context) {
|
|
||||||
TextStyle style;
|
|
||||||
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
switch (type) {
|
|
||||||
case TextType.h1:
|
|
||||||
style = isDark
|
|
||||||
? headline1Style.copyWith(color: Colors.white)
|
|
||||||
: headline1Style;
|
|
||||||
break;
|
|
||||||
case TextType.h2:
|
|
||||||
style = isDark
|
|
||||||
? headline2Style.copyWith(color: Colors.white)
|
|
||||||
: headline2Style;
|
|
||||||
break;
|
|
||||||
case TextType.h3:
|
|
||||||
style = isDark
|
|
||||||
? headline3Style.copyWith(color: Colors.white)
|
|
||||||
: headline3Style;
|
|
||||||
break;
|
|
||||||
case TextType.h4:
|
|
||||||
style = isDark
|
|
||||||
? headline4Style.copyWith(color: Colors.white)
|
|
||||||
: headline4Style;
|
|
||||||
break;
|
|
||||||
case TextType.h4Underlined:
|
|
||||||
style = isDark
|
|
||||||
? headline4UnderlinedStyle.copyWith(color: Colors.white)
|
|
||||||
: headline4UnderlinedStyle;
|
|
||||||
break;
|
|
||||||
case TextType.h5:
|
|
||||||
style = isDark
|
|
||||||
? headline5Style.copyWith(color: Colors.white)
|
|
||||||
: headline5Style;
|
|
||||||
break;
|
|
||||||
case TextType.body1:
|
|
||||||
style = isDark ? body1Style.copyWith(color: Colors.white) : body1Style;
|
|
||||||
break;
|
|
||||||
case TextType.body2:
|
|
||||||
style = isDark
|
|
||||||
? body2Style.copyWith(color: Colors.white.withOpacity(0.6))
|
|
||||||
: body2Style;
|
|
||||||
break;
|
|
||||||
case TextType.small:
|
|
||||||
style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle;
|
|
||||||
break;
|
|
||||||
case TextType.onboardingTitle:
|
|
||||||
style = isDark
|
|
||||||
? onboardingTitle.copyWith(color: Colors.white)
|
|
||||||
: onboardingTitle;
|
|
||||||
break;
|
|
||||||
case TextType.medium:
|
|
||||||
style =
|
|
||||||
isDark ? mediumStyle.copyWith(color: Colors.white) : mediumStyle;
|
|
||||||
break;
|
|
||||||
case TextType.buttonTitleText:
|
|
||||||
style = !isDark
|
|
||||||
? buttonTitleText.copyWith(color: Colors.white)
|
|
||||||
: buttonTitleText;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (this.style != null) {
|
|
||||||
style = style.merge(this.style);
|
|
||||||
}
|
|
||||||
return Text(
|
|
||||||
text!,
|
|
||||||
style: style,
|
|
||||||
maxLines: maxLines,
|
|
||||||
overflow: overflow,
|
|
||||||
softWrap: softWrap,
|
|
||||||
textAlign: textAlign,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||||
|
|
||||||
class BrandTimer extends StatefulWidget {
|
class BrandTimer extends StatefulWidget {
|
||||||
|
@ -52,11 +51,12 @@ class _BrandTimerState extends State<BrandTimer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => BrandText.medium(
|
Widget build(final BuildContext context) => Text(
|
||||||
_timeString,
|
_timeString ?? '',
|
||||||
style: const TextStyle(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: NamedFontWeight.demiBold,
|
fontWeight: NamedFontWeight.demiBold,
|
||||||
),
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
void _getTime() {
|
void _getTime() {
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
|
|
||||||
enum BrandButtonTypes { rised, text, iconText }
|
|
||||||
|
|
||||||
class BrandButton {
|
class BrandButton {
|
||||||
static ConstrainedBox rised({
|
static ConstrainedBox rised({
|
||||||
|
@ -58,53 +55,4 @@ class BrandButton {
|
||||||
),
|
),
|
||||||
child: TextButton(onPressed: onPressed, child: Text(title)),
|
child: TextButton(onPressed: onPressed, child: Text(title)),
|
||||||
);
|
);
|
||||||
|
|
||||||
static IconTextButton emptyWithIconText({
|
|
||||||
required final VoidCallback onPressed,
|
|
||||||
required final String title,
|
|
||||||
required final Icon icon,
|
|
||||||
final Key? key,
|
|
||||||
}) =>
|
|
||||||
IconTextButton(
|
|
||||||
key: key,
|
|
||||||
title: title,
|
|
||||||
onPressed: onPressed,
|
|
||||||
icon: icon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class IconTextButton extends StatelessWidget {
|
|
||||||
const IconTextButton({
|
|
||||||
super.key,
|
|
||||||
this.onPressed,
|
|
||||||
this.title,
|
|
||||||
this.icon,
|
|
||||||
});
|
|
||||||
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
final String? title;
|
|
||||||
final Icon? icon;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: onPressed,
|
|
||||||
child: Container(
|
|
||||||
height: 48,
|
|
||||||
width: double.infinity,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
BrandText.body1(title),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: icon,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ActionButton extends StatelessWidget {
|
/// Basically a [TextButton] to be used in dialogs
|
||||||
const ActionButton({
|
class DialogActionButton extends StatelessWidget {
|
||||||
|
const DialogActionButton({
|
||||||
super.key,
|
super.key,
|
||||||
this.text,
|
this.text,
|
||||||
this.onPressed,
|
this.onPressed,
|
|
@ -1,6 +1,16 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// For some reason original [SegmentedButton] does not have animations.
|
||||||
|
///
|
||||||
|
/// The [SegmentedButtons] was written for SelfPrivacy before [SegmentedButton] was introduced.
|
||||||
|
/// While it doesn't have that much options to pass, it has cute little animation.
|
||||||
|
/// It is based on [ToggleButtons].
|
||||||
class SegmentedButtons extends StatelessWidget {
|
class SegmentedButtons extends StatelessWidget {
|
||||||
|
/// Creates a segmented buttons widget. This is a SelfPrivacy implementation.
|
||||||
|
///
|
||||||
|
/// Provide the button titles in [titles] as a [List<String>].
|
||||||
|
/// Current selection is provided in [isSelected] as a [List<bool>].
|
||||||
|
/// This widget will call [onPressed] with the index of the button that was pressed.
|
||||||
const SegmentedButtons({
|
const SegmentedButtons({
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
|
@ -8,15 +18,24 @@ class SegmentedButtons extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The current selection state of the buttons.
|
||||||
|
///
|
||||||
|
/// The length of this list must be equal to the length of [titles].
|
||||||
|
/// Several buttons can be selected at the same time.
|
||||||
final List<bool> isSelected;
|
final List<bool> isSelected;
|
||||||
|
|
||||||
|
/// The callback that is called when a button is pressed.
|
||||||
|
/// It will be called with the index of the button that was pressed.
|
||||||
final Function(int)? onPressed;
|
final Function(int)? onPressed;
|
||||||
|
|
||||||
|
/// The titles of the buttons.
|
||||||
final List<String> titles;
|
final List<String> titles;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => LayoutBuilder(
|
Widget build(final BuildContext context) => LayoutBuilder(
|
||||||
builder: (final context, final constraints) => ToggleButtons(
|
builder: (final context, final constraints) => ToggleButtons(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minWidth: (constraints.maxWidth - 8) / 3,
|
minWidth: (constraints.maxWidth - 8) / titles.length,
|
||||||
minHeight: 40 + Theme.of(context).visualDensity.vertical * 4,
|
minHeight: 40 + Theme.of(context).visualDensity.vertical * 4,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(48),
|
borderRadius: BorderRadius.circular(48),
|
||||||
|
@ -38,7 +57,7 @@ class SegmentedButtons extends StatelessWidget {
|
||||||
opacity: isSelected[index] ? 1 : 0,
|
opacity: isSelected[index] ? 1 : 0,
|
||||||
child: AnimatedScale(
|
child: AnimatedScale(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
scale: isSelected[index] ? 1 : 0,
|
scale: isSelected[index] ? 1 : 0,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
|
@ -53,7 +72,7 @@ class SegmentedButtons extends StatelessWidget {
|
||||||
? const EdgeInsets.only(left: 24)
|
? const EdgeInsets.only(left: 24)
|
||||||
: EdgeInsets.zero,
|
: EdgeInsets.zero,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: Theme.of(context).textTheme.labelLarge,
|
style: Theme.of(context).textTheme.labelLarge,
|
113
lib/ui/components/drawers/progress_drawer.dart
Normal file
113
lib/ui/components/drawers/progress_drawer.dart
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ProgressDrawer extends StatelessWidget {
|
||||||
|
/// A [Drawer] that displays a list of steps and the current step.
|
||||||
|
/// Used in setup wizards. The [trailing] widget is displayed at the bottom.
|
||||||
|
/// The [steps] are translated using [EasyLocalization].
|
||||||
|
const ProgressDrawer({
|
||||||
|
required this.steps,
|
||||||
|
required this.currentStep,
|
||||||
|
required this.constraints,
|
||||||
|
required this.trailing,
|
||||||
|
required this.title,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<String> steps;
|
||||||
|
final int currentStep;
|
||||||
|
final Widget trailing;
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => SizedBox(
|
||||||
|
width: 300,
|
||||||
|
height: constraints.maxHeight,
|
||||||
|
child: Drawer(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...steps.map((final step) {
|
||||||
|
final index = steps.indexOf(step);
|
||||||
|
return _StepIndicator(
|
||||||
|
title: step.tr(),
|
||||||
|
isCurrent: index == currentStep,
|
||||||
|
isCompleted: index < currentStep,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// const Spacer(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: trailing,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StepIndicator extends StatelessWidget {
|
||||||
|
const _StepIndicator({
|
||||||
|
required this.title,
|
||||||
|
required this.isCompleted,
|
||||||
|
required this.isCurrent,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final bool isCompleted;
|
||||||
|
final bool isCurrent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => ListTile(
|
||||||
|
selected: isCurrent,
|
||||||
|
leading: isCurrent
|
||||||
|
? const _StepCurrentIcon()
|
||||||
|
: isCompleted
|
||||||
|
? const _StepCompletedIcon()
|
||||||
|
: const _StepPendingIcon(),
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
),
|
||||||
|
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StepCompletedIcon extends StatelessWidget {
|
||||||
|
const _StepCompletedIcon();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => const Icon(Icons.check_circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StepPendingIcon extends StatelessWidget {
|
||||||
|
const _StepPendingIcon();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => const Icon(Icons.circle_outlined);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StepCurrentIcon extends StatelessWidget {
|
||||||
|
const _StepCurrentIcon();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) =>
|
||||||
|
const Icon(Icons.build_circle_outlined);
|
||||||
|
}
|
52
lib/ui/components/drawers/support_drawer.dart
Normal file
52
lib/ui/components/drawers/support_drawer.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
|
|
||||||
|
class SupportDrawer extends StatelessWidget {
|
||||||
|
const SupportDrawer({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final currentArticle =
|
||||||
|
context.watch<SupportSystemCubit>().state.currentArticle;
|
||||||
|
return Drawer(
|
||||||
|
width: 440,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.help_outline),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Text(
|
||||||
|
'support.title'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Scaffold.of(context).closeEndDrawer(),
|
||||||
|
icon: const Icon(Icons.chevron_right_outlined),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: BrandMarkdown(
|
||||||
|
fileName: currentArticle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class BrandError extends StatelessWidget {
|
|
||||||
const BrandError({super.key, this.error, this.stackTrace});
|
|
||||||
|
|
||||||
final Object? error;
|
|
||||||
final StackTrace? stackTrace;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => SafeArea(
|
|
||||||
child: Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(error.toString()),
|
|
||||||
const Text('stackTrace: '),
|
|
||||||
Text(stackTrace.toString()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -6,10 +6,8 @@ import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
|
||||||
|
@ -32,7 +30,12 @@ class JobsContent extends StatelessWidget {
|
||||||
if (state is JobsStateEmpty) {
|
if (state is JobsStateEmpty) {
|
||||||
widgets = [
|
widgets = [
|
||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
Center(child: BrandText.body1('jobs.empty'.tr())),
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'jobs.empty'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (installationState is ServerInstallationFinished) {
|
if (installationState is ServerInstallationFinished) {
|
||||||
|
@ -65,38 +68,49 @@ class JobsContent extends StatelessWidget {
|
||||||
];
|
];
|
||||||
} else if (state is JobsStateWithJobs) {
|
} else if (state is JobsStateWithJobs) {
|
||||||
widgets = [
|
widgets = [
|
||||||
...state.clientJobList
|
...state.clientJobList.map(
|
||||||
.map(
|
(final j) => Row(
|
||||||
(final j) => Row(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Expanded(
|
child: Card(
|
||||||
child: BrandCards.small(
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
child: Text(j.title),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 10,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.errorContainer,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<JobsCubit>().removeJob(j.id),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'basis.remove'.tr(),
|
j.title,
|
||||||
style: TextStyle(
|
style:
|
||||||
color:
|
Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||||
Theme.of(context).colorScheme.onErrorContainer,
|
color: Theme.of(context)
|
||||||
),
|
.colorScheme
|
||||||
|
.onSurfaceVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(width: 10),
|
||||||
.toList(),
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.errorContainer,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => context.read<JobsCubit>().removeJob(j.id),
|
||||||
|
child: Text(
|
||||||
|
'basis.remove'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () => context.read<JobsCubit>().applyAll(),
|
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||||
|
@ -109,8 +123,9 @@ class JobsContent extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
Center(
|
Center(
|
||||||
child: BrandText.h2(
|
child: Text(
|
||||||
'jobs.title'.tr(),
|
'jobs.title'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
300
lib/ui/components/list_tiles/log_list_tile.dart
Normal file
300
lib/ui/components/list_tiles/log_list_tile.dart
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/message.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
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: 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
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: 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
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: 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
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: 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,7 +1,7 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class NotReadyCard extends StatelessWidget {
|
class NotReadyCard extends StatelessWidget {
|
||||||
|
@ -13,11 +13,7 @@ class NotReadyCard extends StatelessWidget {
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => context.pushRoute(const InitializingRoute()),
|
||||||
materialRoute(
|
|
||||||
const InitializingPage(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
title: Text(
|
||||||
'not_ready_card.in_menu'.tr(),
|
'not_ready_card.in_menu'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
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:ionicons/ionicons.dart';
|
import 'package:ionicons/ionicons.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/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
|
||||||
|
|
||||||
class BrandFab extends StatefulWidget {
|
class BrandFab extends StatefulWidget {
|
||||||
const BrandFab({super.key});
|
const BrandFab({
|
||||||
|
this.extended = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool extended;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BrandFab> createState() => _BrandFabState();
|
State<BrandFab> createState() => _BrandFabState();
|
||||||
|
@ -56,28 +60,40 @@ class _BrandFabState extends State<BrandFab>
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: Make a hero animation to the screen
|
// TODO: Make a hero animation to the screen
|
||||||
showBrandBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (final BuildContext context) => const BrandBottomSheet(
|
builder: (final BuildContext context) => const JobsContent(),
|
||||||
isExpended: true,
|
|
||||||
child: JobsContent(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: AnimatedBuilder(
|
isExtended: widget.extended,
|
||||||
animation: _colorTween,
|
child: Row(
|
||||||
builder: (final BuildContext context, final Widget? child) {
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
final double v = _animationController.value;
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
final IconData icon =
|
children: [
|
||||||
v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
AnimatedBuilder(
|
||||||
return Transform.scale(
|
animation: _colorTween,
|
||||||
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
|
builder: (final BuildContext context, final Widget? child) {
|
||||||
child: Icon(
|
final double v = _animationController.value;
|
||||||
icon,
|
final IconData icon =
|
||||||
color: _colorTween.value,
|
v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
|
||||||
|
return Transform.scale(
|
||||||
|
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: _colorTween.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (widget.extended)
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
),
|
),
|
||||||
);
|
if (widget.extended)
|
||||||
},
|
Text(
|
||||||
|
'jobs.title'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
|
|
||||||
class ProgressBar extends StatefulWidget {
|
class ProgressBar extends StatefulWidget {
|
||||||
|
@ -65,7 +63,7 @@ class _ProgressBarState extends State<ProgressBar> {
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: BrandColors.gray4,
|
color: const Color(0xFFDDDDDD),
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
),
|
),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
@ -119,3 +117,13 @@ class _ProgressBarState extends State<ProgressBar> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TextStyle progressTextStyleLight = TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.black,
|
||||||
|
height: 1.7,
|
||||||
|
);
|
||||||
|
|
||||||
|
final TextStyle progressTextStyleDark = progressTextStyleLight.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
);
|
||||||
|
|
|
@ -72,7 +72,10 @@ class ServiceConsumptionTitle extends StatelessWidget {
|
||||||
service.svgIcon,
|
service.svgIcon,
|
||||||
width: 24.0,
|
width: 24.0,
|
||||||
height: 24.0,
|
height: 24.0,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
colorFilter: ColorFilter.mode(
|
||||||
|
Theme.of(context).colorScheme.onBackground,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
|
|
||||||
// TODO: Delete this file.
|
|
||||||
|
|
||||||
class SwitcherBlock extends StatelessWidget {
|
|
||||||
const SwitcherBlock({
|
|
||||||
required this.child,
|
|
||||||
required this.isActive,
|
|
||||||
required this.onChange,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final bool isActive;
|
|
||||||
final ValueChanged<bool> onChange;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Flexible(child: child),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Switch(
|
|
||||||
activeColor: BrandColors.green1,
|
|
||||||
activeTrackColor: BrandColors.green2,
|
|
||||||
onChanged: onChange,
|
|
||||||
value: isActive,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,21 +1,7 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
|
||||||
|
|
||||||
Future<T?> showBrandBottomSheet<T>({
|
|
||||||
required final BuildContext context,
|
|
||||||
required final WidgetBuilder builder,
|
|
||||||
}) =>
|
|
||||||
showCupertinoModalBottomSheet<T>(
|
|
||||||
builder: builder,
|
|
||||||
barrierColor: Colors.black45,
|
|
||||||
context: context,
|
|
||||||
shadow: const BoxShadow(color: Colors.transparent),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
);
|
|
||||||
|
|
||||||
void showPopUpAlert({
|
void showPopUpAlert({
|
||||||
required final String description,
|
required final String description,
|
||||||
|
@ -26,16 +12,16 @@ void showPopUpAlert({
|
||||||
final String? cancelButtonTitle,
|
final String? cancelButtonTitle,
|
||||||
}) {
|
}) {
|
||||||
getIt.get<NavigationService>().showPopUpDialog(
|
getIt.get<NavigationService>().showPopUpDialog(
|
||||||
BrandAlert(
|
AlertDialog(
|
||||||
title: alertTitle ?? 'basis.alert'.tr(),
|
title: Text(alertTitle ?? 'basis.alert'.tr()),
|
||||||
contentText: description,
|
content: Text(description),
|
||||||
actions: [
|
actions: [
|
||||||
ActionButton(
|
DialogActionButton(
|
||||||
text: actionButtonTitle,
|
text: actionButtonTitle,
|
||||||
isRed: true,
|
isRed: true,
|
||||||
onPressed: actionButtonOnPressed,
|
onPressed: actionButtonOnPressed,
|
||||||
),
|
),
|
||||||
ActionButton(
|
DialogActionButton(
|
||||||
text: cancelButtonTitle ?? 'basis.cancel'.tr(),
|
text: cancelButtonTitle ?? 'basis.cancel'.tr(),
|
||||||
onPressed: cancelButtonOnPressed,
|
onPressed: cancelButtonOnPressed,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
/// A helper widget that calls a callback when its size changes.
|
||||||
|
///
|
||||||
|
/// This is useful when you want to know the size of a widget, and use it in
|
||||||
|
/// another leaf of the tree.
|
||||||
|
///
|
||||||
|
/// The [onChange] callback is called after the widget is rendered, and the
|
||||||
|
/// size of the widget is different from the previous render.
|
||||||
class WidgetSize extends StatefulWidget {
|
class WidgetSize extends StatefulWidget {
|
||||||
|
/// Creates a helper widget that calls a callback when its size changes.
|
||||||
const WidgetSize({
|
const WidgetSize({
|
||||||
required this.onChange,
|
required this.onChange,
|
||||||
required this.child,
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The child widget, the size of which is to be measured.
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Function onChange;
|
|
||||||
|
/// The callback to be called when the size of the widget changes.
|
||||||
|
final Function(Size) onChange;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<WidgetSize> createState() => _WidgetSizeState();
|
State<WidgetSize> createState() => _WidgetSizeState();
|
||||||
|
@ -34,6 +46,11 @@ class _WidgetSizeState extends State<WidgetSize> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final newSize = context.size;
|
final newSize = context.size;
|
||||||
|
|
||||||
|
if (newSize == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldSize == newSize) {
|
if (oldSize == newSize) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
195
lib/ui/layouts/brand_hero_screen.dart
Normal file
195
lib/ui/layouts/brand_hero_screen.dart
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:ionicons/ionicons.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
|
||||||
|
import 'package:selfprivacy/ui/helpers/widget_size.dart';
|
||||||
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
|
|
||||||
|
class BrandHeroScreen extends StatelessWidget {
|
||||||
|
const BrandHeroScreen({
|
||||||
|
required this.children,
|
||||||
|
super.key,
|
||||||
|
this.hasBackButton = true,
|
||||||
|
this.hasFlashButton = false,
|
||||||
|
this.heroIcon,
|
||||||
|
this.heroIconWidget,
|
||||||
|
this.heroTitle = '',
|
||||||
|
this.heroSubtitle,
|
||||||
|
this.onBackButtonPressed,
|
||||||
|
this.bodyPadding = const EdgeInsets.all(16.0),
|
||||||
|
this.ignoreBreakpoints = false,
|
||||||
|
this.hasSupportDrawer = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Widget> children;
|
||||||
|
final bool hasBackButton;
|
||||||
|
final bool hasFlashButton;
|
||||||
|
final IconData? heroIcon;
|
||||||
|
final Widget? heroIconWidget;
|
||||||
|
final String heroTitle;
|
||||||
|
final String? heroSubtitle;
|
||||||
|
final VoidCallback? onBackButtonPressed;
|
||||||
|
final EdgeInsetsGeometry bodyPadding;
|
||||||
|
|
||||||
|
/// On non-mobile screens the buttons of the app bar are hidden.
|
||||||
|
/// This is because this widget implies that it is nested inside a bigger layout.
|
||||||
|
/// If it is not nested, set this to true.
|
||||||
|
final bool ignoreBreakpoints;
|
||||||
|
|
||||||
|
/// Usually support drawer is provided by the parent layout.
|
||||||
|
/// If it is not provided, set this to true.
|
||||||
|
final bool hasSupportDrawer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final Widget heroIconWidget = this.heroIconWidget ??
|
||||||
|
Icon(
|
||||||
|
heroIcon ?? Icons.help,
|
||||||
|
size: 48.0,
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
);
|
||||||
|
final bool hasHeroIcon = heroIcon != null || this.heroIconWidget != null;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
endDrawerEnableOpenDragGesture: false,
|
||||||
|
endDrawer: hasSupportDrawer ? const SupportDrawer() : null,
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HeroSliverAppBar(
|
||||||
|
heroTitle: heroTitle,
|
||||||
|
hasHeroIcon: hasHeroIcon,
|
||||||
|
hasBackButton: hasBackButton,
|
||||||
|
onBackButtonPressed: onBackButtonPressed,
|
||||||
|
heroIconWidget: heroIconWidget,
|
||||||
|
hasFlashButton: hasFlashButton,
|
||||||
|
ignoreBreakpoints: ignoreBreakpoints,
|
||||||
|
),
|
||||||
|
if (heroSubtitle != null)
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildListDelegate([
|
||||||
|
Text(
|
||||||
|
heroSubtitle!,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
),
|
||||||
|
textAlign: hasHeroIcon ? TextAlign.center : TextAlign.start,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: bodyPadding,
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildListDelegate(children),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeroSliverAppBar extends StatefulWidget {
|
||||||
|
const HeroSliverAppBar({
|
||||||
|
required this.heroTitle,
|
||||||
|
required this.hasHeroIcon,
|
||||||
|
required this.hasBackButton,
|
||||||
|
required this.onBackButtonPressed,
|
||||||
|
required this.heroIconWidget,
|
||||||
|
required this.hasFlashButton,
|
||||||
|
required this.ignoreBreakpoints,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String heroTitle;
|
||||||
|
final bool hasHeroIcon;
|
||||||
|
final bool hasBackButton;
|
||||||
|
final bool hasFlashButton;
|
||||||
|
final VoidCallback? onBackButtonPressed;
|
||||||
|
final Widget heroIconWidget;
|
||||||
|
final bool ignoreBreakpoints;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HeroSliverAppBar> createState() => _HeroSliverAppBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
|
||||||
|
Size _size = Size.zero;
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final isMobile =
|
||||||
|
widget.ignoreBreakpoints ? true : Breakpoints.small.isActive(context);
|
||||||
|
final isJobsListEmpty = context.watch<JobsCubit>().state is JobsStateEmpty;
|
||||||
|
return SliverAppBar(
|
||||||
|
expandedHeight:
|
||||||
|
widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height,
|
||||||
|
primary: true,
|
||||||
|
pinned: isMobile,
|
||||||
|
stretch: true,
|
||||||
|
surfaceTintColor: isMobile ? null : Colors.transparent,
|
||||||
|
leading: (widget.hasBackButton && isMobile)
|
||||||
|
? const AutoLeadingButton()
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
actions: [
|
||||||
|
if (widget.hasFlashButton && isMobile)
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (final BuildContext context) => const JobsContent(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
isJobsListEmpty ? Ionicons.flash_outline : Ionicons.flash,
|
||||||
|
),
|
||||||
|
color: isJobsListEmpty
|
||||||
|
? Theme.of(context).colorScheme.onBackground
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox.shrink(),
|
||||||
|
],
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
title: LayoutBuilder(
|
||||||
|
builder: (final context, final constraints) => SizedBox(
|
||||||
|
width: constraints.maxWidth - 72.0,
|
||||||
|
child: WidgetSize(
|
||||||
|
onChange: (final Size size) => setState(() => _size = size),
|
||||||
|
child: Text(
|
||||||
|
widget.heroTitle,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expandedTitleScale: 1.2,
|
||||||
|
centerTitle: true,
|
||||||
|
collapseMode: CollapseMode.pin,
|
||||||
|
titlePadding: const EdgeInsets.only(
|
||||||
|
bottom: 12.0,
|
||||||
|
top: 16.0,
|
||||||
|
),
|
||||||
|
background: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 72.0),
|
||||||
|
if (widget.hasHeroIcon) widget.heroIconWidget,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
70
lib/ui/layouts/responsive_layout_with_infobox.dart
Normal file
70
lib/ui/layouts/responsive_layout_with_infobox.dart
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
|
|
||||||
|
class ResponsiveLayoutWithInfobox extends StatelessWidget {
|
||||||
|
const ResponsiveLayoutWithInfobox({
|
||||||
|
required this.primaryColumn,
|
||||||
|
this.topChild,
|
||||||
|
this.secondaryColumn,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget? topChild;
|
||||||
|
final Widget primaryColumn;
|
||||||
|
final Widget? secondaryColumn;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final hasSecondaryColumn = secondaryColumn != null;
|
||||||
|
final hasTopChild = topChild != null;
|
||||||
|
|
||||||
|
if (Breakpoints.large.isActive(context)) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (final context, final constraints) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (hasTopChild)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: constraints.maxWidth * 0.9,
|
||||||
|
child: topChild,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (hasTopChild) const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: hasSecondaryColumn
|
||||||
|
? constraints.maxWidth * 0.7
|
||||||
|
: constraints.maxWidth * 0.9,
|
||||||
|
child: primaryColumn,
|
||||||
|
),
|
||||||
|
if (hasSecondaryColumn) const SizedBox(width: 16),
|
||||||
|
if (hasSecondaryColumn)
|
||||||
|
SizedBox(
|
||||||
|
width: constraints.maxWidth * 0.2,
|
||||||
|
child: secondaryColumn,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (hasTopChild) topChild!,
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
primaryColumn,
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
if (hasSecondaryColumn) secondaryColumn!,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
277
lib/ui/layouts/root_scaffold_with_navigation.dart
Normal file
277
lib/ui/layouts/root_scaffold_with_navigation.dart
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/drawers/support_drawer.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.pop(),
|
||||||
|
)
|
||||||
|
: 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: NavigationBar(
|
||||||
|
selectedIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex,
|
||||||
|
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,27 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/backup.dart';
|
import 'package:selfprivacy/logic/models/json/backup.dart';
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
|
|
||||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
class BackupDetails extends StatefulWidget {
|
@RoutePage()
|
||||||
const BackupDetails({super.key});
|
class BackupDetailsPage extends StatefulWidget {
|
||||||
|
const BackupDetailsPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BackupDetails> createState() => _BackupDetailsState();
|
State<BackupDetailsPage> createState() => _BackupDetailsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BackupDetailsState extends State<BackupDetails>
|
class _BackupDetailsPageState extends State<BackupDetailsPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
|
@ -57,7 +58,10 @@ class _BackupDetailsState extends State<BackupDetails>
|
||||||
text: 'backup.initialize'.tr(),
|
text: 'backup.initialize'.tr(),
|
||||||
),
|
),
|
||||||
if (backupStatus == BackupStatusEnum.initializing)
|
if (backupStatus == BackupStatusEnum.initializing)
|
||||||
BrandText.body1('backup.waiting_for_rebuild'.tr()),
|
Text(
|
||||||
|
'backup.waiting_for_rebuild'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
if (backupStatus != BackupStatusEnum.initializing &&
|
if (backupStatus != BackupStatusEnum.initializing &&
|
||||||
backupStatus != BackupStatusEnum.noKey)
|
backupStatus != BackupStatusEnum.noKey)
|
||||||
OutlinedCard(
|
OutlinedCard(
|
||||||
|
@ -227,7 +231,10 @@ class _BackupDetailsState extends State<BackupDetails>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (backupStatus == BackupStatusEnum.error)
|
if (backupStatus == BackupStatusEnum.error)
|
||||||
BrandText.body1(backupError.toString()),
|
Text(
|
||||||
|
backupError.toString(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.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';
|
||||||
|
@ -5,11 +6,12 @@ import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
import 'package:selfprivacy/ui/pages/devices/new_device.dart';
|
import 'package:selfprivacy/ui/pages/devices/new_device.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class DevicesScreen extends StatefulWidget {
|
class DevicesScreen extends StatefulWidget {
|
||||||
const DevicesScreen({super.key});
|
const DevicesScreen({super.key});
|
||||||
|
|
||||||
|
@ -25,7 +27,7 @@ class _DevicesScreenState extends State<DevicesScreen> {
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
context.read<ApiDevicesCubit>().refresh();
|
await context.read<ApiDevicesCubit>().refresh();
|
||||||
},
|
},
|
||||||
child: BrandHeroScreen(
|
child: BrandHeroScreen(
|
||||||
heroTitle: 'devices.main_screen.header'.tr(),
|
heroTitle: 'devices.main_screen.header'.tr(),
|
||||||
|
@ -90,8 +92,7 @@ class _DevicesInfo extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...devicesStatus.otherDevices
|
...devicesStatus.otherDevices
|
||||||
.map((final device) => _DeviceTile(device: device))
|
.map((final device) => _DeviceTile(device: device)),
|
||||||
.toList(),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
||||||
class NewDeviceScreen extends StatelessWidget {
|
class NewDeviceScreen extends StatelessWidget {
|
||||||
const NewDeviceScreen({super.key});
|
const NewDeviceScreen({super.key});
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class DnsDetailsPage extends StatefulWidget {
|
class DnsDetailsPage extends StatefulWidget {
|
||||||
const DnsDetailsPage({super.key});
|
const DnsDetailsPage({super.key});
|
||||||
|
|
||||||
|
@ -158,8 +160,7 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
@ -200,8 +201,7 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,73 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info/package_info.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class AboutApplicationPage extends StatelessWidget {
|
class AboutApplicationPage extends StatelessWidget {
|
||||||
const AboutApplicationPage({super.key});
|
const AboutApplicationPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => SafeArea(
|
Widget build(final BuildContext context) {
|
||||||
child: Scaffold(
|
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||||
appBar: PreferredSize(
|
is ServerInstallationFinished;
|
||||||
preferredSize: const Size.fromHeight(52),
|
|
||||||
child: BrandHeader(
|
return BrandHeroScreen(
|
||||||
title: 'about_application_page.title'.tr(),
|
hasBackButton: true,
|
||||||
hasBackButton: true,
|
hasFlashButton: false,
|
||||||
|
heroTitle: 'about_application_page.title'.tr(),
|
||||||
|
children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: _packageVersion(),
|
||||||
|
builder: (final context, final snapshot) => Text(
|
||||||
|
'about_application_page.application_version_text'
|
||||||
|
.tr(args: [snapshot.data.toString()]),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isReady)
|
||||||
|
FutureBuilder(
|
||||||
|
future: _apiVersion(),
|
||||||
|
builder: (final context, final snapshot) => Text(
|
||||||
|
'about_application_page.api_version_text'
|
||||||
|
.tr(args: [snapshot.data.toString()]),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: ListView(
|
const SizedBox(height: 10),
|
||||||
padding: paddingH15V0,
|
// Button to call showAboutDialog
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationName: 'SelfPrivacy',
|
||||||
|
applicationLegalese: '© 2022 SelfPrivacy',
|
||||||
|
// Link to privacy policy
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 10),
|
|
||||||
FutureBuilder(
|
|
||||||
future: _packageVersion(),
|
|
||||||
builder: (final context, final snapshot) => BrandText.body1(
|
|
||||||
'about_application_page.application_version_text'
|
|
||||||
.tr(args: [snapshot.data.toString()]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FutureBuilder(
|
|
||||||
future: _apiVersion(),
|
|
||||||
builder: (final context, final snapshot) => BrandText.body1(
|
|
||||||
'about_application_page.api_version_text'
|
|
||||||
.tr(args: [snapshot.data.toString()]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
// Button to call showAboutDialog
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => showAboutDialog(
|
onPressed: () => launchUrl(
|
||||||
context: context,
|
Uri.parse('https://selfprivacy.ru/privacy-policy'),
|
||||||
applicationName: 'SelfPrivacy',
|
mode: LaunchMode.externalApplication,
|
||||||
applicationLegalese: '© 2022 SelfPrivacy',
|
|
||||||
// Link to privacy policy
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => launchUrl(
|
|
||||||
Uri.parse('https://selfprivacy.ru/privacy-policy'),
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
),
|
|
||||||
child: Text('about_application_page.privacy_policy'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: const Text('Show about dialog'),
|
child: Text('about_application_page.privacy_policy'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
child: const Text('Show about dialog'),
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: 8),
|
||||||
|
const Divider(height: 0),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const BrandMarkdown(
|
||||||
|
fileName: 'about',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> _packageVersion() async {
|
Future<String> _packageVersion() async {
|
||||||
String packageVersion = 'unknown';
|
String packageVersion = 'unknown';
|
||||||
|
|
|
@ -1,229 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_switch/brand_switch.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
class AppSettingsPage extends StatefulWidget {
|
|
||||||
const AppSettingsPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AppSettingsPage> createState() => _AppSettingsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppSettingsPageState extends State<AppSettingsPage> {
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) {
|
|
||||||
final bool isDarkModeOn =
|
|
||||||
context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
|
||||||
|
|
||||||
return SafeArea(
|
|
||||||
child: Builder(
|
|
||||||
builder: (final context) => Scaffold(
|
|
||||||
appBar: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(52),
|
|
||||||
child: BrandHeader(
|
|
||||||
title: 'application_settings.title'.tr(),
|
|
||||||
hasBackButton: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
padding: paddingH15V0,
|
|
||||||
children: [
|
|
||||||
const Divider(height: 1),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: _TextColumn(
|
|
||||||
title: 'application_settings.dark_theme_title'.tr(),
|
|
||||||
value:
|
|
||||||
'application_settings.dark_theme_description'.tr(),
|
|
||||||
hasWarning: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
BrandSwitch(
|
|
||||||
value: Theme.of(context).brightness == Brightness.dark,
|
|
||||||
onChanged: (final value) => context
|
|
||||||
.read<AppSettingsCubit>()
|
|
||||||
.updateDarkMode(isDarkModeOn: !isDarkModeOn),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 0),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: _TextColumn(
|
|
||||||
title: 'application_settings.reset_config_title'.tr(),
|
|
||||||
value: 'application_settings.reset_config_description'
|
|
||||||
.tr(),
|
|
||||||
hasWarning: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: BrandColors.red1,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'basis.reset'.tr(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: BrandColors.white,
|
|
||||||
fontWeight: NamedFontWeight.demiBold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final _) => BrandAlert(
|
|
||||||
title: 'modals.are_you_sure'.tr(),
|
|
||||||
contentText: 'modals.purge_all_keys'.tr(),
|
|
||||||
actions: [
|
|
||||||
ActionButton(
|
|
||||||
text: 'modals.purge_all_keys_confirm'.tr(),
|
|
||||||
isRed: true,
|
|
||||||
onPressed: () {
|
|
||||||
context
|
|
||||||
.read<ServerInstallationCubit>()
|
|
||||||
.clearAppConfig();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 0),
|
|
||||||
_deleteServer(context)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _deleteServer(final BuildContext context) {
|
|
||||||
final bool isDisabled =
|
|
||||||
context.watch<ServerInstallationCubit>().state.serverDetails == null;
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: _TextColumn(
|
|
||||||
title: 'application_settings.delete_server_title'.tr(),
|
|
||||||
value: 'application_settings.delete_server_description'.tr(),
|
|
||||||
hasWarning: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: BrandColors.red1,
|
|
||||||
),
|
|
||||||
onPressed: isDisabled
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final _) => BrandAlert(
|
|
||||||
title: 'modals.are_you_sure'.tr(),
|
|
||||||
contentText: 'modals.delete_server_volume'.tr(),
|
|
||||||
actions: [
|
|
||||||
ActionButton(
|
|
||||||
text: 'modals.yes'.tr(),
|
|
||||||
isRed: true,
|
|
||||||
onPressed: () async {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (final context) => Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: const CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await context
|
|
||||||
.read<ServerInstallationCubit>()
|
|
||||||
.serverDelete();
|
|
||||||
if (!mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ActionButton(
|
|
||||||
text: 'basis.cancel'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'basis.delete'.tr(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: BrandColors.white,
|
|
||||||
fontWeight: NamedFontWeight.demiBold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TextColumn extends StatelessWidget {
|
|
||||||
const _TextColumn({
|
|
||||||
required this.title,
|
|
||||||
required this.value,
|
|
||||||
this.hasWarning = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final String value;
|
|
||||||
final bool hasWarning;
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
BrandText.body1(
|
|
||||||
title,
|
|
||||||
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
BrandText.body1(
|
|
||||||
value,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
height: 1.53,
|
|
||||||
color: BrandColors.gray1,
|
|
||||||
).merge(TextStyle(color: hasWarning ? BrandColors.warning : null)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
151
lib/ui/pages/more/app_settings/app_settings.dart
Normal file
151
lib/ui/pages/more/app_settings/app_settings.dart
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class AppSettingsPage extends StatefulWidget {
|
||||||
|
const AppSettingsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppSettingsPage> createState() => _AppSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final bool isDarkModeOn =
|
||||||
|
context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
||||||
|
|
||||||
|
final bool isSystemDarkModeOn =
|
||||||
|
context.watch<AppSettingsCubit>().state.isAutoDarkModeOn;
|
||||||
|
|
||||||
|
return BrandHeroScreen(
|
||||||
|
hasBackButton: true,
|
||||||
|
hasFlashButton: false,
|
||||||
|
bodyPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
heroTitle: 'application_settings.title'.tr(),
|
||||||
|
children: [
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('application_settings.system_dark_theme_title'.tr()),
|
||||||
|
subtitle:
|
||||||
|
Text('application_settings.system_dark_theme_description'.tr()),
|
||||||
|
value: isSystemDarkModeOn,
|
||||||
|
onChanged: (final value) => context
|
||||||
|
.read<AppSettingsCubit>()
|
||||||
|
.updateAutoDarkMode(isAutoDarkModeOn: !isSystemDarkModeOn),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('application_settings.dark_theme_title'.tr()),
|
||||||
|
subtitle: Text('application_settings.dark_theme_description'.tr()),
|
||||||
|
value: Theme.of(context).brightness == Brightness.dark,
|
||||||
|
onChanged: isSystemDarkModeOn
|
||||||
|
? null
|
||||||
|
: (final value) => context
|
||||||
|
.read<AppSettingsCubit>()
|
||||||
|
.updateDarkMode(isDarkModeOn: !isDarkModeOn),
|
||||||
|
),
|
||||||
|
const Divider(height: 0),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text(
|
||||||
|
'application_settings.dangerous_settings'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const _ResetAppTile(),
|
||||||
|
// const Divider(height: 0),
|
||||||
|
_deleteServer(context)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _deleteServer(final BuildContext context) {
|
||||||
|
final bool isDisabled =
|
||||||
|
context.watch<ServerInstallationCubit>().state.serverDetails == null;
|
||||||
|
return ListTile(
|
||||||
|
title: Text('application_settings.delete_server_title'.tr()),
|
||||||
|
subtitle: Text('application_settings.delete_server_description'.tr()),
|
||||||
|
textColor: isDisabled
|
||||||
|
? Theme.of(context).colorScheme.onBackground.withOpacity(0.5)
|
||||||
|
: Theme.of(context).colorScheme.onBackground,
|
||||||
|
onTap: isDisabled
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (final _) => AlertDialog(
|
||||||
|
title: Text('modals.are_you_sure'.tr()),
|
||||||
|
content: Text('modals.delete_server_volume'.tr()),
|
||||||
|
actions: [
|
||||||
|
DialogActionButton(
|
||||||
|
text: 'modals.yes'.tr(),
|
||||||
|
isRed: true,
|
||||||
|
onPressed: () async {
|
||||||
|
unawaited(
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (final context) => Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await context
|
||||||
|
.read<ServerInstallationCubit>()
|
||||||
|
.serverDelete();
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DialogActionButton(
|
||||||
|
text: 'basis.cancel'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ResetAppTile extends StatelessWidget {
|
||||||
|
const _ResetAppTile();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => ListTile(
|
||||||
|
title: Text('application_settings.reset_config_title'.tr()),
|
||||||
|
subtitle: Text('application_settings.reset_config_description'.tr()),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (final _) => AlertDialog(
|
||||||
|
title: Text('modals.are_you_sure'.tr()),
|
||||||
|
content: Text('modals.purge_all_keys'.tr()),
|
||||||
|
actions: [
|
||||||
|
DialogActionButton(
|
||||||
|
text: 'modals.purge_all_keys_confirm'.tr(),
|
||||||
|
isRed: true,
|
||||||
|
onPressed: () {
|
||||||
|
context.read<ServerInstallationCubit>().clearAppConfig();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DialogActionButton(
|
||||||
|
text: 'basis.cancel'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
85
lib/ui/pages/more/app_settings/developer_settings.dart
Normal file
85
lib/ui/pages/more/app_settings/developer_settings.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/staging_options.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class DeveloperSettingsPage extends StatefulWidget {
|
||||||
|
const DeveloperSettingsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DeveloperSettingsPage> createState() => _DeveloperSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||||
|
hasBackButton: true,
|
||||||
|
hasFlashButton: false,
|
||||||
|
bodyPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
heroTitle: 'developer_settings.title'.tr(),
|
||||||
|
heroSubtitle: 'developer_settings.subtitle'.tr(),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text(
|
||||||
|
'developer_settings.server_setup'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('developer_settings.use_staging_acme'.tr()),
|
||||||
|
subtitle:
|
||||||
|
Text('developer_settings.use_staging_acme_description'.tr()),
|
||||||
|
value: StagingOptions.stagingAcme,
|
||||||
|
onChanged: null,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text(
|
||||||
|
'developer_settings.routing'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('developer_settings.reset_onboarding'.tr()),
|
||||||
|
subtitle:
|
||||||
|
Text('developer_settings.reset_onboarding_description'.tr()),
|
||||||
|
enabled:
|
||||||
|
!context.watch<AppSettingsCubit>().state.isOnboardingShowing,
|
||||||
|
onTap: () => context
|
||||||
|
.read<AppSettingsCubit>()
|
||||||
|
.turnOffOnboarding(isOnboardingShowing: true),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text(
|
||||||
|
'developer_settings.cubit_statuses'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('ApiDevicesCubit'),
|
||||||
|
subtitle: Text(
|
||||||
|
context.watch<ApiDevicesCubit>().state.status.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('RecoveryKeyCubit'),
|
||||||
|
subtitle: Text(
|
||||||
|
context.watch<RecoveryKeyCubit>().state.loadingStatus.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,20 +1,21 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.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/message.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/log_list_tile.dart';
|
||||||
|
|
||||||
class Console extends StatefulWidget {
|
@RoutePage()
|
||||||
const Console({super.key});
|
class ConsolePage extends StatefulWidget {
|
||||||
|
const ConsolePage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Console> createState() => _ConsoleState();
|
State<ConsolePage> createState() => _ConsolePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConsoleState extends State<Console> {
|
class _ConsolePageState extends State<ConsolePage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
getIt.get<ConsoleModel>().addListener(update);
|
getIt.get<ConsoleModel>().addListener(update);
|
||||||
|
@ -28,21 +29,31 @@ class _ConsoleState extends State<Console> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void update() => setState(() => {});
|
bool paused = false;
|
||||||
|
|
||||||
|
void update() {
|
||||||
|
if (!paused) {
|
||||||
|
setState(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => SafeArea(
|
Widget build(final BuildContext context) => SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: AppBar(
|
||||||
preferredSize: const Size.fromHeight(53),
|
title: Text('console_page.title'.tr()),
|
||||||
child: Column(
|
leading: IconButton(
|
||||||
children: [
|
icon: const Icon(Icons.arrow_back),
|
||||||
BrandHeader(
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
title: 'console_page.title'.tr(),
|
|
||||||
hasBackButton: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
paused ? Icons.play_arrow_outlined : Icons.pause_outlined,
|
||||||
|
),
|
||||||
|
onPressed: () => setState(() => paused = !paused),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: getIt.allReady(),
|
future: getIt.allReady(),
|
||||||
|
@ -61,30 +72,7 @@ class _ConsoleState extends State<Console> {
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
...UnmodifiableListView(
|
...UnmodifiableListView(
|
||||||
messages
|
messages
|
||||||
.map((final message) {
|
.map((final message) => LogListItem(message: message))
|
||||||
final bool isError =
|
|
||||||
message.type == MessageType.warning;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
style: DefaultTextStyle.of(context).style,
|
|
||||||
children: <TextSpan>[
|
|
||||||
TextSpan(
|
|
||||||
text:
|
|
||||||
'${message.timeString}${isError ? '(Error)' : ''}: \n',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color:
|
|
||||||
isError ? BrandColors.red1 : null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(text: message.text),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.toList()
|
.toList()
|
||||||
.reversed,
|
.reversed,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
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:ionicons/ionicons.dart';
|
import 'package:ionicons/ionicons.dart';
|
||||||
|
@ -5,23 +6,13 @@ import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/pages/devices/devices.dart';
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/users/users.dart';
|
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
|
|
||||||
import 'package:selfprivacy/ui/pages/more/about_us.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/more/app_settings/app_setting.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/more/console.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/more/about_application.dart';
|
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class MorePage extends StatelessWidget {
|
class MorePage extends StatelessWidget {
|
||||||
const MorePage({super.key});
|
const MorePage({super.key});
|
||||||
|
|
||||||
|
@ -34,12 +25,14 @@ class MorePage extends StatelessWidget {
|
||||||
context.watch<ApiServerVolumeCubit>().state.usesBinds;
|
context.watch<ApiServerVolumeCubit>().state.usesBinds;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: Breakpoints.small.isActive(context)
|
||||||
preferredSize: const Size.fromHeight(52),
|
? PreferredSize(
|
||||||
child: BrandHeader(
|
preferredSize: const Size.fromHeight(52),
|
||||||
title: 'basis.more'.tr(),
|
child: BrandHeader(
|
||||||
),
|
title: 'basis.more'.tr(),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -50,7 +43,7 @@ class MorePage extends StatelessWidget {
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'storage.start_migration_button'.tr(),
|
title: 'storage.start_migration_button'.tr(),
|
||||||
iconData: Icons.drive_file_move_outline,
|
iconData: Icons.drive_file_move_outline,
|
||||||
goTo: ServicesMigrationPage(
|
goTo: () => ServicesMigrationRoute(
|
||||||
diskStatus: context
|
diskStatus: context
|
||||||
.watch<ApiServerVolumeCubit>()
|
.watch<ApiServerVolumeCubit>()
|
||||||
.state
|
.state
|
||||||
|
@ -77,7 +70,7 @@ class MorePage extends StatelessWidget {
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'more_page.configuration_wizard'.tr(),
|
title: 'more_page.configuration_wizard'.tr(),
|
||||||
iconData: Icons.change_history_outlined,
|
iconData: Icons.change_history_outlined,
|
||||||
goTo: const InitializingPage(),
|
goTo: () => const InitializingRoute(),
|
||||||
subtitle: 'not_ready_card.in_menu'.tr(),
|
subtitle: 'not_ready_card.in_menu'.tr(),
|
||||||
accent: true,
|
accent: true,
|
||||||
),
|
),
|
||||||
|
@ -85,47 +78,43 @@ class MorePage extends StatelessWidget {
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'more_page.create_ssh_key'.tr(),
|
title: 'more_page.create_ssh_key'.tr(),
|
||||||
iconData: Ionicons.key_outline,
|
iconData: Ionicons.key_outline,
|
||||||
goTo: const UserDetails(
|
goTo: () => UserDetailsRoute(
|
||||||
login: 'root',
|
login: 'root',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isReady)
|
if (isReady)
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
iconData: Icons.password_outlined,
|
iconData: Icons.password_outlined,
|
||||||
goTo: const RecoveryKey(),
|
goTo: () => const RecoveryKeyRoute(),
|
||||||
title: 'recovery_key.key_main_header'.tr(),
|
title: 'recovery_key.key_main_header'.tr(),
|
||||||
),
|
),
|
||||||
if (isReady)
|
if (isReady)
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
iconData: Icons.devices_outlined,
|
iconData: Icons.devices_outlined,
|
||||||
goTo: const DevicesScreen(),
|
goTo: () => const DevicesRoute(),
|
||||||
title: 'devices.main_screen.header'.tr(),
|
title: 'devices.main_screen.header'.tr(),
|
||||||
),
|
),
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'more_page.application_settings'.tr(),
|
title: 'more_page.application_settings'.tr(),
|
||||||
iconData: Icons.settings_outlined,
|
iconData: Icons.settings_outlined,
|
||||||
goTo: const AppSettingsPage(),
|
goTo: () => const AppSettingsRoute(),
|
||||||
),
|
|
||||||
_MoreMenuItem(
|
|
||||||
title: 'more_page.about_project'.tr(),
|
|
||||||
iconData: BrandIcons.engineer,
|
|
||||||
goTo: const AboutUsPage(),
|
|
||||||
),
|
),
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'more_page.about_application'.tr(),
|
title: 'more_page.about_application'.tr(),
|
||||||
iconData: BrandIcons.fire,
|
iconData: BrandIcons.fire,
|
||||||
goTo: const AboutApplicationPage(),
|
goTo: () => const AboutApplicationRoute(),
|
||||||
|
longGoTo: const DeveloperSettingsRoute(),
|
||||||
),
|
),
|
||||||
if (!isReady)
|
if (!isReady)
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'more_page.onboarding'.tr(),
|
title: 'more_page.onboarding'.tr(),
|
||||||
iconData: BrandIcons.start,
|
iconData: BrandIcons.start,
|
||||||
goTo: const OnboardingPage(nextPage: RootPage()),
|
goTo: () => const OnboardingRoute(),
|
||||||
),
|
),
|
||||||
_MoreMenuItem(
|
_MoreMenuItem(
|
||||||
title: 'more_page.console'.tr(),
|
title: 'more_page.console'.tr(),
|
||||||
iconData: BrandIcons.terminal,
|
iconData: BrandIcons.terminal,
|
||||||
goTo: const Console(),
|
goTo: () => const ConsoleRoute(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -140,14 +129,16 @@ class _MoreMenuItem extends StatelessWidget {
|
||||||
const _MoreMenuItem({
|
const _MoreMenuItem({
|
||||||
required this.iconData,
|
required this.iconData,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
required this.goTo,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.goTo,
|
this.longGoTo,
|
||||||
this.accent = false,
|
this.accent = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final IconData iconData;
|
final IconData iconData;
|
||||||
final String title;
|
final String title;
|
||||||
final Widget? goTo;
|
final PageRouteInfo Function() goTo;
|
||||||
|
final PageRouteInfo? longGoTo;
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
final bool accent;
|
final bool accent;
|
||||||
|
|
||||||
|
@ -160,9 +151,9 @@ class _MoreMenuItem extends StatelessWidget {
|
||||||
tertiary: accent,
|
tertiary: accent,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
onTap: goTo != null
|
onTap: () => context.pushRoute(goTo()),
|
||||||
? () => Navigator.of(context).push(materialRoute(goTo!))
|
onLongPress:
|
||||||
: null,
|
longGoTo != null ? () => context.pushRoute(longGoTo!) : null,
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
iconData,
|
iconData,
|
||||||
size: 24,
|
size: 24,
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class OnboardingPage extends StatefulWidget {
|
class OnboardingPage extends StatefulWidget {
|
||||||
const OnboardingPage({required this.nextPage, super.key});
|
const OnboardingPage({super.key});
|
||||||
|
|
||||||
final Widget nextPage;
|
|
||||||
@override
|
@override
|
||||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||||
}
|
}
|
||||||
|
@ -22,14 +23,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Scaffold(
|
Widget build(final BuildContext context) => Scaffold(
|
||||||
body: PageView(
|
body: PageView(
|
||||||
controller: pageController,
|
controller: pageController,
|
||||||
children: [
|
children: [
|
||||||
_withPadding(firstPage()),
|
_withPadding(firstPage()),
|
||||||
_withPadding(secondPage()),
|
_withPadding(secondPage()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _withPadding(final Widget child) => Padding(
|
Widget _withPadding(final Widget child) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
|
@ -76,7 +77,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
pageController.animateToPage(
|
pageController.animateToPage(
|
||||||
1,
|
1,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
text: 'basis.next'.tr(),
|
text: 'basis.next'.tr(),
|
||||||
|
@ -142,10 +143,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
context.router.replaceAll([
|
||||||
materialRoute(widget.nextPage),
|
const RootRoute(),
|
||||||
(final route) => false,
|
const InitializingRoute(),
|
||||||
);
|
]);
|
||||||
},
|
},
|
||||||
text: 'basis.got_it'.tr(),
|
text: 'basis.got_it'.tr(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
@ -10,13 +11,12 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.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';
|
||||||
import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart';
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart';
|
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
|
|
||||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ProvidersPage extends StatefulWidget {
|
class ProvidersPage extends StatefulWidget {
|
||||||
const ProvidersPage({super.key});
|
const ProvidersPage({super.key});
|
||||||
|
|
||||||
|
@ -61,12 +61,14 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: Breakpoints.small.isActive(context)
|
||||||
preferredSize: const Size.fromHeight(52),
|
? PreferredSize(
|
||||||
child: BrandHeader(
|
preferredSize: const Size.fromHeight(52),
|
||||||
title: 'basis.providers_title'.tr(),
|
child: BrandHeader(
|
||||||
),
|
title: 'basis.providers_title'.tr(),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: paddingH15V0,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
|
@ -81,8 +83,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
subtitle: diskStatus.isDiskOkay
|
subtitle: diskStatus.isDiskOkay
|
||||||
? 'storage.status_ok'.tr()
|
? 'storage.status_ok'.tr()
|
||||||
: 'storage.status_error'.tr(),
|
: 'storage.status_error'.tr(),
|
||||||
onTap: () => Navigator.of(context)
|
onTap: () => context.pushRoute(const ServerDetailsRoute()),
|
||||||
.push(materialRoute(const ServerDetailsScreen())),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_Card(
|
_Card(
|
||||||
|
@ -92,11 +93,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
subtitle: appConfig.isDomainSelected
|
subtitle: appConfig.isDomainSelected
|
||||||
? appConfig.serverDomain!.domainName
|
? appConfig.serverDomain!.domainName
|
||||||
: '',
|
: '',
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => context.pushRoute(const DnsDetailsRoute()),
|
||||||
materialRoute(
|
|
||||||
const DnsDetailsPage(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// TODO: When backups are fixed, show this card
|
// TODO: When backups are fixed, show this card
|
||||||
|
@ -108,8 +105,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
icon: BrandIcons.save,
|
icon: BrandIcons.save,
|
||||||
title: 'backup.card_title'.tr(),
|
title: 'backup.card_title'.tr(),
|
||||||
subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '',
|
subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '',
|
||||||
onTap: () => Navigator.of(context)
|
onTap: () => context.pushRoute(const BackupDetailsRoute()),
|
||||||
.push(materialRoute(const BackupDetails())),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -6,20 +6,21 @@ import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key_receiving.dart';
|
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key_receiving.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
class RecoveryKey extends StatefulWidget {
|
@RoutePage()
|
||||||
const RecoveryKey({super.key});
|
class RecoveryKeyPage extends StatefulWidget {
|
||||||
|
const RecoveryKeyPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RecoveryKey> createState() => _RecoveryKeyState();
|
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecoveryKeyState extends State<RecoveryKey> {
|
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -250,7 +251,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
materialRoute(
|
materialRoute(
|
||||||
RecoveryKeyReceiving(recoveryKey: token), // TO DO
|
RecoveryKeyReceiving(recoveryKey: token), // TO DO
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
|
|
||||||
class RecoveryKeyReceiving extends StatelessWidget {
|
class RecoveryKeyReceiving extends StatelessWidget {
|
||||||
|
|
|
@ -1,89 +1,153 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart';
|
import 'package:selfprivacy/ui/layouts/root_scaffold_with_navigation.dart';
|
||||||
import 'package:selfprivacy/ui/pages/more/more.dart';
|
import 'package:selfprivacy/ui/router/root_destinations.dart';
|
||||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/services/services.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/users/users.dart';
|
|
||||||
|
|
||||||
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
||||||
class RootPage extends StatefulWidget {
|
@RoutePage()
|
||||||
|
class RootPage extends StatefulWidget implements AutoRouteWrapper {
|
||||||
const RootPage({super.key});
|
const RootPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RootPage> createState() => _RootPageState();
|
State<RootPage> createState() => _RootPageState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrappedRoute(final BuildContext context) => this;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
||||||
late TabController tabController;
|
bool shouldUseSplitView() => false;
|
||||||
|
|
||||||
late final AnimationController _controller = AnimationController(
|
final destinations = rootDestinations;
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
late final Animation<double> _animation = CurvedAnimation(
|
|
||||||
parent: _controller,
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
tabController = TabController(length: 4, vsync: this);
|
|
||||||
tabController.addListener(() {
|
|
||||||
setState(() {
|
|
||||||
tabController.index == 2
|
|
||||||
? _controller.forward()
|
|
||||||
: _controller.reverse();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
tabController.dispose();
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||||
is ServerInstallationFinished;
|
is ServerInstallationFinished;
|
||||||
|
|
||||||
return Provider<ChangeTab>(
|
if (context.read<AppSettingsCubit>().state.isOnboardingShowing) {
|
||||||
create: (final _) => ChangeTab(tabController.animateTo),
|
context.router.replace(const OnboardingRoute());
|
||||||
child: Scaffold(
|
}
|
||||||
body: TabBarView(
|
|
||||||
controller: tabController,
|
return AutoRouter(
|
||||||
children: const [
|
builder: (final context, final child) {
|
||||||
ProvidersPage(),
|
final currentDestinationIndex = destinations.indexWhere(
|
||||||
ServicesPage(),
|
(final destination) =>
|
||||||
UsersPage(),
|
context.router.isRouteActive(destination.route.routeName),
|
||||||
MorePage(),
|
);
|
||||||
|
final isOtherRouterActive =
|
||||||
|
context.router.root.current.name != RootRoute.name;
|
||||||
|
final routeName = getRouteTitle(context.router.current.name).tr();
|
||||||
|
return RootScaffoldWithNavigation(
|
||||||
|
title: routeName,
|
||||||
|
destinations: destinations,
|
||||||
|
showBottomBar:
|
||||||
|
!(currentDestinationIndex == -1 && !isOtherRouterActive),
|
||||||
|
showFab: isReady,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainScreenNavigationRail extends StatelessWidget {
|
||||||
|
const MainScreenNavigationRail({
|
||||||
|
required this.destinations,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<RouteDestination> destinations;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
int? activeIndex = destinations.indexWhere(
|
||||||
|
(final destination) =>
|
||||||
|
context.router.isRouteActive(destination.route.routeName),
|
||||||
|
);
|
||||||
|
if (activeIndex == -1) {
|
||||||
|
activeIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
width: 72,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (final context, final constraints) => SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: NavigationRail(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
labelType: NavigationRailLabelType.all,
|
||||||
|
destinations: destinations
|
||||||
|
.map(
|
||||||
|
(final destination) => NavigationRailDestination(
|
||||||
|
icon: Icon(destination.icon),
|
||||||
|
label: Text(destination.label),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
selectedIndex: activeIndex,
|
||||||
|
onDestinationSelected: (final index) {
|
||||||
|
context.router.replaceAll([destinations[index].route]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainScreenNavigationDrawer extends StatelessWidget {
|
||||||
|
const MainScreenNavigationDrawer({
|
||||||
|
required this.destinations,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<RouteDestination> destinations;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
int? activeIndex = destinations.indexWhere(
|
||||||
|
(final destination) =>
|
||||||
|
context.router.isRouteActive(destination.route.routeName),
|
||||||
|
);
|
||||||
|
if (activeIndex == -1) {
|
||||||
|
activeIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
width: 296,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (final context, final constraints) => NavigationDrawer(
|
||||||
|
// backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
// surfaceTintColor: Colors.transparent,
|
||||||
|
key: const Key('PrimaryNavigationDrawer'),
|
||||||
|
selectedIndex: activeIndex,
|
||||||
|
onDestinationSelected: (final index) {
|
||||||
|
context.router.replaceAll([destinations[index].route]);
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
...destinations.map(
|
||||||
|
(final destination) => NavigationDrawerDestination(
|
||||||
|
icon: Icon(destination.icon),
|
||||||
|
label: Text(destination.label),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BrandTabBar(
|
|
||||||
controller: tabController,
|
|
||||||
),
|
|
||||||
floatingActionButton: isReady
|
|
||||||
? SizedBox(
|
|
||||||
height: 104 + 16,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
ScaleTransition(
|
|
||||||
scale: _animation,
|
|
||||||
child: const AddUserFab(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const BrandFab(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.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/metrics/metrics_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
|
||||||
|
@ -10,18 +10,17 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart';
|
import 'package:selfprivacy/ui/components/buttons/segmented_buttons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart';
|
import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart';
|
||||||
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
import 'package:selfprivacy/utils/extensions/duration.dart';
|
import 'package:selfprivacy/utils/extensions/duration.dart';
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:timezone/timezone.dart';
|
import 'package:timezone/timezone.dart';
|
||||||
|
|
||||||
|
@ -32,6 +31,7 @@ part 'time_zone/time_zone.dart';
|
||||||
|
|
||||||
var navigatorKey = GlobalKey<NavigatorState>();
|
var navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ServerDetailsScreen extends StatefulWidget {
|
class ServerDetailsScreen extends StatefulWidget {
|
||||||
const ServerDetailsScreen({super.key});
|
const ServerDetailsScreen({super.key});
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (final context) => context.read<ServerDetailsCubit>()..check(),
|
create: (final context) => context.read<ServerDetailsCubit>()..check(),
|
||||||
child: BrandHeroScreen(
|
child: BrandHeroScreen(
|
||||||
|
hasFlashButton: true,
|
||||||
heroIcon: BrandIcons.server,
|
heroIcon: BrandIcons.server,
|
||||||
heroTitle: 'server.card_title'.tr(),
|
heroTitle: 'server.card_title'.tr(),
|
||||||
heroSubtitle: 'server.description'.tr(),
|
heroSubtitle: 'server.description'.tr(),
|
||||||
|
|
|
@ -23,15 +23,13 @@ class _TextDetails extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...details.metadata
|
...details.metadata.map(
|
||||||
.map(
|
(final metadata) => ListTileOnSurfaceVariant(
|
||||||
(final metadata) => ListTileOnSurfaceVariant(
|
leadingIcon: metadata.type.icon,
|
||||||
leadingIcon: metadata.type.icon,
|
title: metadata.name,
|
||||||
title: metadata.name,
|
subtitle: metadata.value,
|
||||||
subtitle: metadata.value,
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -39,24 +37,6 @@ class _TextDetails extends StatelessWidget {
|
||||||
throw Exception('wrong state');
|
throw Exception('wrong state');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getRowTitle(final String title) => Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 10),
|
|
||||||
child: BrandText.h5(
|
|
||||||
title,
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget getRowValue(final String title, {final bool isBold = false}) =>
|
|
||||||
BrandText.body1(
|
|
||||||
title,
|
|
||||||
style: isBold
|
|
||||||
? const TextStyle(
|
|
||||||
fontWeight: NamedFontWeight.demiBold,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TempMessage extends StatelessWidget {
|
class _TempMessage extends StatelessWidget {
|
||||||
|
@ -69,7 +49,10 @@ class _TempMessage extends StatelessWidget {
|
||||||
Widget build(final BuildContext context) => SizedBox(
|
Widget build(final BuildContext context) => SizedBox(
|
||||||
height: MediaQuery.of(context).size.height - 100,
|
height: MediaQuery.of(context).size.height - 100,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: BrandText.body2(message),
|
child: Text(
|
||||||
|
message,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,66 +57,72 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Scaffold(
|
Widget build(final BuildContext context) {
|
||||||
appBar: AppBar(
|
final isDesktop = Breakpoints.mediumAndUp.isActive(context);
|
||||||
title: isSearching
|
return Scaffold(
|
||||||
? TextField(
|
appBar: AppBar(
|
||||||
readOnly: false,
|
automaticallyImplyLeading: false,
|
||||||
textAlign: TextAlign.start,
|
title: (isDesktop || isSearching)
|
||||||
textInputAction: TextInputAction.next,
|
? TextField(
|
||||||
enabled: true,
|
readOnly: false,
|
||||||
controller: searchController,
|
textAlign: TextAlign.start,
|
||||||
decoration: InputDecoration(
|
textInputAction: TextInputAction.next,
|
||||||
errorText: null,
|
enabled: true,
|
||||||
hintText: 'server.timezone_search_bar'.tr(),
|
controller: searchController,
|
||||||
),
|
decoration: InputDecoration(
|
||||||
)
|
errorText: null,
|
||||||
: Padding(
|
hintText: 'server.timezone_search_bar'.tr(),
|
||||||
padding: const EdgeInsets.only(top: 4.0),
|
|
||||||
child: Text('server.select_timezone'.tr()),
|
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
)
|
||||||
icon: const Icon(Icons.arrow_back),
|
: Padding(
|
||||||
onPressed: isSearching
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
? () => setState(() => isSearching = false)
|
child: Text('server.select_timezone'.tr()),
|
||||||
: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
if (!isSearching)
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
onPressed: () => setState(() => isSearching = true),
|
|
||||||
),
|
),
|
||||||
],
|
leading: !isDesktop
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: isSearching
|
||||||
|
? () => setState(() => isSearching = false)
|
||||||
|
: () => Navigator.of(context).pop(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
actions: [
|
||||||
|
if (!isSearching && !isDesktop)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
onPressed: () => setState(() => isSearching = true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
controller: scrollController,
|
||||||
|
children: locations
|
||||||
|
.where(
|
||||||
|
(final Location location) => timezoneFilterValue == null
|
||||||
|
? true
|
||||||
|
: location.name
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(timezoneFilterValue!) ||
|
||||||
|
Duration(
|
||||||
|
milliseconds: location.currentTimeZone.offset,
|
||||||
|
)
|
||||||
|
.toDayHourMinuteFormat()
|
||||||
|
.contains(timezoneFilterValue!),
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
.asMap()
|
||||||
|
.map(
|
||||||
|
(final key, final value) => locationToListTile(key, value),
|
||||||
|
)
|
||||||
|
.values
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
),
|
||||||
child: ListView(
|
);
|
||||||
controller: scrollController,
|
}
|
||||||
children: locations
|
|
||||||
.where(
|
|
||||||
(final Location location) => timezoneFilterValue == null
|
|
||||||
? true
|
|
||||||
: location.name
|
|
||||||
.toLowerCase()
|
|
||||||
.contains(timezoneFilterValue!) ||
|
|
||||||
Duration(
|
|
||||||
milliseconds: location.currentTimeZone.offset,
|
|
||||||
)
|
|
||||||
.toDayHourMinuteFormat()
|
|
||||||
.contains(timezoneFilterValue!),
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
.asMap()
|
|
||||||
.map(
|
|
||||||
(final key, final value) => locationToListTile(key, value),
|
|
||||||
)
|
|
||||||
.values
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
MapEntry<int, Container> locationToListTile(
|
MapEntry<int, ListTile> locationToListTile(
|
||||||
final int key,
|
final int key,
|
||||||
final Location location,
|
final Location location,
|
||||||
) {
|
) {
|
||||||
|
@ -126,46 +132,19 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
||||||
|
|
||||||
return MapEntry(
|
return MapEntry(
|
||||||
key,
|
key,
|
||||||
Container(
|
ListTile(
|
||||||
height: 75,
|
title: Text(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
location.name,
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: BrandColors.dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: InkWell(
|
subtitle: Text(
|
||||||
onTap: () {
|
'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
||||||
context.read<ServerDetailsCubit>().repository.setTimezone(
|
|
||||||
location.name,
|
|
||||||
);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
BrandText.body1(
|
|
||||||
location.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
BrandText.small(
|
|
||||||
'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.read<ServerDetailsCubit>().repository.setTimezone(
|
||||||
|
location.name,
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart';
|
import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
|
||||||
import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart';
|
import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart';
|
||||||
import 'package:selfprivacy/ui/components/storage_list_items/service_migration_list_item.dart';
|
import 'package:selfprivacy/ui/components/storage_list_items/service_migration_list_item.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ServicesMigrationPage extends StatefulWidget {
|
class ServicesMigrationPage extends StatefulWidget {
|
||||||
const ServicesMigrationPage({
|
const ServicesMigrationPage({
|
||||||
required this.services,
|
required this.services,
|
||||||
|
@ -110,22 +108,20 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
...widget.diskStatus.diskVolumes
|
...widget.diskStatus.diskVolumes.map(
|
||||||
.map(
|
(final volume) => Column(
|
||||||
(final volume) => Column(
|
children: [
|
||||||
children: [
|
ServerStorageListItem(
|
||||||
ServerStorageListItem(
|
volume: recalculatedDiskUsages(
|
||||||
volume: recalculatedDiskUsages(
|
volume,
|
||||||
volume,
|
widget.services,
|
||||||
widget.services,
|
),
|
||||||
),
|
dense: true,
|
||||||
dense: true,
|
|
||||||
),
|
|
||||||
const SizedBox(height: headerVerticalPadding),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: headerVerticalPadding),
|
||||||
.toList(),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -138,23 +134,21 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (widget.services.isEmpty)
|
if (widget.services.isEmpty)
|
||||||
const Center(child: CircularProgressIndicator()),
|
const Center(child: CircularProgressIndicator()),
|
||||||
...widget.services
|
...widget.services.map(
|
||||||
.map(
|
(final service) => Column(
|
||||||
(final service) => Column(
|
children: [
|
||||||
children: [
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
ServiceMigrationListItem(
|
||||||
ServiceMigrationListItem(
|
service: service,
|
||||||
service: service,
|
diskStatus: widget.diskStatus,
|
||||||
diskStatus: widget.diskStatus,
|
selectedVolume: serviceToDisk[service.id]!,
|
||||||
selectedVolume: serviceToDisk[service.id]!,
|
onChange: onChange,
|
||||||
onChange: onChange,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
const Divider(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 4),
|
||||||
.toList(),
|
const Divider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: InfoBox(
|
child: InfoBox(
|
||||||
|
@ -180,17 +174,10 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
context.router.popUntilRoot();
|
||||||
materialRoute(const RootPage()),
|
showModalBottomSheet(
|
||||||
(final predicate) => false,
|
|
||||||
);
|
|
||||||
showBrandBottomSheet(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (final BuildContext context) =>
|
builder: (final BuildContext context) => const JobsContent(),
|
||||||
const BrandBottomSheet(
|
|
||||||
isExpended: true,
|
|
||||||
child: JobsContent(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
|
@ -5,12 +6,11 @@ import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.d
|
||||||
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/price.dart';
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ExtendingVolumePage extends StatefulWidget {
|
class ExtendingVolumePage extends StatefulWidget {
|
||||||
const ExtendingVolumePage({
|
const ExtendingVolumePage({
|
||||||
required this.diskVolumeToResize,
|
required this.diskVolumeToResize,
|
||||||
|
@ -155,10 +155,7 @@ class _ExtendingVolumePageState extends State<ExtendingVolumePage> {
|
||||||
DiskSize.fromGibibyte(_currentSliderGbValue),
|
DiskSize.fromGibibyte(_currentSliderGbValue),
|
||||||
context.read<ApiServerVolumeCubit>().reload,
|
context.read<ApiServerVolumeCubit>().reload,
|
||||||
);
|
);
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
context.router.popUntilRoot();
|
||||||
materialRoute(const RootPage()),
|
|
||||||
(final predicate) => false,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Text('storage.extend_volume_button.title'.tr()),
|
child: Text('storage.extend_volume_button.title'.tr()),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/extending_volume.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart';
|
import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ServerStoragePage extends StatefulWidget {
|
class ServerStoragePage extends StatefulWidget {
|
||||||
const ServerStoragePage({
|
const ServerStoragePage({
|
||||||
required this.diskStatus,
|
required this.diskStatus,
|
||||||
|
@ -45,28 +46,26 @@ class _ServerStoragePageState extends State<ServerStoragePage> {
|
||||||
heroTitle: 'storage.card_title'.tr(),
|
heroTitle: 'storage.card_title'.tr(),
|
||||||
children: [
|
children: [
|
||||||
// ...sections,
|
// ...sections,
|
||||||
...widget.diskStatus.diskVolumes
|
...widget.diskStatus.diskVolumes.map(
|
||||||
.map(
|
(final volume) => Column(
|
||||||
(final volume) => Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
ServerStorageSection(
|
||||||
ServerStorageSection(
|
volume: volume,
|
||||||
volume: volume,
|
diskStatus: widget.diskStatus,
|
||||||
diskStatus: widget.diskStatus,
|
services: services
|
||||||
services: services
|
.where(
|
||||||
.where(
|
(final service) =>
|
||||||
(final service) =>
|
service.storageUsage.volume == volume.name,
|
||||||
service.storageUsage.volume == volume.name,
|
)
|
||||||
)
|
.toList(),
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const Divider(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 16),
|
||||||
.toList(),
|
const Divider(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -93,24 +92,20 @@ class ServerStorageSection extends StatelessWidget {
|
||||||
volume: volume,
|
volume: volume,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
...services
|
...services.map(
|
||||||
.map(
|
(final service) => ServerConsumptionListTile(
|
||||||
(final service) => ServerConsumptionListTile(
|
service: service,
|
||||||
service: service,
|
volume: volume,
|
||||||
volume: volume,
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
if (volume.isResizable) ...[
|
if (volume.isResizable) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
BrandOutlinedButton(
|
BrandOutlinedButton(
|
||||||
title: 'storage.extend_volume_button.title'.tr(),
|
title: 'storage.extend_volume_button.title'.tr(),
|
||||||
onPressed: () => Navigator.of(context).push(
|
onPressed: () => context.pushRoute(
|
||||||
materialRoute(
|
ExtendingVolumeRoute(
|
||||||
ExtendingVolumePage(
|
diskVolumeToResize: volume,
|
||||||
diskVolumeToResize: volume,
|
diskStatus: diskStatus,
|
||||||
diskStatus: diskStatus,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -138,7 +133,10 @@ class ServerConsumptionListTile extends StatelessWidget {
|
||||||
service.svgIcon,
|
service.svgIcon,
|
||||||
width: 24.0,
|
width: 24.0,
|
||||||
height: 24.0,
|
height: 24.0,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
colorFilter: ColorFilter.mode(
|
||||||
|
Theme.of(context).colorScheme.onBackground,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
rightSideText: service.storageUsage.used.toString(),
|
rightSideText: service.storageUsage.used.toString(),
|
||||||
percentage: service.storageUsage.used.byte / volume.sizeTotal.byte,
|
percentage: service.storageUsage.used.byte / volume.sizeTotal.byte,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_status.dart';
|
import 'package:selfprivacy/logic/models/disk_status.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/server_storage.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart';
|
import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
||||||
class StorageCard extends StatelessWidget {
|
class StorageCard extends StatelessWidget {
|
||||||
const StorageCard({
|
const StorageCard({
|
||||||
|
@ -45,13 +45,8 @@ class StorageCard extends StatelessWidget {
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: InkResponse(
|
child: InkResponse(
|
||||||
highlightShape: BoxShape.rectangle,
|
highlightShape: BoxShape.rectangle,
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () =>
|
||||||
materialRoute(
|
context.pushRoute(ServerStorageRoute(diskStatus: diskStatus)),
|
||||||
ServerStoragePage(
|
|
||||||
diskStatus: diskStatus,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
@ -6,12 +7,12 @@ import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/utils/launch_url.dart';
|
import 'package:selfprivacy/utils/launch_url.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ServicePage extends StatefulWidget {
|
class ServicePage extends StatefulWidget {
|
||||||
const ServicePage({required this.serviceId, super.key});
|
const ServicePage({required this.serviceId, super.key});
|
||||||
|
|
||||||
|
@ -46,11 +47,15 @@ class _ServicePageState extends State<ServicePage> {
|
||||||
|
|
||||||
return BrandHeroScreen(
|
return BrandHeroScreen(
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
|
hasFlashButton: true,
|
||||||
heroIconWidget: SvgPicture.string(
|
heroIconWidget: SvgPicture.string(
|
||||||
service.svgIcon,
|
service.svgIcon,
|
||||||
width: 48.0,
|
width: 48.0,
|
||||||
height: 48.0,
|
height: 48.0,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
colorFilter: ColorFilter.mode(
|
||||||
|
Theme.of(context).colorScheme.onBackground,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
heroTitle: service.displayName,
|
heroTitle: service.displayName,
|
||||||
children: [
|
children: [
|
||||||
|
@ -108,14 +113,12 @@ class _ServicePageState extends State<ServicePage> {
|
||||||
ListTile(
|
ListTile(
|
||||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||||
// Open page ServicesMigrationPage
|
// Open page ServicesMigrationPage
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => context.pushRoute(
|
||||||
materialRoute(
|
ServicesMigrationRoute(
|
||||||
ServicesMigrationPage(
|
services: [service],
|
||||||
services: [service],
|
diskStatus:
|
||||||
diskStatus:
|
context.read<ApiServerVolumeCubit>().state.diskStatus,
|
||||||
context.read<ApiServerVolumeCubit>().state.diskStatus,
|
isMigration: false,
|
||||||
isMigration: false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: const Icon(Icons.drive_file_move_outlined),
|
leading: const Icon(Icons.drive_file_move_outlined),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
@ -5,17 +6,16 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.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';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/ui/pages/services/service_page.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
import 'package:selfprivacy/utils/launch_url.dart';
|
import 'package:selfprivacy/utils/launch_url.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
|
||||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class ServicesPage extends StatefulWidget {
|
class ServicesPage extends StatefulWidget {
|
||||||
const ServicesPage({super.key});
|
const ServicesPage({super.key});
|
||||||
|
|
||||||
|
@ -34,32 +34,35 @@ class _ServicesPageState extends State<ServicesPage> {
|
||||||
.sort((final a, final b) => a.status.index.compareTo(b.status.index));
|
.sort((final a, final b) => a.status.index.compareTo(b.status.index));
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: Breakpoints.small.isActive(context)
|
||||||
preferredSize: const Size.fromHeight(52),
|
? PreferredSize(
|
||||||
child: BrandHeader(
|
preferredSize: const Size.fromHeight(52),
|
||||||
title: 'basis.services'.tr(),
|
child: BrandHeader(
|
||||||
),
|
title: 'basis.services'.tr(),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
context.read<ServicesCubit>().reload();
|
await context.read<ServicesCubit>().reload();
|
||||||
},
|
},
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: paddingH15V0,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
BrandText.body1('basis.services_title'.tr()),
|
Text(
|
||||||
|
'basis.services_title'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
|
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
|
||||||
...services
|
...services.map(
|
||||||
.map(
|
(final service) => Padding(
|
||||||
(final service) => Padding(
|
padding: const EdgeInsets.only(
|
||||||
padding: const EdgeInsets.only(
|
bottom: 30,
|
||||||
bottom: 30,
|
),
|
||||||
),
|
child: _Card(service: service),
|
||||||
child: _Card(service: service),
|
),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -98,81 +101,106 @@ class _Card extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return Card(
|
||||||
onTap: isReady
|
clipBehavior: Clip.antiAlias,
|
||||||
? () => Navigator.of(context)
|
child: InkResponse(
|
||||||
.push(materialRoute(ServicePage(serviceId: service.id)))
|
highlightShape: BoxShape.rectangle,
|
||||||
: null,
|
onTap: isReady
|
||||||
child: BrandCards.big(
|
? () => context.pushRoute(
|
||||||
child: Column(
|
ServiceRoute(serviceId: service.id),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
)
|
||||||
children: [
|
: null,
|
||||||
Row(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.all(16.0),
|
||||||
IconStatusMask(
|
child: Column(
|
||||||
status: getStatus(service.status),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
icon: SvgPicture.string(
|
children: [
|
||||||
service.svgIcon,
|
Row(
|
||||||
width: 30.0,
|
|
||||||
height: 30.0,
|
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ClipRect(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
children: [
|
||||||
Column(
|
IconStatusMask(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
status: getStatus(service.status),
|
||||||
children: [
|
icon: SvgPicture.string(
|
||||||
const SizedBox(height: 10),
|
service.svgIcon,
|
||||||
BrandText.h2(service.displayName),
|
width: 30.0,
|
||||||
const SizedBox(height: 10),
|
height: 30.0,
|
||||||
if (service.url != '' && service.url != null)
|
colorFilter: const ColorFilter.mode(
|
||||||
Column(
|
Colors.white,
|
||||||
children: [
|
BlendMode.srcIn,
|
||||||
GestureDetector(
|
),
|
||||||
onTap: () => launchURL(
|
),
|
||||||
service.url,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'${service.url}',
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (service.id == 'mailserver')
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
domainName,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
BrandText.body2(service.loginInfo),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandText.body2(service.description),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
Column(
|
||||||
],
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
service.displayName,
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (service.url != '' && service.url != null)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
_ServiceLink(
|
||||||
|
url: service.url ?? '',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (service.id == 'mailserver')
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
_ServiceLink(
|
||||||
|
url: domainName,
|
||||||
|
isActive: false,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
service.loginInfo,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
service.description,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ServiceLink extends StatelessWidget {
|
||||||
|
const _ServiceLink({
|
||||||
|
required this.url,
|
||||||
|
this.isActive = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String url;
|
||||||
|
final bool isActive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => GestureDetector(
|
||||||
|
onTap: isActive
|
||||||
|
? () => launchURL(
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
child: Text(
|
||||||
|
url,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -6,11 +6,10 @@ import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
|
||||||
import 'package:selfprivacy/utils/network_utils.dart';
|
import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
|
|
||||||
class DnsProviderPicker extends StatefulWidget {
|
class DnsProviderPicker extends StatefulWidget {
|
||||||
|
@ -130,18 +129,15 @@ class ProviderInputDataPage extends StatelessWidget {
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
builder: (final BuildContext context) => BrandBottomSheet(
|
builder: (final BuildContext context) => Padding(
|
||||||
isExpended: true,
|
padding: paddingH15V0,
|
||||||
child: Padding(
|
child: ListView(
|
||||||
padding: paddingH15V0,
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
child: ListView(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
BrandMarkdown(
|
||||||
children: [
|
fileName: providerInfo.pathToHow,
|
||||||
BrandMarkdown(
|
),
|
||||||
fileName: providerInfo.pathToHow,
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
|
@ -9,19 +9,21 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/drawers/progress_drawer.dart';
|
||||||
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/dns_provider_picker.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/dns_provider_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart';
|
import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class InitializingPage extends StatelessWidget {
|
class InitializingPage extends StatelessWidget {
|
||||||
const InitializingPage({super.key});
|
const InitializingPage({super.key});
|
||||||
|
|
||||||
|
@ -49,99 +51,155 @@ class InitializingPage extends StatelessWidget {
|
||||||
][cubit.state.progress.index]();
|
][cubit.state.progress.index]();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
'initializing.steps.hosting',
|
||||||
|
'initializing.steps.server_type',
|
||||||
|
'initializing.steps.dns_provider',
|
||||||
|
'initializing.steps.backups_provider',
|
||||||
|
'initializing.steps.domain',
|
||||||
|
'initializing.steps.master_account',
|
||||||
|
'initializing.steps.server',
|
||||||
|
'initializing.steps.dns_setup',
|
||||||
|
'initializing.steps.nixos_installation',
|
||||||
|
'initializing.steps.server_reboot',
|
||||||
|
'initializing.steps.final_checks',
|
||||||
|
];
|
||||||
|
|
||||||
return BlocListener<ServerInstallationCubit, ServerInstallationState>(
|
return BlocListener<ServerInstallationCubit, ServerInstallationState>(
|
||||||
listener: (final context, final state) {
|
listener: (final context, final state) {
|
||||||
if (cubit.state is ServerInstallationFinished) {
|
if (cubit.state is ServerInstallationFinished) {
|
||||||
Navigator.of(context)
|
context.router.popUntilRoot();
|
||||||
.pushReplacement(materialRoute(const RootPage()));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
endDrawer: const SupportDrawer(),
|
||||||
actions: [
|
endDrawerEnableOpenDragGesture: false,
|
||||||
if (cubit.state is ServerInstallationFinished)
|
appBar: Breakpoints.large.isActive(context)
|
||||||
IconButton(
|
? null
|
||||||
icon: const Icon(Icons.check),
|
: AppBar(
|
||||||
onPressed: () {
|
actions: [
|
||||||
Navigator.of(context)
|
if (cubit.state is ServerInstallationFinished)
|
||||||
.pushReplacement(materialRoute(const RootPage()));
|
IconButton(
|
||||||
},
|
icon: const Icon(Icons.check),
|
||||||
)
|
onPressed: () {
|
||||||
],
|
context.router.popUntilRoot();
|
||||||
title: Text(
|
},
|
||||||
'more_page.configuration_wizard'.tr(),
|
|
||||||
),
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(28),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
|
||||||
child: ProgressBar(
|
|
||||||
steps: const [
|
|
||||||
'Hosting',
|
|
||||||
'Server Type',
|
|
||||||
'CloudFlare',
|
|
||||||
'Backblaze',
|
|
||||||
'Domain',
|
|
||||||
'User',
|
|
||||||
'Server',
|
|
||||||
'Installation',
|
|
||||||
],
|
|
||||||
activeIndex: cubit.state.porgressBar,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0.0),
|
|
||||||
child: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: actualInitializingPage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: MediaQuery.of(context).size.height -
|
|
||||||
MediaQuery.of(context).padding.top -
|
|
||||||
MediaQuery.of(context).padding.bottom -
|
|
||||||
566,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: BrandButton.text(
|
|
||||||
title: cubit.state is ServerInstallationFinished
|
|
||||||
? 'basis.close'.tr()
|
|
||||||
: 'basis.later'.tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
|
||||||
materialRoute(const RootPage()),
|
|
||||||
(final predicate) => false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (cubit.state is ServerInstallationEmpty ||
|
const SizedBox.shrink(),
|
||||||
cubit.state is ServerInstallationNotFinished)
|
],
|
||||||
Container(
|
title: Text(
|
||||||
alignment: Alignment.center,
|
'more_page.configuration_wizard'.tr(),
|
||||||
child: BrandButton.text(
|
),
|
||||||
title: 'basis.connect_to_existing'.tr(),
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(28),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
|
child: ProgressBar(
|
||||||
|
steps: const [
|
||||||
|
'Hosting',
|
||||||
|
'Server Type',
|
||||||
|
'CloudFlare',
|
||||||
|
'Backblaze',
|
||||||
|
'Domain',
|
||||||
|
'User',
|
||||||
|
'Server',
|
||||||
|
'Installation',
|
||||||
|
],
|
||||||
|
activeIndex: cubit.state.porgressBar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: LayoutBuilder(
|
||||||
|
builder: (final context, final constraints) => Row(
|
||||||
|
children: [
|
||||||
|
if (Breakpoints.large.isActive(context))
|
||||||
|
ProgressDrawer(
|
||||||
|
steps: steps,
|
||||||
|
currentStep: cubit.state.progress.index,
|
||||||
|
title: 'more_page.configuration_wizard'.tr(),
|
||||||
|
constraints: constraints,
|
||||||
|
trailing: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (cubit.state is ServerInstallationEmpty ||
|
||||||
|
cubit.state is ServerInstallationNotFinished)
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: BrandButton.filled(
|
||||||
|
text: 'basis.connect_to_existing'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
context.router.replace(const RecoveryRoute());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: double.infinity,
|
||||||
|
),
|
||||||
|
child: OutlinedButton(
|
||||||
|
child: Text(
|
||||||
|
cubit.state is ServerInstallationFinished
|
||||||
|
? 'basis.close'.tr()
|
||||||
|
: 'basis.later'.tr(),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).push(
|
context.router.popUntilRoot();
|
||||||
materialRoute(
|
|
||||||
const RecoveryRouting(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: constraints.maxWidth -
|
||||||
|
(Breakpoints.large.isActive(context) ? 300 : 0),
|
||||||
|
height: constraints.maxHeight,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: Breakpoints.large.isActive(context)
|
||||||
|
? const EdgeInsets.all(16.0)
|
||||||
|
: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0.0),
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: actualInitializingPage,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!Breakpoints.large.isActive(context))
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: BrandButton.text(
|
||||||
|
title:
|
||||||
|
cubit.state is ServerInstallationFinished
|
||||||
|
? 'basis.close'.tr()
|
||||||
|
: 'basis.later'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
context.router.popUntilRoot();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (cubit.state is ServerInstallationEmpty ||
|
||||||
|
cubit.state is ServerInstallationNotFinished)
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: BrandButton.text(
|
||||||
|
title: 'basis.connect_to_existing'.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
context.router
|
||||||
|
.replace(const RecoveryRoute());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -182,15 +240,6 @@ class InitializingPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
void _showModal(final BuildContext context, final Widget widget) {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (final BuildContext context) => widget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _stepDnsProviderToken(
|
Widget _stepDnsProviderToken(
|
||||||
final ServerInstallationCubit initializingCubit,
|
final ServerInstallationCubit initializingCubit,
|
||||||
) =>
|
) =>
|
||||||
|
@ -213,50 +262,57 @@ class InitializingPage extends StatelessWidget {
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (final context) {
|
builder: (final context) {
|
||||||
final formCubitState = context.watch<BackblazeFormCubit>().state;
|
final formCubitState = context.watch<BackblazeFormCubit>().state;
|
||||||
return Column(
|
return ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'${'initializing.connect_to_server_provider'.tr()}Backblaze',
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
'${'initializing.connect_to_server_provider'.tr()}Backblaze',
|
||||||
),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
const SizedBox(height: 32),
|
|
||||||
CubitFormTextField(
|
|
||||||
formFieldCubit: context.read<BackblazeFormCubit>().keyId,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'KeyID',
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
CubitFormTextField(
|
primaryColumn: Column(
|
||||||
formFieldCubit:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
context.read<BackblazeFormCubit>().applicationKey,
|
children: [
|
||||||
textAlign: TextAlign.center,
|
CubitFormTextField(
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
formFieldCubit: context.read<BackblazeFormCubit>().keyId,
|
||||||
decoration: const InputDecoration(
|
textAlign: TextAlign.center,
|
||||||
hintText: 'Master Application Key',
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
),
|
decoration: const InputDecoration(
|
||||||
),
|
hintText: 'KeyID',
|
||||||
const SizedBox(height: 32),
|
|
||||||
BrandButton.rised(
|
|
||||||
onPressed: formCubitState.isSubmitting
|
|
||||||
? null
|
|
||||||
: () => context.read<BackblazeFormCubit>().trySubmit(),
|
|
||||||
text: 'basis.connect'.tr(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
BrandButton.text(
|
|
||||||
onPressed: () => _showModal(
|
|
||||||
context,
|
|
||||||
const _HowTo(
|
|
||||||
fileName: 'how_backblaze',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: 'initializing.how'.tr(),
|
const SizedBox(height: 16),
|
||||||
),
|
CubitFormTextField(
|
||||||
],
|
formFieldCubit:
|
||||||
|
context.read<BackblazeFormCubit>().applicationKey,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Master Application Key',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: formCubitState.isSubmitting
|
||||||
|
? null
|
||||||
|
: () => context.read<BackblazeFormCubit>().trySubmit(),
|
||||||
|
text: 'basis.connect'.tr(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
BrandButton.text(
|
||||||
|
onPressed: () {
|
||||||
|
context.read<SupportSystemCubit>().showArticle(
|
||||||
|
article: 'how_backblaze',
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
Scaffold.of(context).openEndDrawer();
|
||||||
|
},
|
||||||
|
title: 'initializing.how'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -269,9 +325,8 @@ class InitializingPage extends StatelessWidget {
|
||||||
builder: (final context) {
|
builder: (final context) {
|
||||||
final DomainSetupState state =
|
final DomainSetupState state =
|
||||||
context.watch<DomainSetupCubit>().state;
|
context.watch<DomainSetupCubit>().state;
|
||||||
return SizedBox(
|
return ResponsiveLayoutWithInfobox(
|
||||||
width: double.infinity,
|
topChild: Column(
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
@ -283,7 +338,11 @@ class InitializingPage extends StatelessWidget {
|
||||||
'initializing.use_this_domain_text'.tr(),
|
'initializing.use_this_domain_text'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
],
|
||||||
|
),
|
||||||
|
primaryColumn: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
if (state is Empty)
|
if (state is Empty)
|
||||||
Text(
|
Text(
|
||||||
'initializing.no_connected_domains'.tr(),
|
'initializing.no_connected_domains'.tr(),
|
||||||
|
@ -323,7 +382,7 @@ class InitializingPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
if (state is Empty) ...[
|
if (state is Empty) ...[
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
BrandButton.rised(
|
BrandButton.filled(
|
||||||
onPressed: () => context.read<DomainSetupCubit>().load(),
|
onPressed: () => context.read<DomainSetupCubit>().load(),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -333,14 +392,17 @@ class InitializingPage extends StatelessWidget {
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
BrandText.buttonTitleText('domain.update_list'.tr()),
|
Text(
|
||||||
|
'domain.update_list'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (state is Loaded) ...[
|
if (state is Loaded) ...[
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
BrandButton.rised(
|
BrandButton.filled(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
context.read<DomainSetupCubit>().saveDomain(),
|
context.read<DomainSetupCubit>().saveDomain(),
|
||||||
text: 'initializing.save_domain'.tr(),
|
text: 'initializing.save_domain'.tr(),
|
||||||
|
@ -361,74 +423,83 @@ class InitializingPage extends StatelessWidget {
|
||||||
builder: (final context) {
|
builder: (final context) {
|
||||||
final formCubitState = context.watch<RootUserFormCubit>().state;
|
final formCubitState = context.watch<RootUserFormCubit>().state;
|
||||||
|
|
||||||
return Column(
|
return ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'initializing.create_master_account'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'initializing.enter_username_and_password'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
if (formCubitState.isErrorShown) const SizedBox(height: 16),
|
|
||||||
if (formCubitState.isErrorShown)
|
|
||||||
Text(
|
Text(
|
||||||
'users.username_rule'.tr(),
|
'initializing.create_master_account'.tr(),
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
color: Theme.of(context).colorScheme.error,
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'initializing.enter_username_and_password'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
primaryColumn: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (formCubitState.isErrorShown) const SizedBox(height: 16),
|
||||||
|
if (formCubitState.isErrorShown)
|
||||||
|
Text(
|
||||||
|
'users.username_rule'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
CubitFormTextField(
|
||||||
|
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'basis.username'.tr(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 16),
|
||||||
CubitFormTextField(
|
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
|
||||||
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
bloc: context.read<RootUserFormCubit>().isVisible,
|
||||||
textAlign: TextAlign.center,
|
builder: (final context, final state) {
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
final bool isVisible = state.value;
|
||||||
decoration: InputDecoration(
|
return CubitFormTextField(
|
||||||
hintText: 'basis.username'.tr(),
|
obscureText: !isVisible,
|
||||||
),
|
formFieldCubit:
|
||||||
),
|
context.read<RootUserFormCubit>().password,
|
||||||
const SizedBox(height: 16),
|
textAlign: TextAlign.center,
|
||||||
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
bloc: context.read<RootUserFormCubit>().isVisible,
|
decoration: InputDecoration(
|
||||||
builder: (final context, final state) {
|
hintText: 'basis.password'.tr(),
|
||||||
final bool isVisible = state.value;
|
suffixIcon: IconButton(
|
||||||
return CubitFormTextField(
|
icon: Icon(
|
||||||
obscureText: !isVisible,
|
isVisible
|
||||||
formFieldCubit:
|
? Icons.visibility
|
||||||
context.read<RootUserFormCubit>().password,
|
: Icons.visibility_off,
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
onPressed: () => context
|
||||||
decoration: InputDecoration(
|
.read<RootUserFormCubit>()
|
||||||
hintText: 'basis.password'.tr(),
|
.isVisible
|
||||||
suffixIcon: IconButton(
|
.setValue(!isVisible),
|
||||||
icon: Icon(
|
|
||||||
isVisible ? Icons.visibility : Icons.visibility_off,
|
|
||||||
),
|
),
|
||||||
onPressed: () => context
|
suffixIconConstraints:
|
||||||
.read<RootUserFormCubit>()
|
const BoxConstraints(minWidth: 60),
|
||||||
.isVisible
|
prefixIconConstraints:
|
||||||
.setValue(!isVisible),
|
const BoxConstraints(maxWidth: 60),
|
||||||
|
prefixIcon: Container(),
|
||||||
),
|
),
|
||||||
suffixIconConstraints:
|
);
|
||||||
const BoxConstraints(minWidth: 60),
|
},
|
||||||
prefixIconConstraints:
|
),
|
||||||
const BoxConstraints(maxWidth: 60),
|
const SizedBox(height: 32),
|
||||||
prefixIcon: Container(),
|
BrandButton.filled(
|
||||||
),
|
onPressed: formCubitState.isSubmitting
|
||||||
);
|
? null
|
||||||
},
|
: () => context.read<RootUserFormCubit>().trySubmit(),
|
||||||
),
|
text: 'basis.connect'.tr(),
|
||||||
const SizedBox(height: 32),
|
),
|
||||||
BrandButton.rised(
|
],
|
||||||
onPressed: formCubitState.isSubmitting
|
),
|
||||||
? null
|
|
||||||
: () => context.read<RootUserFormCubit>().trySubmit(),
|
|
||||||
text: 'basis.connect'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -438,27 +509,28 @@ class InitializingPage extends StatelessWidget {
|
||||||
final bool isLoading =
|
final bool isLoading =
|
||||||
(appConfigCubit.state as ServerInstallationNotFinished).isLoading;
|
(appConfigCubit.state as ServerInstallationNotFinished).isLoading;
|
||||||
return Builder(
|
return Builder(
|
||||||
builder: (final context) => Column(
|
builder: (final context) => ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'initializing.final'.tr(),
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
'initializing.final'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
const SizedBox(height: 16),
|
||||||
'initializing.create_server'.tr(),
|
Text(
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
'initializing.create_server'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
const SizedBox(height: 128),
|
),
|
||||||
BrandButton.rised(
|
],
|
||||||
onPressed:
|
),
|
||||||
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
|
primaryColumn: BrandButton.filled(
|
||||||
text: isLoading
|
onPressed:
|
||||||
? 'basis.loading'.tr()
|
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
|
||||||
: 'initializing.create_server'.tr(),
|
text: isLoading
|
||||||
),
|
? 'basis.loading'.tr()
|
||||||
],
|
: 'initializing.create_server'.tr(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -487,84 +559,67 @@ class InitializingPage extends StatelessWidget {
|
||||||
return Builder(
|
return Builder(
|
||||||
builder: (final context) => SizedBox(
|
builder: (final context) => SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Column(
|
child: ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'initializing.checks'.tr(args: [doneCount.toString(), '4']),
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
if (text != null)
|
|
||||||
Text(
|
Text(
|
||||||
text,
|
'initializing.checks'.tr(args: [doneCount.toString(), '4']),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 128),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 10),
|
if (text != null)
|
||||||
if (doneCount == 0 && state.dnsMatches != null)
|
Text(
|
||||||
Column(
|
text,
|
||||||
children: state.dnsMatches!.entries.map((final entry) {
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
final String domain = entry.key;
|
),
|
||||||
final bool isCorrect = entry.value;
|
],
|
||||||
return Row(
|
),
|
||||||
children: [
|
primaryColumn: Column(
|
||||||
if (isCorrect)
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const Icon(Icons.check, color: Colors.green),
|
children: [
|
||||||
if (!isCorrect)
|
const SizedBox(height: 128),
|
||||||
const Icon(Icons.schedule, color: Colors.amber),
|
const SizedBox(height: 10),
|
||||||
const SizedBox(width: 10),
|
if (doneCount == 0 && state.dnsMatches != null)
|
||||||
Text(domain),
|
Column(
|
||||||
],
|
children: state.dnsMatches!.entries.map((final entry) {
|
||||||
);
|
final String domain = entry.key;
|
||||||
}).toList(),
|
final bool isCorrect = entry.value;
|
||||||
),
|
return Row(
|
||||||
const SizedBox(height: 10),
|
children: [
|
||||||
if (!state.isLoading)
|
if (isCorrect)
|
||||||
Row(
|
const Icon(Icons.check, color: Colors.green),
|
||||||
children: [
|
if (!isCorrect)
|
||||||
Text(
|
const Icon(Icons.schedule, color: Colors.amber),
|
||||||
'initializing.until_the_next_check'.tr(),
|
const SizedBox(width: 10),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
Text(domain),
|
||||||
),
|
],
|
||||||
BrandTimer(
|
);
|
||||||
startDateTime: state.timerStart!,
|
}).toList(),
|
||||||
duration: state.duration!,
|
),
|
||||||
)
|
const SizedBox(height: 10),
|
||||||
],
|
if (!state.isLoading)
|
||||||
),
|
Row(
|
||||||
if (state.isLoading)
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'initializing.check'.tr(),
|
'initializing.until_the_next_check'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
],
|
BrandTimer(
|
||||||
|
startDateTime: state.timerStart!,
|
||||||
|
duration: state.duration!,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (state.isLoading)
|
||||||
|
Text(
|
||||||
|
'initializing.check'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HowTo extends StatelessWidget {
|
|
||||||
const _HowTo({
|
|
||||||
required this.fileName,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String fileName;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => BrandBottomSheet(
|
|
||||||
isExpended: true,
|
|
||||||
child: Padding(
|
|
||||||
padding: paddingH15V0,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
children: [
|
|
||||||
BrandMarkdown(
|
|
||||||
fileName: fileName,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,16 +2,15 @@ import 'package:cubit_form/cubit_form.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_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
|
import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
|
||||||
import 'package:selfprivacy/utils/launch_url.dart';
|
import 'package:selfprivacy/utils/launch_url.dart';
|
||||||
|
|
||||||
class ServerProviderPicker extends StatefulWidget {
|
class ServerProviderPicker extends StatefulWidget {
|
||||||
|
@ -98,56 +97,49 @@ class ProviderInputDataPage extends StatelessWidget {
|
||||||
final ServerProviderFormCubit providerCubit;
|
final ServerProviderFormCubit providerCubit;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Column(
|
Widget build(final BuildContext context) => ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
"${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}",
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
"${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}",
|
||||||
),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'initializing.connect_to_server_provider_text'.tr(),
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
CubitFormTextField(
|
|
||||||
formFieldCubit: providerCubit.apiKey,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'Provider API Token',
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 32),
|
Text(
|
||||||
BrandButton.filled(
|
'initializing.connect_to_server_provider_text'.tr(),
|
||||||
child: Text('basis.connect'.tr()),
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
onPressed: () => providerCubit.trySubmit(),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
BrandOutlinedButton(
|
primaryColumn: Column(
|
||||||
child: Text('initializing.how'.tr()),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
onPressed: () => showModalBottomSheet<void>(
|
children: [
|
||||||
context: context,
|
CubitFormTextField(
|
||||||
isScrollControlled: true,
|
formFieldCubit: providerCubit.apiKey,
|
||||||
backgroundColor: Colors.transparent,
|
textAlign: TextAlign.center,
|
||||||
builder: (final BuildContext context) => BrandBottomSheet(
|
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||||
isExpended: true,
|
decoration: const InputDecoration(
|
||||||
child: Padding(
|
hintText: 'Provider API Token',
|
||||||
padding: paddingH15V0,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
children: [
|
|
||||||
BrandMarkdown(
|
|
||||||
fileName: providerInfo.pathToHow,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
],
|
BrandButton.filled(
|
||||||
|
child: Text('basis.connect'.tr()),
|
||||||
|
onPressed: () => providerCubit.trySubmit(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
BrandOutlinedButton(
|
||||||
|
child: Text('initializing.how'.tr()),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<SupportSystemCubit>().showArticle(
|
||||||
|
article: providerInfo.pathToHow,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,175 +156,182 @@ class ProviderSelectionPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => SizedBox(
|
Widget build(final BuildContext context) => SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Column(
|
child: ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'initializing.connect_to_server'.tr(),
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
'initializing.connect_to_server'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
Text(
|
const SizedBox(height: 10),
|
||||||
'initializing.select_provider'.tr(),
|
Text(
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
'initializing.select_provider'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
OutlinedCard(
|
],
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(16.0),
|
primaryColumn: Column(
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
OutlinedCard(
|
||||||
children: [
|
child: Padding(
|
||||||
Row(
|
padding: const EdgeInsets.all(16.0),
|
||||||
children: [
|
child: Column(
|
||||||
Container(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
width: 40,
|
children: [
|
||||||
height: 40,
|
Row(
|
||||||
padding: const EdgeInsets.all(10),
|
children: [
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
borderRadius: BorderRadius.circular(40),
|
width: 40,
|
||||||
color: const Color(0xFFD50C2D),
|
height: 40,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
color: const Color(0xFFD50C2D),
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/images/logos/hetzner.svg',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: SvgPicture.asset(
|
const SizedBox(width: 16),
|
||||||
'assets/images/logos/hetzner.svg',
|
Text(
|
||||||
|
'Hetzner Cloud',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(width: 16),
|
),
|
||||||
Text(
|
const SizedBox(height: 16),
|
||||||
'Hetzner Cloud',
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
'initializing.select_provider_countries_title'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
],
|
),
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 16),
|
'initializing.select_provider_countries_text_hetzner'
|
||||||
Text(
|
.tr(),
|
||||||
'initializing.select_provider_countries_title'.tr(),
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'initializing.select_provider_countries_text_hetzner'
|
'initializing.select_provider_price_title'.tr(),
|
||||||
.tr(),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
),
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 16),
|
'initializing.select_provider_price_text_hetzner'.tr(),
|
||||||
Text(
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
'initializing.select_provider_price_title'.tr(),
|
),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
const SizedBox(height: 16),
|
||||||
),
|
Text(
|
||||||
Text(
|
'initializing.select_provider_payment_title'.tr(),
|
||||||
'initializing.select_provider_price_text_hetzner'.tr(),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
),
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 16),
|
'initializing.select_provider_payment_text_hetzner'
|
||||||
Text(
|
.tr(),
|
||||||
'initializing.select_provider_payment_title'.tr(),
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'initializing.select_provider_payment_text_hetzner'.tr(),
|
'initializing.select_provider_email_notice'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
BrandButton.filled(
|
||||||
'initializing.select_provider_email_notice'.tr(),
|
child: Text('basis.select'.tr()),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
onPressed: () {
|
||||||
),
|
serverInstallationCubit
|
||||||
const SizedBox(height: 16),
|
.setServerProviderType(ServerProvider.hetzner);
|
||||||
BrandButton.filled(
|
callback(ServerProvider.hetzner);
|
||||||
child: Text('basis.select'.tr()),
|
},
|
||||||
onPressed: () {
|
),
|
||||||
serverInstallationCubit
|
// Outlined button that will open website
|
||||||
.setServerProviderType(ServerProvider.hetzner);
|
BrandOutlinedButton(
|
||||||
callback(ServerProvider.hetzner);
|
onPressed: () =>
|
||||||
},
|
launchURL('https://www.hetzner.com/cloud'),
|
||||||
),
|
title: 'initializing.select_provider_site_button'.tr(),
|
||||||
// Outlined button that will open website
|
),
|
||||||
BrandOutlinedButton(
|
],
|
||||||
onPressed: () =>
|
),
|
||||||
launchURL('https://www.hetzner.com/cloud'),
|
|
||||||
title: 'initializing.select_provider_site_button'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
OutlinedCard(
|
||||||
OutlinedCard(
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(16.0),
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Container(
|
||||||
Container(
|
width: 40,
|
||||||
width: 40,
|
height: 40,
|
||||||
height: 40,
|
padding: const EdgeInsets.all(10),
|
||||||
padding: const EdgeInsets.all(10),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(40),
|
||||||
borderRadius: BorderRadius.circular(40),
|
color: const Color(0xFF0080FF),
|
||||||
color: const Color(0xFF0080FF),
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/images/logos/digital_ocean.svg',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: SvgPicture.asset(
|
const SizedBox(width: 16),
|
||||||
'assets/images/logos/digital_ocean.svg',
|
Text(
|
||||||
|
'Digital Ocean',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(width: 16),
|
),
|
||||||
Text(
|
const SizedBox(height: 16),
|
||||||
'Digital Ocean',
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
'initializing.select_provider_countries_title'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
],
|
),
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 16),
|
'initializing.select_provider_countries_text_do'.tr(),
|
||||||
Text(
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
'initializing.select_provider_countries_title'.tr(),
|
),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
const SizedBox(height: 16),
|
||||||
),
|
Text(
|
||||||
Text(
|
'initializing.select_provider_price_title'.tr(),
|
||||||
'initializing.select_provider_countries_text_do'.tr(),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
),
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 16),
|
'initializing.select_provider_price_text_do'.tr(),
|
||||||
Text(
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
'initializing.select_provider_price_title'.tr(),
|
),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
const SizedBox(height: 16),
|
||||||
),
|
Text(
|
||||||
Text(
|
'initializing.select_provider_payment_title'.tr(),
|
||||||
'initializing.select_provider_price_text_do'.tr(),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
),
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 16),
|
'initializing.select_provider_payment_text_do'.tr(),
|
||||||
Text(
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
'initializing.select_provider_payment_title'.tr(),
|
),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
const SizedBox(height: 16),
|
||||||
),
|
BrandButton.filled(
|
||||||
Text(
|
child: Text('basis.select'.tr()),
|
||||||
'initializing.select_provider_payment_text_do'.tr(),
|
onPressed: () {
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
serverInstallationCubit.setServerProviderType(
|
||||||
),
|
ServerProvider.digitalOcean,
|
||||||
const SizedBox(height: 16),
|
);
|
||||||
BrandButton.filled(
|
callback(ServerProvider.digitalOcean);
|
||||||
child: Text('basis.select'.tr()),
|
},
|
||||||
onPressed: () {
|
),
|
||||||
serverInstallationCubit
|
// Outlined button that will open website
|
||||||
.setServerProviderType(ServerProvider.digitalOcean);
|
BrandOutlinedButton(
|
||||||
callback(ServerProvider.digitalOcean);
|
onPressed: () =>
|
||||||
},
|
launchURL('https://www.digitalocean.com'),
|
||||||
),
|
title: 'initializing.select_provider_site_button'.tr(),
|
||||||
// Outlined button that will open website
|
),
|
||||||
BrandOutlinedButton(
|
],
|
||||||
onPressed: () =>
|
),
|
||||||
launchURL('https://www.digitalocean.com'),
|
|
||||||
title: 'initializing.select_provider_site_button'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
InfoBox(text: 'initializing.select_provider_notice'.tr()),
|
secondaryColumn:
|
||||||
],
|
InfoBox(text: 'initializing.select_provider_notice'.tr()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_depe
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_type.dart';
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
|
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
|
||||||
|
|
||||||
class ServerTypePicker extends StatefulWidget {
|
class ServerTypePicker extends StatefulWidget {
|
||||||
const ServerTypePicker({
|
const ServerTypePicker({
|
||||||
|
@ -70,50 +71,67 @@ class SelectLocationPage extends StatelessWidget {
|
||||||
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
|
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
|
||||||
return Text('initializing.no_locations_found'.tr());
|
return Text('initializing.no_locations_found'.tr());
|
||||||
}
|
}
|
||||||
return Column(
|
return ResponsiveLayoutWithInfobox(
|
||||||
children: [
|
topChild: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
'initializing.choose_location_type'.tr(),
|
children: [
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
Text(
|
||||||
),
|
'initializing.choose_location_type'.tr(),
|
||||||
const SizedBox(height: 16),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
Text(
|
),
|
||||||
'initializing.choose_location_type_text'.tr(),
|
const SizedBox(height: 16),
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
Text(
|
||||||
),
|
'initializing.choose_location_type_text'.tr(),
|
||||||
const SizedBox(height: 16),
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
...(snapshot.data! as List<ServerProviderLocation>).map(
|
),
|
||||||
(final location) => SizedBox(
|
],
|
||||||
width: double.infinity,
|
),
|
||||||
child: InkWell(
|
primaryColumn: Column(
|
||||||
onTap: () {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
callback(location);
|
children: [
|
||||||
},
|
...(snapshot.data! as List<ServerProviderLocation>).map(
|
||||||
child: Card(
|
(final location) => Column(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(16.0),
|
SizedBox(
|
||||||
child: Column(
|
width: double.infinity,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Card(
|
||||||
children: [
|
clipBehavior: Clip.antiAlias,
|
||||||
Text(
|
child: InkResponse(
|
||||||
'${location.flag ?? ''} ${location.title}',
|
highlightShape: BoxShape.rectangle,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
onTap: () {
|
||||||
),
|
callback(location);
|
||||||
const SizedBox(height: 8),
|
},
|
||||||
if (location.description != null)
|
child: Padding(
|
||||||
Text(
|
padding: const EdgeInsets.all(16.0),
|
||||||
location.description!,
|
child: Column(
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${location.flag ?? ''} ${location.title}',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (location.description != null)
|
||||||
|
Text(
|
||||||
|
location.description!,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 24),
|
),
|
||||||
],
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
@ -180,121 +198,145 @@ class SelectTypePage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Column(
|
return ResponsiveLayoutWithInfobox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
topChild: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'initializing.choose_server_type'.tr(),
|
Text(
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
'initializing.choose_server_type'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
const SizedBox(height: 16),
|
||||||
'initializing.choose_server_type_text'.tr(),
|
Text(
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
'initializing.choose_server_type_text'.tr(),
|
||||||
),
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
...(snapshot.data! as List<ServerType>).map(
|
],
|
||||||
(final type) => SizedBox(
|
),
|
||||||
width: double.infinity,
|
primaryColumn: Column(
|
||||||
child: InkWell(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
onTap: () {
|
children: [
|
||||||
serverInstallationCubit.setServerType(type);
|
...(snapshot.data! as List<ServerType>).map(
|
||||||
},
|
(final type) => Column(
|
||||||
child: Card(
|
children: [
|
||||||
child: Padding(
|
SizedBox(
|
||||||
padding: const EdgeInsets.all(16.0),
|
width: double.infinity,
|
||||||
child: Column(
|
child: InkWell(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
onTap: () {
|
||||||
children: [
|
serverInstallationCubit.setServerType(type);
|
||||||
Text(
|
},
|
||||||
type.title,
|
child: Card(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.all(16.0),
|
||||||
const SizedBox(height: 8),
|
child: Column(
|
||||||
Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Text(
|
||||||
Icons.memory_outlined,
|
type.title,
|
||||||
color:
|
style: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.onSurface,
|
.textTheme
|
||||||
),
|
.titleMedium,
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'server.core_count'.plural(type.cores),
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.memory_outlined,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'initializing.choose_server_type_ram'
|
|
||||||
.tr(args: [type.ram.toString()]),
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.sd_card_outlined,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'initializing.choose_server_type_storage'
|
|
||||||
.tr(
|
|
||||||
args: [type.disk.gibibyte.toString()],
|
|
||||||
),
|
),
|
||||||
style:
|
const SizedBox(height: 8),
|
||||||
Theme.of(context).textTheme.bodyMedium,
|
Row(
|
||||||
),
|
children: [
|
||||||
],
|
Icon(
|
||||||
),
|
Icons.memory_outlined,
|
||||||
const SizedBox(height: 8),
|
color: Theme.of(context)
|
||||||
const Divider(height: 8),
|
.colorScheme
|
||||||
const SizedBox(height: 8),
|
.onSurface,
|
||||||
Row(
|
),
|
||||||
children: [
|
const SizedBox(width: 8),
|
||||||
Icon(
|
Text(
|
||||||
Icons.payments_outlined,
|
'server.core_count'
|
||||||
color:
|
.plural(type.cores),
|
||||||
Theme.of(context).colorScheme.onSurface,
|
style: Theme.of(context)
|
||||||
),
|
.textTheme
|
||||||
const SizedBox(width: 8),
|
.bodyMedium,
|
||||||
Text(
|
),
|
||||||
'initializing.choose_server_type_payment_per_month'
|
|
||||||
.tr(
|
|
||||||
args: [
|
|
||||||
'${type.price.value.toString()} ${type.price.currency}'
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style:
|
const SizedBox(height: 8),
|
||||||
Theme.of(context).textTheme.bodyLarge,
|
Row(
|
||||||
),
|
children: [
|
||||||
],
|
Icon(
|
||||||
|
Icons.memory_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'initializing.choose_server_type_ram'
|
||||||
|
.tr(args: [type.ram.toString()]),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.sd_card_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'initializing.choose_server_type_storage'
|
||||||
|
.tr(
|
||||||
|
args: [
|
||||||
|
type.disk.gibibyte.toString()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Divider(height: 8),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.payments_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'initializing.choose_server_type_payment_per_month'
|
||||||
|
.tr(
|
||||||
|
args: [
|
||||||
|
'${type.price.value.toString()} ${type.price.currency}'
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
InfoBox(text: 'initializing.choose_server_type_notice'.tr()),
|
secondaryColumn:
|
||||||
],
|
InfoBox(text: 'initializing.choose_server_type_notice'.tr()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
@ -17,6 +17,7 @@ class RecoverByNewDeviceKeyInstruction extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.method_device_description'.tr(),
|
heroSubtitle: 'recovering.method_device_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
onBackButtonPressed:
|
onBackButtonPressed:
|
||||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||||
children: [
|
children: [
|
||||||
|
@ -61,6 +62,7 @@ class RecoverByNewDeviceKeyInput extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.method_device_input_description'.tr(),
|
heroSubtitle: 'recovering.method_device_input_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
children: [
|
children: [
|
||||||
CubitFormTextField(
|
CubitFormTextField(
|
||||||
formFieldCubit:
|
formFieldCubit:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
@ -28,6 +28,7 @@ class RecoverByOldTokenInstruction extends StatelessWidget {
|
||||||
heroTitle: 'recovering.recovery_main_header'.tr(),
|
heroTitle: 'recovering.recovery_main_header'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
onBackButtonPressed:
|
onBackButtonPressed:
|
||||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||||
children: [
|
children: [
|
||||||
|
@ -72,6 +73,7 @@ class RecoverByOldToken extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.method_device_input_description'.tr(),
|
heroSubtitle: 'recovering.method_device_input_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
children: [
|
children: [
|
||||||
CubitFormTextField(
|
CubitFormTextField(
|
||||||
formFieldCubit:
|
formFieldCubit:
|
||||||
|
|
|
@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
||||||
class RecoverByRecoveryKey extends StatelessWidget {
|
class RecoverByRecoveryKey extends StatelessWidget {
|
||||||
const RecoverByRecoveryKey({super.key});
|
const RecoverByRecoveryKey({super.key});
|
||||||
|
@ -31,6 +31,7 @@ class RecoverByRecoveryKey extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.method_recovery_input_description'.tr(),
|
heroSubtitle: 'recovering.method_recovery_input_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
onBackButtonPressed:
|
onBackButtonPressed:
|
||||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
|
||||||
|
|
||||||
class RecoveryConfirmBackblaze extends StatelessWidget {
|
class RecoveryConfirmBackblaze extends StatelessWidget {
|
||||||
const RecoveryConfirmBackblaze({super.key});
|
const RecoveryConfirmBackblaze({super.key});
|
||||||
|
@ -28,6 +26,8 @@ class RecoveryConfirmBackblaze extends StatelessWidget {
|
||||||
heroTitle: 'recovering.confirm_backblaze'.tr(),
|
heroTitle: 'recovering.confirm_backblaze'.tr(),
|
||||||
heroSubtitle: 'recovering.confirm_backblaze_description'.tr(),
|
heroSubtitle: 'recovering.confirm_backblaze_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
|
hasSupportDrawer: true,
|
||||||
onBackButtonPressed: () {
|
onBackButtonPressed: () {
|
||||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||||
},
|
},
|
||||||
|
@ -57,27 +57,15 @@ class RecoveryConfirmBackblaze extends StatelessWidget {
|
||||||
text: 'basis.connect'.tr(),
|
text: 'basis.connect'.tr(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
BrandButton.text(
|
Builder(
|
||||||
onPressed: () => showModalBottomSheet<void>(
|
builder: (final context) => BrandButton.text(
|
||||||
context: context,
|
onPressed: () =>
|
||||||
isScrollControlled: true,
|
context.read<SupportSystemCubit>().showArticle(
|
||||||
backgroundColor: Colors.transparent,
|
article: 'how_backblaze',
|
||||||
builder: (final BuildContext context) => BrandBottomSheet(
|
context: context,
|
||||||
isExpended: true,
|
|
||||||
child: Padding(
|
|
||||||
padding: paddingH15V0,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
children: const [
|
|
||||||
BrandMarkdown(
|
|
||||||
fileName: 'how_backblaze',
|
|
||||||
),
|
),
|
||||||
],
|
title: 'initializing.how'.tr(),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
title: 'initializing.how'.tr(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
|
||||||
|
|
||||||
class RecoveryConfirmCloudflare extends StatelessWidget {
|
class RecoveryConfirmCloudflare extends StatelessWidget {
|
||||||
const RecoveryConfirmCloudflare({super.key});
|
const RecoveryConfirmCloudflare({super.key});
|
||||||
|
@ -31,6 +29,8 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
|
||||||
),
|
),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
|
hasSupportDrawer: true,
|
||||||
onBackButtonPressed:
|
onBackButtonPressed:
|
||||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||||
children: [
|
children: [
|
||||||
|
@ -49,27 +49,15 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
|
||||||
text: 'basis.connect'.tr(),
|
text: 'basis.connect'.tr(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
BrandButton.text(
|
Builder(
|
||||||
onPressed: () => showModalBottomSheet<void>(
|
builder: (final context) => BrandButton.text(
|
||||||
context: context,
|
onPressed: () =>
|
||||||
isScrollControlled: true,
|
context.read<SupportSystemCubit>().showArticle(
|
||||||
backgroundColor: Colors.transparent,
|
article: 'how_cloudflare',
|
||||||
builder: (final BuildContext context) => BrandBottomSheet(
|
context: context,
|
||||||
isExpended: true,
|
|
||||||
child: Padding(
|
|
||||||
padding: paddingH15V0,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
children: const [
|
|
||||||
BrandMarkdown(
|
|
||||||
fileName: 'how_cloudflare',
|
|
||||||
),
|
),
|
||||||
],
|
title: 'initializing.how'.tr(),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
title: 'initializing.how'.tr(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
||||||
class RecoveryConfirmServer extends StatefulWidget {
|
class RecoveryConfirmServer extends StatefulWidget {
|
||||||
const RecoveryConfirmServer({super.key});
|
const RecoveryConfirmServer({super.key});
|
||||||
|
@ -38,6 +38,7 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
||||||
? 'recovering.choose_server_description'.tr()
|
? 'recovering.choose_server_description'.tr()
|
||||||
: 'recovering.confirm_server_description'.tr(),
|
: 'recovering.confirm_server_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
onBackButtonPressed: () {
|
onBackButtonPressed: () {
|
||||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,9 +2,9 @@ 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:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ class RecoveryMethodSelect extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.method_select_description'.tr(),
|
heroSubtitle: 'recovering.method_select_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
onBackButtonPressed:
|
onBackButtonPressed:
|
||||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||||
children: [
|
children: [
|
||||||
|
@ -74,6 +75,7 @@ class RecoveryFallbackMethodSelect extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.fallback_select_description'.tr(),
|
heroSubtitle: 'recovering.fallback_select_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
children: [
|
children: [
|
||||||
OutlinedCard(
|
OutlinedCard(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.dart';
|
import 'package:selfprivacy/ui/pages/root_route.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart';
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart';
|
||||||
|
@ -17,6 +18,7 @@ import 'package:selfprivacy/ui/pages/setup/recovering/recovery_server_provider_c
|
||||||
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_method_select.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
class RecoveryRouting extends StatelessWidget {
|
class RecoveryRouting extends StatelessWidget {
|
||||||
const RecoveryRouting({super.key});
|
const RecoveryRouting({super.key});
|
||||||
|
|
||||||
|
@ -110,6 +112,7 @@ class SelectDomainToRecover extends StatelessWidget {
|
||||||
heroSubtitle: 'recovering.domain_recovery_description'.tr(),
|
heroSubtitle: 'recovering.domain_recovery_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
onBackButtonPressed: () {
|
onBackButtonPressed: () {
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
materialRoute(const RootPage()),
|
materialRoute(const RootPage()),
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
|
||||||
|
|
||||||
class RecoveryServerProviderConnected extends StatelessWidget {
|
class RecoveryServerProviderConnected extends StatelessWidget {
|
||||||
const RecoveryServerProviderConnected({super.key});
|
const RecoveryServerProviderConnected({super.key});
|
||||||
|
@ -32,6 +30,8 @@ class RecoveryServerProviderConnected extends StatelessWidget {
|
||||||
),
|
),
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
|
ignoreBreakpoints: true,
|
||||||
|
hasSupportDrawer: true,
|
||||||
onBackButtonPressed: () {
|
onBackButtonPressed: () {
|
||||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||||
},
|
},
|
||||||
|
@ -52,26 +52,14 @@ class RecoveryServerProviderConnected extends StatelessWidget {
|
||||||
child: Text('basis.continue'.tr()),
|
child: Text('basis.continue'.tr()),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
BrandButton.text(
|
Builder(
|
||||||
title: 'initializing.how'.tr(),
|
builder: (final context) => BrandButton.text(
|
||||||
onPressed: () => showModalBottomSheet<void>(
|
title: 'initializing.how'.tr(),
|
||||||
context: context,
|
onPressed: () =>
|
||||||
isScrollControlled: true,
|
context.read<SupportSystemCubit>().showArticle(
|
||||||
backgroundColor: Colors.transparent,
|
article: 'how_hetzner',
|
||||||
builder: (final BuildContext context) => BrandBottomSheet(
|
context: context,
|
||||||
isExpended: true,
|
|
||||||
child: Padding(
|
|
||||||
padding: paddingH15V0,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
children: const [
|
|
||||||
BrandMarkdown(
|
|
||||||
fileName: 'how_hetzner',
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
part of 'users.dart';
|
|
||||||
|
|
||||||
class AddUserFab extends StatelessWidget {
|
|
||||||
const AddUserFab({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => FloatingActionButton.small(
|
|
||||||
heroTag: 'new_user_fab',
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (final BuildContext context) => Padding(
|
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
|
||||||
child: const NewUser(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.person_add_outlined),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -11,21 +11,25 @@ class _NoUsers extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(BrandIcons.users, size: 50, color: BrandColors.grey7),
|
Icon(
|
||||||
|
BrandIcons.users,
|
||||||
|
size: 50,
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
BrandText.h2(
|
Text(
|
||||||
'users.nobody_here'.tr(),
|
'users.nobody_here'.tr(),
|
||||||
style: const TextStyle(
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
color: BrandColors.grey7,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
BrandText.medium(
|
Text(
|
||||||
text,
|
text,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
color: BrandColors.grey7,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -43,21 +47,25 @@ class _CouldNotLoadUsers extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(BrandIcons.users, size: 50, color: BrandColors.grey7),
|
Icon(
|
||||||
|
BrandIcons.users,
|
||||||
|
size: 50,
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
BrandText.h2(
|
Text(
|
||||||
'users.could_not_fetch_users'.tr(),
|
'users.could_not_fetch_users'.tr(),
|
||||||
style: const TextStyle(
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
color: BrandColors.grey7,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
BrandText.medium(
|
Text(
|
||||||
text,
|
text,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
color: BrandColors.grey7,
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
part of 'users.dart';
|
part of 'users.dart';
|
||||||
|
|
||||||
class NewUser extends StatelessWidget {
|
@RoutePage()
|
||||||
const NewUser({super.key});
|
class NewUserPage extends StatelessWidget {
|
||||||
|
const NewUserPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
|
@ -10,108 +11,89 @@ class NewUser extends StatelessWidget {
|
||||||
|
|
||||||
final String domainName = UiHelpers.getDomainName(config);
|
final String domainName = UiHelpers.getDomainName(config);
|
||||||
|
|
||||||
return BrandBottomSheet(
|
return BlocProvider(
|
||||||
child: BlocProvider(
|
create: (final BuildContext context) {
|
||||||
create: (final BuildContext context) {
|
final jobCubit = context.read<JobsCubit>();
|
||||||
final jobCubit = context.read<JobsCubit>();
|
final jobState = jobCubit.state;
|
||||||
final jobState = jobCubit.state;
|
final users = <User>[];
|
||||||
final users = <User>[];
|
users.addAll(context.read<UsersCubit>().state.users);
|
||||||
users.addAll(context.read<UsersCubit>().state.users);
|
if (jobState is JobsStateWithJobs) {
|
||||||
if (jobState is JobsStateWithJobs) {
|
final jobs = jobState.clientJobList;
|
||||||
final jobs = jobState.clientJobList;
|
for (final job in jobs) {
|
||||||
for (final job in jobs) {
|
if (job is CreateUserJob) {
|
||||||
if (job is CreateUserJob) {
|
users.add(job.user);
|
||||||
users.add(job.user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return UserFormCubit(
|
}
|
||||||
jobsCubit: jobCubit,
|
return UserFormCubit(
|
||||||
fieldFactory: FieldCubitFactory(context),
|
jobsCubit: jobCubit,
|
||||||
);
|
fieldFactory: FieldCubitFactory(context),
|
||||||
},
|
);
|
||||||
child: Builder(
|
},
|
||||||
builder: (final BuildContext context) {
|
child: Builder(
|
||||||
final FormCubitState formCubitState =
|
builder: (final BuildContext context) {
|
||||||
context.watch<UserFormCubit>().state;
|
final FormCubitState formCubitState =
|
||||||
|
context.watch<UserFormCubit>().state;
|
||||||
|
|
||||||
return BlocListener<UserFormCubit, FormCubitState>(
|
return BlocListener<UserFormCubit, FormCubitState>(
|
||||||
listener:
|
listener: (final BuildContext context, final FormCubitState state) {
|
||||||
(final BuildContext context, final FormCubitState state) {
|
if (state.isSubmitted) {
|
||||||
if (state.isSubmitted) {
|
context.router.pop();
|
||||||
Navigator.pop(context);
|
}
|
||||||
}
|
},
|
||||||
},
|
child: BrandHeroScreen(
|
||||||
child: Column(
|
heroTitle: 'users.new_user'.tr(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
heroIcon: Icons.person_add_outlined,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
if (formCubitState.isErrorShown)
|
||||||
BrandHeader(
|
Text(
|
||||||
title: 'users.new_user'.tr(),
|
'users.username_rule'.tr(),
|
||||||
),
|
style: TextStyle(
|
||||||
const SizedBox(width: 14),
|
color: Theme.of(context).colorScheme.error,
|
||||||
Padding(
|
|
||||||
padding: paddingH15V0,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (formCubitState.isErrorShown)
|
|
||||||
Text(
|
|
||||||
'users.username_rule'.tr(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 14),
|
|
||||||
IntrinsicHeight(
|
|
||||||
child: CubitFormTextField(
|
|
||||||
formFieldCubit: context.read<UserFormCubit>().login,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'users.login'.tr(),
|
|
||||||
suffixText: '@$domainName',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
CubitFormTextField(
|
|
||||||
formFieldCubit:
|
|
||||||
context.read<UserFormCubit>().password,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
alignLabelWithHint: false,
|
|
||||||
labelText: 'basis.password'.tr(),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8),
|
|
||||||
child: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
BrandIcons.refresh,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
onPressed: context
|
|
||||||
.read<UserFormCubit>()
|
|
||||||
.genNewPassword,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
BrandButton.rised(
|
|
||||||
onPressed: formCubitState.isSubmitting
|
|
||||||
? null
|
|
||||||
: () => context.read<UserFormCubit>().trySubmit(),
|
|
||||||
text: 'basis.create'.tr(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
Text('users.new_user_info_note'.tr()),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(width: 14),
|
||||||
),
|
IntrinsicHeight(
|
||||||
);
|
child: CubitFormTextField(
|
||||||
},
|
formFieldCubit: context.read<UserFormCubit>().login,
|
||||||
),
|
decoration: InputDecoration(
|
||||||
|
labelText: 'users.login'.tr(),
|
||||||
|
suffixText: '@$domainName',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
CubitFormTextField(
|
||||||
|
formFieldCubit: context.read<UserFormCubit>().password,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
alignLabelWithHint: false,
|
||||||
|
labelText: 'basis.password'.tr(),
|
||||||
|
suffixIcon: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
BrandIcons.refresh,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
onPressed: context.read<UserFormCubit>().genNewPassword,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: formCubitState.isSubmitting
|
||||||
|
? null
|
||||||
|
: () => context.read<UserFormCubit>().trySubmit(),
|
||||||
|
text: 'basis.create'.tr(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
Text('users.new_user_info_note'.tr()),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue