refactor: Replace UsersCubit with UsersBloc

pull/440/head
Inex Code 2024-02-09 18:01:05 +03:00
parent e5f00f8770
commit 455b1ed7f9
14 changed files with 320 additions and 228 deletions

View File

@ -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/server_jobs/server_jobs_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/cubit/app_settings/app_settings_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_installation/server_installation_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 {
const BlocAndProviderConfig({super.key, this.child});
@ -27,7 +27,7 @@ class BlocAndProviderConfig extends StatefulWidget {
class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
late final ServerInstallationCubit serverInstallationCubit;
late final SupportSystemCubit supportSystemCubit;
late final UsersCubit usersCubit;
late final UsersBloc usersBloc;
late final ServicesBloc servicesBloc;
late final BackupsBloc backupsBloc;
late final DnsRecordsCubit dnsRecordsCubit;
@ -43,7 +43,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
super.initState();
serverInstallationCubit = ServerInstallationCubit()..load();
supportSystemCubit = SupportSystemCubit();
usersCubit = UsersCubit();
usersBloc = UsersBloc();
servicesBloc = ServicesBloc();
backupsBloc = BackupsBloc();
dnsRecordsCubit = DnsRecordsCubit();
@ -77,7 +77,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
lazy: false,
),
BlocProvider(
create: (final _) => usersCubit,
create: (final _) => usersBloc,
lazy: false,
),
BlocProvider(
@ -105,7 +105,6 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
BlocProvider(create: (final _) => volumesBloc),
BlocProvider(
create: (final _) => JobsCubit(
usersCubit: usersCubit,
servicesBloc: servicesBloc,
),
),

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart';
@ -13,16 +15,25 @@ class ConnectionStatusBloc
connectionStatus: ConnectionStatus.nonexistent,
),
) {
on<ConnectionStatusChanged>((final event, final emit) {
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
});
final apiConnectionRepository = getIt<ApiConnectionRepository>();
apiConnectionRepository.connectionStatusStream.listen(
_apiConnectionStatusSubscription =
apiConnectionRepository.connectionStatusStream.listen(
(final ConnectionStatus connectionStatus) {
add(
ConnectionStatusChanged(connectionStatus),
);
},
);
on<ConnectionStatusChanged>((final event, final emit) {
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
});
}
StreamSubscription? _apiConnectionStatusSubscription;
@override
Future<void> close() {
_apiConnectionStatusSubscription?.cancel();
return super.close();
}
}

View File

@ -3,7 +3,7 @@ part of 'devices_bloc.dart';
sealed class DevicesState extends Equatable {
DevicesState({
required final List<ApiToken> devices,
}) : _hashCode = devices.hashCode;
}) : _hashCode = Object.hashAll(devices);
final int _hashCode;

View 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();
}
}

View 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];
}

View File

@ -1,10 +1,14 @@
part of 'users_cubit.dart';
part of 'users_bloc.dart';
class UsersState extends ServerInstallationDependendState {
const UsersState(this.users, this.isLoading);
sealed class UsersState extends Equatable {
UsersState({
required final List<User> users,
}) : _hashCode = Object.hashAll(users);
final List<User> users;
final bool isLoading;
final int _hashCode;
List<User> get users =>
getIt<ApiConnectionRepository>().apiData.users.data ?? const [];
User get rootUser =>
users.firstWhere((final user) => user.type == UserType.root);
@ -15,9 +19,6 @@ class UsersState extends ServerInstallationDependendState {
List<User> get normalUsers =>
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'
/// to the beginning and sorts the rest alphabetically
///
@ -44,17 +45,29 @@ class UsersState extends ServerInstallationDependendState {
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) =>
users.any((final User user) => user.login == login);
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];
}

View File

@ -6,7 +6,6 @@ import 'package:flutter_bloc/flutter_bloc.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/bloc/services/services_bloc.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart';
export 'package:provider/provider.dart';
@ -15,12 +14,10 @@ part 'client_jobs_state.dart';
class JobsCubit extends Cubit<JobsState> {
JobsCubit({
required this.usersCubit,
required this.servicesBloc,
}) : super(JobsStateEmpty());
final ServerApi api = ServerApi();
final UsersCubit usersCubit;
final ServicesBloc servicesBloc;
void addJob(final ClientJob job) {

View File

@ -1,8 +1,8 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.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/users/users_cubit.dart';
class FieldCubitFactory {
FieldCubitFactory(this.context);
@ -27,7 +27,7 @@ class FieldCubitFactory {
),
ValidationModel(
(final String login) =>
context.read<UsersCubit>().state.isLoginRegistered(login),
context.read<UsersBloc>().state.isLoginRegistered(login),
'validations.already_exist'.tr(),
),
RequiredStringValidation('validations.required'.tr()),

View File

@ -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,
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:hive/hive.dart';
import 'package:pub_semver/pub_semver.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/hive/server_details.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/recovery_token_status.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() {
_dataStream.close();
_connectionStatusStream.close();
@ -106,6 +218,7 @@ class ApiConnectionRepository {
_apiData.recoveryKeyStatus.data =
await _apiData.recoveryKeyStatus.fetchData();
_apiData.devices.data = await _apiData.devices.fetchData();
_apiData.users.data = await _apiData.users.fetchData();
_dataStream.add(_apiData);
connectionStatus = ConnectionStatus.connected;
@ -149,6 +262,7 @@ class ApiConnectionRepository {
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.devices
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.users.refetchData(version, () => _dataStream.add(_apiData));
}
void emitData() {
@ -188,6 +302,9 @@ class ApiData {
),
devices = ApiDataElement<List<ApiToken>>(
fetchData: () async => (await api.getApiTokens()).data,
),
users = ApiDataElement<List<User>>(
fetchData: () async => api.getAllUsers(),
);
ApiDataElement<List<ServerJob>> serverJobs;
@ -198,6 +315,7 @@ class ApiData {
ApiDataElement<List<ServerDiskVolume>> volumes;
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
ApiDataElement<List<ApiToken>> devices;
ApiDataElement<List<User>> users;
}
enum ConnectionStatus {

View File

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.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/models/hive/user.dart';
import 'package:selfprivacy/logic/models/service.dart';
@ -48,7 +49,7 @@ class CreateUserJob extends ClientJob {
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.createUser(user);
await getIt<ApiConnectionRepository>().createUser(user);
}
@override
@ -64,7 +65,8 @@ class ResetUserPasswordJob extends ClientJob {
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.changeUserPassword(user, user.password!);
await getIt<ApiConnectionRepository>()
.changeUserPassword(user, user.password!);
}
@override
@ -85,7 +87,7 @@ class DeleteUserJob extends ClientJob {
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.deleteUser(user);
await getIt<ApiConnectionRepository>().deleteUser(user);
}
@override
@ -129,7 +131,7 @@ class CreateSSHKeyJob extends ClientJob {
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.addSshKey(user, publicKey);
await getIt<ApiConnectionRepository>().addSshKey(user, publicKey);
}
@override
@ -155,7 +157,7 @@ class DeleteSSHKeyJob extends ClientJob {
@override
void execute(final JobsCubit cubit) async {
await cubit.usersCubit.deleteSshKey(user, publicKey);
await getIt<ApiConnectionRepository>().deleteSshKey(user, publicKey);
}
@override

View File

@ -16,7 +16,7 @@ class NewUserPage extends StatelessWidget {
final jobCubit = context.read<JobsCubit>();
final jobState = jobCubit.state;
final users = <User>[];
users.addAll(context.read<UsersCubit>().state.users);
users.addAll(context.read<UsersBloc>().state.users);
if (jobState is JobsStateWithJobs) {
final jobs = jobState.clientJobList;
for (final job in jobs) {

View File

@ -15,7 +15,7 @@ class UserDetailsPage extends StatelessWidget {
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,
orElse: () => const User(
type: UserType.normal,

View File

@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.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/forms/factories/field_cubit_factory.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/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/job.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
@ -49,18 +49,18 @@ class UsersPage extends StatelessWidget {
iconData: BrandIcons.users,
);
} else {
child = BlocBuilder<UsersCubit, UsersState>(
child = BlocBuilder<UsersBloc, UsersState>(
builder: (final BuildContext context, final UsersState state) {
final users = state.orderedUsers;
if (users.isEmpty) {
if (state.isLoading) {
if (state is UsersRefreshing) {
return const Center(
child: CircularProgressIndicator(),
);
}
return RefreshIndicator(
onRefresh: () async {
await context.read<UsersCubit>().refresh();
await context.read<UsersBloc>().refresh();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
@ -76,7 +76,7 @@ class UsersPage extends StatelessWidget {
const SizedBox(height: 18),
BrandOutlinedButton(
onPressed: () {
context.read<UsersCubit>().refresh();
context.read<UsersBloc>().refresh();
},
title: 'users.refresh_users'.tr(),
),
@ -88,7 +88,7 @@ class UsersPage extends StatelessWidget {
}
return RefreshIndicator(
onRefresh: () async {
await context.read<UsersCubit>().refresh();
await context.read<UsersBloc>().refresh();
},
child: Column(
children: [