mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-23 17:26:35 +00:00
refactor: Replace UsersCubit with UsersBloc
This commit is contained in:
parent
e5f00f8770
commit
455b1ed7f9
|
@ -6,6 +6,7 @@ import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
|
@ -13,7 +14,6 @@ import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
|
||||||
|
|
||||||
class BlocAndProviderConfig extends StatefulWidget {
|
class BlocAndProviderConfig extends StatefulWidget {
|
||||||
const BlocAndProviderConfig({super.key, this.child});
|
const BlocAndProviderConfig({super.key, this.child});
|
||||||
|
@ -27,7 +27,7 @@ class BlocAndProviderConfig extends StatefulWidget {
|
||||||
class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
late final ServerInstallationCubit serverInstallationCubit;
|
late final ServerInstallationCubit serverInstallationCubit;
|
||||||
late final SupportSystemCubit supportSystemCubit;
|
late final SupportSystemCubit supportSystemCubit;
|
||||||
late final UsersCubit usersCubit;
|
late final UsersBloc usersBloc;
|
||||||
late final ServicesBloc servicesBloc;
|
late final ServicesBloc servicesBloc;
|
||||||
late final BackupsBloc backupsBloc;
|
late final BackupsBloc backupsBloc;
|
||||||
late final DnsRecordsCubit dnsRecordsCubit;
|
late final DnsRecordsCubit dnsRecordsCubit;
|
||||||
|
@ -43,7 +43,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
super.initState();
|
super.initState();
|
||||||
serverInstallationCubit = ServerInstallationCubit()..load();
|
serverInstallationCubit = ServerInstallationCubit()..load();
|
||||||
supportSystemCubit = SupportSystemCubit();
|
supportSystemCubit = SupportSystemCubit();
|
||||||
usersCubit = UsersCubit();
|
usersBloc = UsersBloc();
|
||||||
servicesBloc = ServicesBloc();
|
servicesBloc = ServicesBloc();
|
||||||
backupsBloc = BackupsBloc();
|
backupsBloc = BackupsBloc();
|
||||||
dnsRecordsCubit = DnsRecordsCubit();
|
dnsRecordsCubit = DnsRecordsCubit();
|
||||||
|
@ -77,7 +77,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
lazy: false,
|
lazy: false,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => usersCubit,
|
create: (final _) => usersBloc,
|
||||||
lazy: false,
|
lazy: false,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
|
@ -105,7 +105,6 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
BlocProvider(create: (final _) => volumesBloc),
|
BlocProvider(create: (final _) => volumesBloc),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => JobsCubit(
|
create: (final _) => JobsCubit(
|
||||||
usersCubit: usersCubit,
|
|
||||||
servicesBloc: servicesBloc,
|
servicesBloc: servicesBloc,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
@ -13,16 +15,25 @@ class ConnectionStatusBloc
|
||||||
connectionStatus: ConnectionStatus.nonexistent,
|
connectionStatus: ConnectionStatus.nonexistent,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
on<ConnectionStatusChanged>((final event, final emit) {
|
||||||
|
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
|
||||||
|
});
|
||||||
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
apiConnectionRepository.connectionStatusStream.listen(
|
_apiConnectionStatusSubscription =
|
||||||
|
apiConnectionRepository.connectionStatusStream.listen(
|
||||||
(final ConnectionStatus connectionStatus) {
|
(final ConnectionStatus connectionStatus) {
|
||||||
add(
|
add(
|
||||||
ConnectionStatusChanged(connectionStatus),
|
ConnectionStatusChanged(connectionStatus),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
on<ConnectionStatusChanged>((final event, final emit) {
|
}
|
||||||
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
|
|
||||||
});
|
StreamSubscription? _apiConnectionStatusSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiConnectionStatusSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ part of 'devices_bloc.dart';
|
||||||
sealed class DevicesState extends Equatable {
|
sealed class DevicesState extends Equatable {
|
||||||
DevicesState({
|
DevicesState({
|
||||||
required final List<ApiToken> devices,
|
required final List<ApiToken> devices,
|
||||||
}) : _hashCode = devices.hashCode;
|
}) : _hashCode = Object.hashAll(devices);
|
||||||
|
|
||||||
final int _hashCode;
|
final int _hashCode;
|
||||||
|
|
||||||
|
|
105
lib/logic/bloc/users/users_bloc.dart
Normal file
105
lib/logic/bloc/users/users_bloc.dart
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
|
||||||
|
part 'users_event.dart';
|
||||||
|
part 'users_state.dart';
|
||||||
|
|
||||||
|
class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||||
|
UsersBloc() : super(UsersInitial()) {
|
||||||
|
on<UsersListChanged>(
|
||||||
|
_updateList,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<UsersListRefresh>(
|
||||||
|
_reload,
|
||||||
|
transformer: droppable(),
|
||||||
|
);
|
||||||
|
on<UsersConnectionStatusChanged>(
|
||||||
|
_mapConnectionStatusChangedToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiConnectionStatusSubscription =
|
||||||
|
apiConnectionRepository.connectionStatusStream.listen(
|
||||||
|
(final ConnectionStatus connectionStatus) {
|
||||||
|
add(
|
||||||
|
UsersConnectionStatusChanged(connectionStatus),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
add(
|
||||||
|
UsersListChanged(apiData.users.data ?? []),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateList(
|
||||||
|
final UsersListChanged event,
|
||||||
|
final Emitter<UsersState> emit,
|
||||||
|
) async {
|
||||||
|
if (event.users.isEmpty) {
|
||||||
|
emit(UsersInitial());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final newState = UsersLoaded(
|
||||||
|
users: event.users,
|
||||||
|
);
|
||||||
|
emit(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.users.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _reload(
|
||||||
|
final UsersListRefresh event,
|
||||||
|
final Emitter<UsersState> emit,
|
||||||
|
) async {
|
||||||
|
emit(UsersRefreshing(users: state.users));
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapConnectionStatusChangedToState(
|
||||||
|
final UsersConnectionStatusChanged event,
|
||||||
|
final Emitter<UsersState> emit,
|
||||||
|
) async {
|
||||||
|
switch (event.connectionStatus) {
|
||||||
|
case ConnectionStatus.nonexistent:
|
||||||
|
emit(UsersInitial());
|
||||||
|
break;
|
||||||
|
case ConnectionStatus.connected:
|
||||||
|
if (state is! UsersLoaded) {
|
||||||
|
emit(UsersRefreshing(users: state.users));
|
||||||
|
}
|
||||||
|
case ConnectionStatus.reconnecting:
|
||||||
|
case ConnectionStatus.offline:
|
||||||
|
case ConnectionStatus.unauthorized:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
StreamSubscription? _apiConnectionStatusSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<UsersState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
_apiConnectionStatusSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
30
lib/logic/bloc/users/users_event.dart
Normal file
30
lib/logic/bloc/users/users_event.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
part of 'users_bloc.dart';
|
||||||
|
|
||||||
|
sealed class UsersEvent extends Equatable {
|
||||||
|
const UsersEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersListChanged extends UsersEvent {
|
||||||
|
const UsersListChanged(this.users);
|
||||||
|
|
||||||
|
final List<User> users;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [users];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersListRefresh extends UsersEvent {
|
||||||
|
const UsersListRefresh();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersConnectionStatusChanged extends UsersEvent {
|
||||||
|
const UsersConnectionStatusChanged(this.connectionStatus);
|
||||||
|
|
||||||
|
final ConnectionStatus connectionStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [connectionStatus];
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
part of 'users_cubit.dart';
|
part of 'users_bloc.dart';
|
||||||
|
|
||||||
class UsersState extends ServerInstallationDependendState {
|
sealed class UsersState extends Equatable {
|
||||||
const UsersState(this.users, this.isLoading);
|
UsersState({
|
||||||
|
required final List<User> users,
|
||||||
|
}) : _hashCode = Object.hashAll(users);
|
||||||
|
|
||||||
final List<User> users;
|
final int _hashCode;
|
||||||
final bool isLoading;
|
|
||||||
|
List<User> get users =>
|
||||||
|
getIt<ApiConnectionRepository>().apiData.users.data ?? const [];
|
||||||
|
|
||||||
User get rootUser =>
|
User get rootUser =>
|
||||||
users.firstWhere((final user) => user.type == UserType.root);
|
users.firstWhere((final user) => user.type == UserType.root);
|
||||||
|
@ -15,9 +19,6 @@ class UsersState extends ServerInstallationDependendState {
|
||||||
List<User> get normalUsers =>
|
List<User> get normalUsers =>
|
||||||
users.where((final user) => user.type == UserType.normal).toList();
|
users.where((final user) => user.type == UserType.normal).toList();
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [users, isLoading];
|
|
||||||
|
|
||||||
/// Makes a copy of existing users list, but places 'primary'
|
/// Makes a copy of existing users list, but places 'primary'
|
||||||
/// to the beginning and sorts the rest alphabetically
|
/// to the beginning and sorts the rest alphabetically
|
||||||
///
|
///
|
||||||
|
@ -44,17 +45,29 @@ class UsersState extends ServerInstallationDependendState {
|
||||||
return primaryUser == null ? normalUsers : [primaryUser] + normalUsers;
|
return primaryUser == null ? normalUsers : [primaryUser] + normalUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
UsersState copyWith({
|
|
||||||
final List<User>? users,
|
|
||||||
final bool? isLoading,
|
|
||||||
}) =>
|
|
||||||
UsersState(
|
|
||||||
users ?? this.users,
|
|
||||||
isLoading ?? this.isLoading,
|
|
||||||
);
|
|
||||||
|
|
||||||
bool isLoginRegistered(final String login) =>
|
bool isLoginRegistered(final String login) =>
|
||||||
users.any((final User user) => user.login == login);
|
users.any((final User user) => user.login == login);
|
||||||
|
|
||||||
bool get isEmpty => users.isEmpty;
|
bool get isEmpty => users.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UsersInitial extends UsersState {
|
||||||
|
UsersInitial() : super(users: const []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersRefreshing extends UsersState {
|
||||||
|
UsersRefreshing({required super.users});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsersLoaded extends UsersState {
|
||||||
|
UsersLoaded({required super.users});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
@ -15,12 +14,10 @@ part 'client_jobs_state.dart';
|
||||||
|
|
||||||
class JobsCubit extends Cubit<JobsState> {
|
class JobsCubit extends Cubit<JobsState> {
|
||||||
JobsCubit({
|
JobsCubit({
|
||||||
required this.usersCubit,
|
|
||||||
required this.servicesBloc,
|
required this.servicesBloc,
|
||||||
}) : super(JobsStateEmpty());
|
}) : super(JobsStateEmpty());
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
final ServerApi api = ServerApi();
|
||||||
final UsersCubit usersCubit;
|
|
||||||
final ServicesBloc servicesBloc;
|
final ServicesBloc servicesBloc;
|
||||||
|
|
||||||
void addJob(final ClientJob job) {
|
void addJob(final ClientJob job) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
|
||||||
|
|
||||||
class FieldCubitFactory {
|
class FieldCubitFactory {
|
||||||
FieldCubitFactory(this.context);
|
FieldCubitFactory(this.context);
|
||||||
|
@ -27,7 +27,7 @@ class FieldCubitFactory {
|
||||||
),
|
),
|
||||||
ValidationModel(
|
ValidationModel(
|
||||||
(final String login) =>
|
(final String login) =>
|
||||||
context.read<UsersCubit>().state.isLoginRegistered(login),
|
context.read<UsersBloc>().state.isLoginRegistered(login),
|
||||||
'validations.already_exist'.tr(),
|
'validations.already_exist'.tr(),
|
||||||
),
|
),
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
|
||||||
|
|
||||||
export 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
part 'users_state.dart';
|
|
||||||
|
|
||||||
class UsersCubit extends ServerConnectionDependentCubit<UsersState> {
|
|
||||||
UsersCubit()
|
|
||||||
: super(
|
|
||||||
const UsersState(
|
|
||||||
<User>[],
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Box<User> box = Hive.box<User>(BNames.usersBox);
|
|
||||||
Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> load() async {
|
|
||||||
final List<User> loadedUsers = box.values.toList();
|
|
||||||
if (loadedUsers.isNotEmpty) {
|
|
||||||
emit(
|
|
||||||
UsersState(
|
|
||||||
loadedUsers,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unawaited(refresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
if (getIt<ApiConnectionRepository>().connectionStatus ==
|
|
||||||
ConnectionStatus.nonexistent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit(state.copyWith(isLoading: true));
|
|
||||||
final List<User> usersFromServer = await api.getAllUsers();
|
|
||||||
if (usersFromServer.isNotEmpty) {
|
|
||||||
emit(
|
|
||||||
UsersState(
|
|
||||||
usersFromServer,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Update the users it the box
|
|
||||||
await box.clear();
|
|
||||||
await box.addAll(usersFromServer);
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_fetch_users'.tr());
|
|
||||||
emit(state.copyWith(isLoading: false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createUser(final User user) async {
|
|
||||||
// If user exists on server, do nothing
|
|
||||||
if (state.users
|
|
||||||
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String? password = user.password;
|
|
||||||
if (password == null) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_create_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If API returned error, do nothing
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.createUser(user.login, password);
|
|
||||||
if (result.data == null) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'users.could_not_create_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<User> loadedUsers = List<User>.from(state.users);
|
|
||||||
loadedUsers.add(result.data!);
|
|
||||||
await box.clear();
|
|
||||||
await box.addAll(loadedUsers);
|
|
||||||
emit(state.copyWith(users: loadedUsers));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteUser(final User user) async {
|
|
||||||
// If user is primary or root, don't delete
|
|
||||||
if (user.type != UserType.normal) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_delete_user'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<User> loadedUsers = List<User>.from(state.users);
|
|
||||||
final GenericResult result = await api.deleteUser(user.login);
|
|
||||||
if (result.success && result.data) {
|
|
||||||
loadedUsers.removeWhere((final User u) => u.login == user.login);
|
|
||||||
await box.clear();
|
|
||||||
await box.addAll(loadedUsers);
|
|
||||||
emit(state.copyWith(users: loadedUsers));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('jobs.generic_error'.tr());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.data) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> changeUserPassword(
|
|
||||||
final User user,
|
|
||||||
final String newPassword,
|
|
||||||
) async {
|
|
||||||
if (user.type == UserType.root) {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar('users.could_not_change_password'.tr());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.updateUser(user.login, newPassword);
|
|
||||||
if (result.data == null) {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
result.message ?? 'users.could_not_change_password'.tr(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addSshKey(final User user, final String publicKey) async {
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.addSshKey(user.login, publicKey);
|
|
||||||
if (result.data != null) {
|
|
||||||
final User updatedUser = result.data!;
|
|
||||||
final int index =
|
|
||||||
state.users.indexWhere((final User u) => u.login == user.login);
|
|
||||||
await box.putAt(index, updatedUser);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
users: box.values.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteSshKey(final User user, final String publicKey) async {
|
|
||||||
final GenericResult<User?> result =
|
|
||||||
await api.removeSshKey(user.login, publicKey);
|
|
||||||
if (result.data != null) {
|
|
||||||
final User updatedUser = result.data!;
|
|
||||||
final int index =
|
|
||||||
state.users.indexWhere((final User u) => u.login == user.login);
|
|
||||||
await box.putAt(index, updatedUser);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
users: box.values.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() async {
|
|
||||||
emit(
|
|
||||||
const UsersState(
|
|
||||||
<User>[],
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pub_semver/pub_semver.dart';
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
@ -8,6 +9,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.da
|
||||||
import 'package:selfprivacy/logic/models/backup.dart';
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||||
|
@ -68,6 +70,116 @@ class ApiConnectionRepository {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> createUser(final User user) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If user exists on server, do nothing
|
||||||
|
if (loadedUsers
|
||||||
|
.any((final User u) => u.login == user.login && u.isFoundOnServer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String? password = user.password;
|
||||||
|
if (password == null) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('users.could_not_create_user'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If API returned error, do nothing
|
||||||
|
final GenericResult<User?> result =
|
||||||
|
await api.createUser(user.login, password);
|
||||||
|
if (result.data == null) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'users.could_not_create_user'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_apiData.users.data?.add(result.data!);
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteUser(final User user) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If user is primary or root, don't delete
|
||||||
|
if (user.type != UserType.normal) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('users.could_not_delete_user'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final GenericResult result = await api.deleteUser(user.login);
|
||||||
|
if (result.success && result.data) {
|
||||||
|
_apiData.users.data?.removeWhere((final User u) => u.login == user.login);
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success || !result.data) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> changeUserPassword(
|
||||||
|
final User user,
|
||||||
|
final String newPassword,
|
||||||
|
) async {
|
||||||
|
if (user.type == UserType.root) {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar('users.could_not_change_password'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final GenericResult<User?> result = await api.updateUser(
|
||||||
|
user.login,
|
||||||
|
newPassword,
|
||||||
|
);
|
||||||
|
if (result.data == null) {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
result.message ?? 'users.could_not_change_password'.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addSshKey(final User user, final String publicKey) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final GenericResult<User?> result =
|
||||||
|
await api.addSshKey(user.login, publicKey);
|
||||||
|
if (result.data != null) {
|
||||||
|
final User updatedUser = result.data!;
|
||||||
|
final int index =
|
||||||
|
loadedUsers.indexWhere((final User u) => u.login == user.login);
|
||||||
|
loadedUsers[index] = updatedUser;
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
} else {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteSshKey(final User user, final String publicKey) async {
|
||||||
|
final List<User>? loadedUsers = _apiData.users.data;
|
||||||
|
if (loadedUsers == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final GenericResult<User?> result =
|
||||||
|
await api.removeSshKey(user.login, publicKey);
|
||||||
|
if (result.data != null) {
|
||||||
|
final User updatedUser = result.data!;
|
||||||
|
final int index =
|
||||||
|
loadedUsers.indexWhere((final User u) => u.login == user.login);
|
||||||
|
loadedUsers[index] = updatedUser;
|
||||||
|
_apiData.users.invalidate();
|
||||||
|
} else {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(result.message ?? 'jobs.generic_error'.tr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_dataStream.close();
|
_dataStream.close();
|
||||||
_connectionStatusStream.close();
|
_connectionStatusStream.close();
|
||||||
|
@ -106,6 +218,7 @@ class ApiConnectionRepository {
|
||||||
_apiData.recoveryKeyStatus.data =
|
_apiData.recoveryKeyStatus.data =
|
||||||
await _apiData.recoveryKeyStatus.fetchData();
|
await _apiData.recoveryKeyStatus.fetchData();
|
||||||
_apiData.devices.data = await _apiData.devices.fetchData();
|
_apiData.devices.data = await _apiData.devices.fetchData();
|
||||||
|
_apiData.users.data = await _apiData.users.fetchData();
|
||||||
_dataStream.add(_apiData);
|
_dataStream.add(_apiData);
|
||||||
|
|
||||||
connectionStatus = ConnectionStatus.connected;
|
connectionStatus = ConnectionStatus.connected;
|
||||||
|
@ -149,6 +262,7 @@ class ApiConnectionRepository {
|
||||||
.refetchData(version, () => _dataStream.add(_apiData));
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
await _apiData.devices
|
await _apiData.devices
|
||||||
.refetchData(version, () => _dataStream.add(_apiData));
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.users.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitData() {
|
void emitData() {
|
||||||
|
@ -188,6 +302,9 @@ class ApiData {
|
||||||
),
|
),
|
||||||
devices = ApiDataElement<List<ApiToken>>(
|
devices = ApiDataElement<List<ApiToken>>(
|
||||||
fetchData: () async => (await api.getApiTokens()).data,
|
fetchData: () async => (await api.getApiTokens()).data,
|
||||||
|
),
|
||||||
|
users = ApiDataElement<List<User>>(
|
||||||
|
fetchData: () async => api.getAllUsers(),
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiDataElement<List<ServerJob>> serverJobs;
|
ApiDataElement<List<ServerJob>> serverJobs;
|
||||||
|
@ -198,6 +315,7 @@ class ApiData {
|
||||||
ApiDataElement<List<ServerDiskVolume>> volumes;
|
ApiDataElement<List<ServerDiskVolume>> volumes;
|
||||||
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
|
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
|
||||||
ApiDataElement<List<ApiToken>> devices;
|
ApiDataElement<List<ApiToken>> devices;
|
||||||
|
ApiDataElement<List<User>> users;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectionStatus {
|
enum ConnectionStatus {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.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/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
|
@ -48,7 +49,7 @@ class CreateUserJob extends ClientJob {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
void execute(final JobsCubit cubit) async {
|
||||||
await cubit.usersCubit.createUser(user);
|
await getIt<ApiConnectionRepository>().createUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -64,7 +65,8 @@ class ResetUserPasswordJob extends ClientJob {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
void execute(final JobsCubit cubit) async {
|
||||||
await cubit.usersCubit.changeUserPassword(user, user.password!);
|
await getIt<ApiConnectionRepository>()
|
||||||
|
.changeUserPassword(user, user.password!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -85,7 +87,7 @@ class DeleteUserJob extends ClientJob {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
void execute(final JobsCubit cubit) async {
|
||||||
await cubit.usersCubit.deleteUser(user);
|
await getIt<ApiConnectionRepository>().deleteUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -129,7 +131,7 @@ class CreateSSHKeyJob extends ClientJob {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
void execute(final JobsCubit cubit) async {
|
||||||
await cubit.usersCubit.addSshKey(user, publicKey);
|
await getIt<ApiConnectionRepository>().addSshKey(user, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -155,7 +157,7 @@ class DeleteSSHKeyJob extends ClientJob {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void execute(final JobsCubit cubit) async {
|
void execute(final JobsCubit cubit) async {
|
||||||
await cubit.usersCubit.deleteSshKey(user, publicKey);
|
await getIt<ApiConnectionRepository>().deleteSshKey(user, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -16,7 +16,7 @@ class NewUserPage extends StatelessWidget {
|
||||||
final jobCubit = context.read<JobsCubit>();
|
final jobCubit = context.read<JobsCubit>();
|
||||||
final jobState = jobCubit.state;
|
final jobState = jobCubit.state;
|
||||||
final users = <User>[];
|
final users = <User>[];
|
||||||
users.addAll(context.read<UsersCubit>().state.users);
|
users.addAll(context.read<UsersBloc>().state.users);
|
||||||
if (jobState is JobsStateWithJobs) {
|
if (jobState is JobsStateWithJobs) {
|
||||||
final jobs = jobState.clientJobList;
|
final jobs = jobState.clientJobList;
|
||||||
for (final job in jobs) {
|
for (final job in jobs) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ class UserDetailsPage extends StatelessWidget {
|
||||||
|
|
||||||
final String domainName = UiHelpers.getDomainName(config);
|
final String domainName = UiHelpers.getDomainName(config);
|
||||||
|
|
||||||
final User user = context.watch<UsersCubit>().state.users.firstWhere(
|
final User user = context.watch<UsersBloc>().state.users.firstWhere(
|
||||||
(final User user) => user.login == login,
|
(final User user) => user.login == login,
|
||||||
orElse: () => const User(
|
orElse: () => const User(
|
||||||
type: UserType.normal,
|
type: UserType.normal,
|
||||||
|
|
|
@ -4,12 +4,12 @@ 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/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';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/user/ssh_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/user/ssh_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
|
@ -49,18 +49,18 @@ class UsersPage extends StatelessWidget {
|
||||||
iconData: BrandIcons.users,
|
iconData: BrandIcons.users,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
child = BlocBuilder<UsersCubit, UsersState>(
|
child = BlocBuilder<UsersBloc, UsersState>(
|
||||||
builder: (final BuildContext context, final UsersState state) {
|
builder: (final BuildContext context, final UsersState state) {
|
||||||
final users = state.orderedUsers;
|
final users = state.orderedUsers;
|
||||||
if (users.isEmpty) {
|
if (users.isEmpty) {
|
||||||
if (state.isLoading) {
|
if (state is UsersRefreshing) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await context.read<UsersCubit>().refresh();
|
await context.read<UsersBloc>().refresh();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
@ -76,7 +76,7 @@ class UsersPage extends StatelessWidget {
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
BrandOutlinedButton(
|
BrandOutlinedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<UsersCubit>().refresh();
|
context.read<UsersBloc>().refresh();
|
||||||
},
|
},
|
||||||
title: 'users.refresh_users'.tr(),
|
title: 'users.refresh_users'.tr(),
|
||||||
),
|
),
|
||||||
|
@ -88,7 +88,7 @@ class UsersPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await context.read<UsersCubit>().refresh();
|
await context.read<UsersBloc>().refresh();
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
Loading…
Reference in a new issue