♻️ Better image loading animation and more commonly used blurhash

This commit is contained in:
2026-01-02 18:32:37 +08:00
parent f1f5113b01
commit 78c1a284a5
44 changed files with 2043 additions and 2185 deletions

View File

@@ -155,7 +155,7 @@ class PublisherSelector extends StatelessWidget {
if (isReadOnly || currentPublisher == null) {
return ProfilePictureWidget(
radius: 16,
fileId: currentPublisher?.picture?.id,
file: currentPublisher?.picture,
).center().padding(right: 8);
}
@@ -179,7 +179,7 @@ class PublisherSelector extends StatelessWidget {
.map(
(e) => ProfilePictureWidget(
radius: 16,
fileId: e.value?.picture?.id,
file: e.value?.picture,
).center().padding(right: 8),
)
.toList();
@@ -355,10 +355,7 @@ class CreatorHubScreen extends HookConsumerWidget {
value: item,
child: ListTile(
minTileHeight: 48,
leading: ProfilePictureWidget(
radius: 16,
fileId: item.picture?.id,
),
leading: ProfilePictureWidget(radius: 16, file: item.picture),
title: Text(item.nick),
subtitle: Text('@${item.name}'),
trailing: currentPublisher.value?.id == item.id
@@ -889,7 +886,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: ProfilePictureWidget(
fileId: member.account!.profile.picture?.id,
file: member.account!.profile.picture,
),
title: Row(
spacing: 6,
@@ -1137,7 +1134,7 @@ class _PublisherInviteSheet extends HookConsumerWidget {
final invite = items[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: invite.publisher!.picture?.id,
file: invite.publisher!.picture,
fallbackIcon: Symbols.group,
),
title: Text(invite.publisher!.nick),

View File

@@ -69,157 +69,141 @@ class StickerPackDetailContent extends HookConsumerWidget {
}
return pack.when(
data:
(pack) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
data: (pack) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
Text(pack!.description),
Row(
spacing: 4,
children: [
Text(pack!.description),
Row(
spacing: 4,
children: [
const Icon(Symbols.folder, size: 16),
Text(
'${packContent.value?.length ?? 0}/24',
style: GoogleFonts.robotoMono(),
),
],
).opacity(0.85),
Row(
spacing: 4,
children: [
const Icon(Symbols.sell, size: 16),
Text(pack.prefix, style: GoogleFonts.robotoMono()),
],
).opacity(0.85),
Row(
spacing: 4,
children: [
const Icon(Symbols.tag, size: 16),
Flexible(
child: SelectableText(
pack.id,
maxLines: 1,
style: GoogleFonts.robotoMono(),
),
),
],
).opacity(0.85),
const Icon(Symbols.folder, size: 16),
Text(
'${packContent.value?.length ?? 0}/24',
style: GoogleFonts.robotoMono(),
),
],
).padding(horizontal: 24, vertical: 24),
const Divider(height: 1),
Expanded(
child: packContent.when(
data:
(stickers) => RefreshIndicator(
onRefresh:
() => ref.refresh(
stickerPackContentProvider(id).future,
),
child: GridView.builder(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 20,
),
gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 80,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: stickers.length,
itemBuilder: (context, index) {
final sticker = stickers[index];
return ContextMenuWidget(
menuProvider: (_) {
return Menu(
children: [
MenuAction(
title: 'stickerCopyPlaceholder'.tr(),
image: MenuImage.icon(Symbols.copy_all),
callback: () {
Clipboard.setData(
ClipboardData(
text:
':${pack.prefix}+${sticker.slug}:',
),
);
},
),
MenuSeparator(),
MenuAction(
title: 'edit'.tr(),
image: MenuImage.icon(Symbols.edit),
callback: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
titleText: 'editSticker'.tr(),
child: StickerForm(
packId: id,
id: sticker.id,
),
),
).then((value) {
if (value != null) {
ref.invalidate(
stickerPackContentProvider(id),
);
}
});
},
),
MenuAction(
title: 'delete'.tr(),
image: MenuImage.icon(Symbols.delete),
callback: () {
deleteSticker(sticker);
},
),
],
).opacity(0.85),
Row(
spacing: 4,
children: [
const Icon(Symbols.sell, size: 16),
Text(pack.prefix, style: GoogleFonts.robotoMono()),
],
).opacity(0.85),
Row(
spacing: 4,
children: [
const Icon(Symbols.tag, size: 16),
Flexible(
child: SelectableText(
pack.id,
maxLines: 1,
style: GoogleFonts.robotoMono(),
),
),
],
).opacity(0.85),
],
).padding(horizontal: 24, vertical: 24),
const Divider(height: 1),
Expanded(
child: packContent.when(
data: (stickers) => RefreshIndicator(
onRefresh: () =>
ref.refresh(stickerPackContentProvider(id).future),
child: GridView.builder(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 20,
),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 80,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: stickers.length,
itemBuilder: (context, index) {
final sticker = stickers[index];
return ContextMenuWidget(
menuProvider: (_) {
return Menu(
children: [
MenuAction(
title: 'stickerCopyPlaceholder'.tr(),
image: MenuImage.icon(Symbols.copy_all),
callback: () {
Clipboard.setData(
ClipboardData(
text: ':${pack.prefix}+${sticker.slug}:',
),
);
},
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
child: Container(
decoration: BoxDecoration(
color:
Theme.of(
context,
).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all(
Radius.circular(8),
),
MenuSeparator(),
MenuAction(
title: 'edit'.tr(),
image: MenuImage.icon(Symbols.edit),
callback: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SheetScaffold(
titleText: 'editSticker'.tr(),
child: StickerForm(
packId: id,
id: sticker.id,
),
),
child: CloudImageWidget(
fileId: sticker.image.id,
fit: BoxFit.contain,
),
),
),
);
},
).then((value) {
if (value != null) {
ref.invalidate(
stickerPackContentProvider(id),
);
}
});
},
),
MenuAction(
title: 'delete'.tr(),
image: MenuImage.icon(Symbols.delete),
callback: () {
deleteSticker(sticker);
},
),
],
);
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
child: Container(
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: CloudImageWidget(
file: sticker.image,
fit: BoxFit.contain,
),
),
),
error:
(err, _) =>
Text(
'Error: $err',
).textAlignment(TextAlign.center).center(),
loading: () => const CircularProgressIndicator().center(),
);
},
),
),
],
error: (err, _) =>
Text('Error: $err').textAlignment(TextAlign.center).center(),
loading: () => const CircularProgressIndicator().center(),
),
),
error:
(err, _) =>
Text('Error: $err').textAlignment(TextAlign.center).center(),
],
),
error: (err, _) =>
Text('Error: $err').textAlignment(TextAlign.center).center(),
loading: () => const CircularProgressIndicator().center(),
);
}
@@ -241,65 +225,60 @@ class StickerPackActionMenu extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return PopupMenuButton(
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
itemBuilder:
(context) => [
PopupMenuItem(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
titleText: 'editStickerPack'.tr(),
child: StickerPackForm(
pubName: pubName,
packId: packId,
),
),
).then((value) {
if (value != null) {
ref.invalidate(stickerPackProvider(packId));
}
});
},
child: Row(
children: [
Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
const Gap(12),
const Text('editStickerPack').tr(),
],
itemBuilder: (context) => [
PopupMenuItem(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SheetScaffold(
titleText: 'editStickerPack'.tr(),
child: StickerPackForm(pubName: pubName, packId: packId),
),
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.delete, color: Colors.red),
const Gap(12),
const Text(
'deleteStickerPack',
style: TextStyle(color: Colors.red),
).tr(),
],
).then((value) {
if (value != null) {
ref.invalidate(stickerPackProvider(packId));
}
});
},
child: Row(
children: [
Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
onTap: () {
showConfirmAlert(
'deleteStickerPackHint'.tr(),
'deleteStickerPack'.tr(),
isDanger: true,
).then((confirm) {
if (confirm) {
final client = ref.watch(apiClientProvider);
client.delete('/sphere/stickers/$packId');
ref.invalidate(stickerPacksProvider);
if (context.mounted) context.pop(true);
}
});
},
),
],
const Gap(12),
const Text('editStickerPack').tr(),
],
),
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.delete, color: Colors.red),
const Gap(12),
const Text(
'deleteStickerPack',
style: TextStyle(color: Colors.red),
).tr(),
],
),
onTap: () {
showConfirmAlert(
'deleteStickerPackHint'.tr(),
'deleteStickerPack'.tr(),
isDanger: true,
).then((confirm) {
if (confirm) {
final client = ref.watch(apiClientProvider);
client.delete('/sphere/stickers/$packId');
ref.invalidate(stickerPacksProvider);
if (context.mounted) context.pop(true);
}
});
},
),
],
);
}
}
@@ -372,10 +351,9 @@ class StickerForm extends HookConsumerWidget {
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child:
(image.value?.isEmpty ?? true)
? const SizedBox.shrink()
: CloudImageWidget(fileId: image.value!),
child: (image.value?.isEmpty ?? true)
? const SizedBox.shrink()
: CloudImageWidget(fileId: image.value!),
),
),
),
@@ -383,10 +361,8 @@ class StickerForm extends HookConsumerWidget {
onPressed: () {
showModalBottomSheet(
context: context,
builder:
(context) => CloudFilePicker(
allowedTypes: {UniversalFileType.image},
),
builder: (context) =>
CloudFilePicker(allowedTypes: {UniversalFileType.image}),
).then((value) {
if (value == null) return;
image.value = value[0].id;
@@ -412,8 +388,8 @@ class StickerForm extends HookConsumerWidget {
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
],
),