💄 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:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
part 'friends_overview.g.dart';
|
part 'friends_overview.g.dart';
|
||||||
|
|
||||||
@@ -50,8 +51,9 @@ class FriendsOverviewWidget extends HookConsumerWidget {
|
|||||||
return friendsOverviewAsync.when(
|
return friendsOverviewAsync.when(
|
||||||
data: (friends) {
|
data: (friends) {
|
||||||
// Filter for online friends
|
// Filter for online friends
|
||||||
final onlineFriends =
|
final onlineFriends = friends
|
||||||
friends.where((friend) => friend.status.isOnline).toList();
|
.where((friend) => friend.status.isOnline)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (onlineFriends.isEmpty && hideWhenEmpty) {
|
if (onlineFriends.isEmpty && hideWhenEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -62,12 +64,23 @@ class FriendsOverviewWidget extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
spacing: 8,
|
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.group),
|
Icon(
|
||||||
Text('friendsOnline').tr(),
|
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)
|
if (onlineFriends.isEmpty)
|
||||||
Container(
|
Container(
|
||||||
height: 80,
|
height: 80,
|
||||||
@@ -105,16 +118,115 @@ class FriendsOverviewWidget extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
loading:
|
loading: () {
|
||||||
() => const SizedBox(
|
final card = Card(
|
||||||
height: 80,
|
margin: EdgeInsets.zero,
|
||||||
child: Center(child: CircularProgressIndicator()),
|
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
|
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 {
|
class _FriendTile extends ConsumerWidget {
|
||||||
final SnFriendOverviewItem friend;
|
final SnFriendOverviewItem friend;
|
||||||
|
|
||||||
@@ -141,10 +253,10 @@ class _FriendTile extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 24,
|
radius: 24,
|
||||||
backgroundImage:
|
backgroundImage: uri != null
|
||||||
uri != null ? CachedNetworkImageProvider(uri) : null,
|
? CachedNetworkImageProvider(uri)
|
||||||
child:
|
: null,
|
||||||
uri == null
|
child: uri == null
|
||||||
? Text(
|
? Text(
|
||||||
friend.account.nick.isNotEmpty
|
friend.account.nick.isNotEmpty
|
||||||
? friend.account.nick[0].toUpperCase()
|
? friend.account.nick[0].toUpperCase()
|
||||||
@@ -163,16 +275,13 @@ class _FriendTile extends ConsumerWidget {
|
|||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: friend.activities.isNotEmpty
|
||||||
friend.activities.isNotEmpty
|
|
||||||
? Colors.blue.withOpacity(0.8)
|
? Colors.blue.withOpacity(0.8)
|
||||||
: Colors.green,
|
: Colors.green,
|
||||||
shape:
|
shape: friend.activities.isNotEmpty
|
||||||
friend.activities.isNotEmpty
|
|
||||||
? BoxShape.rectangle
|
? BoxShape.rectangle
|
||||||
: BoxShape.circle,
|
: BoxShape.circle,
|
||||||
borderRadius:
|
borderRadius: friend.activities.isNotEmpty
|
||||||
friend.activities.isNotEmpty
|
|
||||||
? BorderRadius.circular(4)
|
? BorderRadius.circular(4)
|
||||||
: null,
|
: null,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
@@ -180,8 +289,7 @@ class _FriendTile extends ConsumerWidget {
|
|||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child:
|
child: friend.activities.isNotEmpty
|
||||||
friend.activities.isNotEmpty
|
|
||||||
? Icon(
|
? Icon(
|
||||||
Symbols.play_arrow,
|
Symbols.play_arrow,
|
||||||
size: 10,
|
size: 10,
|
||||||
|
|||||||
@@ -94,9 +94,19 @@ class PostFeaturedList extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.highlight),
|
Icon(
|
||||||
const Text('highlightPost').tr(),
|
Symbols.highlight,
|
||||||
Spacer(),
|
size: 20,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'highlightPost'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
|
|||||||
@@ -1401,10 +1401,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "48c11d0943b93b6fb29103d956ff89aafeae48f6058a3939649be2093dcff0bf"
|
sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.1"
|
version: "4.7.2"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ dependencies:
|
|||||||
livekit_client: ^2.5.4
|
livekit_client: ^2.5.4
|
||||||
pasteboard: ^0.4.0
|
pasteboard: ^0.4.0
|
||||||
flutter_colorpicker: ^1.1.0
|
flutter_colorpicker: ^1.1.0
|
||||||
image: ^4.7.1
|
image: ^4.7.2
|
||||||
record: ^6.1.2
|
record: ^6.1.2
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
flutter_otp_text_field: ^1.5.1+1
|
flutter_otp_text_field: ^1.5.1+1
|
||||||
|
|||||||
Reference in New Issue
Block a user