Compare commits

...

12 Commits

Author SHA1 Message Date
Inex Code 4930fc2387 feat: Show the error screen when libsecret fails
continuous-integration/drone/push Build is passing Details
2024-05-02 15:05:38 +03:00
Inex Code 11d0e58334 fix: Flatpak builds didn't work 2024-04-26 18:08:04 +03:00
NaiJi ✨ a6b846cc78 feat(backups): Show how much space a service uses on backup (#500)
continuous-integration/drone/push Build is passing Details
Fixes #434

![image](/attachments/351cc025-8dae-44f2-9bca-18f8950e0780)

Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #500
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
Co-authored-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
Co-committed-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
2024-04-24 13:18:02 +03:00
NaiJi ✨ 6819192219 feat: Add country names to installation process (#501)
continuous-integration/drone/push Build is passing Details
Fixes #494

Reviewed-on: #501
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
Co-authored-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
Co-committed-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
2024-04-24 12:54:32 +03:00
NaiJi ✨ ffdb9d92fb Merge pull request 'fix(backups): Implement filtering for enabled services for backups' (#499) from filter-enabled-backup-services into master
continuous-integration/drone/push Build is passing Details
Reviewed-on: #499
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
2024-04-17 18:48:56 +03:00
NaiJi ✨ 1c42598787 fix(backups): Implement filtering for enabled services for backups
- Resolve: #433
2024-04-16 23:03:11 +04:00
dettlaff c179a109fd fix: add subtitle for flash button (#462)
continuous-integration/drone/push Build is passing Details
closes #453

![image](/attachments/398ae5b1-df90-43cf-8389-0be4bafde9fd)

idk how to change hover

Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #462
Co-authored-by: dettlaff <dettlaff@riseup.net>
Co-committed-by: dettlaff <dettlaff@riseup.net>
2024-04-11 13:53:31 +03:00
def add2366e6b feat: add copy link to service page (#461)
continuous-integration/drone/push Build is passing Details
closes #452

Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #461
Co-authored-by: def <dettlaff@riseup.net>
Co-committed-by: def <dettlaff@riseup.net>
2024-04-11 13:14:20 +03:00
dettlaff 0dc281a4f6 feat: add route to service cards in storage page (#446)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #446
Co-authored-by: dettlaff <dettlaff@riseup.net>
Co-committed-by: dettlaff <dettlaff@riseup.net>
2024-04-11 13:04:22 +03:00
dettlaff a4737e9f05 feat: cubit.state.progress check for connect_to_existing
continuous-integration/drone/push Build is passing Details
2024-04-03 13:00:27 +03:00
Inex Code bf66717854 fix(docs): Digital Ocean DNS used wrong manual
continuous-integration/drone/push Build is failing Details
2024-04-03 12:54:33 +03:00
Inex Code d3b7f31c65 chore: Upgrade flutter and dependencies 2024-04-02 18:11:29 +03:00
30 changed files with 524 additions and 364 deletions

6
.gitignore vendored
View File

@ -40,3 +40,9 @@ app.*.symbols
# Obfuscation related # Obfuscation related
app.*.map.json app.*.map.json
# Flatpak
.flatpak-builder/
flatpak-build/
flatpak-repo/
*.flatpak

View File

@ -52,8 +52,8 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.selfprivacy.app" applicationId "org.selfprivacy.app"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 34
compileSdkVersion 33 compileSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

View File

@ -606,5 +606,16 @@
"reset_onboarding": "Reset onboarding switch", "reset_onboarding": "Reset onboarding switch",
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again", "reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
"cubit_statuses": "Cubit loading statuses" "cubit_statuses": "Cubit loading statuses"
},
"countries": {
"germany": "Germany",
"netherlands": "Netherlands",
"singapore": "Singapore",
"united_kingdom": "United Kingdom",
"canada": "Canada",
"india": "India",
"australia": "Australia",
"united_states": "United States",
"finland": "Finland"
} }
} }

View File

@ -1,6 +1,6 @@
app-id: org.selfprivacy.app app-id: org.selfprivacy.app
runtime: org.freedesktop.Platform runtime: org.freedesktop.Platform
runtime-version: '22.08' runtime-version: '23.08'
sdk: org.freedesktop.Sdk sdk: org.freedesktop.Sdk
command: selfprivacy command: selfprivacy
finish-args: finish-args:
@ -11,6 +11,7 @@ finish-args:
- "--share=network" - "--share=network"
- "--own-name=org.selfprivacy.app" - "--own-name=org.selfprivacy.app"
- "--device=dri" - "--device=dri"
- "--talk-name=org.freedesktop.secrets"
modules: modules:
- name: selfprivacy - name: selfprivacy
buildsystem: simple buildsystem: simple
@ -35,7 +36,7 @@ modules:
sources: sources:
- type: git - type: git
url: https://gitlab.gnome.org/GNOME/libsecret.git url: https://gitlab.gnome.org/GNOME/libsecret.git
tag: 0.20.5 tag: 0.21.4
- name: libjsoncpp - name: libjsoncpp
buildsystem: meson buildsystem: meson
config-opts: config-opts:

View File

@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
@ -28,33 +28,47 @@ class HiveConfig {
await Hive.openBox(BNames.appSettingsBox); await Hive.openBox(BNames.appSettingsBox);
final HiveAesCipher cipher = HiveAesCipher( try {
await getEncryptedKey(BNames.serverInstallationEncryptionKey), final HiveAesCipher cipher = HiveAesCipher(
); await getEncryptedKey(BNames.serverInstallationEncryptionKey),
);
await Hive.openBox<User>(BNames.usersDeprecated); await Hive.openBox<User>(BNames.usersDeprecated);
await Hive.openBox<User>(BNames.usersBox, encryptionCipher: cipher); await Hive.openBox<User>(BNames.usersBox, encryptionCipher: cipher);
final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated); final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated);
if (deprecatedUsers.isNotEmpty) { if (deprecatedUsers.isNotEmpty) {
final Box<User> users = Hive.box<User>(BNames.usersBox); final Box<User> users = Hive.box<User>(BNames.usersBox);
await users.addAll(deprecatedUsers.values.toList()); await users.addAll(deprecatedUsers.values.toList());
await deprecatedUsers.clear(); await deprecatedUsers.clear();
}
await Hive.openBox(
BNames.serverInstallationBox,
encryptionCipher: cipher,
);
} on PlatformException catch (e) {
print('HiveConfig: Error while opening boxes: $e');
rethrow;
} }
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
} }
static Future<Uint8List> getEncryptedKey(final String encKey) async { static Future<Uint8List> getEncryptedKey(final String encKey) async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage(); const FlutterSecureStorage secureStorage = FlutterSecureStorage();
final bool hasEncryptionKey = await secureStorage.containsKey(key: encKey); try {
if (!hasEncryptionKey) { final bool hasEncryptionKey =
final List<int> key = Hive.generateSecureKey(); await secureStorage.containsKey(key: encKey);
await secureStorage.write(key: encKey, value: base64UrlEncode(key)); if (!hasEncryptionKey) {
} final List<int> key = Hive.generateSecureKey();
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
}
final String? string = await secureStorage.read(key: encKey); final String? string = await secureStorage.read(key: encKey);
return base64Url.decode(string!); return base64Url.decode(string!);
} on PlatformException catch (e) {
print('HiveConfig: Error while getting encryption key: $e');
rethrow;
}
} }
} }

View File

@ -65,14 +65,59 @@ class DigitalOceanLocation {
emoji = '🇮🇳'; emoji = '🇮🇳';
break; break;
case 'syd':
emoji = '🇦🇺';
break;
case 'nyc': case 'nyc':
case 'sfo': case 'sfo':
emoji = '🇺🇸'; emoji = '🇺🇸';
break; break;
} }
return emoji; return emoji;
} }
String get countryDisplayKey {
String displayKey = 'countries.';
switch (slug.substring(0, 3)) {
case 'fra':
displayKey += 'germany';
break;
case 'ams':
displayKey += 'netherlands';
break;
case 'sgp':
displayKey += 'singapore';
break;
case 'lon':
displayKey += 'united_kingdom';
break;
case 'tor':
displayKey += 'canada';
break;
case 'blr':
displayKey += 'india';
break;
case 'syd':
displayKey += 'australia';
break;
case 'nyc':
case 'sfo':
displayKey += 'united_states';
break;
default:
displayKey = slug;
}
return displayKey;
}
} }
@JsonSerializable() @JsonSerializable()

View File

@ -155,6 +155,27 @@ class HetznerLocation {
} }
return emoji; return emoji;
} }
String get countryDisplayKey {
String displayKey = 'countries.';
switch (country.substring(0, 2)) {
case 'DE':
displayKey += 'germany';
break;
case 'FI':
displayKey += 'finland';
break;
case 'US':
displayKey += 'united_states';
break;
default:
displayKey = country;
}
return displayKey;
}
} }
/// A Volume is a highly-available, scalable, and SSD-based block storage for Servers. /// A Volume is a highly-available, scalable, and SSD-based block storage for Servers.

View File

@ -2,12 +2,14 @@ class ServerProviderLocation {
ServerProviderLocation({ ServerProviderLocation({
required this.title, required this.title,
required this.identifier, required this.identifier,
required this.countryDisplayKey,
this.description, this.description,
this.flag = '', this.flag = '',
}); });
final String title; final String title;
final String identifier; final String identifier;
final String countryDisplayKey;
final String? description; final String? description;
final String flag; final String flag;
} }

View File

@ -438,6 +438,7 @@ class DigitalOceanServerProvider extends ServerProvider {
description: rawLocation.name, description: rawLocation.name,
flag: rawLocation.flag, flag: rawLocation.flag,
identifier: rawLocation.slug, identifier: rawLocation.slug,
countryDisplayKey: rawLocation.countryDisplayKey,
); );
} catch (e) { } catch (e) {
continue; continue;

View File

@ -156,6 +156,7 @@ class HetznerServerProvider extends ServerProvider {
description: server.location.description, description: server.location.description,
flag: server.location.flag, flag: server.location.flag,
identifier: server.location.name, identifier: server.location.name,
countryDisplayKey: server.location.countryDisplayKey,
), ),
), ),
); );
@ -456,6 +457,7 @@ class HetznerServerProvider extends ServerProvider {
description: rawLocation.description, description: rawLocation.description,
flag: rawLocation.flag, flag: rawLocation.flag,
identifier: rawLocation.name, identifier: rawLocation.name,
countryDisplayKey: rawLocation.countryDisplayKey,
); );
} catch (e) { } catch (e) {
continue; continue;

View File

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/bloc_config.dart'; import 'package:selfprivacy/config/bloc_config.dart';
import 'package:selfprivacy/config/bloc_observer.dart'; import 'package:selfprivacy/config/bloc_observer.dart';
@ -9,13 +10,20 @@ import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/config/localization.dart'; import 'package:selfprivacy/config/localization.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/theming/factory/app_theme_factory.dart'; import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
import 'package:selfprivacy/ui/pages/errors/failed_to_init_secure_storage.dart';
import 'package:selfprivacy/ui/router/router.dart'; import 'package:selfprivacy/ui/router/router.dart';
// import 'package:wakelock/wakelock.dart'; // import 'package:wakelock/wakelock.dart';
import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/data/latest.dart' as tz;
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await HiveConfig.init(); try {
await HiveConfig.init();
} on PlatformException catch (e) {
runApp(
FailedToInitSecureStorageScreen(e: e),
);
}
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
// try { // try {

View File

@ -24,8 +24,10 @@ abstract class AppThemeFactory {
final ColorScheme? dynamicColorsScheme = final ColorScheme? dynamicColorsScheme =
await _getDynamicColors(brightness); await _getDynamicColors(brightness);
final Color? accentColor = await _getAccentColor();
final ColorScheme fallbackColorScheme = ColorScheme.fromSeed( final ColorScheme fallbackColorScheme = ColorScheme.fromSeed(
seedColor: fallbackColor, seedColor: accentColor ?? fallbackColor,
brightness: brightness, brightness: brightness,
); );
@ -55,6 +57,14 @@ abstract class AppThemeFactory {
} }
} }
static Future<Color?> _getAccentColor() {
try {
return DynamicColorPlugin.getAccentColor();
} on PlatformException {
return Future.value(null);
}
}
static Future<CorePalette?> getCorePalette() async { static Future<CorePalette?> getCorePalette() async {
try { try {
return await DynamicColorPlugin.getCorePalette(); return await DynamicColorPlugin.getCorePalette();

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ionicons/ionicons.dart'; import 'package:ionicons/ionicons.dart';
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
@ -126,7 +127,9 @@ class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final isMobile = final isMobile =
widget.ignoreBreakpoints ? true : Breakpoints.small.isActive(context); widget.ignoreBreakpoints ? true : Breakpoints.small.isActive(context);
final isJobsListEmpty = context.watch<JobsCubit>().state is JobsStateEmpty; final isJobsListEmpty = widget.hasFlashButton
? context.watch<JobsCubit>().state is JobsStateEmpty
: true;
return SliverAppBar( return SliverAppBar(
expandedHeight: expandedHeight:
widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height, widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height,
@ -164,6 +167,7 @@ class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
color: isJobsListEmpty color: isJobsListEmpty
? Theme.of(context).colorScheme.onBackground ? Theme.of(context).colorScheme.onBackground
: Theme.of(context).colorScheme.primary, : Theme.of(context).colorScheme.primary,
tooltip: 'jobs.title'.tr(),
), ),
), ),
const SizedBox.shrink(), const SizedBox.shrink(),

View File

@ -100,7 +100,7 @@ class _RootAppBar extends StatelessWidget {
leading: context.router.pageCount > 1 leading: context.router.pageCount > 1
? IconButton( ? IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () => context.router.pop(), onPressed: () => context.router.maybePop(),
) )
: null, : null,
actions: const [ actions: const [

View File

@ -38,8 +38,14 @@ class BackupDetailsPage extends StatelessWidget {
: StateType.uninitialized; : StateType.uninitialized;
final bool preventActions = backupsState.preventActions; final bool preventActions = backupsState.preventActions;
final List<Backup> backups = backupsState.backups; final List<Backup> backups = backupsState.backups;
final List<Service> services = final List<Service> services = context
context.watch<ServicesBloc>().state.servicesThatCanBeBackedUp; .watch<ServicesBloc>()
.state
.servicesThatCanBeBackedUp
.where(
(final service) => service.isEnabled,
)
.toList();
final Duration? autobackupPeriod = backupsState.autobackupPeriod; final Duration? autobackupPeriod = backupsState.autobackupPeriod;
final List<ServerJob> backupJobs = context final List<ServerJob> backupJobs = context
.watch<ServerJobsBloc>() .watch<ServerJobsBloc>()

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
@ -103,6 +104,29 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
...widget.services.map( ...widget.services.map(
(final Service service) { (final Service service) {
final bool busy = busyServices.contains(service.id); final bool busy = busyServices.contains(service.id);
final List<Widget> descriptionWidgets = [];
if (busy) {
descriptionWidgets.add(Text('backup.service_busy'.tr()));
} else {
descriptionWidgets.add(
Text(
'service_page.uses'.tr(
namedArgs: {
'usage': service.storageUsage.used.toString(),
'volume': context
.read<VolumesBloc>()
.state
.getVolume(service.storageUsage.volume ?? '')
.displayName,
},
),
style: Theme.of(context).textTheme.labelMedium,
),
);
descriptionWidgets.add(
Text(service.backupDescription),
);
}
return CheckboxListTile.adaptive( return CheckboxListTile.adaptive(
onChanged: !busy onChanged: !busy
? (final bool? value) { ? (final bool? value) {
@ -122,8 +146,9 @@ class _CreateBackupsModalState extends State<CreateBackupsModal> {
title: Text( title: Text(
service.displayName, service.displayName,
), ),
subtitle: Text( subtitle: Column(
busy ? 'backup.service_busy'.tr() : service.backupDescription, crossAxisAlignment: CrossAxisAlignment.start,
children: descriptionWidgets,
), ),
secondary: SvgPicture.string( secondary: SvgPicture.string(
service.svgIcon, service.svgIcon,

View File

@ -0,0 +1,71 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/pages/more/about_application.dart';
class FailedToInitSecureStorageScreen extends StatelessWidget {
const FailedToInitSecureStorageScreen({
required this.e,
super.key,
});
final PlatformException e;
@override
Widget build(final BuildContext context) => MaterialApp(
home: BrandHeroScreen(
heroIcon: Icons.error_outline,
heroTitle: 'Failed to initialize secure storage',
hasBackButton: false,
children: [
const Text(
'SelfPrivacy requires a secure storage provided by your operating system to encrypt sensitive data, but it failed to initialize.',
),
if (Platform.isLinux)
const Text(
'Please make sure that the libsecret library is installed.',
),
const Gap(16),
Text('Error: ${e.message}'),
const Gap(16),
const Divider(),
const Gap(16),
const LinkListTile(
title: 'Our website',
subtitle: 'selfprivacy.org',
uri: 'https://selfprivacy.org/',
icon: Icons.language_outlined,
),
const LinkListTile(
title: 'Documentation',
subtitle: 'selfprivacy.org/docs',
uri: 'https://selfprivacy.org/docs/',
icon: Icons.library_books_outlined,
),
const LinkListTile(
title: 'Privacy Policy',
subtitle: 'selfprivacy.org/privacy-policy',
uri: 'https://selfprivacy.org/privacy-policy/',
icon: Icons.policy_outlined,
),
const LinkListTile(
title: 'Matrix support chat',
subtitle: '#chat:selfprivacy.org',
uri: 'https://matrix.to/#/#chat:selfprivacy.org',
icon: Icons.question_answer_outlined,
longPressText: '#chat:selfprivacy.org',
),
const LinkListTile(
title: 'Telegram support chat',
subtitle: '@selfprivacy_chat',
uri: 'https://t.me/selfprivacy_chat',
icon: Icons.question_answer_outlined,
longPressText: '@selfprivacy_chat',
),
],
),
);
}

View File

@ -35,7 +35,8 @@ class CpuChart extends StatelessWidget {
lineTouchData: LineTouchData( lineTouchData: LineTouchData(
enabled: true, enabled: true,
touchTooltipData: LineTouchTooltipData( touchTooltipData: LineTouchTooltipData(
tooltipBgColor: Theme.of(context).colorScheme.surface, getTooltipColor: (final LineBarSpot _) =>
Theme.of(context).colorScheme.surface,
tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.all(8),
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) { getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
final List<LineTooltipItem> res = []; final List<LineTooltipItem> res = [];

View File

@ -38,7 +38,8 @@ class NetworkChart extends StatelessWidget {
lineTouchData: LineTouchData( lineTouchData: LineTouchData(
enabled: true, enabled: true,
touchTooltipData: LineTouchTooltipData( touchTooltipData: LineTouchTooltipData(
tooltipBgColor: Theme.of(context).colorScheme.surface, getTooltipColor: (final LineBarSpot _) =>
Theme.of(context).colorScheme.surface,
tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.all(8),
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) { getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
final List<LineTooltipItem> res = []; final List<LineTooltipItem> res = [];

View File

@ -43,8 +43,8 @@ class _ServerStoragePageState extends State<ServerStoragePage> {
return BrandHeroScreen( return BrandHeroScreen(
hasBackButton: true, hasBackButton: true,
heroTitle: 'storage.card_title'.tr(), heroTitle: 'storage.card_title'.tr(),
bodyPadding: const EdgeInsets.symmetric(vertical: 16.0),
children: [ children: [
// ...sections,
...widget.diskStatus.diskVolumes.map( ...widget.diskStatus.diskVolumes.map(
(final volume) => Column( (final volume) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -87,24 +87,35 @@ class ServerStorageSection extends StatelessWidget {
Widget build(final BuildContext context) => Column( Widget build(final BuildContext context) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ServerStorageListItem( Padding(
volume: volume, padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ServerStorageListItem(
volume: volume,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
...services.map( ...services.map(
(final service) => ServerConsumptionListTile( (final service) => ServerConsumptionListTile(
service: service, service: service,
volume: volume, volume: volume,
onTap: () {
context.pushRoute(
ServiceRoute(serviceId: service.id),
);
},
), ),
), ),
if (volume.isResizable) ...[ if (volume.isResizable) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
BrandOutlinedButton( Padding(
title: 'storage.extend_volume_button.title'.tr(), padding: const EdgeInsets.symmetric(horizontal: 16.0),
onPressed: () => context.pushRoute( child: BrandOutlinedButton(
ExtendingVolumeRoute( title: 'storage.extend_volume_button.title'.tr(),
diskVolumeToResize: volume, onPressed: () => context.pushRoute(
diskStatus: diskStatus, ExtendingVolumeRoute(
diskVolumeToResize: volume,
diskStatus: diskStatus,
),
), ),
), ),
), ),
@ -117,33 +128,38 @@ class ServerConsumptionListTile extends StatelessWidget {
const ServerConsumptionListTile({ const ServerConsumptionListTile({
required this.service, required this.service,
required this.volume, required this.volume,
required this.onTap,
super.key, super.key,
}); });
final Service service; final Service service;
final DiskVolume volume; final DiskVolume volume;
final VoidCallback onTap;
@override @override
Widget build(final BuildContext context) => Padding( Widget build(final BuildContext context) => InkWell(
padding: const EdgeInsets.symmetric(vertical: 8), onTap: onTap,
child: ConsumptionListItem( child: Padding(
title: service.displayName, padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
icon: SvgPicture.string( child: ConsumptionListItem(
service.svgIcon, title: service.displayName,
width: 24.0, icon: SvgPicture.string(
height: 24.0, service.svgIcon,
colorFilter: ColorFilter.mode( width: 22.0,
Theme.of(context).colorScheme.onBackground, height: 24.0,
BlendMode.srcIn, colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onBackground,
BlendMode.srcIn,
),
), ),
rightSideText: service.storageUsage.used.toString(),
percentage: service.storageUsage.used.byte / volume.sizeTotal.byte,
color: volume.root
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
dense: true,
), ),
rightSideText: service.storageUsage.used.toString(),
percentage: service.storageUsage.used.byte / volume.sizeTotal.byte,
color: volume.root
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
dense: true,
), ),
); );
} }

View File

@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
@ -11,6 +12,7 @@ import 'package:selfprivacy/ui/components/cards/filled_card.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/router/router.dart'; import 'package:selfprivacy/ui/router/router.dart';
import 'package:selfprivacy/utils/launch_url.dart'; import 'package:selfprivacy/utils/launch_url.dart';
import 'package:selfprivacy/utils/platform_adapter.dart';
@RoutePage() @RoutePage()
class ServicePage extends StatefulWidget { class ServicePage extends StatefulWidget {
@ -65,6 +67,11 @@ class _ServicePageState extends State<ServicePage> {
ListTile( ListTile(
iconColor: Theme.of(context).colorScheme.onBackground, iconColor: Theme.of(context).colorScheme.onBackground,
onTap: () => launchURL(service.url), onTap: () => launchURL(service.url),
onLongPress: () {
PlatformAdapter.setClipboard(service.url!);
getIt<NavigationService>()
.showSnackBar('basis.copied_to_clipboard'.tr());
},
leading: const Icon(Icons.open_in_browser), leading: const Icon(Icons.open_in_browser),
title: Text( title: Text(
'service_page.open_in_browser'.tr(), 'service_page.open_in_browser'.tr(),

View File

@ -57,7 +57,7 @@ class _DnsProviderPickerState extends State<DnsProviderPicker> {
providerCubit: widget.formCubit, providerCubit: widget.formCubit,
providerInfo: const ProviderPageInfo( providerInfo: const ProviderPageInfo(
providerType: DnsProviderType.digitalOcean, providerType: DnsProviderType.digitalOcean,
pathToHow: 'how_digital_ocean_dns', pathToHow: 'how_digital_ocean',
), ),
); );

View File

@ -130,7 +130,9 @@ class InitializingPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (cubit.state is ServerInstallationEmpty || if (cubit.state is ServerInstallationEmpty ||
cubit.state is ServerInstallationNotFinished) cubit.state is ServerInstallationNotFinished &&
cubit.state.progress ==
ServerSetupProgress.nothingYet)
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
child: BrandButton.filled( child: BrandButton.filled(

View File

@ -120,6 +120,14 @@ class SelectLocationPage extends StatelessWidget {
.titleMedium, .titleMedium,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(
location.countryDisplayKey.tr(),
style: Theme.of(context)
.textTheme
.bodyMedium,
),
if (location.description != null)
const SizedBox(height: 4),
if (location.description != null) if (location.description != null)
Text( Text(
location.description!, location.description!,

View File

@ -38,7 +38,7 @@ class NewUserPage extends StatelessWidget {
return BlocListener<UserFormCubit, FormCubitState>( return BlocListener<UserFormCubit, FormCubitState>(
listener: (final BuildContext context, final FormCubitState state) { listener: (final BuildContext context, final FormCubitState state) {
if (state.isSubmitted) { if (state.isSubmitted) {
context.router.pop(); context.router.maybePop();
} }
}, },
child: BrandHeroScreen( child: BrandHeroScreen(

View File

@ -103,7 +103,7 @@ class _DeleteUserTile extends StatelessWidget {
TextButton( TextButton(
child: Text('basis.cancel'.tr()), child: Text('basis.cancel'.tr()),
onPressed: () { onPressed: () {
context.router.pop(); context.router.maybePop();
}, },
), ),
TextButton( TextButton(
@ -115,8 +115,8 @@ class _DeleteUserTile extends StatelessWidget {
), ),
onPressed: () { onPressed: () {
context.read<JobsCubit>().addJob(DeleteUserJob(user: user)); context.read<JobsCubit>().addJob(DeleteUserJob(user: user));
context.router.childControllers.first.pop(); context.router.childControllers.first.maybePop();
context.router.pop(); context.router.maybePop();
}, },
), ),
], ],
@ -244,7 +244,7 @@ class _SshKeysCard extends StatelessWidget {
publicKey: key, publicKey: key,
), ),
); );
context.popRoute(); context.maybePop();
}, },
), ),
], ],

File diff suppressed because it is too large Load Diff

View File

@ -5,30 +5,29 @@ version: 0.11.0+22
environment: environment:
sdk: '>=3.2.1 <4.0.0' sdk: '>=3.2.1 <4.0.0'
flutter: ">=3.16.1" flutter: ">=3.19.5"
dependencies: dependencies:
animations: ^2.0.8 animations: ^2.0.11
auto_route: ^7.8.4 auto_route: ^8.0.3
auto_size_text: ^3.0.0 auto_size_text: ^3.0.0
bloc_concurrency: ^0.2.3 bloc_concurrency: ^0.2.5
crypt: ^4.3.1 crypt: ^4.3.1
collection: ^1.18.0 collection: ^1.18.0
cubit_form: ^2.0.1 cubit_form: ^2.0.1
device_info_plus: ^9.1.1 device_info_plus: ^10.0.1
dio: ^5.4.0 dio: ^5.4.2+1
duration: ^3.0.13 duration: ^3.0.13
dynamic_color: ^1.6.8 dynamic_color: ^1.7.0
easy_localization: ^3.0.3 easy_localization: ^3.0.5
either_option: ^2.0.1-dev.1
equatable: ^2.0.5 equatable: ^2.0.5
fl_chart: ^0.65.0 fl_chart: ^0.67.0
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.1.3 flutter_bloc: ^8.1.5
flutter_markdown: ^0.6.18+2 flutter_markdown: ^0.6.22
flutter_secure_storage: ^9.0.0 flutter_secure_storage: ^9.0.0
flutter_svg: ^2.0.9 flutter_svg: ^2.0.10+1
gap: ^3.0.1 gap: ^3.0.1
get_it: ^7.6.4 get_it: ^7.6.4
gql: ^1.0.0 gql: ^1.0.0
@ -38,12 +37,11 @@ dependencies:
hive: ^2.2.3 hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
http: ^1.1.2 http: ^1.1.2
intl: ^0.18.0 intl: ^0.18.1
ionicons: ^0.2.2 ionicons: ^0.2.2
json_annotation: ^4.8.1 json_annotation: ^4.8.1
local_auth: ^2.1.7 material_color_utilities: ^0.8.0
material_color_utilities: ^0.5.0 modal_bottom_sheet: ^3.0.0
modal_bottom_sheet: ^3.0.0-pre
nanoid: ^1.0.0 nanoid: ^1.0.0
package_info: ^2.0.2 package_info: ^2.0.2
pretty_dio_logger: ^1.3.1 pretty_dio_logger: ^1.3.1
@ -55,14 +53,14 @@ dependencies:
# wakelock: ^0.6.2 # wakelock: ^0.6.2
dev_dependencies: dev_dependencies:
auto_route_generator: ^7.3.2 auto_route_generator: ^8.0.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.9
flutter_launcher_icons: ^0.13.1 flutter_launcher_icons: ^0.13.1
hive_generator: ^2.0.1 hive_generator: ^2.0.1
json_serializable: ^6.7.1 json_serializable: ^6.7.1
flutter_lints: ^3.0.1 flutter_lints: ^3.0.2
flutter_icons: flutter_icons:
android: "launcher_icon" android: "launcher_icon"

View File

@ -9,7 +9,6 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h> #include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_auth_windows/local_auth_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -19,8 +18,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus connectivity_plus
dynamic_color dynamic_color
flutter_secure_storage_windows flutter_secure_storage_windows
local_auth_windows
url_launcher_windows url_launcher_windows
) )