💄 Unifined layout of dashboard cards
This commit is contained in:
@@ -12,6 +12,7 @@ import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
part 'friends_overview.g.dart';
|
||||
|
||||
@@ -50,8 +51,9 @@ class FriendsOverviewWidget extends HookConsumerWidget {
|
||||
return friendsOverviewAsync.when(
|
||||
data: (friends) {
|
||||
// Filter for online friends
|
||||
final onlineFriends =
|
||||
friends.where((friend) => friend.status.isOnline).toList();
|
||||
final onlineFriends = friends
|
||||
.where((friend) => friend.status.isOnline)
|
||||
.toList();
|
||||
|
||||
if (onlineFriends.isEmpty && hideWhenEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
@@ -62,12 +64,23 @@ class FriendsOverviewWidget extends HookConsumerWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.group),
|
||||
Text('friendsOnline').tr(),
|
||||
Icon(
|
||||
Symbols.group,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'friendsOnline'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16).height(48),
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
if (onlineFriends.isEmpty)
|
||||
Container(
|
||||
height: 80,
|
||||
@@ -105,16 +118,115 @@ class FriendsOverviewWidget extends HookConsumerWidget {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 80,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
loading: () {
|
||||
final card = Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.group,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'friendsOnline'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
SizedBox(
|
||||
height: 80,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 4),
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: List.generate(
|
||||
4,
|
||||
(index) => const SkeletonFriendTile(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget result = Skeletonizer(child: card);
|
||||
if (padding != null) {
|
||||
result = Padding(padding: padding!, child: result);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
error: (error, stack) => const SizedBox.shrink(), // Hide on error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SkeletonFriendTile extends StatelessWidget {
|
||||
const SkeletonFriendTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 60,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Avatar with online indicator
|
||||
Stack(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: Text(
|
||||
'A',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Online indicator - green dot for skeleton
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(4),
|
||||
// Name placeholder
|
||||
Text(
|
||||
'Friend',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
).center();
|
||||
}
|
||||
}
|
||||
|
||||
class _FriendTile extends ConsumerWidget {
|
||||
final SnFriendOverviewItem friend;
|
||||
|
||||
@@ -141,19 +253,19 @@ class _FriendTile extends ConsumerWidget {
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundImage:
|
||||
uri != null ? CachedNetworkImageProvider(uri) : null,
|
||||
child:
|
||||
uri == null
|
||||
? Text(
|
||||
friend.account.nick.isNotEmpty
|
||||
? friend.account.nick[0].toUpperCase()
|
||||
: friend.account.name[0].toUpperCase(),
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
backgroundImage: uri != null
|
||||
? CachedNetworkImageProvider(uri)
|
||||
: null,
|
||||
child: uri == null
|
||||
? Text(
|
||||
friend.account.nick.isNotEmpty
|
||||
? friend.account.nick[0].toUpperCase()
|
||||
: friend.account.name[0].toUpperCase(),
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
// Online indicator - show play arrow if user has activities, otherwise green dot
|
||||
Positioned(
|
||||
@@ -163,32 +275,28 @@ class _FriendTile extends ConsumerWidget {
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
friend.activities.isNotEmpty
|
||||
? Colors.blue.withOpacity(0.8)
|
||||
: Colors.green,
|
||||
shape:
|
||||
friend.activities.isNotEmpty
|
||||
? BoxShape.rectangle
|
||||
: BoxShape.circle,
|
||||
borderRadius:
|
||||
friend.activities.isNotEmpty
|
||||
? BorderRadius.circular(4)
|
||||
: null,
|
||||
color: friend.activities.isNotEmpty
|
||||
? Colors.blue.withOpacity(0.8)
|
||||
: Colors.green,
|
||||
shape: friend.activities.isNotEmpty
|
||||
? BoxShape.rectangle
|
||||
: BoxShape.circle,
|
||||
borderRadius: friend.activities.isNotEmpty
|
||||
? BorderRadius.circular(4)
|
||||
: null,
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.surface,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child:
|
||||
friend.activities.isNotEmpty
|
||||
? Icon(
|
||||
Symbols.play_arrow,
|
||||
size: 10,
|
||||
color: Colors.white,
|
||||
fill: 1,
|
||||
)
|
||||
: null,
|
||||
child: friend.activities.isNotEmpty
|
||||
? Icon(
|
||||
Symbols.play_arrow,
|
||||
size: 10,
|
||||
color: Colors.white,
|
||||
fill: 1,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -94,9 +94,19 @@ class PostFeaturedList extends HookConsumerWidget {
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.highlight),
|
||||
const Text('highlightPost').tr(),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Symbols.highlight,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'highlightPost'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
|
||||
@@ -1401,10 +1401,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "48c11d0943b93b6fb29103d956ff89aafeae48f6058a3939649be2093dcff0bf"
|
||||
sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.1"
|
||||
version: "4.7.2"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -102,7 +102,7 @@ dependencies:
|
||||
livekit_client: ^2.5.4
|
||||
pasteboard: ^0.4.0
|
||||
flutter_colorpicker: ^1.1.0
|
||||
image: ^4.7.1
|
||||
image: ^4.7.2
|
||||
record: ^6.1.2
|
||||
qr_flutter: ^4.1.0
|
||||
flutter_otp_text_field: ^1.5.1+1
|
||||
|
||||
Reference in New Issue
Block a user