feat: Show skeleton animations during users load

This commit is contained in:
Inex Code 2025-03-26 03:32:39 +03:00
parent e40cc1da90
commit 9b5fe26251
No known key found for this signature in database
4 changed files with 51 additions and 29 deletions
lib
logic
bloc/users
models/hive
ui
molecules/list_items
pages/users

View file

@ -77,11 +77,11 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
case ConnectionStatus.nonexistent:
emit(UsersInitial());
break;
case ConnectionStatus.reconnecting:
case ConnectionStatus.connected:
if (state is! UsersLoaded) {
emit(UsersRefreshing(users: state.users));
}
case ConnectionStatus.reconnecting:
case ConnectionStatus.offline:
case ConnectionStatus.unauthorized:
break;

View file

@ -27,6 +27,15 @@ class User extends Equatable {
isFoundOnServer: true,
);
const User.fake({
this.login = 'fake_username',
this.type = UserType.normal,
this.password = 'fake',
this.sshKeys = const [],
this.isFoundOnServer = true,
this.note,
});
@HiveField(0)
final String login;

View file

@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/ui/router/router.dart';
import 'package:skeletonizer/skeletonizer.dart';
class UserListItem extends StatelessWidget {
const UserListItem({
@ -19,9 +20,11 @@ class UserListItem extends StatelessWidget {
onTap: () {
context.pushRoute(UserDetailsRoute(login: user.login));
},
leading: CircleAvatar(
child: Text(
user.login[0].toUpperCase(),
leading: Skeleton.leaf(
child: CircleAvatar(
child: Text(
user.login[0].toUpperCase(),
),
),
),
trailing: isPrimaryUser

View file

@ -14,6 +14,7 @@ import 'package:selfprivacy/ui/molecules/placeholders/empty_page_placeholder.dar
import 'package:selfprivacy/ui/organisms/headers/brand_header.dart';
import 'package:selfprivacy/ui/router/router.dart';
import 'package:selfprivacy/utils/breakpoints.dart';
import 'package:skeletonizer/skeletonizer.dart';
@RoutePage()
class UsersPage extends StatelessWidget {
@ -39,12 +40,11 @@ class UsersPage extends StatelessWidget {
context.watch<OutdatedServerCheckerBloc>().state;
final users = state.orderedUsers;
if (users.isEmpty) {
if (state is UsersRefreshing) {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
final isLoading = users.isEmpty &&
(state is UsersRefreshing || state is UsersInitial);
final fakeUsers =
List.generate(7, (final int index) => const User.fake());
if (users.isEmpty && !isLoading) {
return const _UsersNotLoaded();
}
return RefreshIndicator(
@ -70,33 +70,43 @@ class UsersPage extends StatelessWidget {
horizontal: 16.0,
vertical: 8.0,
),
child: FilledButton.tonal(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.person_add_outlined,
size: 18.0,
),
const SizedBox(width: 8),
Text('users.new_user'.tr()),
],
child: Skeletonizer(
enabled: isLoading,
enableSwitchAnimation: true,
child: FilledButton.tonal(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.person_add_outlined,
size: 18.0,
),
const SizedBox(width: 8),
Text('users.new_user'.tr()),
],
),
onPressed: () {
context.pushRoute(const NewUserRoute());
},
),
onPressed: () {
context.pushRoute(const NewUserRoute());
},
),
),
Expanded(
child: ListView.builder(
itemCount: users.length,
itemCount: isLoading ? fakeUsers.length : users.length,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemBuilder:
(final BuildContext context, final int index) =>
UserListItem(
user: users[index],
isPrimaryUser: users[index].type == UserType.primary,
Skeletonizer(
enabled: isLoading,
enableSwitchAnimation: true,
child: UserListItem(
user: isLoading ? fakeUsers[index] : users[index],
isPrimaryUser: isLoading
? index == 0
: users[index].type == UserType.primary,
),
),
),
),