mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-04 23:24:20 +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]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.json]
|
||||
indent_size = 4
|
||||
|
|
|
@ -29,16 +29,16 @@ linter:
|
|||
# producing the lint.
|
||||
rules:
|
||||
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_put_required_named_parameters_first: 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_setters_without_getters: true
|
||||
collection_methods_unrelated_type: true
|
||||
combinators_ordering: true
|
||||
eol_at_end_of_file: true
|
||||
no_adjacent_strings_in_list: true
|
||||
prefer_constructors_over_static_methods: true
|
||||
prefer_expression_function_bodies: true
|
||||
prefer_final_in_for_each: true
|
||||
|
@ -48,12 +48,18 @@ linter:
|
|||
prefer_if_elements_to_conditional_expressions: true
|
||||
prefer_mixin: true
|
||||
prefer_null_aware_method_calls: true
|
||||
prefer_single_quotes: true
|
||||
require_trailing_commas: true
|
||||
sized_box_shrink_expand: true
|
||||
sort_constructors_first: true
|
||||
unawaited_futures: true
|
||||
unnecessary_await_in_return: true
|
||||
unnecessary_null_aware_operator_on_extension_on_nullable: true
|
||||
unnecessary_null_checks: true
|
||||
unnecessary_parenthesis: true
|
||||
unnecessary_statements: true
|
||||
unnecessary_to_list_in_spreads: true
|
||||
unreachable_from_main: true
|
||||
use_enums: true
|
||||
use_if_null_to_convert_nulls_to_bools: true
|
||||
use_is_even_rather_than_modulo: true
|
||||
|
@ -61,6 +67,7 @@ linter:
|
|||
use_named_constants: true
|
||||
use_setters_to_change_properties: true
|
||||
use_string_buffers: true
|
||||
use_string_in_part_of_directives: true
|
||||
use_super_parameters: true
|
||||
use_to_and_as_if_applicable: true
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"test": "en-test",
|
||||
"locale": "en",
|
||||
"basis": {
|
||||
"app_name": "SelfPrivacy",
|
||||
"providers": "Providers",
|
||||
"providers_title": "Your Data Center",
|
||||
"select": "Select",
|
||||
|
@ -46,7 +47,8 @@
|
|||
},
|
||||
"console_page": {
|
||||
"title": "Console",
|
||||
"waiting": "Waiting for initialization…"
|
||||
"waiting": "Waiting for initialization…",
|
||||
"copy": "Copy"
|
||||
},
|
||||
"about_us_page": {
|
||||
"title": "About us"
|
||||
|
@ -59,8 +61,11 @@
|
|||
},
|
||||
"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_description": "Switch your application theme",
|
||||
"dangerous_settings": "Dangerous settings",
|
||||
"reset_config_title": "Reset application config",
|
||||
"reset_config_description": "Reset api keys and root user",
|
||||
"delete_server_title": "Delete server",
|
||||
|
@ -251,6 +256,7 @@
|
|||
"subtitle": "Private VPN server"
|
||||
},
|
||||
"users": {
|
||||
"details_title": "User details",
|
||||
"add_new_user": "Add a first user",
|
||||
"new_user": "New user",
|
||||
"delete_user": "Delete user",
|
||||
|
@ -335,7 +341,20 @@
|
|||
"create_master_account": "Create master account",
|
||||
"enter_username_and_password": "Enter username and strong password",
|
||||
"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": {
|
||||
"generic_error": "Operation failed, please try again.",
|
||||
|
@ -478,5 +497,19 @@
|
|||
"root_name": "Cannot be 'root'",
|
||||
"length_not_equal": "Length is [], should be {}",
|
||||
"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_volumes/server_volume_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/provider_volumes/provider_volume_cubit.dart';
|
||||
|
||||
|
@ -23,7 +24,9 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
const isDark = false;
|
||||
const isAutoDark = true;
|
||||
final serverInstallationCubit = ServerInstallationCubit()..load();
|
||||
final supportSystemCubit = SupportSystemCubit();
|
||||
final usersCubit = UsersCubit(serverInstallationCubit);
|
||||
final servicesCubit = ServicesCubit(serverInstallationCubit);
|
||||
final backupsCubit = BackupsCubit(serverInstallationCubit);
|
||||
|
@ -41,9 +44,13 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
BlocProvider(
|
||||
create: (final _) => AppSettingsCubit(
|
||||
isDarkModeOn: isDark,
|
||||
isAutoDarkModeOn: isAutoDark,
|
||||
isOnboardingShowing: true,
|
||||
)..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => supportSystemCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
|
|
|
@ -2,53 +2,16 @@ import 'package:flutter/material.dart';
|
|||
|
||||
class BrandColors {
|
||||
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 = [
|
||||
Color(0xFF555555),
|
||||
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 = [
|
||||
Color(0xFFEF4E09),
|
||||
Color(0xFFEFD135),
|
||||
];
|
||||
|
||||
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);
|
||||
if (deprecatedUsers.isNotEmpty) {
|
||||
final Box<User> users = Hive.box<User>(BNames.usersBox);
|
||||
users.addAll(deprecatedUsers.values.toList());
|
||||
deprecatedUsers.clear();
|
||||
await users.addAll(deprecatedUsers.values.toList());
|
||||
await deprecatedUsers.clear();
|
||||
}
|
||||
|
||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||
|
@ -63,6 +63,9 @@ class BNames {
|
|||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isDarkModeOn = 'isDarkModeOn';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
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 NextLink? forward,
|
||||
]) async* {
|
||||
_logToAppConsole(request);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
GraphQlRequestMessage(
|
||||
operation: request.operation,
|
||||
variables: request.variables,
|
||||
context: request.context,
|
||||
),
|
||||
);
|
||||
yield* forward!(request);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +35,13 @@ class ResponseLoggingParser extends ResponseParser {
|
|||
@override
|
||||
Response parseResponse(final Map<String, dynamic> body) {
|
||||
final response = super.parseResponse(body);
|
||||
_logToAppConsole(response);
|
||||
getIt.get<ConsoleModel>().addMessage(
|
||||
GraphQlResponseMessage(
|
||||
data: response.data,
|
||||
errors: response.errors,
|
||||
context: response.context,
|
||||
),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,9 +65,11 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
final RequestInterceptorHandler handler,
|
||||
) async {
|
||||
addMessage(
|
||||
Message(
|
||||
text:
|
||||
'request-uri: ${options.uri}\nheaders: ${options.headers}\ndata: ${options.data}',
|
||||
RestApiRequestMessage(
|
||||
method: options.method,
|
||||
data: options.data.toString(),
|
||||
headers: options.headers,
|
||||
uri: options.uri,
|
||||
),
|
||||
);
|
||||
return super.onRequest(options, handler);
|
||||
|
@ -79,9 +81,11 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
|||
final ResponseInterceptorHandler handler,
|
||||
) async {
|
||||
addMessage(
|
||||
Message(
|
||||
text:
|
||||
'response-uri: ${response.realUri}\ncode: ${response.statusCode}\ndata: ${response.toString()}\n',
|
||||
RestApiResponseMessage(
|
||||
method: response.requestOptions.method,
|
||||
statusCode: response.statusCode,
|
||||
data: response.data.toString(),
|
||||
uri: response.realUri,
|
||||
),
|
||||
);
|
||||
return super.onResponse(
|
||||
|
|
|
@ -15,10 +15,12 @@ part 'app_settings_state.dart';
|
|||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||
AppSettingsCubit({
|
||||
required final bool isDarkModeOn,
|
||||
required final bool isAutoDarkModeOn,
|
||||
required final bool isOnboardingShowing,
|
||||
}) : super(
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isAutoDarkModeOn: isAutoDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
|
@ -27,10 +29,12 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
|||
|
||||
void load() async {
|
||||
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||
final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn);
|
||||
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isAutoDarkModeOn: isAutoDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
|
@ -49,9 +53,14 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
|
|||
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
||||
}
|
||||
|
||||
void turnOffOnboarding() {
|
||||
box.put(BNames.isOnboardingShowing, false);
|
||||
void updateAutoDarkMode({required final bool isAutoDarkModeOn}) {
|
||||
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 {
|
||||
const AppSettingsState({
|
||||
required this.isDarkModeOn,
|
||||
required this.isAutoDarkModeOn,
|
||||
required this.isOnboardingShowing,
|
||||
this.corePalette,
|
||||
});
|
||||
|
||||
final bool isDarkModeOn;
|
||||
final bool isAutoDarkModeOn;
|
||||
final bool isOnboardingShowing;
|
||||
final color_utils.CorePalette? corePalette;
|
||||
|
||||
AppSettingsState copyWith({
|
||||
final bool? isDarkModeOn,
|
||||
final bool? isAutoDarkModeOn,
|
||||
final bool? isOnboardingShowing,
|
||||
final color_utils.CorePalette? corePalette,
|
||||
}) =>
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||
isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
|
||||
corePalette: corePalette ?? this.corePalette,
|
||||
);
|
||||
|
@ -26,5 +30,6 @@ class AppSettingsState extends Equatable {
|
|||
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
|
||||
|
||||
@override
|
||||
List<dynamic> get props => [isDarkModeOn, isOnboardingShowing, corePalette];
|
||||
List<dynamic> get props =>
|
||||
[isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette];
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ class ApiDevicesCubit
|
|||
|
||||
@override
|
||||
void load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
_refetch();
|
||||
}
|
||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
_refetch();
|
||||
// }
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
|
|
|
@ -22,9 +22,6 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() => super.close();
|
||||
|
||||
Future<void> saveDomain() async {
|
||||
assert(state is Loaded, 'wrong state');
|
||||
final String domainName = (state as Loaded).domain;
|
||||
|
|
|
@ -36,10 +36,6 @@ class UserFormCubit extends FormCubit {
|
|||
|
||||
@override
|
||||
FutureOr<void> onSubmit() {
|
||||
print('onSubmit');
|
||||
print('initialUser: $initialUser');
|
||||
print('login: ${login.state.value}');
|
||||
print('password: ${password.state.value}');
|
||||
if (initialUser == null) {
|
||||
final User user = User(
|
||||
login: login.state.value,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||
|
@ -20,7 +22,7 @@ class ApiProviderVolumeCubit
|
|||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
_refetch();
|
||||
unawaited(_refetch());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +33,7 @@ class ApiProviderVolumeCubit
|
|||
|
||||
Future<void> refresh() async {
|
||||
emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
|
||||
_refetch();
|
||||
unawaited(_refetch());
|
||||
}
|
||||
|
||||
Future<void> _refetch() async {
|
||||
|
@ -56,14 +58,14 @@ class ApiProviderVolumeCubit
|
|||
await ApiController.currentVolumeProviderApiFactory!
|
||||
.getVolumeProvider()
|
||||
.attachVolume(volume.providerVolume!, server.id);
|
||||
refresh();
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<void> detachVolume(final DiskVolume volume) async {
|
||||
await ApiController.currentVolumeProviderApiFactory!
|
||||
.getVolumeProvider()
|
||||
.detachVolume(volume.providerVolume!);
|
||||
refresh();
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<bool> resizeVolume(
|
||||
|
@ -125,14 +127,14 @@ class ApiProviderVolumeCubit
|
|||
await Future.delayed(const Duration(seconds: 10));
|
||||
|
||||
await ServerApi().mountVolume(volume!.name);
|
||||
refresh();
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
Future<void> deleteVolume(final DiskVolume volume) async {
|
||||
await ApiController.currentVolumeProviderApiFactory!
|
||||
.getVolumeProvider()
|
||||
.deleteVolume(volume.providerVolume!);
|
||||
refresh();
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
@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/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
|
||||
|
@ -14,21 +16,21 @@ class RecoveryKeyCubit
|
|||
|
||||
@override
|
||||
void load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
||||
if (status == null) {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: status,
|
||||
loadingStatus: LoadingStatus.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
final RecoveryKeyStatus? status = await _getRecoveryKeyStatus();
|
||||
if (status == null) {
|
||||
emit(state.copyWith(loadingStatus: LoadingStatus.error));
|
||||
} 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 {
|
||||
|
@ -60,7 +62,7 @@ class RecoveryKeyCubit
|
|||
final APIGenericResult<String> response =
|
||||
await api.generateRecoveryToken(expirationDate, numberOfUses);
|
||||
if (response.success) {
|
||||
refresh();
|
||||
unawaited(refresh());
|
||||
return response.data;
|
||||
} else {
|
||||
throw GenerationError(response.message ?? 'Unknown error');
|
||||
|
|
|
@ -215,7 +215,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
|
||||
void setDnsApiToken(final String dnsApiToken) async {
|
||||
if (state is ServerInstallationRecovery) {
|
||||
setAndValidateCloudflareToken(dnsApiToken);
|
||||
await setAndValidateCloudflareToken(dnsApiToken);
|
||||
return;
|
||||
}
|
||||
await repository.setDnsApiToken(dnsApiToken);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
|
@ -276,7 +277,7 @@ class ServerInstallationRepository {
|
|||
return;
|
||||
}
|
||||
await saveServerDetails(serverDetails);
|
||||
onSuccess(serverDetails);
|
||||
unawaited(onSuccess(serverDetails));
|
||||
},
|
||||
cancelButtonOnPressed: onCancel,
|
||||
);
|
||||
|
@ -326,15 +327,15 @@ class ServerInstallationRepository {
|
|||
return;
|
||||
}
|
||||
await saveServerDetails(serverDetails);
|
||||
onSuccess(serverDetails);
|
||||
unawaited(onSuccess(serverDetails));
|
||||
},
|
||||
cancelButtonOnPressed: onCancel,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
saveServerDetails(createServerResult.data!);
|
||||
onSuccess(createServerResult.data!);
|
||||
await saveServerDetails(createServerResult.data!);
|
||||
unawaited(onSuccess(createServerResult.data!));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
showInstallationErrorPopUp();
|
||||
|
|
|
@ -24,7 +24,7 @@ class ApiServerVolumeCubit
|
|||
@override
|
||||
Future<void> load() async {
|
||||
if (serverInstallationCubit.state is ServerInstallationFinished) {
|
||||
reload();
|
||||
unawaited(reload());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
|||
}
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
reload();
|
||||
unawaited(reload());
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
emit(
|
||||
state.copyWith(
|
||||
|
@ -62,7 +62,7 @@ class ServicesCubit extends ServerInstallationDependendCubit<ServicesState> {
|
|||
.toList(),
|
||||
),
|
||||
);
|
||||
reload();
|
||||
unawaited(reload());
|
||||
}
|
||||
|
||||
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:hive/hive.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 {
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/text_themes.dart';
|
||||
|
||||
class NavigationService {
|
||||
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
NavigatorState? get navigator => navigatorKey.currentState;
|
||||
|
||||
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(
|
||||
context: context,
|
||||
|
@ -21,8 +24,7 @@ class NavigationService {
|
|||
void showSnackBar(final String text) {
|
||||
final ScaffoldMessengerState state = scaffoldMessengerKey.currentState!;
|
||||
final SnackBar snack = SnackBar(
|
||||
backgroundColor: BrandColors.black.withOpacity(0.8),
|
||||
content: Text(text, style: buttonTitleText),
|
||||
content: Text(text),
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
state.showSnackBar(snack);
|
||||
|
|
|
@ -1,20 +1,74 @@
|
|||
import 'package:graphql/client.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
final DateFormat formatter = DateFormat('hh:mm');
|
||||
|
||||
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})
|
||||
: type = MessageType.warning,
|
||||
: severity = MessageSeverity.warning,
|
||||
time = DateTime.now();
|
||||
|
||||
final String? text;
|
||||
final DateTime time;
|
||||
final MessageType type;
|
||||
final MessageSeverity severity;
|
||||
String get timeString => formatter.format(time);
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
enum MessageSeverity {
|
||||
normal,
|
||||
warning,
|
||||
}
|
||||
|
||||
class RestApiRequestMessage extends Message {
|
||||
RestApiRequestMessage({
|
||||
this.method,
|
||||
this.uri,
|
||||
this.data,
|
||||
this.headers,
|
||||
}) : super(text: 'request-uri: $uri\nheaders: $headers\ndata: $data');
|
||||
|
||||
final String? method;
|
||||
final Uri? uri;
|
||||
final String? data;
|
||||
final Map<String, dynamic>? headers;
|
||||
}
|
||||
|
||||
class RestApiResponseMessage extends Message {
|
||||
RestApiResponseMessage({
|
||||
this.method,
|
||||
this.uri,
|
||||
this.statusCode,
|
||||
this.data,
|
||||
}) : super(text: 'response-uri: $uri\ncode: $statusCode\ndata: $data');
|
||||
|
||||
final String? method;
|
||||
final Uri? uri;
|
||||
final int? statusCode;
|
||||
final String? data;
|
||||
}
|
||||
|
||||
class GraphQlResponseMessage extends Message {
|
||||
GraphQlResponseMessage({
|
||||
this.data,
|
||||
this.errors,
|
||||
this.context,
|
||||
}) : super(text: 'GraphQL Response\ndata: $data');
|
||||
|
||||
final Map<String, dynamic>? data;
|
||||
final List<GraphQLError>? errors;
|
||||
final Context? context;
|
||||
}
|
||||
|
||||
class GraphQlRequestMessage extends Message {
|
||||
GraphQlRequestMessage({
|
||||
this.operation,
|
||||
this.variables,
|
||||
this.context,
|
||||
}) : super(text: 'GraphQL Request\noperation: $operation');
|
||||
|
||||
final Operation? operation;
|
||||
final Map<String, dynamic>? variables;
|
||||
final Context? context;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/theming/factory/app_theme_factory.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/router/router.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
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 {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await HiveConfig.init();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
try {
|
||||
/// Wakelock support for Linux
|
||||
|
@ -43,21 +41,20 @@ void main() async {
|
|||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
|
||||
BlocOverrides.runZoned(
|
||||
() => runApp(
|
||||
Localization(
|
||||
child: MyApp(
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
),
|
||||
Bloc.observer = SimpleBlocObserver();
|
||||
|
||||
runApp(
|
||||
Localization(
|
||||
child: SelfprivacyApp(
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
),
|
||||
),
|
||||
blocObserver: SimpleBlocObserver(),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({
|
||||
class SelfprivacyApp extends StatelessWidget {
|
||||
SelfprivacyApp({
|
||||
required this.lightThemeData,
|
||||
required this.darkThemeData,
|
||||
super.key,
|
||||
|
@ -66,42 +63,42 @@ class MyApp extends StatelessWidget {
|
|||
final ThemeData lightThemeData;
|
||||
final ThemeData darkThemeData;
|
||||
|
||||
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Localization(
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
||||
child: BlocAndProviderConfig(
|
||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AppSettingsState appSettings,
|
||||
) =>
|
||||
MaterialApp(
|
||||
scaffoldMessengerKey:
|
||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: context.locale,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'SelfPrivacy',
|
||||
theme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
themeMode:
|
||||
appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
|
||||
home: appSettings.isOnboardingShowing
|
||||
? const OnboardingPage(nextPage: InitializingPage())
|
||||
: const RootPage(),
|
||||
builder: (final BuildContext context, final Widget? widget) {
|
||||
Widget error = const Text('...rendering error...');
|
||||
if (widget is Scaffold || widget is Navigator) {
|
||||
error = Scaffold(body: Center(child: error));
|
||||
}
|
||||
ErrorWidget.builder =
|
||||
(final FlutterErrorDetails errorDetails) => error;
|
||||
return widget!;
|
||||
},
|
||||
),
|
||||
child: BlocAndProviderConfig(
|
||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AppSettingsState appSettings,
|
||||
) =>
|
||||
MaterialApp.router(
|
||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||
routerDelegate: _appRouter.delegate(),
|
||||
scaffoldMessengerKey:
|
||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: context.locale,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'SelfPrivacy',
|
||||
theme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
themeMode: appSettings.isAutoDarkModeOn
|
||||
? ThemeMode.system
|
||||
: appSettings.isDarkModeOn
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
builder: (final BuildContext context, final Widget? widget) {
|
||||
Widget error = const Text('...rendering error...');
|
||||
if (widget is Scaffold || widget is Navigator) {
|
||||
error = Scaffold(body: Center(child: error));
|
||||
}
|
||||
ErrorWidget.builder =
|
||||
(final FlutterErrorDetails errorDetails) => error;
|
||||
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(),
|
||||
)
|
||||
: 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,
|
||||
child: AnimatedSlide(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
offset: Offset(
|
||||
-(1 - value),
|
||||
0,
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
width: constraints.maxWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
|
|
|
@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
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';
|
||||
|
||||
class BrandMarkdown extends StatefulWidget {
|
||||
|
@ -37,24 +35,7 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
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,
|
||||
),
|
||||
);
|
||||
final MarkdownStyleSheet markdown = MarkdownStyleSheet();
|
||||
return MarkdownBody(
|
||||
shrinkWrap: true,
|
||||
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:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||
|
||||
class BrandTimer extends StatefulWidget {
|
||||
|
@ -52,11 +51,12 @@ class _BrandTimerState extends State<BrandTimer> {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => BrandText.medium(
|
||||
_timeString,
|
||||
style: const TextStyle(
|
||||
fontWeight: NamedFontWeight.demiBold,
|
||||
),
|
||||
Widget build(final BuildContext context) => Text(
|
||||
_timeString ?? '',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: NamedFontWeight.demiBold,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
|
||||
void _getTime() {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
|
||||
enum BrandButtonTypes { rised, text, iconText }
|
||||
|
||||
class BrandButton {
|
||||
static ConstrainedBox rised({
|
||||
|
@ -58,53 +55,4 @@ class BrandButton {
|
|||
),
|
||||
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';
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
const ActionButton({
|
||||
/// Basically a [TextButton] to be used in dialogs
|
||||
class DialogActionButton extends StatelessWidget {
|
||||
const DialogActionButton({
|
||||
super.key,
|
||||
this.text,
|
||||
this.onPressed,
|
|
@ -1,6 +1,16 @@
|
|||
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 {
|
||||
/// 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({
|
||||
required this.isSelected,
|
||||
required this.onPressed,
|
||||
|
@ -8,15 +18,24 @@ class SegmentedButtons extends StatelessWidget {
|
|||
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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// The titles of the buttons.
|
||||
final List<String> titles;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => LayoutBuilder(
|
||||
builder: (final context, final constraints) => ToggleButtons(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: (constraints.maxWidth - 8) / 3,
|
||||
minWidth: (constraints.maxWidth - 8) / titles.length,
|
||||
minHeight: 40 + Theme.of(context).visualDensity.vertical * 4,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(48),
|
||||
|
@ -38,7 +57,7 @@ class SegmentedButtons extends StatelessWidget {
|
|||
opacity: isSelected[index] ? 1 : 0,
|
||||
child: AnimatedScale(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
alignment: Alignment.centerLeft,
|
||||
scale: isSelected[index] ? 1 : 0,
|
||||
child: Icon(
|
||||
|
@ -53,7 +72,7 @@ class SegmentedButtons extends StatelessWidget {
|
|||
? const EdgeInsets.only(left: 24)
|
||||
: EdgeInsets.zero,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
child: Text(
|
||||
title,
|
||||
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_jobs/server_jobs_cubit.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/brand_cards/brand_cards.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.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/helpers/modals.dart';
|
||||
|
||||
|
@ -32,7 +30,12 @@ class JobsContent extends StatelessWidget {
|
|||
if (state is JobsStateEmpty) {
|
||||
widgets = [
|
||||
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) {
|
||||
|
@ -65,38 +68,49 @@ class JobsContent extends StatelessWidget {
|
|||
];
|
||||
} else if (state is JobsStateWithJobs) {
|
||||
widgets = [
|
||||
...state.clientJobList
|
||||
.map(
|
||||
(final j) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BrandCards.small(
|
||||
child: Text(j.title),
|
||||
...state.clientJobList.map(
|
||||
(final j) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
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(
|
||||
'basis.remove'.tr(),
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
j.title,
|
||||
style:
|
||||
Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
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(
|
||||
'basis.remove'.tr(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
BrandButton.rised(
|
||||
onPressed: () => context.read<JobsCubit>().applyAll(),
|
||||
|
@ -109,8 +123,9 @@ class JobsContent extends StatelessWidget {
|
|||
children: [
|
||||
const SizedBox(height: 15),
|
||||
Center(
|
||||
child: BrandText.h2(
|
||||
child: Text(
|
||||
'jobs.title'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
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:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class NotReadyCard extends StatelessWidget {
|
||||
|
@ -13,11 +13,7 @@ class NotReadyCard extends StatelessWidget {
|
|||
child: ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const InitializingPage(),
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(const InitializingRoute()),
|
||||
title: Text(
|
||||
'not_ready_card.in_menu'.tr(),
|
||||
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_bloc/flutter_bloc.dart';
|
||||
import 'package:ionicons/ionicons.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/helpers/modals.dart';
|
||||
|
||||
class BrandFab extends StatefulWidget {
|
||||
const BrandFab({super.key});
|
||||
const BrandFab({
|
||||
this.extended = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool extended;
|
||||
|
||||
@override
|
||||
State<BrandFab> createState() => _BrandFabState();
|
||||
|
@ -56,28 +60,40 @@ class _BrandFabState extends State<BrandFab>
|
|||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
// TODO: Make a hero animation to the screen
|
||||
showBrandBottomSheet(
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (final BuildContext context) => const BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: JobsContent(),
|
||||
),
|
||||
builder: (final BuildContext context) => const JobsContent(),
|
||||
);
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: _colorTween,
|
||||
builder: (final BuildContext context, final Widget? child) {
|
||||
final double v = _animationController.value;
|
||||
final IconData icon =
|
||||
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,
|
||||
isExtended: widget.extended,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedBuilder(
|
||||
animation: _colorTween,
|
||||
builder: (final BuildContext context, final Widget? child) {
|
||||
final double v = _animationController.value;
|
||||
final IconData icon =
|
||||
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:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/text_themes.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
|
||||
class ProgressBar extends StatefulWidget {
|
||||
|
@ -65,7 +63,7 @@ class _ProgressBarState extends State<ProgressBar> {
|
|||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: BoxDecoration(
|
||||
color: BrandColors.gray4,
|
||||
color: const Color(0xFFDDDDDD),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
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,
|
||||
width: 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),
|
||||
|
|
|
@ -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:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/ui/components/action_button/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,
|
||||
);
|
||||
import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart';
|
||||
|
||||
void showPopUpAlert({
|
||||
required final String description,
|
||||
|
@ -26,16 +12,16 @@ void showPopUpAlert({
|
|||
final String? cancelButtonTitle,
|
||||
}) {
|
||||
getIt.get<NavigationService>().showPopUpDialog(
|
||||
BrandAlert(
|
||||
title: alertTitle ?? 'basis.alert'.tr(),
|
||||
contentText: description,
|
||||
AlertDialog(
|
||||
title: Text(alertTitle ?? 'basis.alert'.tr()),
|
||||
content: Text(description),
|
||||
actions: [
|
||||
ActionButton(
|
||||
DialogActionButton(
|
||||
text: actionButtonTitle,
|
||||
isRed: true,
|
||||
onPressed: actionButtonOnPressed,
|
||||
),
|
||||
ActionButton(
|
||||
DialogActionButton(
|
||||
text: cancelButtonTitle ?? 'basis.cancel'.tr(),
|
||||
onPressed: cancelButtonOnPressed,
|
||||
),
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
import 'package:flutter/material.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 {
|
||||
/// Creates a helper widget that calls a callback when its size changes.
|
||||
const WidgetSize({
|
||||
required this.onChange,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The child widget, the size of which is to be measured.
|
||||
final Widget child;
|
||||
final Function onChange;
|
||||
|
||||
/// The callback to be called when the size of the widget changes.
|
||||
final Function(Size) onChange;
|
||||
|
||||
@override
|
||||
State<WidgetSize> createState() => _WidgetSizeState();
|
||||
|
@ -34,6 +46,11 @@ class _WidgetSizeState extends State<WidgetSize> {
|
|||
}
|
||||
|
||||
final newSize = context.size;
|
||||
|
||||
if (newSize == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldSize == newSize) {
|
||||
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:flutter/material.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/models/json/backup.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/outlined_card.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_text/brand_text.dart';
|
||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||
|
||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class BackupDetails extends StatefulWidget {
|
||||
const BackupDetails({super.key});
|
||||
@RoutePage()
|
||||
class BackupDetailsPage extends StatefulWidget {
|
||||
const BackupDetailsPage({super.key});
|
||||
|
||||
@override
|
||||
State<BackupDetails> createState() => _BackupDetailsState();
|
||||
State<BackupDetailsPage> createState() => _BackupDetailsPageState();
|
||||
}
|
||||
|
||||
class _BackupDetailsState extends State<BackupDetails>
|
||||
class _BackupDetailsPageState extends State<BackupDetailsPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
|
@ -57,7 +58,10 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
text: 'backup.initialize'.tr(),
|
||||
),
|
||||
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 &&
|
||||
backupStatus != BackupStatusEnum.noKey)
|
||||
OutlinedCard(
|
||||
|
@ -227,7 +231,10 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
),
|
||||
),
|
||||
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:easy_localization/easy_localization.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/server_installation/server_installation_cubit.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/pages/devices/new_device.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DevicesScreen extends StatefulWidget {
|
||||
const DevicesScreen({super.key});
|
||||
|
||||
|
@ -25,7 +27,7 @@ class _DevicesScreenState extends State<DevicesScreen> {
|
|||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<ApiDevicesCubit>().refresh();
|
||||
await context.read<ApiDevicesCubit>().refresh();
|
||||
},
|
||||
child: BrandHeroScreen(
|
||||
heroTitle: 'devices.main_screen.header'.tr(),
|
||||
|
@ -90,8 +92,7 @@ class _DevicesInfo extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
...devicesStatus.otherDevices
|
||||
.map((final device) => _DeviceTile(device: device))
|
||||
.toList(),
|
||||
.map((final device) => _DeviceTile(device: device)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/devices/devices_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/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
||||
class NewDeviceScreen extends StatelessWidget {
|
||||
const NewDeviceScreen({super.key});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/cubit/server_installation/server_installation_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/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DnsDetailsPage extends StatefulWidget {
|
||||
const DnsDetailsPage({super.key});
|
||||
|
||||
|
@ -158,8 +160,7 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
|
|||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
ListTile(
|
||||
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:selfprivacy/config/brand_theme.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/ui/components/brand_text/brand_text.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AboutApplicationPage extends StatelessWidget {
|
||||
const AboutApplicationPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'about_application_page.title'.tr(),
|
||||
hasBackButton: true,
|
||||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
return BrandHeroScreen(
|
||||
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(
|
||||
padding: paddingH15V0,
|
||||
const SizedBox(height: 10),
|
||||
// Button to call showAboutDialog
|
||||
TextButton(
|
||||
onPressed: () => showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'SelfPrivacy',
|
||||
applicationLegalese: '© 2022 SelfPrivacy',
|
||||
// Link to privacy policy
|
||||
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(
|
||||
onPressed: () => showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'SelfPrivacy',
|
||||
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()),
|
||||
),
|
||||
],
|
||||
onPressed: () => launchUrl(
|
||||
Uri.parse('https://selfprivacy.ru/privacy-policy'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
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 {
|
||||
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 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.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 {
|
||||
const Console({super.key});
|
||||
@RoutePage()
|
||||
class ConsolePage extends StatefulWidget {
|
||||
const ConsolePage({super.key});
|
||||
|
||||
@override
|
||||
State<Console> createState() => _ConsoleState();
|
||||
State<ConsolePage> createState() => _ConsolePageState();
|
||||
}
|
||||
|
||||
class _ConsoleState extends State<Console> {
|
||||
class _ConsolePageState extends State<ConsolePage> {
|
||||
@override
|
||||
void initState() {
|
||||
getIt.get<ConsoleModel>().addListener(update);
|
||||
|
@ -28,21 +29,31 @@ class _ConsoleState extends State<Console> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void update() => setState(() => {});
|
||||
bool paused = false;
|
||||
|
||||
void update() {
|
||||
if (!paused) {
|
||||
setState(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(53),
|
||||
child: Column(
|
||||
children: [
|
||||
BrandHeader(
|
||||
title: 'console_page.title'.tr(),
|
||||
hasBackButton: true,
|
||||
),
|
||||
],
|
||||
appBar: AppBar(
|
||||
title: Text('console_page.title'.tr()),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
paused ? Icons.play_arrow_outlined : Icons.pause_outlined,
|
||||
),
|
||||
onPressed: () => setState(() => paused = !paused),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: getIt.allReady(),
|
||||
|
@ -61,30 +72,7 @@ class _ConsoleState extends State<Console> {
|
|||
const SizedBox(height: 20),
|
||||
...UnmodifiableListView(
|
||||
messages
|
||||
.map((final 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
.map((final message) => LogListItem(message: message))
|
||||
.toList()
|
||||
.reversed,
|
||||
),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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_volumes/server_volume_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_icons/brand_icons.dart';
|
||||
import 'package:selfprivacy/ui/pages/devices/devices.dart';
|
||||
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.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';
|
||||
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MorePage extends StatelessWidget {
|
||||
const MorePage({super.key});
|
||||
|
||||
|
@ -34,12 +25,14 @@ class MorePage extends StatelessWidget {
|
|||
context.watch<ApiServerVolumeCubit>().state.usesBinds;
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'basis.more'.tr(),
|
||||
),
|
||||
),
|
||||
appBar: Breakpoints.small.isActive(context)
|
||||
? PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'basis.more'.tr(),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
|
@ -50,7 +43,7 @@ class MorePage extends StatelessWidget {
|
|||
_MoreMenuItem(
|
||||
title: 'storage.start_migration_button'.tr(),
|
||||
iconData: Icons.drive_file_move_outline,
|
||||
goTo: ServicesMigrationPage(
|
||||
goTo: () => ServicesMigrationRoute(
|
||||
diskStatus: context
|
||||
.watch<ApiServerVolumeCubit>()
|
||||
.state
|
||||
|
@ -77,7 +70,7 @@ class MorePage extends StatelessWidget {
|
|||
_MoreMenuItem(
|
||||
title: 'more_page.configuration_wizard'.tr(),
|
||||
iconData: Icons.change_history_outlined,
|
||||
goTo: const InitializingPage(),
|
||||
goTo: () => const InitializingRoute(),
|
||||
subtitle: 'not_ready_card.in_menu'.tr(),
|
||||
accent: true,
|
||||
),
|
||||
|
@ -85,47 +78,43 @@ class MorePage extends StatelessWidget {
|
|||
_MoreMenuItem(
|
||||
title: 'more_page.create_ssh_key'.tr(),
|
||||
iconData: Ionicons.key_outline,
|
||||
goTo: const UserDetails(
|
||||
goTo: () => UserDetailsRoute(
|
||||
login: 'root',
|
||||
),
|
||||
),
|
||||
if (isReady)
|
||||
_MoreMenuItem(
|
||||
iconData: Icons.password_outlined,
|
||||
goTo: const RecoveryKey(),
|
||||
goTo: () => const RecoveryKeyRoute(),
|
||||
title: 'recovery_key.key_main_header'.tr(),
|
||||
),
|
||||
if (isReady)
|
||||
_MoreMenuItem(
|
||||
iconData: Icons.devices_outlined,
|
||||
goTo: const DevicesScreen(),
|
||||
goTo: () => const DevicesRoute(),
|
||||
title: 'devices.main_screen.header'.tr(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'more_page.application_settings'.tr(),
|
||||
iconData: Icons.settings_outlined,
|
||||
goTo: const AppSettingsPage(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'more_page.about_project'.tr(),
|
||||
iconData: BrandIcons.engineer,
|
||||
goTo: const AboutUsPage(),
|
||||
goTo: () => const AppSettingsRoute(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'more_page.about_application'.tr(),
|
||||
iconData: BrandIcons.fire,
|
||||
goTo: const AboutApplicationPage(),
|
||||
goTo: () => const AboutApplicationRoute(),
|
||||
longGoTo: const DeveloperSettingsRoute(),
|
||||
),
|
||||
if (!isReady)
|
||||
_MoreMenuItem(
|
||||
title: 'more_page.onboarding'.tr(),
|
||||
iconData: BrandIcons.start,
|
||||
goTo: const OnboardingPage(nextPage: RootPage()),
|
||||
goTo: () => const OnboardingRoute(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'more_page.console'.tr(),
|
||||
iconData: BrandIcons.terminal,
|
||||
goTo: const Console(),
|
||||
goTo: () => const ConsoleRoute(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -140,14 +129,16 @@ class _MoreMenuItem extends StatelessWidget {
|
|||
const _MoreMenuItem({
|
||||
required this.iconData,
|
||||
required this.title,
|
||||
required this.goTo,
|
||||
this.subtitle,
|
||||
this.goTo,
|
||||
this.longGoTo,
|
||||
this.accent = false,
|
||||
});
|
||||
|
||||
final IconData iconData;
|
||||
final String title;
|
||||
final Widget? goTo;
|
||||
final PageRouteInfo Function() goTo;
|
||||
final PageRouteInfo? longGoTo;
|
||||
final String? subtitle;
|
||||
final bool accent;
|
||||
|
||||
|
@ -160,9 +151,9 @@ class _MoreMenuItem extends StatelessWidget {
|
|||
tertiary: accent,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
onTap: goTo != null
|
||||
? () => Navigator.of(context).push(materialRoute(goTo!))
|
||||
: null,
|
||||
onTap: () => context.pushRoute(goTo()),
|
||||
onLongPress:
|
||||
longGoTo != null ? () => context.pushRoute(longGoTo!) : null,
|
||||
leading: Icon(
|
||||
iconData,
|
||||
size: 24,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
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/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
@RoutePage()
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({required this.nextPage, super.key});
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
final Widget nextPage;
|
||||
@override
|
||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||
}
|
||||
|
@ -22,14 +23,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Scaffold(
|
||||
body: PageView(
|
||||
controller: pageController,
|
||||
children: [
|
||||
_withPadding(firstPage()),
|
||||
_withPadding(secondPage()),
|
||||
],
|
||||
),
|
||||
);
|
||||
body: PageView(
|
||||
controller: pageController,
|
||||
children: [
|
||||
_withPadding(firstPage()),
|
||||
_withPadding(secondPage()),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget _withPadding(final Widget child) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
|
@ -76,7 +77,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
pageController.animateToPage(
|
||||
1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
);
|
||||
},
|
||||
text: 'basis.next'.tr(),
|
||||
|
@ -142,10 +143,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(widget.nextPage),
|
||||
(final route) => false,
|
||||
);
|
||||
context.router.replaceAll([
|
||||
const RootRoute(),
|
||||
const InitializingRoute(),
|
||||
]);
|
||||
},
|
||||
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:flutter/material.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/icon_status_mask/icon_status_mask.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/pages/dns_details/dns_details.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||
|
||||
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
@RoutePage()
|
||||
class ProvidersPage extends StatefulWidget {
|
||||
const ProvidersPage({super.key});
|
||||
|
||||
|
@ -61,12 +61,14 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'basis.providers_title'.tr(),
|
||||
),
|
||||
),
|
||||
appBar: Breakpoints.small.isActive(context)
|
||||
? PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'basis.providers_title'.tr(),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body: ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
|
@ -81,8 +83,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
subtitle: diskStatus.isDiskOkay
|
||||
? 'storage.status_ok'.tr()
|
||||
: 'storage.status_error'.tr(),
|
||||
onTap: () => Navigator.of(context)
|
||||
.push(materialRoute(const ServerDetailsScreen())),
|
||||
onTap: () => context.pushRoute(const ServerDetailsRoute()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_Card(
|
||||
|
@ -92,11 +93,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
subtitle: appConfig.isDomainSelected
|
||||
? appConfig.serverDomain!.domainName
|
||||
: '',
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const DnsDetailsPage(),
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(const DnsDetailsRoute()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// TODO: When backups are fixed, show this card
|
||||
|
@ -108,8 +105,7 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
icon: BrandIcons.save,
|
||||
title: 'backup.card_title'.tr(),
|
||||
subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '',
|
||||
onTap: () => Navigator.of(context)
|
||||
.push(materialRoute(const BackupDetails())),
|
||||
onTap: () => context.pushRoute(const BackupDetailsRoute()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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:flutter/material.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/cubit/recovery_key/recovery_key_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/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key_receiving.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
class RecoveryKey extends StatefulWidget {
|
||||
const RecoveryKey({super.key});
|
||||
@RoutePage()
|
||||
class RecoveryKeyPage extends StatefulWidget {
|
||||
const RecoveryKeyPage({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryKey> createState() => _RecoveryKeyState();
|
||||
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||
}
|
||||
|
||||
class _RecoveryKeyState extends State<RecoveryKey> {
|
||||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -250,7 +251,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Navigator.of(context).push(
|
||||
await Navigator.of(context).push(
|
||||
materialRoute(
|
||||
RecoveryKeyReceiving(recoveryKey: token), // TO DO
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||
|
||||
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: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/brand_tab_bar/brand_tab_bar.dart';
|
||||
import 'package:selfprivacy/ui/pages/more/more.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/layouts/root_scaffold_with_navigation.dart';
|
||||
import 'package:selfprivacy/ui/router/root_destinations.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});
|
||||
|
||||
@override
|
||||
State<RootPage> createState() => _RootPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(final BuildContext context) => this;
|
||||
}
|
||||
|
||||
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
bool shouldUseSplitView() => false;
|
||||
|
||||
late final AnimationController _controller = AnimationController(
|
||||
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();
|
||||
}
|
||||
final destinations = rootDestinations;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
return Provider<ChangeTab>(
|
||||
create: (final _) => ChangeTab(tabController.animateTo),
|
||||
child: Scaffold(
|
||||
body: TabBarView(
|
||||
controller: tabController,
|
||||
children: const [
|
||||
ProvidersPage(),
|
||||
ServicesPage(),
|
||||
UsersPage(),
|
||||
MorePage(),
|
||||
if (context.read<AppSettingsCubit>().state.isOnboardingShowing) {
|
||||
context.router.replace(const OnboardingRoute());
|
||||
}
|
||||
|
||||
return AutoRouter(
|
||||
builder: (final context, final child) {
|
||||
final currentDestinationIndex = destinations.indexWhere(
|
||||
(final destination) =>
|
||||
context.router.isRouteActive(destination.route.routeName),
|
||||
);
|
||||
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:easy_localization/easy_localization.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/cubit/client_jobs/client_jobs_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/models/auto_upgrade_settings.dart';
|
||||
import 'package:selfprivacy/logic/models/job.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/segmented_buttons.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.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_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/pages/server_details/charts/cpu_chart.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/utils/breakpoints.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:timezone/timezone.dart';
|
||||
|
||||
|
@ -32,6 +31,7 @@ part 'time_zone/time_zone.dart';
|
|||
|
||||
var navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
@RoutePage()
|
||||
class ServerDetailsScreen extends StatefulWidget {
|
||||
const ServerDetailsScreen({super.key});
|
||||
|
||||
|
@ -75,6 +75,7 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
|
|||
return BlocProvider(
|
||||
create: (final context) => context.read<ServerDetailsCubit>()..check(),
|
||||
child: BrandHeroScreen(
|
||||
hasFlashButton: true,
|
||||
heroIcon: BrandIcons.server,
|
||||
heroTitle: 'server.card_title'.tr(),
|
||||
heroSubtitle: 'server.description'.tr(),
|
||||
|
|
|
@ -23,15 +23,13 @@ class _TextDetails extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
...details.metadata
|
||||
.map(
|
||||
(final metadata) => ListTileOnSurfaceVariant(
|
||||
leadingIcon: metadata.type.icon,
|
||||
title: metadata.name,
|
||||
subtitle: metadata.value,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...details.metadata.map(
|
||||
(final metadata) => ListTileOnSurfaceVariant(
|
||||
leadingIcon: metadata.type.icon,
|
||||
title: metadata.name,
|
||||
subtitle: metadata.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -39,24 +37,6 @@ class _TextDetails extends StatelessWidget {
|
|||
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 {
|
||||
|
@ -69,7 +49,10 @@ class _TempMessage extends StatelessWidget {
|
|||
Widget build(final BuildContext context) => SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 100,
|
||||
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
|
||||
Widget build(final BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: isSearching
|
||||
? TextField(
|
||||
readOnly: false,
|
||||
textAlign: TextAlign.start,
|
||||
textInputAction: TextInputAction.next,
|
||||
enabled: true,
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
errorText: null,
|
||||
hintText: 'server.timezone_search_bar'.tr(),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text('server.select_timezone'.tr()),
|
||||
Widget build(final BuildContext context) {
|
||||
final isDesktop = Breakpoints.mediumAndUp.isActive(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: (isDesktop || isSearching)
|
||||
? TextField(
|
||||
readOnly: false,
|
||||
textAlign: TextAlign.start,
|
||||
textInputAction: TextInputAction.next,
|
||||
enabled: true,
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
errorText: null,
|
||||
hintText: 'server.timezone_search_bar'.tr(),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: isSearching
|
||||
? () => setState(() => isSearching = false)
|
||||
: () => Navigator.of(context).pop(),
|
||||
),
|
||||
actions: [
|
||||
if (!isSearching)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () => setState(() => isSearching = true),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text('server.select_timezone'.tr()),
|
||||
),
|
||||
],
|
||||
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 Location location,
|
||||
) {
|
||||
|
@ -126,46 +132,19 @@ class _SelectTimezoneState extends State<SelectTimezone> {
|
|||
|
||||
return MapEntry(
|
||||
key,
|
||||
Container(
|
||||
height: 75,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: BrandColors.dividerColor,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
location.name,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}',
|
||||
),
|
||||
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:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.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/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.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/pages/root_route.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:flutter/material.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/models/disk_size.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/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||
import 'package:selfprivacy/logic/models/disk_status.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/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 {
|
||||
const ServicesMigrationPage({
|
||||
required this.services,
|
||||
|
@ -110,22 +108,20 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
|||
),
|
||||
child: Column(
|
||||
children: [
|
||||
...widget.diskStatus.diskVolumes
|
||||
.map(
|
||||
(final volume) => Column(
|
||||
children: [
|
||||
ServerStorageListItem(
|
||||
volume: recalculatedDiskUsages(
|
||||
volume,
|
||||
widget.services,
|
||||
),
|
||||
dense: true,
|
||||
),
|
||||
const SizedBox(height: headerVerticalPadding),
|
||||
],
|
||||
...widget.diskStatus.diskVolumes.map(
|
||||
(final volume) => Column(
|
||||
children: [
|
||||
ServerStorageListItem(
|
||||
volume: recalculatedDiskUsages(
|
||||
volume,
|
||||
widget.services,
|
||||
),
|
||||
dense: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: headerVerticalPadding),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -138,23 +134,21 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
|||
children: <Widget>[
|
||||
if (widget.services.isEmpty)
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
...widget.services
|
||||
.map(
|
||||
(final service) => Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
ServiceMigrationListItem(
|
||||
service: service,
|
||||
diskStatus: widget.diskStatus,
|
||||
selectedVolume: serviceToDisk[service.id]!,
|
||||
onChange: onChange,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Divider(),
|
||||
],
|
||||
...widget.services.map(
|
||||
(final service) => Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
ServiceMigrationListItem(
|
||||
service: service,
|
||||
diskStatus: widget.diskStatus,
|
||||
selectedVolume: serviceToDisk[service.id]!,
|
||||
onChange: onChange,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: 4),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: InfoBox(
|
||||
|
@ -180,17 +174,10 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(const RootPage()),
|
||||
(final predicate) => false,
|
||||
);
|
||||
showBrandBottomSheet(
|
||||
context.router.popUntilRoot();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (final BuildContext context) =>
|
||||
const BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: JobsContent(),
|
||||
),
|
||||
builder: (final BuildContext context) => const JobsContent(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/models/disk_size.dart';
|
||||
import 'package:selfprivacy/logic/models/price.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.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 {
|
||||
const ExtendingVolumePage({
|
||||
required this.diskVolumeToResize,
|
||||
|
@ -155,10 +155,7 @@ class _ExtendingVolumePageState extends State<ExtendingVolumePage> {
|
|||
DiskSize.fromGibibyte(_currentSliderGbValue),
|
||||
context.read<ApiServerVolumeCubit>().reload,
|
||||
);
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(const RootPage()),
|
||||
(final predicate) => false,
|
||||
);
|
||||
context.router.popUntilRoot();
|
||||
},
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.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/models/service.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.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/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ServerStoragePage extends StatefulWidget {
|
||||
const ServerStoragePage({
|
||||
required this.diskStatus,
|
||||
|
@ -45,28 +46,26 @@ class _ServerStoragePageState extends State<ServerStoragePage> {
|
|||
heroTitle: 'storage.card_title'.tr(),
|
||||
children: [
|
||||
// ...sections,
|
||||
...widget.diskStatus.diskVolumes
|
||||
.map(
|
||||
(final volume) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ServerStorageSection(
|
||||
volume: volume,
|
||||
diskStatus: widget.diskStatus,
|
||||
services: services
|
||||
.where(
|
||||
(final service) =>
|
||||
service.storageUsage.volume == volume.name,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
...widget.diskStatus.diskVolumes.map(
|
||||
(final volume) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ServerStorageSection(
|
||||
volume: volume,
|
||||
diskStatus: widget.diskStatus,
|
||||
services: services
|
||||
.where(
|
||||
(final service) =>
|
||||
service.storageUsage.volume == volume.name,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
);
|
||||
|
@ -93,24 +92,20 @@ class ServerStorageSection extends StatelessWidget {
|
|||
volume: volume,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...services
|
||||
.map(
|
||||
(final service) => ServerConsumptionListTile(
|
||||
service: service,
|
||||
volume: volume,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...services.map(
|
||||
(final service) => ServerConsumptionListTile(
|
||||
service: service,
|
||||
volume: volume,
|
||||
),
|
||||
),
|
||||
if (volume.isResizable) ...[
|
||||
const SizedBox(height: 16),
|
||||
BrandOutlinedButton(
|
||||
title: 'storage.extend_volume_button.title'.tr(),
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
ExtendingVolumePage(
|
||||
diskVolumeToResize: volume,
|
||||
diskStatus: diskStatus,
|
||||
),
|
||||
onPressed: () => context.pushRoute(
|
||||
ExtendingVolumeRoute(
|
||||
diskVolumeToResize: volume,
|
||||
diskStatus: diskStatus,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -138,7 +133,10 @@ class ServerConsumptionListTile extends StatelessWidget {
|
|||
service.svgIcon,
|
||||
width: 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(),
|
||||
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:flutter/material.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/ui/components/icon_status_mask/icon_status_mask.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/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
|
||||
class StorageCard extends StatelessWidget {
|
||||
const StorageCard({
|
||||
|
@ -45,13 +45,8 @@ class StorageCard extends StatelessWidget {
|
|||
clipBehavior: Clip.antiAlias,
|
||||
child: InkResponse(
|
||||
highlightShape: BoxShape.rectangle,
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
ServerStoragePage(
|
||||
diskStatus: diskStatus,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () =>
|
||||
context.pushRoute(ServerStorageRoute(diskStatus: diskStatus)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/models/job.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
import 'package:selfprivacy/utils/launch_url.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ServicePage extends StatefulWidget {
|
||||
const ServicePage({required this.serviceId, super.key});
|
||||
|
||||
|
@ -46,11 +47,15 @@ class _ServicePageState extends State<ServicePage> {
|
|||
|
||||
return BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
hasFlashButton: true,
|
||||
heroIconWidget: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onBackground,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
heroTitle: service.displayName,
|
||||
children: [
|
||||
|
@ -108,14 +113,12 @@ class _ServicePageState extends State<ServicePage> {
|
|||
ListTile(
|
||||
iconColor: Theme.of(context).colorScheme.onBackground,
|
||||
// Open page ServicesMigrationPage
|
||||
onTap: () => Navigator.of(context).push(
|
||||
materialRoute(
|
||||
ServicesMigrationPage(
|
||||
services: [service],
|
||||
diskStatus:
|
||||
context.read<ApiServerVolumeCubit>().state.diskStatus,
|
||||
isMigration: false,
|
||||
),
|
||||
onTap: () => context.pushRoute(
|
||||
ServicesMigrationRoute(
|
||||
services: [service],
|
||||
diskStatus:
|
||||
context.read<ApiServerVolumeCubit>().state.diskStatus,
|
||||
isMigration: false,
|
||||
),
|
||||
),
|
||||
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_svg/flutter_svg.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/models/service.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_text/brand_text.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: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/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ServicesPage extends StatefulWidget {
|
||||
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));
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'basis.services'.tr(),
|
||||
),
|
||||
),
|
||||
appBar: Breakpoints.small.isActive(context)
|
||||
? PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: BrandHeader(
|
||||
title: 'basis.services'.tr(),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<ServicesCubit>().reload();
|
||||
await context.read<ServicesCubit>().reload();
|
||||
},
|
||||
child: ListView(
|
||||
padding: paddingH15V0,
|
||||
children: [
|
||||
BrandText.body1('basis.services_title'.tr()),
|
||||
Text(
|
||||
'basis.services_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
|
||||
...services
|
||||
.map(
|
||||
(final service) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 30,
|
||||
),
|
||||
child: _Card(service: service),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
...services.map(
|
||||
(final service) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 30,
|
||||
),
|
||||
child: _Card(service: service),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -98,81 +101,106 @@ class _Card extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isReady
|
||||
? () => Navigator.of(context)
|
||||
.push(materialRoute(ServicePage(serviceId: service.id)))
|
||||
: null,
|
||||
child: BrandCards.big(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconStatusMask(
|
||||
status: getStatus(service.status),
|
||||
icon: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
width: 30.0,
|
||||
height: 30.0,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ClipRect(
|
||||
child: Stack(
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkResponse(
|
||||
highlightShape: BoxShape.rectangle,
|
||||
onTap: isReady
|
||||
? () => context.pushRoute(
|
||||
ServiceRoute(serviceId: service.id),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
BrandText.h2(service.displayName),
|
||||
const SizedBox(height: 10),
|
||||
if (service.url != '' && service.url != null)
|
||||
Column(
|
||||
children: [
|
||||
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),
|
||||
],
|
||||
IconStatusMask(
|
||||
status: getStatus(service.status),
|
||||
icon: SvgPicture.string(
|
||||
service.svgIcon,
|
||||
width: 30.0,
|
||||
height: 30.0,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Colors.white,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
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/forms/setup/initializing/dns_provider_form_cubit.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/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';
|
||||
|
||||
class DnsProviderPicker extends StatefulWidget {
|
||||
|
@ -130,18 +129,15 @@ class ProviderInputDataPage extends StatelessWidget {
|
|||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (final BuildContext context) => BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: Padding(
|
||||
padding: paddingH15V0,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: [
|
||||
BrandMarkdown(
|
||||
fileName: providerInfo.pathToHow,
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: (final BuildContext context) => Padding(
|
||||
padding: paddingH15V0,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: [
|
||||
BrandMarkdown(
|
||||
fileName: providerInfo.pathToHow,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.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/server_installation/server_installation_cubit.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/domain_setup_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/ui/components/brand_button/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/logic/cubit/support_system/support_system_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.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/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/server_provider_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/utils/route_transitions/basic.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||
|
||||
@RoutePage()
|
||||
class InitializingPage extends StatelessWidget {
|
||||
const InitializingPage({super.key});
|
||||
|
||||
|
@ -49,99 +51,155 @@ class InitializingPage extends StatelessWidget {
|
|||
][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>(
|
||||
listener: (final context, final state) {
|
||||
if (cubit.state is ServerInstallationFinished) {
|
||||
Navigator.of(context)
|
||||
.pushReplacement(materialRoute(const RootPage()));
|
||||
context.router.popUntilRoot();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
if (cubit.state is ServerInstallationFinished)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.check),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pushReplacement(materialRoute(const RootPage()));
|
||||
},
|
||||
)
|
||||
],
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
endDrawer: const SupportDrawer(),
|
||||
endDrawerEnableOpenDragGesture: false,
|
||||
appBar: Breakpoints.large.isActive(context)
|
||||
? null
|
||||
: AppBar(
|
||||
actions: [
|
||||
if (cubit.state is ServerInstallationFinished)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.check),
|
||||
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(),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
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: 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: () {
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const RecoveryRouting(),
|
||||
),
|
||||
);
|
||||
context.router.popUntilRoot();
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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(
|
||||
final ServerInstallationCubit initializingCubit,
|
||||
) =>
|
||||
|
@ -213,50 +262,57 @@ class InitializingPage extends StatelessWidget {
|
|||
child: Builder(
|
||||
builder: (final context) {
|
||||
final formCubitState = context.watch<BackblazeFormCubit>().state;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${'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',
|
||||
return ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${'initializing.connect_to_server_provider'.tr()}Backblaze',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
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: () => _showModal(
|
||||
context,
|
||||
const _HowTo(
|
||||
fileName: 'how_backblaze',
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<BackblazeFormCubit>().keyId,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'KeyID',
|
||||
),
|
||||
),
|
||||
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) {
|
||||
final DomainSetupState state =
|
||||
context.watch<DomainSetupCubit>().state;
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
return ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
|
@ -283,7 +338,11 @@ class InitializingPage extends StatelessWidget {
|
|||
'initializing.use_this_domain_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (state is Empty)
|
||||
Text(
|
||||
'initializing.no_connected_domains'.tr(),
|
||||
|
@ -323,7 +382,7 @@ class InitializingPage extends StatelessWidget {
|
|||
],
|
||||
if (state is Empty) ...[
|
||||
const SizedBox(height: 30),
|
||||
BrandButton.rised(
|
||||
BrandButton.filled(
|
||||
onPressed: () => context.read<DomainSetupCubit>().load(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
@ -333,14 +392,17 @@ class InitializingPage extends StatelessWidget {
|
|||
color: Colors.white,
|
||||
),
|
||||
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) ...[
|
||||
const SizedBox(height: 32),
|
||||
BrandButton.rised(
|
||||
BrandButton.filled(
|
||||
onPressed: () =>
|
||||
context.read<DomainSetupCubit>().saveDomain(),
|
||||
text: 'initializing.save_domain'.tr(),
|
||||
|
@ -361,74 +423,83 @@ class InitializingPage extends StatelessWidget {
|
|||
builder: (final context) {
|
||||
final formCubitState = context.watch<RootUserFormCubit>().state;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'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)
|
||||
return ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'users.username_rule'.tr(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
'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,
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<RootUserFormCubit>().userName,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'basis.username'.tr(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
|
||||
bloc: context.read<RootUserFormCubit>().isVisible,
|
||||
builder: (final context, final state) {
|
||||
final bool isVisible = state.value;
|
||||
return CubitFormTextField(
|
||||
obscureText: !isVisible,
|
||||
formFieldCubit:
|
||||
context.read<RootUserFormCubit>().password,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'basis.password'.tr(),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
isVisible ? Icons.visibility : Icons.visibility_off,
|
||||
const SizedBox(height: 16),
|
||||
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
|
||||
bloc: context.read<RootUserFormCubit>().isVisible,
|
||||
builder: (final context, final state) {
|
||||
final bool isVisible = state.value;
|
||||
return CubitFormTextField(
|
||||
obscureText: !isVisible,
|
||||
formFieldCubit:
|
||||
context.read<RootUserFormCubit>().password,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'basis.password'.tr(),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
isVisible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
),
|
||||
onPressed: () => context
|
||||
.read<RootUserFormCubit>()
|
||||
.isVisible
|
||||
.setValue(!isVisible),
|
||||
),
|
||||
onPressed: () => context
|
||||
.read<RootUserFormCubit>()
|
||||
.isVisible
|
||||
.setValue(!isVisible),
|
||||
suffixIconConstraints:
|
||||
const BoxConstraints(minWidth: 60),
|
||||
prefixIconConstraints:
|
||||
const BoxConstraints(maxWidth: 60),
|
||||
prefixIcon: Container(),
|
||||
),
|
||||
suffixIconConstraints:
|
||||
const BoxConstraints(minWidth: 60),
|
||||
prefixIconConstraints:
|
||||
const BoxConstraints(maxWidth: 60),
|
||||
prefixIcon: Container(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
BrandButton.rised(
|
||||
onPressed: formCubitState.isSubmitting
|
||||
? null
|
||||
: () => context.read<RootUserFormCubit>().trySubmit(),
|
||||
text: 'basis.connect'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
BrandButton.filled(
|
||||
onPressed: formCubitState.isSubmitting
|
||||
? null
|
||||
: () => context.read<RootUserFormCubit>().trySubmit(),
|
||||
text: 'basis.connect'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -438,27 +509,28 @@ class InitializingPage extends StatelessWidget {
|
|||
final bool isLoading =
|
||||
(appConfigCubit.state as ServerInstallationNotFinished).isLoading;
|
||||
return Builder(
|
||||
builder: (final context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.final'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.create_server'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 128),
|
||||
BrandButton.rised(
|
||||
onPressed:
|
||||
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
|
||||
text: isLoading
|
||||
? 'basis.loading'.tr()
|
||||
: 'initializing.create_server'.tr(),
|
||||
),
|
||||
],
|
||||
builder: (final context) => ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.final'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.create_server'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
primaryColumn: BrandButton.filled(
|
||||
onPressed:
|
||||
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
|
||||
text: isLoading
|
||||
? 'basis.loading'.tr()
|
||||
: 'initializing.create_server'.tr(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -487,84 +559,67 @@ class InitializingPage extends StatelessWidget {
|
|||
return Builder(
|
||||
builder: (final context) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.checks'.tr(args: [doneCount.toString(), '4']),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (text != null)
|
||||
child: ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
'initializing.checks'.tr(args: [doneCount.toString(), '4']),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 128),
|
||||
const SizedBox(height: 10),
|
||||
if (doneCount == 0 && state.dnsMatches != null)
|
||||
Column(
|
||||
children: state.dnsMatches!.entries.map((final entry) {
|
||||
final String domain = entry.key;
|
||||
final bool isCorrect = entry.value;
|
||||
return Row(
|
||||
children: [
|
||||
if (isCorrect)
|
||||
const Icon(Icons.check, color: Colors.green),
|
||||
if (!isCorrect)
|
||||
const Icon(Icons.schedule, color: Colors.amber),
|
||||
const SizedBox(width: 10),
|
||||
Text(domain),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!state.isLoading)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.until_the_next_check'.tr(),
|
||||
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,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
if (text != null)
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 128),
|
||||
const SizedBox(height: 10),
|
||||
if (doneCount == 0 && state.dnsMatches != null)
|
||||
Column(
|
||||
children: state.dnsMatches!.entries.map((final entry) {
|
||||
final String domain = entry.key;
|
||||
final bool isCorrect = entry.value;
|
||||
return Row(
|
||||
children: [
|
||||
if (isCorrect)
|
||||
const Icon(Icons.check, color: Colors.green),
|
||||
if (!isCorrect)
|
||||
const Icon(Icons.schedule, color: Colors.amber),
|
||||
const SizedBox(width: 10),
|
||||
Text(domain),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!state.isLoading)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.until_the_next_check'.tr(),
|
||||
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:flutter/material.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/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/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.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/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/ui/components/info_box/info_box.dart';
|
||||
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
|
||||
import 'package:selfprivacy/utils/launch_url.dart';
|
||||
|
||||
class ServerProviderPicker extends StatefulWidget {
|
||||
|
@ -98,56 +97,49 @@ class ProviderInputDataPage extends StatelessWidget {
|
|||
final ServerProviderFormCubit providerCubit;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${'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',
|
||||
Widget build(final BuildContext context) => ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
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: () => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (final BuildContext context) => BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: Padding(
|
||||
padding: paddingH15V0,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: [
|
||||
BrandMarkdown(
|
||||
fileName: providerInfo.pathToHow,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.connect_to_server_provider_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CubitFormTextField(
|
||||
formFieldCubit: providerCubit.apiKey,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Provider API Token',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
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
|
||||
Widget build(final BuildContext context) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.connect_to_server'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.select_provider'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFFD50C2D),
|
||||
child: ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.connect_to_server'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'initializing.select_provider'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
children: [
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
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(
|
||||
'assets/images/logos/hetzner.svg',
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Hetzner Cloud',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Hetzner Cloud',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_hetzner'
|
||||
.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_email_notice'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.filled(
|
||||
child: Text('basis.select'.tr()),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.hetzner);
|
||||
callback(ServerProvider.hetzner);
|
||||
},
|
||||
),
|
||||
// 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),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_hetzner'
|
||||
.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_hetzner'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_hetzner'
|
||||
.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_email_notice'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.filled(
|
||||
child: Text('basis.select'.tr()),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.hetzner);
|
||||
callback(ServerProvider.hetzner);
|
||||
},
|
||||
),
|
||||
// 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),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFF0080FF),
|
||||
const SizedBox(height: 16),
|
||||
OutlinedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
color: const Color(0xFF0080FF),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/logos/digital_ocean.svg',
|
||||
),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/logos/digital_ocean.svg',
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Digital Ocean',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Digital Ocean',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.filled(
|
||||
child: Text('basis.select'.tr()),
|
||||
onPressed: () {
|
||||
serverInstallationCubit
|
||||
.setServerProviderType(ServerProvider.digitalOcean);
|
||||
callback(ServerProvider.digitalOcean);
|
||||
},
|
||||
),
|
||||
// Outlined button that will open website
|
||||
BrandOutlinedButton(
|
||||
onPressed: () =>
|
||||
launchURL('https://www.digitalocean.com'),
|
||||
title: 'initializing.select_provider_site_button'.tr(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_countries_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_countries_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_price_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_price_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.select_provider_payment_title'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'initializing.select_provider_payment_text_do'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.filled(
|
||||
child: Text('basis.select'.tr()),
|
||||
onPressed: () {
|
||||
serverInstallationCubit.setServerProviderType(
|
||||
ServerProvider.digitalOcean,
|
||||
);
|
||||
callback(ServerProvider.digitalOcean);
|
||||
},
|
||||
),
|
||||
// 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/models/server_provider_location.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/layouts/responsive_layout_with_infobox.dart';
|
||||
|
||||
class ServerTypePicker extends StatefulWidget {
|
||||
const ServerTypePicker({
|
||||
|
@ -70,50 +71,67 @@ class SelectLocationPage extends StatelessWidget {
|
|||
if ((snapshot.data as List<ServerProviderLocation>).isEmpty) {
|
||||
return Text('initializing.no_locations_found'.tr());
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'initializing.choose_location_type'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.choose_location_type_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...(snapshot.data! as List<ServerProviderLocation>).map(
|
||||
(final location) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
callback(location);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
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,
|
||||
return ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.choose_location_type'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.choose_location_type_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...(snapshot.data! as List<ServerProviderLocation>).map(
|
||||
(final location) => Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkResponse(
|
||||
highlightShape: BoxShape.rectangle,
|
||||
onTap: () {
|
||||
callback(location);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
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 {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
|
@ -180,121 +198,145 @@ class SelectTypePage extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.choose_server_type'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'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,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit.setServerType(type);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
type.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.memory_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
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()],
|
||||
return ResponsiveLayoutWithInfobox(
|
||||
topChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'initializing.choose_server_type'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'initializing.choose_server_type_text'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
primaryColumn: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...(snapshot.data! as List<ServerType>).map(
|
||||
(final type) => Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
serverInstallationCubit.setServerType(type);
|
||||
},
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
type.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium,
|
||||
),
|
||||
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}'
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.memory_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'server.core_count'
|
||||
.plural(type.cores),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
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: 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 {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
import 'package:cubit_form/cubit_form.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(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
onBackButtonPressed:
|
||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||
children: [
|
||||
|
@ -61,6 +62,7 @@ class RecoverByNewDeviceKeyInput extends StatelessWidget {
|
|||
heroSubtitle: 'recovering.method_device_input_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
children: [
|
||||
CubitFormTextField(
|
||||
formFieldCubit:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:cubit_form/cubit_form.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(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
onBackButtonPressed:
|
||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||
children: [
|
||||
|
@ -72,6 +73,7 @@ class RecoverByOldToken extends StatelessWidget {
|
|||
heroSubtitle: 'recovering.method_device_input_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
children: [
|
||||
CubitFormTextField(
|
||||
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/setup/recovering/recovery_device_form_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/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
||||
class RecoverByRecoveryKey extends StatelessWidget {
|
||||
const RecoverByRecoveryKey({super.key});
|
||||
|
@ -31,6 +31,7 @@ class RecoverByRecoveryKey extends StatelessWidget {
|
|||
heroSubtitle: 'recovering.method_recovery_input_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
onBackButtonPressed:
|
||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||
children: [
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.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/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
||||
class RecoveryConfirmBackblaze extends StatelessWidget {
|
||||
const RecoveryConfirmBackblaze({super.key});
|
||||
|
@ -28,6 +26,8 @@ class RecoveryConfirmBackblaze extends StatelessWidget {
|
|||
heroTitle: 'recovering.confirm_backblaze'.tr(),
|
||||
heroSubtitle: 'recovering.confirm_backblaze_description'.tr(),
|
||||
hasBackButton: true,
|
||||
ignoreBreakpoints: true,
|
||||
hasSupportDrawer: true,
|
||||
onBackButtonPressed: () {
|
||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||
},
|
||||
|
@ -57,27 +57,15 @@ class RecoveryConfirmBackblaze extends StatelessWidget {
|
|||
text: 'basis.connect'.tr(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.text(
|
||||
onPressed: () => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (final BuildContext context) => BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: Padding(
|
||||
padding: paddingH15V0,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: const [
|
||||
BrandMarkdown(
|
||||
fileName: 'how_backblaze',
|
||||
Builder(
|
||||
builder: (final context) => BrandButton.text(
|
||||
onPressed: () =>
|
||||
context.read<SupportSystemCubit>().showArticle(
|
||||
article: 'how_backblaze',
|
||||
context: context,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
title: 'initializing.how'.tr(),
|
||||
),
|
||||
title: 'initializing.how'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.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/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_md/brand_md.dart';
|
||||
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
||||
class RecoveryConfirmCloudflare extends StatelessWidget {
|
||||
const RecoveryConfirmCloudflare({super.key});
|
||||
|
@ -31,6 +29,8 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
|
|||
),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
hasSupportDrawer: true,
|
||||
onBackButtonPressed:
|
||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||
children: [
|
||||
|
@ -49,27 +49,15 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
|
|||
text: 'basis.connect'.tr(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.text(
|
||||
onPressed: () => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (final BuildContext context) => BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: Padding(
|
||||
padding: paddingH15V0,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: const [
|
||||
BrandMarkdown(
|
||||
fileName: 'how_cloudflare',
|
||||
Builder(
|
||||
builder: (final context) => BrandButton.text(
|
||||
onPressed: () =>
|
||||
context.read<SupportSystemCubit>().showArticle(
|
||||
article: 'how_cloudflare',
|
||||
context: context,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.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/brand_cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
|
||||
class RecoveryConfirmServer extends StatefulWidget {
|
||||
const RecoveryConfirmServer({super.key});
|
||||
|
@ -38,6 +38,7 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
|||
? 'recovering.choose_server_description'.tr()
|
||||
: 'recovering.confirm_server_description'.tr(),
|
||||
hasBackButton: true,
|
||||
ignoreBreakpoints: true,
|
||||
onBackButtonPressed: () {
|
||||
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_bloc/flutter_bloc.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/brand_cards/outlined_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/cards/outlined_card.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/utils/route_transitions/basic.dart';
|
||||
|
||||
|
@ -17,6 +17,7 @@ class RecoveryMethodSelect extends StatelessWidget {
|
|||
heroSubtitle: 'recovering.method_select_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
onBackButtonPressed:
|
||||
context.read<ServerInstallationCubit>().revertRecoveryStep,
|
||||
children: [
|
||||
|
@ -74,6 +75,7 @@ class RecoveryFallbackMethodSelect extends StatelessWidget {
|
|||
heroSubtitle: 'recovering.fallback_select_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
children: [
|
||||
OutlinedCard(
|
||||
child: ListTile(
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/setup/recovering/recovery_domain_form_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.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_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/utils/route_transitions/basic.dart';
|
||||
|
||||
@RoutePage()
|
||||
class RecoveryRouting extends StatelessWidget {
|
||||
const RecoveryRouting({super.key});
|
||||
|
||||
|
@ -110,6 +112,7 @@ class SelectDomainToRecover extends StatelessWidget {
|
|||
heroSubtitle: 'recovering.domain_recovery_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
onBackButtonPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(const RootPage()),
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import 'package:easy_localization/easy_localization.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/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:cubit_form/cubit_form.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 {
|
||||
const RecoveryServerProviderConnected({super.key});
|
||||
|
@ -32,6 +30,8 @@ class RecoveryServerProviderConnected extends StatelessWidget {
|
|||
),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
ignoreBreakpoints: true,
|
||||
hasSupportDrawer: true,
|
||||
onBackButtonPressed: () {
|
||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||
},
|
||||
|
@ -52,26 +52,14 @@ class RecoveryServerProviderConnected extends StatelessWidget {
|
|||
child: Text('basis.continue'.tr()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
BrandButton.text(
|
||||
title: 'initializing.how'.tr(),
|
||||
onPressed: () => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (final BuildContext context) => BrandBottomSheet(
|
||||
isExpended: true,
|
||||
child: Padding(
|
||||
padding: paddingH15V0,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: const [
|
||||
BrandMarkdown(
|
||||
fileName: 'how_hetzner',
|
||||
Builder(
|
||||
builder: (final context) => BrandButton.text(
|
||||
title: 'initializing.how'.tr(),
|
||||
onPressed: () =>
|
||||
context.read<SupportSystemCubit>().showArticle(
|
||||
article: 'how_hetzner',
|
||||
context: context,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
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),
|
||||
BrandText.h2(
|
||||
Text(
|
||||
'users.nobody_here'.tr(),
|
||||
style: const TextStyle(
|
||||
color: BrandColors.grey7,
|
||||
),
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.medium(
|
||||
Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: BrandColors.grey7,
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -43,21 +47,25 @@ class _CouldNotLoadUsers extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
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),
|
||||
BrandText.h2(
|
||||
Text(
|
||||
'users.could_not_fetch_users'.tr(),
|
||||
style: const TextStyle(
|
||||
color: BrandColors.grey7,
|
||||
),
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BrandText.medium(
|
||||
Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: BrandColors.grey7,
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
part of 'users.dart';
|
||||
|
||||
class NewUser extends StatelessWidget {
|
||||
const NewUser({super.key});
|
||||
@RoutePage()
|
||||
class NewUserPage extends StatelessWidget {
|
||||
const NewUserPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
|
@ -10,108 +11,89 @@ class NewUser extends StatelessWidget {
|
|||
|
||||
final String domainName = UiHelpers.getDomainName(config);
|
||||
|
||||
return BrandBottomSheet(
|
||||
child: BlocProvider(
|
||||
create: (final BuildContext context) {
|
||||
final jobCubit = context.read<JobsCubit>();
|
||||
final jobState = jobCubit.state;
|
||||
final users = <User>[];
|
||||
users.addAll(context.read<UsersCubit>().state.users);
|
||||
if (jobState is JobsStateWithJobs) {
|
||||
final jobs = jobState.clientJobList;
|
||||
for (final job in jobs) {
|
||||
if (job is CreateUserJob) {
|
||||
users.add(job.user);
|
||||
}
|
||||
return BlocProvider(
|
||||
create: (final BuildContext context) {
|
||||
final jobCubit = context.read<JobsCubit>();
|
||||
final jobState = jobCubit.state;
|
||||
final users = <User>[];
|
||||
users.addAll(context.read<UsersCubit>().state.users);
|
||||
if (jobState is JobsStateWithJobs) {
|
||||
final jobs = jobState.clientJobList;
|
||||
for (final job in jobs) {
|
||||
if (job is CreateUserJob) {
|
||||
users.add(job.user);
|
||||
}
|
||||
}
|
||||
return UserFormCubit(
|
||||
jobsCubit: jobCubit,
|
||||
fieldFactory: FieldCubitFactory(context),
|
||||
);
|
||||
},
|
||||
child: Builder(
|
||||
builder: (final BuildContext context) {
|
||||
final FormCubitState formCubitState =
|
||||
context.watch<UserFormCubit>().state;
|
||||
}
|
||||
return UserFormCubit(
|
||||
jobsCubit: jobCubit,
|
||||
fieldFactory: FieldCubitFactory(context),
|
||||
);
|
||||
},
|
||||
child: Builder(
|
||||
builder: (final BuildContext context) {
|
||||
final FormCubitState formCubitState =
|
||||
context.watch<UserFormCubit>().state;
|
||||
|
||||
return BlocListener<UserFormCubit, FormCubitState>(
|
||||
listener:
|
||||
(final BuildContext context, final FormCubitState state) {
|
||||
if (state.isSubmitted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BrandHeader(
|
||||
title: 'users.new_user'.tr(),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
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),
|
||||
],
|
||||
return BlocListener<UserFormCubit, FormCubitState>(
|
||||
listener: (final BuildContext context, final FormCubitState state) {
|
||||
if (state.isSubmitted) {
|
||||
context.router.pop();
|
||||
}
|
||||
},
|
||||
child: BrandHeroScreen(
|
||||
heroTitle: 'users.new_user'.tr(),
|
||||
heroIcon: Icons.person_add_outlined,
|
||||
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),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue