mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-09-21 10:57:52 +00:00
feat: Add a notification if the app doesn't support the server API version
This commit is contained in:
parent
8c5bdf9cd8
commit
58bfa6db93
|
@ -39,6 +39,7 @@
|
||||||
"please_connect": "Please connect your server, domain and DNS provider to dive in!",
|
"please_connect": "Please connect your server, domain and DNS provider to dive in!",
|
||||||
"network_error": "Network error",
|
"network_error": "Network error",
|
||||||
"feature_unsupported_on_api_version": "This feature is only supported on server version {versionConstraint}. Your server is on version {currentVersion}.",
|
"feature_unsupported_on_api_version": "This feature is only supported on server version {versionConstraint}. Your server is on version {currentVersion}.",
|
||||||
|
"server_is_outdated": "This SelfPrivacy version requires API version {versionConstraint}, but your server is on version {currentVersion}. Some features won't work and errors might occur. Please upgrade your server.",
|
||||||
"error": "Error"
|
"error": "Error"
|
||||||
},
|
},
|
||||||
"more_page": {
|
"more_page": {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/connection_status_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/connection_status_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/outdated_server_checker/outdated_server_checker_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_logs/server_logs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_logs/server_logs_bloc.dart';
|
||||||
|
@ -38,6 +39,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
late final ServerDetailsCubit serverDetailsCubit;
|
late final ServerDetailsCubit serverDetailsCubit;
|
||||||
late final VolumesBloc volumesBloc;
|
late final VolumesBloc volumesBloc;
|
||||||
late final ServerLogsBloc serverLogsBloc;
|
late final ServerLogsBloc serverLogsBloc;
|
||||||
|
late final OutdatedServerCheckerBloc outdatedServerCheckerBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -55,6 +57,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
serverDetailsCubit = ServerDetailsCubit();
|
serverDetailsCubit = ServerDetailsCubit();
|
||||||
volumesBloc = VolumesBloc();
|
volumesBloc = VolumesBloc();
|
||||||
serverLogsBloc = ServerLogsBloc();
|
serverLogsBloc = ServerLogsBloc();
|
||||||
|
outdatedServerCheckerBloc = OutdatedServerCheckerBloc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -100,6 +103,9 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => serverLogsBloc,
|
create: (final _) => serverLogsBloc,
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (final _) => outdatedServerCheckerBloc,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
|
||||||
|
part 'outdated_server_checker_event.dart';
|
||||||
|
part 'outdated_server_checker_state.dart';
|
||||||
|
|
||||||
|
const String requiredServerVersion = '>=3.3.0';
|
||||||
|
|
||||||
|
class OutdatedServerCheckerBloc
|
||||||
|
extends Bloc<OutdatedServerCheckerEvent, OutdatedServerCheckerState> {
|
||||||
|
OutdatedServerCheckerBloc() : super(OutdatedServerCheckerInitial()) {
|
||||||
|
on<ServerApiVersionChanged>((final event, final emit) {
|
||||||
|
if (event.newVersion == null) {
|
||||||
|
emit(OutdatedServerCheckerInitial());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final requiredVersion = VersionConstraint.parse(requiredServerVersion);
|
||||||
|
final currentVersion = Version.parse(event.newVersion!);
|
||||||
|
if (requiredVersion.allows(currentVersion)) {
|
||||||
|
emit(OutdatedServerCheckerUpToDate(currentVersion));
|
||||||
|
} else {
|
||||||
|
emit(OutdatedServerCheckerOutdated(currentVersion));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_apiDataSubscription = getIt<ApiConnectionRepository>().dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(ServerApiVersionChanged(apiData.apiVersion.data));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<OutdatedServerCheckerState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
late StreamSubscription _apiDataSubscription;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
part of 'outdated_server_checker_bloc.dart';
|
||||||
|
|
||||||
|
sealed class OutdatedServerCheckerEvent extends Equatable {
|
||||||
|
const OutdatedServerCheckerEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerApiVersionChanged extends OutdatedServerCheckerEvent {
|
||||||
|
const ServerApiVersionChanged(this.newVersion);
|
||||||
|
|
||||||
|
final String? newVersion;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [newVersion];
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
part of 'outdated_server_checker_bloc.dart';
|
||||||
|
|
||||||
|
sealed class OutdatedServerCheckerState extends Equatable {
|
||||||
|
const OutdatedServerCheckerState();
|
||||||
|
|
||||||
|
VersionConstraint get requiredVersion =>
|
||||||
|
VersionConstraint.parse(requiredServerVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OutdatedServerCheckerInitial extends OutdatedServerCheckerState {
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OutdatedServerCheckerOutdated extends OutdatedServerCheckerState {
|
||||||
|
const OutdatedServerCheckerOutdated(this.currentVersion);
|
||||||
|
|
||||||
|
final Version currentVersion;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [currentVersion];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OutdatedServerCheckerUpToDate extends OutdatedServerCheckerState {
|
||||||
|
const OutdatedServerCheckerUpToDate(this.currentVersion);
|
||||||
|
|
||||||
|
final Version currentVersion;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [currentVersion];
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
|
|
||||||
|
class ServerOutdatedCard extends StatelessWidget {
|
||||||
|
const ServerOutdatedCard({
|
||||||
|
required this.requiredVersion,
|
||||||
|
required this.currentVersion,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String requiredVersion;
|
||||||
|
final String currentVersion;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => FilledCard(
|
||||||
|
error: true,
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
title: Text(
|
||||||
|
'basis.server_is_outdated'.tr(
|
||||||
|
namedArgs: {
|
||||||
|
'versionConstraint': requiredVersion,
|
||||||
|
'currentVersion': currentVersion,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onTertiaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/outdated_server_checker/outdated_server_checker_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/dns_records/dns_records_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
@ -12,6 +13,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/server_outdated_card/server_outdated_card.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/utils/breakpoints.dart';
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
|
|
||||||
|
@ -40,6 +42,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
final ServerInstallationState appConfig =
|
final ServerInstallationState appConfig =
|
||||||
context.watch<ServerInstallationCubit>().state;
|
context.watch<ServerInstallationCubit>().state;
|
||||||
|
|
||||||
|
final OutdatedServerCheckerState outdatedServerCheckerState =
|
||||||
|
context.watch<OutdatedServerCheckerBloc>().state;
|
||||||
|
|
||||||
StateType getServerStatus() {
|
StateType getServerStatus() {
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
return StateType.uninitialized;
|
return StateType.uninitialized;
|
||||||
|
@ -76,6 +81,15 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
const NotReadyCard(),
|
const NotReadyCard(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
if (outdatedServerCheckerState is OutdatedServerCheckerOutdated) ...[
|
||||||
|
ServerOutdatedCard(
|
||||||
|
requiredVersion:
|
||||||
|
outdatedServerCheckerState.requiredVersion.toString(),
|
||||||
|
currentVersion:
|
||||||
|
outdatedServerCheckerState.currentVersion.toString(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
_Card(
|
_Card(
|
||||||
state: getServerStatus(),
|
state: getServerStatus(),
|
||||||
icon: BrandIcons.server,
|
icon: BrandIcons.server,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/outdated_server_checker/outdated_server_checker_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
@ -11,6 +12,7 @@ import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/server_outdated_card/server_outdated_card.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.dart';
|
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.dart';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
import 'package:selfprivacy/utils/breakpoints.dart';
|
import 'package:selfprivacy/utils/breakpoints.dart';
|
||||||
|
@ -31,6 +33,9 @@ class _ServicesPageState extends State<ServicesPage> {
|
||||||
final isReady = context.watch<ServerInstallationCubit>().state
|
final isReady = context.watch<ServerInstallationCubit>().state
|
||||||
is ServerInstallationFinished;
|
is ServerInstallationFinished;
|
||||||
|
|
||||||
|
final OutdatedServerCheckerState outdatedServerCheckerState =
|
||||||
|
context.watch<OutdatedServerCheckerBloc>().state;
|
||||||
|
|
||||||
final services = [...context.watch<ServicesBloc>().state.services];
|
final services = [...context.watch<ServicesBloc>().state.services];
|
||||||
services
|
services
|
||||||
.sort((final a, final b) => a.status.index.compareTo(b.status.index));
|
.sort((final a, final b) => a.status.index.compareTo(b.status.index));
|
||||||
|
@ -53,6 +58,16 @@ class _ServicesPageState extends State<ServicesPage> {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: paddingH15V0,
|
padding: paddingH15V0,
|
||||||
children: [
|
children: [
|
||||||
|
if (outdatedServerCheckerState
|
||||||
|
is OutdatedServerCheckerOutdated) ...[
|
||||||
|
ServerOutdatedCard(
|
||||||
|
requiredVersion:
|
||||||
|
outdatedServerCheckerState.requiredVersion.toString(),
|
||||||
|
currentVersion:
|
||||||
|
outdatedServerCheckerState.currentVersion.toString(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
Text(
|
Text(
|
||||||
'basis.services_title'.tr(),
|
'basis.services_title'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/outdated_server_checker/outdated_server_checker_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
|
||||||
|
@ -20,6 +21,7 @@ import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/server_outdated_card/server_outdated_card.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.dart';
|
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.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';
|
||||||
|
@ -42,6 +44,9 @@ class UsersPage extends StatelessWidget {
|
||||||
is ServerInstallationFinished;
|
is ServerInstallationFinished;
|
||||||
Widget child;
|
Widget child;
|
||||||
|
|
||||||
|
final OutdatedServerCheckerState outdatedServerCheckerState =
|
||||||
|
context.watch<OutdatedServerCheckerBloc>().state;
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
child = EmptyPagePlaceholder(
|
child = EmptyPagePlaceholder(
|
||||||
showReadyCard: true,
|
showReadyCard: true,
|
||||||
|
@ -93,6 +98,18 @@ class UsersPage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (outdatedServerCheckerState
|
||||||
|
is OutdatedServerCheckerOutdated) ...[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: ServerOutdatedCard(
|
||||||
|
requiredVersion:
|
||||||
|
outdatedServerCheckerState.requiredVersion.toString(),
|
||||||
|
currentVersion:
|
||||||
|
outdatedServerCheckerState.currentVersion.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: FilledButton.tonal(
|
child: FilledButton.tonal(
|
||||||
|
|
Loading…
Reference in a new issue