♻️ 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

@@ -74,7 +74,7 @@ class AccountScreen extends HookConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (user.value?.profile.background?.id != null) if (user.value?.profile.background != null)
Stack( Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
@@ -112,7 +112,7 @@ class AccountScreen extends HookConsumerWidget {
Builder( Builder(
builder: (context) { builder: (context) {
final hasBackground = final hasBackground =
user.value?.profile.background?.id != null; user.value?.profile.background != null;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
spacing: hasBackground ? 0 : 16, spacing: hasBackground ? 0 : 16,

View File

@@ -74,13 +74,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final cloudFile = final cloudFile = await FileUploader.createCloudFile(
await FileUploader.createCloudFile(
ref: ref, ref: ref,
fileData: UniversalFile( fileData: UniversalFile(data: result, type: UniversalFileType.image),
data: result,
type: UniversalFileType.image,
),
).future; ).future;
if (cloudFile == null) { if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...'); throw ArgumentError('Failed to upload the file...');
@@ -188,8 +184,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
if (usernameColorType.value == 'gradient') ...{ if (usernameColorType.value == 'gradient') ...{
if (usernameColorDirection.text.isNotEmpty) if (usernameColorDirection.text.isNotEmpty)
'direction': usernameColorDirection.text, 'direction': usernameColorDirection.text,
'colors': 'colors': usernameColorColors.value
usernameColorColors.value.where((c) => c.isNotEmpty).toList(), .where((c) => c.isNotEmpty)
.toList(),
}, },
}; };
@@ -206,16 +203,14 @@ class UpdateProfileScreen extends HookConsumerWidget {
'time_zone': timeZoneController.text, 'time_zone': timeZoneController.text,
'birthday': birthday.value?.toUtc().toIso8601String(), 'birthday': birthday.value?.toUtc().toIso8601String(),
'username_color': usernameColorData, 'username_color': usernameColorData,
'links': 'links': links.value
links.value
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty) .where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
.toList(), .toList(),
}, },
); );
final userNotifier = ref.read(userInfoProvider.notifier); final userNotifier = ref.read(userInfoProvider.notifier);
userNotifier.fetchUser(); userNotifier.fetchUser();
links.value = links.value = links.value
links.value
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty) .where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
.toList(); .toList();
} catch (err) { } catch (err) {
@@ -244,10 +239,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
GestureDetector( GestureDetector(
child: Container( child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: child: user.value!.profile.background != null
user.value!.profile.background?.id != null
? CloudImageWidget( ? CloudImageWidget(
fileId: user.value!.profile.background!.id, file: user.value!.profile.background,
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
@@ -261,7 +255,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
bottom: -32, bottom: -32,
child: GestureDetector( child: GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: user.value!.profile.picture?.id, file: user.value!.profile.picture,
radius: 40, radius: 40,
), ),
onTap: () { onTap: () {
@@ -291,14 +285,14 @@ class UpdateProfileScreen extends HookConsumerWidget {
), ),
controller: usernameController, controller: usernameController,
readOnly: true, readOnly: true,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
TextFormField( TextFormField(
decoration: InputDecoration(labelText: 'nickname'.tr()), decoration: InputDecoration(labelText: 'nickname'.tr()),
controller: nicknameController, controller: nicknameController,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
DropdownButtonFormField2<String>( DropdownButtonFormField2<String>(
decoration: InputDecoration( decoration: InputDecoration(
@@ -385,8 +379,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
labelText: 'firstName'.tr(), labelText: 'firstName'.tr(),
), ),
controller: firstNameController, controller: firstNameController,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
@@ -396,8 +389,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
labelText: 'middleName'.tr(), labelText: 'middleName'.tr(),
), ),
controller: middleNameController, controller: middleNameController,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
@@ -407,8 +399,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
labelText: 'lastName'.tr(), labelText: 'lastName'.tr(),
), ),
controller: lastNameController, controller: lastNameController,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
@@ -423,8 +414,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
maxLines: null, maxLines: null,
minLines: 3, minLines: 3,
controller: bioController, controller: bioController,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
Row( Row(
spacing: 16, spacing: 16,
@@ -445,7 +436,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
onSelected: (String selection) { onSelected: (String selection) {
genderController.text = selection; genderController.text = selection;
}, },
fieldViewBuilder: ( fieldViewBuilder:
(
context, context,
controller, controller,
focusNode, focusNode,
@@ -466,9 +458,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
onChanged: (value) { onChanged: (value) {
genderController.text = value; genderController.text = value;
}, },
onTapOutside: onTapOutside: (_) => FocusManager
(_) => .instance
FocusManager.instance.primaryFocus .primaryFocus
?.unfocus(), ?.unfocus(),
); );
}, },
@@ -480,8 +472,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
labelText: 'pronouns'.tr(), labelText: 'pronouns'.tr(),
), ),
controller: pronounsController, controller: pronounsController,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
@@ -496,8 +487,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
labelText: 'location'.tr(), labelText: 'location'.tr(),
), ),
controller: locationController, controller: locationController,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
@@ -507,8 +497,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
if (textEditingValue.text.isEmpty) { if (textEditingValue.text.isEmpty) {
return const Iterable<String>.empty(); return const Iterable<String>.empty();
} }
final lowercaseQuery = final lowercaseQuery = textEditingValue.text
textEditingValue.text.toLowerCase(); .toLowerCase();
return getAvailableTz().where((tz) { return getAvailableTz().where((tz) {
return tz.toLowerCase().contains(lowercaseQuery); return tz.toLowerCase().contains(lowercaseQuery);
}); });
@@ -516,14 +506,16 @@ class UpdateProfileScreen extends HookConsumerWidget {
onSelected: (String selection) { onSelected: (String selection) {
timeZoneController.text = selection; timeZoneController.text = selection;
}, },
fieldViewBuilder: ( fieldViewBuilder:
(
context, context,
controller, controller,
focusNode, focusNode,
onFieldSubmitted, onFieldSubmitted,
) { ) {
// Sync the controller with timeZoneController when the widget is built // Sync the controller with timeZoneController when the widget is built
if (controller.text != timeZoneController.text) { if (controller.text !=
timeZoneController.text) {
controller.text = timeZoneController.text; controller.text = timeZoneController.text;
} }
@@ -540,7 +532,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
onTap: () async { onTap: () async {
try { try {
showLoadingModal(context); showLoadingModal(context);
final machineTz = await getMachineTz(); final machineTz =
await getMachineTz();
controller.text = machineTz; controller.text = machineTz;
timeZoneController.text = machineTz; timeZoneController.text = machineTz;
} finally { } finally {
@@ -569,11 +562,11 @@ class UpdateProfileScreen extends HookConsumerWidget {
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
itemCount: options.length, itemCount: options.length,
itemBuilder: ( itemBuilder:
BuildContext context, (BuildContext context, int index) {
int index, final option = options.elementAt(
) { index,
final option = options.elementAt(index); );
return ListTile( return ListTile(
title: Text( title: Text(
option, option,
@@ -644,8 +637,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainerHighest, ).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -664,9 +656,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
type: usernameColorType.value, type: usernameColorType.value,
value: value:
usernameColorType.value == 'plain' && usernameColorType.value == 'plain' &&
usernameColorValue usernameColorValue.text.isNotEmpty
.text
.isNotEmpty
? usernameColorValue.text ? usernameColorValue.text
: null, : null,
direction: direction:
@@ -724,8 +714,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
? Symbols.check_circle ? Symbols.check_circle
: Symbols.error, : Symbols.error,
size: 16, size: 16,
color: color: canUseColor
canUseColor
? Colors.green ? Colors.green
: Colors.red, : Colors.red,
), ),
@@ -736,8 +725,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
: 'upgradeRequired'.tr(), : 'upgradeRequired'.tr(),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: color: canUseColor
canUseColor
? Colors.green ? Colors.green
: Colors.red, : Colors.red,
), ),
@@ -792,7 +780,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
onSelected: (String selection) { onSelected: (String selection) {
usernameColorValue.text = selection; usernameColorValue.text = selection;
}, },
fieldViewBuilder: ( fieldViewBuilder:
(
context, context,
controller, controller,
focusNode, focusNode,
@@ -814,9 +803,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
onChanged: (value) { onChanged: (value) {
usernameColorValue.text = value; usernameColorValue.text = value;
}, },
onTapOutside: onTapOutside: (_) => FocusManager
(_) => .instance
FocusManager.instance.primaryFocus .primaryFocus
?.unfocus(), ?.unfocus(),
); );
}, },
@@ -862,8 +851,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
child: Text('gradientDirectionToTopLeft'.tr()), child: Text('gradientDirectionToTopLeft'.tr()),
), ),
], ],
value: value: usernameColorDirection.text.isNotEmpty
usernameColorDirection.text.isNotEmpty
? usernameColorDirection.text ? usernameColorDirection.text
: 'to right', : 'to right',
onChanged: (value) { onChanged: (value) {
@@ -911,20 +899,18 @@ class UpdateProfileScreen extends HookConsumerWidget {
onChanged: (value) { onChanged: (value) {
usernameColorColors.value[i] = value; usernameColorColors.value[i] = value;
}, },
onTapOutside: onTapOutside: (_) => FocusManager
(_) => .instance
FocusManager.instance.primaryFocus .primaryFocus
?.unfocus(), ?.unfocus(),
), ),
), ),
IconButton( IconButton(
icon: const Icon(Symbols.delete), icon: const Icon(Symbols.delete),
onPressed: () { onPressed: () {
usernameColorColors.value = usernameColorColors
usernameColorColors.value .value = usernameColorColors.value
.whereIndexed( .whereIndexed((idx, _) => idx != i)
(idx, _) => idx != i,
)
.toList(); .toList();
}, },
), ),
@@ -968,9 +954,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
name: value, name: value,
); );
}, },
onTapOutside: onTapOutside: (_) => FocusManager
(_) => .instance
FocusManager.instance.primaryFocus .primaryFocus
?.unfocus(), ?.unfocus(),
), ),
), ),
@@ -987,17 +973,16 @@ class UpdateProfileScreen extends HookConsumerWidget {
url: value, url: value,
); );
}, },
onTapOutside: onTapOutside: (_) => FocusManager
(_) => .instance
FocusManager.instance.primaryFocus .primaryFocus
?.unfocus(), ?.unfocus(),
), ),
), ),
IconButton( IconButton(
icon: const Icon(Symbols.delete), icon: const Icon(Symbols.delete),
onPressed: () { onPressed: () {
links.value = links.value = links.value
links.value
.whereIndexed((idx, _) => idx != i) .whereIndexed((idx, _) => idx != i)
.toList(); .toList();
}, },

View File

@@ -57,7 +57,7 @@ class _AccountBasicInfo extends StatelessWidget {
return Card( return Card(
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final hasBackground = data.profile.background?.id != null; final hasBackground = data.profile.background != null;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -962,7 +962,7 @@ class AccountProfileScreen extends HookConsumerWidget {
flexibleSpace: Stack( flexibleSpace: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: data.profile.background?.id != null child: data.profile.background != null
? CloudImageWidget( ? CloudImageWidget(
file: data.profile.background, file: data.profile.background,
) )

View File

@@ -113,7 +113,7 @@ class RelationshipListTile extends StatelessWidget {
contentPadding: const EdgeInsets.only(left: 16, right: 12), contentPadding: const EdgeInsets.only(left: 16, right: 12),
leading: AccountPfcGestureDetector( leading: AccountPfcGestureDetector(
uname: account.name, uname: account.name,
child: ProfilePictureWidget(fileId: account.profile.picture?.id), child: ProfilePictureWidget(file: account.profile.picture),
), ),
title: Row( title: Row(
spacing: 6, spacing: 6,

View File

@@ -178,7 +178,7 @@ class EditChatScreen extends HookConsumerWidget {
bottom: -32, bottom: -32,
child: GestureDetector( child: GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: picture.value?.id, file: picture.value,
radius: 40, radius: 40,
fallbackIcon: Symbols.group, fallbackIcon: Symbols.group,
), ),

View File

@@ -98,15 +98,15 @@ class PublicRoomPreview extends HookConsumerWidget {
SizedBox( SizedBox(
height: 26, height: 26,
width: 26, width: 26,
child: (room.type == 1 && room.picture?.id == null) child: (room.type == 1 && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: room.members! files: room.members!
.map((e) => e.account.profile.picture?.id) .map((e) => e.account.profile.picture)
.toList(), .toList(),
) )
: room.picture?.id != null : room.picture != null
? ProfilePictureWidget( ? ProfilePictureWidget(
fileId: room.picture?.id, file: room.picture,
fallbackIcon: Symbols.chat, fallbackIcon: Symbols.chat,
) )
: CircleAvatar( : CircleAvatar(
@@ -131,15 +131,15 @@ class PublicRoomPreview extends HookConsumerWidget {
SizedBox( SizedBox(
height: 26, height: 26,
width: 26, width: 26,
child: (room.type == 1 && room.picture?.id == null) child: (room.type == 1 && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: room.members! files: room.members!
.map((e) => e.account.profile.picture?.id) .map((e) => e.account.profile.picture)
.toList(), .toList(),
) )
: room.picture?.id != null : room.picture != null
? ProfilePictureWidget( ? ProfilePictureWidget(
fileId: room.picture?.id, file: room.picture,
fallbackIcon: Symbols.chat, fallbackIcon: Symbols.chat,
) )
: CircleAvatar( : CircleAvatar(

View File

@@ -427,15 +427,15 @@ class ChatRoomScreen extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 26, height: 26,
width: 26, width: 26,
child: (room!.type == 1 && room.picture?.id == null) child: (room!.type == 1 && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: getValidMembers( files: getValidMembers(
room.members!, room.members!,
).map((e) => e.account.profile.picture?.id).toList(), ).map((e) => e.account.profile.picture).toList(),
) )
: room.picture?.id != null : room.picture != null
? ProfilePictureWidget( ? ProfilePictureWidget(
fileId: room.picture?.id, file: room.picture,
fallbackIcon: Symbols.chat, fallbackIcon: Symbols.chat,
) )
: CircleAvatar( : CircleAvatar(
@@ -473,15 +473,15 @@ class ChatRoomScreen extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 28, height: 28,
width: 28, width: 28,
child: (room!.type == 1 && room.picture?.id == null) child: (room!.type == 1 && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: getValidMembers( files: getValidMembers(
room.members!, room.members!,
).map((e) => e.account.profile.picture?.id).toList(), ).map((e) => e.account.profile.picture).toList(),
) )
: room.picture?.id != null : room.picture != null
? ProfilePictureWidget( ? ProfilePictureWidget(
fileId: room.picture?.id, file: room.picture,
fallbackIcon: Symbols.chat, fallbackIcon: Symbols.chat,
) )
: CircleAvatar( : CircleAvatar(

View File

@@ -279,9 +279,8 @@ class ChatDetailScreen extends HookConsumerWidget {
leading: PageBackButton(shadows: [iconShadow]), leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
background: background:
(currentRoom!.type == 1 && (currentRoom!.type == 1 && currentRoom.background != null)
currentRoom.background?.id != null) ? CloudImageWidget(file: currentRoom.background!)
? CloudImageWidget(fileId: currentRoom.background!.id)
: (currentRoom.type == 1 && : (currentRoom.type == 1 &&
currentRoom.members!.length == 1 && currentRoom.members!.length == 1 &&
currentRoom currentRoom
@@ -293,17 +292,16 @@ class ChatDetailScreen extends HookConsumerWidget {
?.id != ?.id !=
null) null)
? CloudImageWidget( ? CloudImageWidget(
fileId: currentRoom file: currentRoom
.members! .members!
.first .first
.account .account
.profile .profile
.background! .background!,
.id,
) )
: currentRoom.background?.id != null : currentRoom.background != null
? CloudImageWidget( ? CloudImageWidget(
fileId: currentRoom.background!.id, file: currentRoom.background!,
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: Container( : Container(
@@ -702,7 +700,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
leading: AccountPfcGestureDetector( leading: AccountPfcGestureDetector(
uname: member.account.name, uname: member.account.name,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: member.account.profile.picture?.id, file: member.account.profile.picture,
), ),
), ),
title: Row( title: Row(

View File

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

View File

@@ -69,8 +69,7 @@ class StickerPackDetailContent extends HookConsumerWidget {
} }
return pack.when( return pack.when(
data: data: (pack) => Column(
(pack) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Column( Column(
@@ -112,19 +111,15 @@ class StickerPackDetailContent extends HookConsumerWidget {
const Divider(height: 1), const Divider(height: 1),
Expanded( Expanded(
child: packContent.when( child: packContent.when(
data: data: (stickers) => RefreshIndicator(
(stickers) => RefreshIndicator( onRefresh: () =>
onRefresh: ref.refresh(stickerPackContentProvider(id).future),
() => ref.refresh(
stickerPackContentProvider(id).future,
),
child: GridView.builder( child: GridView.builder(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 24,
vertical: 20, vertical: 20,
), ),
gridDelegate: gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 80, maxCrossAxisExtent: 80,
mainAxisSpacing: 8, mainAxisSpacing: 8,
crossAxisSpacing: 8, crossAxisSpacing: 8,
@@ -142,8 +137,7 @@ class StickerPackDetailContent extends HookConsumerWidget {
callback: () { callback: () {
Clipboard.setData( Clipboard.setData(
ClipboardData( ClipboardData(
text: text: ':${pack.prefix}+${sticker.slug}:',
':${pack.prefix}+${sticker.slug}:',
), ),
); );
}, },
@@ -156,8 +150,7 @@ class StickerPackDetailContent extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'editSticker'.tr(), titleText: 'editSticker'.tr(),
child: StickerForm( child: StickerForm(
packId: id, packId: id,
@@ -184,21 +177,16 @@ class StickerPackDetailContent extends HookConsumerWidget {
); );
}, },
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(8)),
Radius.circular(8),
),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainer, ).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(8)),
Radius.circular(8),
),
), ),
child: CloudImageWidget( child: CloudImageWidget(
fileId: sticker.image.id, file: sticker.image,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@@ -207,18 +195,14 @@ class StickerPackDetailContent extends HookConsumerWidget {
}, },
), ),
), ),
error: error: (err, _) =>
(err, _) => Text('Error: $err').textAlignment(TextAlign.center).center(),
Text(
'Error: $err',
).textAlignment(TextAlign.center).center(),
loading: () => const CircularProgressIndicator().center(), loading: () => const CircularProgressIndicator().center(),
), ),
), ),
], ],
), ),
error: error: (err, _) =>
(err, _) =>
Text('Error: $err').textAlignment(TextAlign.center).center(), Text('Error: $err').textAlignment(TextAlign.center).center(),
loading: () => const CircularProgressIndicator().center(), loading: () => const CircularProgressIndicator().center(),
); );
@@ -241,20 +225,15 @@ class StickerPackActionMenu extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PopupMenuButton( return PopupMenuButton(
icon: Icon(Icons.more_vert, shadows: [iconShadow]), icon: Icon(Icons.more_vert, shadows: [iconShadow]),
itemBuilder: itemBuilder: (context) => [
(context) => [
PopupMenuItem( PopupMenuItem(
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'editStickerPack'.tr(), titleText: 'editStickerPack'.tr(),
child: StickerPackForm( child: StickerPackForm(pubName: pubName, packId: packId),
pubName: pubName,
packId: packId,
),
), ),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
@@ -372,8 +351,7 @@ class StickerForm extends HookConsumerWidget {
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all(Radius.circular(8)), borderRadius: BorderRadius.all(Radius.circular(8)),
), ),
child: child: (image.value?.isEmpty ?? true)
(image.value?.isEmpty ?? true)
? const SizedBox.shrink() ? const SizedBox.shrink()
: CloudImageWidget(fileId: image.value!), : CloudImageWidget(fileId: image.value!),
), ),
@@ -383,10 +361,8 @@ class StickerForm extends HookConsumerWidget {
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: builder: (context) =>
(context) => CloudFilePicker( CloudFilePicker(allowedTypes: {UniversalFileType.image}),
allowedTypes: {UniversalFileType.image},
),
).then((value) { ).then((value) {
if (value == null) return; if (value == null) return;
image.value = value[0].id; image.value = value[0].id;
@@ -412,8 +388,8 @@ class StickerForm extends HookConsumerWidget {
borderRadius: BorderRadius.all(Radius.circular(12)), borderRadius: BorderRadius.all(Radius.circular(12)),
), ),
), ),
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
], ],
), ),

View File

@@ -146,7 +146,7 @@ class _AppOverview extends StatelessWidget {
left: 20, left: 20,
bottom: -32, bottom: -32,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: app.picture?.id, file: app.picture,
radius: 40, radius: 40,
fallbackIcon: Symbols.apps, fallbackIcon: Symbols.apps,
), ),

View File

@@ -153,7 +153,7 @@ class CustomAppsScreen extends HookConsumerWidget {
ListTile( ListTile(
title: Text(app.name), title: Text(app.name),
leading: ProfilePictureWidget( leading: ProfilePictureWidget(
fileId: app.picture?.id, file: app.picture,
fallbackIcon: Symbols.apps, fallbackIcon: Symbols.apps,
), ),
subtitle: Text( subtitle: Text(

View File

@@ -143,7 +143,7 @@ class _BotOverview extends StatelessWidget {
left: 20, left: 20,
bottom: -32, bottom: -32,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: bot.account.profile.picture?.id, file: bot.account.profile.picture,
radius: 40, radius: 40,
fallbackIcon: Symbols.smart_toy, fallbackIcon: Symbols.smart_toy,
), ),

View File

@@ -51,8 +51,7 @@ class EditAppScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isNew = id == null; final isNew = id == null;
final app = final app = isNew
isNew
? null ? null
: ref.watch(customAppProvider(publisherName, projectId, id!)); : ref.watch(customAppProvider(publisherName, projectId, id!));
@@ -139,13 +138,9 @@ class EditAppScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final cloudFile = final cloudFile = await FileUploader.createCloudFile(
await FileUploader.createCloudFile(
ref: ref, ref: ref,
fileData: UniversalFile( fileData: UniversalFile(data: result, type: UniversalFileType.image),
data: result,
type: UniversalFileType.image,
),
).future; ).future;
if (cloudFile == null) { if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...'); throw ArgumentError('Failed to upload the file...');
@@ -169,8 +164,7 @@ class EditAppScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'addScope'.tr(), titleText: 'addScope'.tr(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
@@ -212,8 +206,7 @@ class EditAppScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'addRedirectUri'.tr(), titleText: 'addRedirectUri'.tr(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
@@ -242,8 +235,8 @@ class EditAppScreen extends HookConsumerWidget {
} }
return null; return null;
}, },
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
FilledButton.tonalIcon( FilledButton.tonalIcon(
@@ -275,23 +268,20 @@ class EditAppScreen extends HookConsumerWidget {
'picture_id': picture.value?.id, 'picture_id': picture.value?.id,
'background_id': background.value?.id, 'background_id': background.value?.id,
'links': { 'links': {
'home_page': 'home_page': homePageController.text.isNotEmpty
homePageController.text.isNotEmpty
? homePageController.text ? homePageController.text
: null, : null,
'privacy_policy': 'privacy_policy': privacyPolicyController.text.isNotEmpty
privacyPolicyController.text.isNotEmpty
? privacyPolicyController.text ? privacyPolicyController.text
: null, : null,
'terms_of_service': 'terms_of_service': termsController.text.isNotEmpty
termsController.text.isNotEmpty ? termsController.text : null, ? termsController.text
: null,
}, },
'oauth_config': 'oauth_config': oauthEnabled.value
oauthEnabled.value
? { ? {
'redirect_uris': redirectUris.value, 'redirect_uris': redirectUris.value,
'post_logout_redirect_uris': 'post_logout_redirect_uris': postLogoutUris.value.isNotEmpty
postLogoutUris.value.isNotEmpty
? postLogoutUris.value ? postLogoutUris.value
: null, : null,
'allowed_scopes': allowedScopes.value, 'allowed_scopes': allowedScopes.value,
@@ -326,14 +316,12 @@ class EditAppScreen extends HookConsumerWidget {
} }
} }
final bodyContent = final bodyContent = app == null && !isNew
app == null && !isNew
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: app?.hasError == true && !isNew : app?.hasError == true && !isNew
? ResponseErrorWidget( ? ResponseErrorWidget(
error: app!.error, error: app!.error,
onRetry: onRetry: () => ref.invalidate(
() => ref.invalidate(
customAppProvider(publisherName, projectId, id!), customAppProvider(publisherName, projectId, id!),
), ),
) )
@@ -348,12 +336,10 @@ class EditAppScreen extends HookConsumerWidget {
children: [ children: [
GestureDetector( GestureDetector(
child: Container( child: Container(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainerHigh, ).colorScheme.surfaceContainerHigh,
child: child: background.value != null
background.value != null
? CloudFileWidget( ? CloudFileWidget(
item: background.value!, item: background.value!,
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -369,7 +355,7 @@ class EditAppScreen extends HookConsumerWidget {
bottom: -32, bottom: -32,
child: GestureDetector( child: GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: picture.value?.id, file: picture.value,
radius: 40, radius: 40,
fallbackIcon: Symbols.apps, fallbackIcon: Symbols.apps,
), ),
@@ -390,13 +376,10 @@ class EditAppScreen extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'name'.tr(), labelText: 'name'.tr(),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
), ),
), ),
), onTapOutside: (_) =>
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -406,13 +389,10 @@ class EditAppScreen extends HookConsumerWidget {
labelText: 'slug'.tr(), labelText: 'slug'.tr(),
helperText: 'slugHint'.tr(), helperText: 'slugHint'.tr(),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
), ),
), ),
), onTapOutside: (_) =>
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -422,14 +402,11 @@ class EditAppScreen extends HookConsumerWidget {
labelText: 'description'.tr(), labelText: 'description'.tr(),
alignLabelWithHint: true, alignLabelWithHint: true,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
),
), ),
), ),
maxLines: 3, maxLines: 3,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -446,8 +423,7 @@ class EditAppScreen extends HookConsumerWidget {
}, },
children: [ children: [
ExpansionPanel( ExpansionPanel(
headerBuilder: headerBuilder: (context, isExpanded) =>
(context, isExpanded) =>
ListTile(title: Text('appLinks').tr()), ListTile(title: Text('appLinks').tr()),
body: Column( body: Column(
spacing: 16, spacing: 16,
@@ -496,8 +472,7 @@ class EditAppScreen extends HookConsumerWidget {
isExpanded: enableLinks.value, isExpanded: enableLinks.value,
), ),
ExpansionPanel( ExpansionPanel(
headerBuilder: headerBuilder: (context, isExpanded) =>
(context, isExpanded) =>
ListTile(title: Text('oauthConfig').tr()), ListTile(title: Text('oauthConfig').tr()),
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -515,8 +490,8 @@ class EditAppScreen extends HookConsumerWidget {
trailing: IconButton( trailing: IconButton(
icon: const Icon(Symbols.delete), icon: const Icon(Symbols.delete),
onPressed: () { onPressed: () {
redirectUris.value = redirectUris.value = redirectUris
redirectUris.value .value
.where((u) => u != uri) .where((u) => u != uri)
.toList(); .toList();
}, },
@@ -554,9 +529,7 @@ class EditAppScreen extends HookConsumerWidget {
onPressed: () { onPressed: () {
allowedScopes.value = allowedScopes.value =
allowedScopes.value allowedScopes.value
.where( .where((s) => s != scope)
(s) => s != scope,
)
.toList(); .toList();
}, },
), ),
@@ -576,14 +549,13 @@ class EditAppScreen extends HookConsumerWidget {
SwitchListTile( SwitchListTile(
title: Text('requirePkce'.tr()), title: Text('requirePkce'.tr()),
value: requirePkce.value, value: requirePkce.value,
onChanged: onChanged: (value) =>
(value) => requirePkce.value = value, requirePkce.value = value,
), ),
SwitchListTile( SwitchListTile(
title: Text('allowOfflineAccess'.tr()), title: Text('allowOfflineAccess'.tr()),
value: allowOfflineAccess.value, value: allowOfflineAccess.value,
onChanged: onChanged: (value) =>
(value) =>
allowOfflineAccess.value = value, allowOfflineAccess.value = value,
), ),
], ],

View File

@@ -50,8 +50,9 @@ class EditBotScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isNew = id == null; final isNew = id == null;
final botData = final botData = isNew
isNew ? null : ref.watch(botProvider(publisherName, projectId, id!)); ? null
: ref.watch(botProvider(publisherName, projectId, id!));
final formKey = useMemoized(() => GlobalKey<FormState>()); final formKey = useMemoized(() => GlobalKey<FormState>());
final submitting = useState(false); final submitting = useState(false);
@@ -125,13 +126,9 @@ class EditBotScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final cloudFile = final cloudFile = await FileUploader.createCloudFile(
await FileUploader.createCloudFile(
ref: ref, ref: ref,
fileData: UniversalFile( fileData: UniversalFile(data: result, type: UniversalFileType.image),
data: result,
type: UniversalFileType.image,
),
).future; ).future;
if (cloudFile == null) { if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...'); throw ArgumentError('Failed to upload the file...');
@@ -193,16 +190,13 @@ class EditBotScreen extends HookConsumerWidget {
} }
} }
final bodyContent = final bodyContent = botData == null && !isNew
botData == null && !isNew
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: botData?.hasError == true && !isNew : botData?.hasError == true && !isNew
? ResponseErrorWidget( ? ResponseErrorWidget(
error: botData!.error, error: botData!.error,
onRetry: onRetry: () =>
() => ref.invalidate( ref.invalidate(botProvider(publisherName, projectId, id!)),
botProvider(publisherName, projectId, id!),
),
) )
: SingleChildScrollView( : SingleChildScrollView(
child: Column( child: Column(
@@ -215,12 +209,10 @@ class EditBotScreen extends HookConsumerWidget {
children: [ children: [
GestureDetector( GestureDetector(
child: Container( child: Container(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainerHigh, ).colorScheme.surfaceContainerHigh,
child: child: background.value != null
background.value != null
? CloudFileWidget( ? CloudFileWidget(
item: background.value!, item: background.value!,
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -236,7 +228,7 @@ class EditBotScreen extends HookConsumerWidget {
bottom: -32, bottom: -32,
child: GestureDetector( child: GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: picture.value?.id, file: picture.value,
radius: 40, radius: 40,
fallbackIcon: Symbols.smart_toy, fallbackIcon: Symbols.smart_toy,
), ),
@@ -257,9 +249,7 @@ class EditBotScreen extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'name'.tr(), labelText: 'name'.tr(),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
),
), ),
), ),
), ),
@@ -270,9 +260,7 @@ class EditBotScreen extends HookConsumerWidget {
labelText: 'nickname'.tr(), labelText: 'nickname'.tr(),
alignLabelWithHint: true, alignLabelWithHint: true,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
),
), ),
), ),
), ),
@@ -283,9 +271,7 @@ class EditBotScreen extends HookConsumerWidget {
labelText: 'slug'.tr(), labelText: 'slug'.tr(),
helperText: 'slugHint'.tr(), helperText: 'slugHint'.tr(),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
),
), ),
), ),
), ),
@@ -296,9 +282,7 @@ class EditBotScreen extends HookConsumerWidget {
labelText: 'bio'.tr(), labelText: 'bio'.tr(),
alignLabelWithHint: true, alignLabelWithHint: true,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
),
), ),
), ),
maxLines: 3, maxLines: 3,
@@ -432,9 +416,7 @@ class EditBotScreen extends HookConsumerWidget {
color: Theme.of(context).dividerColor, color: Theme.of(context).dividerColor,
width: 1, width: 1,
), ),
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12),
),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -447,9 +429,7 @@ class EditBotScreen extends HookConsumerWidget {
), ),
Text( Text(
birthday.value != null birthday.value != null
? DateFormat.yMMMd().format( ? DateFormat.yMMMd().format(birthday.value!)
birthday.value!,
)
: 'Select a date'.tr(), : 'Select a date'.tr(),
), ),
], ],

View File

@@ -329,7 +329,7 @@ class DeveloperSelector extends HookConsumerWidget {
minTileHeight: 48, minTileHeight: 48,
leading: ProfilePictureWidget( leading: ProfilePictureWidget(
radius: 16, radius: 16,
fileId: item.publisher?.picture?.id, file: item.publisher?.picture,
), ),
title: Text(item.publisher!.nick), title: Text(item.publisher!.nick),
subtitle: Text('@${item.publisher!.name}'), subtitle: Text('@${item.publisher!.name}'),
@@ -348,7 +348,7 @@ class DeveloperSelector extends HookConsumerWidget {
if (isReadOnly || currentDeveloper == null) { if (isReadOnly || currentDeveloper == null) {
return ProfilePictureWidget( return ProfilePictureWidget(
radius: 16, radius: 16,
fileId: currentDeveloper?.publisher?.picture?.id, file: currentDeveloper?.publisher?.picture,
).center().padding(right: 8); ).center().padding(right: 8);
} }
@@ -373,7 +373,7 @@ class DeveloperSelector extends HookConsumerWidget {
...developersMenu.map( ...developersMenu.map(
(e) => ProfilePictureWidget( (e) => ProfilePictureWidget(
radius: 16, radius: 16,
fileId: e.value?.publisher?.picture?.id, file: e.value?.publisher?.picture,
).center().padding(right: 8), ).center().padding(right: 8),
), ),
]; ];
@@ -928,7 +928,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
final publisher = items[index]; final publisher = items[index];
return ListTile( return ListTile(
leading: ProfilePictureWidget( leading: ProfilePictureWidget(
fileId: publisher.picture?.id, file: publisher.picture,
fallbackIcon: Symbols.group, fallbackIcon: Symbols.group,
), ),
title: Text(publisher.nick), title: Text(publisher.nick),

View File

@@ -37,7 +37,7 @@ class SkeletonNotificationTile extends StatelessWidget {
isThreeLine: true, isThreeLine: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: fakePfp != null leading: fakePfp != null
? ProfilePictureWidget(fileId: fakePfp, radius: 20) ? ProfilePictureWidget(file: null, radius: 20)
: CircleAvatar( : CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primaryContainer, backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Icon( child: Icon(

View File

@@ -34,13 +34,11 @@ class ArticleEditScreen extends HookConsumerWidget {
final post = ref.watch(postProvider(id)); final post = ref.watch(postProvider(id));
return post.when( return post.when(
data: (post) => ArticleComposeScreen(originalPost: post), data: (post) => ArticleComposeScreen(originalPost: post),
loading: loading: () => AppScaffold(
() => AppScaffold(
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: const Center(child: CircularProgressIndicator()), body: const Center(child: CircularProgressIndicator()),
), ),
error: error: (e, _) => AppScaffold(
(e, _) => AppScaffold(
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: Text('Error: $e', textAlign: TextAlign.center), body: Text('Error: $e', textAlign: TextAlign.center),
), ),
@@ -191,8 +189,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
MarkdownTextContent( MarkdownTextContent(
content: contentValue.text, content: contentValue.text,
textStyle: theme.textTheme.bodyMedium, textStyle: theme.textTheme.bodyMedium,
attachments: attachments: state.attachments.value
state.attachments.value
.where((e) => e.isOnCloud) .where((e) => e.isOnCloud)
.map((e) => e.data) .map((e) => e.data)
.cast<SnCloudFile>() .cast<SnCloudFile>()
@@ -290,19 +287,18 @@ class ArticleComposeScreen extends HookConsumerWidget {
onExpansionChanged: (expanded) { onExpansionChanged: (expanded) {
isAttachmentsExpanded.value = expanded; isAttachmentsExpanded.value = expanded;
}, },
collapsedBackgroundColor: collapsedBackgroundColor: Theme.of(
Theme.of(context).colorScheme.surfaceContainer, context,
).colorScheme.surfaceContainer,
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('attachments').tr(), Text('attachments').tr(),
Text( Text(
'articleAttachmentHint'.tr(), 'articleAttachmentHint'.tr(),
style: Theme.of( style: Theme.of(context).textTheme.bodySmall
context, ?.copyWith(
).textTheme.bodySmall?.copyWith( color: Theme.of(
color:
Theme.of(
context, context,
).colorScheme.onSurfaceVariant, ).colorScheme.onSurfaceVariant,
), ),
@@ -336,8 +332,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
>( >(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) =>
(context) =>
AttachmentUploaderSheet( AttachmentUploaderSheet(
ref: ref, ref: ref,
state: state, state: state,
@@ -353,21 +348,20 @@ class ArticleComposeScreen extends HookConsumerWidget {
); );
} }
}, },
onUpdate: onUpdate: (value) =>
(value) =>
ComposeLogic.updateAttachment( ComposeLogic.updateAttachment(
state, state,
value, value,
idx, idx,
), ),
onDelete: onDelete: () =>
() => ComposeLogic.deleteAttachment( ComposeLogic.deleteAttachment(
ref, ref,
state, state,
idx, idx,
), ),
onInsert: onInsert: () =>
() => ComposeLogic.insertAttachment( ComposeLogic.insertAttachment(
ref, ref,
state, state,
idx, idx,
@@ -413,10 +407,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
const SizedBox.shrink(), const SizedBox.shrink(),
IconButton( IconButton(
icon: ProfilePictureWidget( icon: ProfilePictureWidget(
fileId: state.currentPublisher.value?.picture?.id, file: state.currentPublisher.value?.picture,
radius: 12, radius: 12,
fallbackIcon: fallbackIcon: state.currentPublisher.value == null
state.currentPublisher.value == null
? Symbols.question_mark ? Symbols.question_mark
: null, : null,
), ),
@@ -448,8 +441,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
valueListenable: state.submitting, valueListenable: state.submitting,
builder: (context, submitting, _) { builder: (context, submitting, _) {
return IconButton( return IconButton(
onPressed: onPressed: submitting
submitting
? null ? null
: () => ComposeLogic.performAction( : () => ComposeLogic.performAction(
ref, ref,
@@ -457,8 +449,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
context, context,
originalPost: originalPost, originalPost: originalPost,
), ),
icon: icon: submitting
submitting
? SizedBox( ? SizedBox(
width: 28, width: 28,
height: 28, height: 28,
@@ -468,9 +459,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
), ),
).center() ).center()
: Icon( : Icon(
originalPost != null originalPost != null ? Symbols.edit : Symbols.upload,
? Symbols.edit
: Symbols.upload,
), ),
); );
}, },
@@ -483,8 +472,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: child: isWideScreen(context)
isWideScreen(context)
? Row( ? Row(
spacing: 16, spacing: 16,
children: [ children: [

View File

@@ -52,7 +52,7 @@ class _PublisherBasisWidget extends StatelessWidget {
return Card( return Card(
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final hasBackground = data.background?.id != null; final hasBackground = data.background != null;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -598,7 +598,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
flexibleSpace: Stack( flexibleSpace: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: data.background?.id != null child: data.background != null
? CloudImageWidget(file: data.background) ? CloudImageWidget(file: data.background)
: Container( : Container(
color: Theme.of( color: Theme.of(

View File

@@ -183,8 +183,8 @@ class RealmDetailScreen extends HookConsumerWidget {
flexibleSpace: Stack( flexibleSpace: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: realm!.background?.id != null child: realm!.background != null
? CloudImageWidget(fileId: realm.background!.id) ? CloudImageWidget(file: realm.background!)
: Container( : Container(
color: Theme.of( color: Theme.of(
context, context,
@@ -281,8 +281,8 @@ class RealmDetailScreen extends HookConsumerWidget {
flexibleSpace: Stack( flexibleSpace: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: realm!.background?.id != null child: realm!.background != null
? CloudImageWidget(fileId: realm.background!.id) ? CloudImageWidget(file: realm.background!)
: Container( : Container(
color: Theme.of( color: Theme.of(
context, context,
@@ -604,7 +604,7 @@ class _RealmMemberListSheet extends HookConsumerWidget {
leading: AccountPfcGestureDetector( leading: AccountPfcGestureDetector(
uname: member.account!.name, uname: member.account!.name,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: member.account!.profile.picture?.id, file: member.account!.profile.picture,
), ),
), ),
title: Row( title: Row(

View File

@@ -90,13 +90,9 @@ class EditRealmScreen extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
submitting.value = true; submitting.value = true;
try { try {
final cloudFile = final cloudFile = await FileUploader.createCloudFile(
await FileUploader.createCloudFile(
ref: ref, ref: ref,
fileData: UniversalFile( fileData: UniversalFile(data: result, type: UniversalFileType.image),
data: result,
type: UniversalFileType.image,
),
).future; ).future;
if (cloudFile == null) { if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...'); throw ArgumentError('Failed to upload the file...');
@@ -162,8 +158,7 @@ class EditRealmScreen extends HookConsumerWidget {
GestureDetector( GestureDetector(
child: Container( child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: child: background.value != null
background.value != null
? CloudFileWidget( ? CloudFileWidget(
item: background.value!, item: background.value!,
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -179,7 +174,7 @@ class EditRealmScreen extends HookConsumerWidget {
bottom: -32, bottom: -32,
child: GestureDetector( child: GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: picture.value?.id, file: picture.value,
radius: 40, radius: 40,
fallbackIcon: Symbols.group, fallbackIcon: Symbols.group,
), ),
@@ -202,15 +197,15 @@ class EditRealmScreen extends HookConsumerWidget {
labelText: 'slug'.tr(), labelText: 'slug'.tr(),
helperText: 'slugHint'.tr(), helperText: 'slugHint'.tr(),
), ),
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( TextFormField(
controller: nameController, controller: nameController,
decoration: InputDecoration(labelText: 'name'.tr()), decoration: InputDecoration(labelText: 'name'.tr()),
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( TextFormField(
@@ -221,8 +216,8 @@ class EditRealmScreen extends HookConsumerWidget {
), ),
minLines: 3, minLines: 3,
maxLines: null, maxLines: null,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Card( Card(

View File

@@ -213,7 +213,7 @@ class _RealmInviteSheet extends HookConsumerWidget {
final invite = items[index]; final invite = items[index];
return ListTile( return ListTile(
leading: ProfilePictureWidget( leading: ProfilePictureWidget(
fileId: invite.realm!.picture?.id, file: invite.realm!.picture,
fallbackIcon: Symbols.group, fallbackIcon: Symbols.group,
), ),
title: Text(invite.realm!.name), title: Text(invite.realm!.name),

View File

@@ -163,7 +163,7 @@ class MarketplaceStickerPackDetailScreen extends HookConsumerWidget {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: CloudImageWidget( child: CloudImageWidget(
fileId: sticker.image.id, file: sticker.image,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),

View File

@@ -24,8 +24,7 @@ class AccountNameplate extends HookConsumerWidget {
final user = ref.watch(accountProvider(name)); final user = ref.watch(accountProvider(name));
return Container( return Container(
decoration: decoration: isOutlined
isOutlined
? BoxDecoration( ? BoxDecoration(
border: Border.all( border: Border.all(
width: 1 / MediaQuery.of(context).devicePixelRatio, width: 1 / MediaQuery.of(context).devicePixelRatio,
@@ -40,9 +39,7 @@ class AccountNameplate extends HookConsumerWidget {
elevation: 0, elevation: 0,
color: Colors.transparent, color: Colors.transparent,
child: user.when( child: user.when(
data: data: (account) => account.profile.background != null
(account) =>
account.profile.background != null
? AspectRatio( ? AspectRatio(
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
child: Stack( child: Stack(
@@ -88,14 +85,13 @@ class AccountNameplate extends HookConsumerWidget {
children: [ children: [
// Profile picture (equivalent to leading) // Profile picture (equivalent to leading)
ProfilePictureWidget( ProfilePictureWidget(
fileId: account.profile.picture?.id, file: account.profile.picture,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
// Text content (equivalent to title and subtitle) // Text content (equivalent to title and subtitle)
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
AccountName( AccountName(
@@ -123,12 +119,10 @@ class AccountNameplate extends HookConsumerWidget {
horizontal: 16.0, horizontal: 16.0,
vertical: 8.0, vertical: 8.0,
), ),
decoration: decoration: isOutlined
isOutlined
? BoxDecoration( ? BoxDecoration(
border: Border.all( border: Border.all(
color: color: Theme.of(context).colorScheme.outline,
Theme.of(context).colorScheme.outline,
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
) )
@@ -136,9 +130,7 @@ class AccountNameplate extends HookConsumerWidget {
child: Row( child: Row(
children: [ children: [
// Profile picture (equivalent to leading) // Profile picture (equivalent to leading)
ProfilePictureWidget( ProfilePictureWidget(file: account.profile.picture),
fileId: account.profile.picture?.id,
),
const SizedBox(width: 16), const SizedBox(width: 16),
// Text content (equivalent to title and subtitle) // Text content (equivalent to title and subtitle)
Expanded( Expanded(
@@ -148,9 +140,7 @@ class AccountNameplate extends HookConsumerWidget {
children: [ children: [
AccountName( AccountName(
account: account, account: account,
style: TextStyle( style: TextStyle(fontWeight: FontWeight.bold),
fontWeight: FontWeight.bold,
),
), ),
Text('@${account.name}'), Text('@${account.name}'),
], ],
@@ -159,8 +149,7 @@ class AccountNameplate extends HookConsumerWidget {
], ],
), ),
), ),
loading: loading: () => Padding(
() => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16.0, horizontal: 16.0,
vertical: 8.0, vertical: 8.0,
@@ -185,8 +174,7 @@ class AccountNameplate extends HookConsumerWidget {
], ],
), ),
), ),
error: error: (error, stackTrace) => Padding(
(error, stackTrace) => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16.0, horizontal: 16.0,
vertical: 8.0, vertical: 8.0,

View File

@@ -62,8 +62,8 @@ class AccountPickerSheet extends HookConsumerWidget {
), ),
), ),
autofocus: true, autofocus: true,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
Expanded( Expanded(
@@ -74,14 +74,13 @@ class AccountPickerSheet extends HookConsumerWidget {
); );
return searchResult.when( return searchResult.when(
data: data: (accounts) => ListView.builder(
(accounts) => ListView.builder(
itemCount: accounts.length, itemCount: accounts.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final account = accounts[index]; final account = accounts[index];
return ListTile( return ListTile(
leading: ProfilePictureWidget( leading: ProfilePictureWidget(
fileId: account.profile.picture?.id, file: account.profile.picture,
), ),
title: Text(account.nick), title: Text(account.nick),
subtitle: Text('@${account.name}'), subtitle: Text('@${account.name}'),
@@ -89,8 +88,8 @@ class AccountPickerSheet extends HookConsumerWidget {
); );
}, },
), ),
loading: loading: () =>
() => const Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')), error: (error, stack) => Center(child: Text('Error: $error')),
); );
}, },

View File

@@ -133,8 +133,7 @@ class _ExpandedSection extends StatelessWidget {
}, },
child: Card( child: Card(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainer, ).colorScheme.surfaceContainer,
child: Column( child: Column(
@@ -144,8 +143,9 @@ class _ExpandedSection extends StatelessWidget {
const Gap(4), const Gap(4),
Text( Text(
'Poll', 'Poll',
style: style: Theme.of(
Theme.of(context).textTheme.bodySmall, context,
).textTheme.bodySmall,
), ),
], ],
), ),
@@ -160,8 +160,8 @@ class _ExpandedSection extends StatelessWidget {
await showModalBottomSheet<SnWalletFund>( await showModalBottomSheet<SnWalletFund>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) =>
(context) => const ComposeFundSheet(), const ComposeFundSheet(),
); );
if (fund != null) { if (fund != null) {
onFundSelected(fund); onFundSelected(fund);
@@ -169,8 +169,7 @@ class _ExpandedSection extends StatelessWidget {
}, },
child: Card( child: Card(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainer, ).colorScheme.surfaceContainer,
child: Column( child: Column(
@@ -180,8 +179,9 @@ class _ExpandedSection extends StatelessWidget {
const Gap(4), const Gap(4),
Text( Text(
'fund'.tr(), 'fund'.tr(),
style: style: Theme.of(
Theme.of(context).textTheme.bodySmall, context,
).textTheme.bodySmall,
), ),
], ],
), ),
@@ -192,11 +192,8 @@ class _ExpandedSection extends StatelessWidget {
), ),
StickerPickerEmbedded( StickerPickerEmbedded(
height: kInputDrawerExpandedHeight, height: kInputDrawerExpandedHeight,
onPick: onPick: (placeholder) =>
(placeholder) => _insertPlaceholder( _insertPlaceholder(messageController, placeholder),
messageController,
placeholder,
),
), ),
], ],
), ),
@@ -373,7 +370,8 @@ class ChatInput extends HookConsumerWidget {
switchOutCurve: Curves.fastEaseInToSlowEaseOut, switchOutCurve: Curves.fastEaseInToSlowEaseOut,
transitionBuilder: (Widget child, Animation<double> animation) { transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition( return SlideTransition(
position: Tween<Offset>( position:
Tween<Offset>(
begin: const Offset(0, -0.3), begin: const Offset(0, -0.3),
end: Offset.zero, end: Offset.zero,
).animate( ).animate(
@@ -389,8 +387,7 @@ class ChatInput extends HookConsumerWidget {
), ),
); );
}, },
child: child: chatSubscribe.isNotEmpty
chatSubscribe.isNotEmpty
? Container( ? Container(
key: const ValueKey('typing-indicator'), key: const ValueKey('typing-indicator'),
width: double.infinity, width: double.infinity,
@@ -445,8 +442,7 @@ class ChatInput extends HookConsumerWidget {
), ),
); );
}, },
child: child: attachments.isNotEmpty
attachments.isNotEmpty
? SizedBox( ? SizedBox(
key: ValueKey('attachments-${attachments.length}'), key: ValueKey('attachments-${attachments.length}'),
height: 180, height: 180,
@@ -462,24 +458,20 @@ class ChatInput extends HookConsumerWidget {
item: attachments[idx], item: attachments[idx],
progress: progress:
attachmentProgress['chat-upload']?[idx], attachmentProgress['chat-upload']?[idx],
onRequestUpload: onRequestUpload: () => onUploadAttachment(idx),
() => onUploadAttachment(idx),
onDelete: () => onDeleteAttachment(idx), onDelete: () => onDeleteAttachment(idx),
onUpdate: (value) { onUpdate: (value) {
attachments[idx] = value; attachments[idx] = value;
onAttachmentsChanged(attachments); onAttachmentsChanged(attachments);
}, },
onMove: onMove: (delta) => onMoveAttachment(idx, delta),
(delta) => onMoveAttachment(idx, delta),
), ),
); );
}, },
separatorBuilder: (_, _) => const Gap(8), separatorBuilder: (_, _) => const Gap(8),
), ),
).padding(vertical: 12) ).padding(vertical: 12)
: const SizedBox.shrink( : const SizedBox.shrink(key: ValueKey('no-attachments')),
key: ValueKey('no-attachments'),
),
), ),
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@@ -501,8 +493,7 @@ class ChatInput extends HookConsumerWidget {
), ),
); );
}, },
child: child: selectedPoll != null
selectedPoll != null
? Container( ? Container(
key: const ValueKey('selected-poll'), key: const ValueKey('selected-poll'),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -510,8 +501,7 @@ class ChatInput extends HookConsumerWidget {
vertical: 8, vertical: 8,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainerHigh, ).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -558,9 +548,7 @@ class ChatInput extends HookConsumerWidget {
], ],
), ),
) )
: const SizedBox.shrink( : const SizedBox.shrink(key: ValueKey('no-selected-poll')),
key: ValueKey('no-selected-poll'),
),
), ),
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@@ -582,8 +570,7 @@ class ChatInput extends HookConsumerWidget {
), ),
); );
}, },
child: child: selectedFund != null
selectedFund != null
? Container( ? Container(
key: const ValueKey('selected-fund'), key: const ValueKey('selected-fund'),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -591,8 +578,7 @@ class ChatInput extends HookConsumerWidget {
vertical: 8, vertical: 8,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainerHigh, ).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -624,11 +610,10 @@ class ChatInput extends HookConsumerWidget {
children: [ children: [
Text( Text(
'${selectedFund!.totalAmount.toStringAsFixed(2)} ${selectedFund!.currency}', '${selectedFund!.totalAmount.toStringAsFixed(2)} ${selectedFund!.currency}',
style: Theme.of( style: Theme.of(context)
context, .textTheme
).textTheme.bodySmall!.copyWith( .bodySmall!
fontWeight: FontWeight.w500, .copyWith(fontWeight: FontWeight.w500),
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -637,12 +622,12 @@ class ChatInput extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 2), padding: const EdgeInsets.only(top: 2),
child: Text( child: Text(
selectedFund!.message!, selectedFund!.message!,
style: Theme.of( style: Theme.of(context)
context, .textTheme
).textTheme.bodySmall!.copyWith( .bodySmall!
.copyWith(
fontSize: 10, fontSize: 10,
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.onSurfaceVariant, ).colorScheme.onSurfaceVariant,
), ),
@@ -666,9 +651,7 @@ class ChatInput extends HookConsumerWidget {
], ],
), ),
) )
: const SizedBox.shrink( : const SizedBox.shrink(key: ValueKey('no-selected-fund')),
key: ValueKey('no-selected-fund'),
),
), ),
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@@ -706,8 +689,7 @@ class ChatInput extends HookConsumerWidget {
vertical: 8, vertical: 8,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: Theme.of(
Theme.of(
context, context,
).colorScheme.surfaceContainerHigh, ).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -737,8 +719,7 @@ class ChatInput extends HookConsumerWidget {
? Symbols.forward ? Symbols.forward
: Symbols.edit, : Symbols.edit,
size: 18, size: 18,
color: color: Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.primary,
), ),
const Gap(8), const Gap(8),
Expanded( Expanded(
@@ -756,11 +737,10 @@ class ChatInput extends HookConsumerWidget {
: messageForwardingTo != null : messageForwardingTo != null
? 'chatForwarding'.tr() ? 'chatForwarding'.tr()
: 'chatEditing'.tr(), : 'chatEditing'.tr(),
style: Theme.of( style: Theme.of(context)
context, .textTheme
).textTheme.bodySmall!.copyWith( .bodySmall!
fontWeight: FontWeight.w500, .copyWith(fontWeight: FontWeight.w500),
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -791,11 +771,9 @@ class ChatInput extends HookConsumerWidget {
messageEditingTo) messageEditingTo)
?.content ?? ?.content ??
'chatNoContent'.tr(), 'chatNoContent'.tr(),
style: Theme.of( style: Theme.of(context).textTheme.bodySmall!
context, .copyWith(
).textTheme.bodySmall!.copyWith( color: Theme.of(
color:
Theme.of(
context, context,
).colorScheme.onSurfaceVariant, ).colorScheme.onSurfaceVariant,
), ),
@@ -815,25 +793,19 @@ class ChatInput extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton( IconButton(
tooltip: tooltip: isExpanded.value
isExpanded.value ? 'collapse'.tr() : 'more'.tr(), ? 'collapse'.tr()
: 'more'.tr(),
icon: AnimatedSwitcher( icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
transitionBuilder: transitionBuilder: (child, animation) =>
(child, animation) => FadeTransition( FadeTransition(opacity: animation, child: child),
opacity: animation, child: isExpanded.value
child: child,
),
child:
isExpanded.value
? const Icon( ? const Icon(
Symbols.close, Symbols.close,
key: ValueKey('close'), key: ValueKey('close'),
) )
: const Icon( : const Icon(Symbols.add, key: ValueKey('add')),
Symbols.add,
key: ValueKey('add'),
),
), ),
onPressed: () { onPressed: () {
isExpanded.value = !isExpanded.value; isExpanded.value = !isExpanded.value;
@@ -892,39 +864,36 @@ class ChatInput extends HookConsumerWidget {
).map((e) => e.account.nick).join(', '), ).map((e) => e.account.nick).join(', '),
], ],
) )
: 'chatMessageHint'.tr( : 'chatMessageHint'.tr(args: [chatRoom.name!]),
args: [chatRoom.name!],
),
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 12, horizontal: 12,
vertical: 12, vertical: 12,
), ),
counterText: counterText: messageController.text.length > 1024
messageController.text.length > 1024
? '${messageController.text.length}/4096' ? '${messageController.text.length}/4096'
: null, : null,
), ),
maxLines: 5, maxLines: 5,
minLines: 1, minLines: 1,
onTapOutside: onTapOutside: (_) =>
(_) =>
FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
textInputAction: textInputAction: settings.enterToSend
settings.enterToSend
? TextInputAction.send ? TextInputAction.send
: null, : null,
onSubmitted: onSubmitted: settings.enterToSend
settings.enterToSend ? (_) => send() : null, ? (_) => send()
: null,
); );
}, },
suggestionsCallback: (pattern) async { suggestionsCallback: (pattern) async {
// Only trigger on @ or : // Only trigger on @ or :
final atIndex = pattern.lastIndexOf('@'); final atIndex = pattern.lastIndexOf('@');
final colonIndex = pattern.lastIndexOf(':'); final colonIndex = pattern.lastIndexOf(':');
final triggerIndex = final triggerIndex = atIndex > colonIndex
atIndex > colonIndex ? atIndex : colonIndex; ? atIndex
: colonIndex;
if (triggerIndex == -1) return []; if (triggerIndex == -1) return [];
final chopped = pattern.substring(triggerIndex); final chopped = pattern.substring(triggerIndex);
if (chopped.contains(' ')) return []; if (chopped.contains(' ')) return [];
@@ -986,9 +955,7 @@ class ChatInput extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
width: 28, width: 28,
height: 28, height: 28,
child: CloudImageWidget( child: CloudImageWidget(file: sticker.image),
fileId: sticker.image.id,
),
), ),
); );
break; break;
@@ -1005,8 +972,9 @@ class ChatInput extends HookConsumerWidget {
final text = messageController.text; final text = messageController.text;
final atIndex = text.lastIndexOf('@'); final atIndex = text.lastIndexOf('@');
final colonIndex = text.lastIndexOf(':'); final colonIndex = text.lastIndexOf(':');
final triggerIndex = final triggerIndex = atIndex > colonIndex
atIndex > colonIndex ? atIndex : colonIndex; ? atIndex
: colonIndex;
if (triggerIndex == -1) return; if (triggerIndex == -1) return;
final newText = text.replaceRange( final newText = text.replaceRange(
triggerIndex, triggerIndex,
@@ -1053,8 +1021,7 @@ class ChatInput extends HookConsumerWidget {
), ),
); );
}, },
child: child: isExpanded.value
isExpanded.value
? _ExpandedSection( ? _ExpandedSection(
messageController: messageController, messageController: messageController,
selectedPoll: selectedPoll, selectedPoll: selectedPoll,

View File

@@ -31,7 +31,7 @@ class MessageListTile extends StatelessWidget {
radius: 20, radius: 20,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: sender.account.profile.picture?.id, file: sender.account.profile.picture,
radius: 20, radius: 20,
), ),
), ),

View File

@@ -24,8 +24,7 @@ class MessageSenderInfo extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final timestamp = final timestamp = DateTime.now().difference(createdAt).inDays > 365
DateTime.now().difference(createdAt).inDays > 365
? DateFormat('yyyy/MM/dd HH:mm').format(createdAt.toLocal()) ? DateFormat('yyyy/MM/dd HH:mm').format(createdAt.toLocal())
: DateTime.now().difference(createdAt).inDays > 0 : DateTime.now().difference(createdAt).inDays > 0
? DateFormat('MM/dd HH:mm').format(createdAt.toLocal()) ? DateFormat('MM/dd HH:mm').format(createdAt.toLocal())
@@ -41,7 +40,7 @@ class MessageSenderInfo extends StatelessWidget {
AccountPfcGestureDetector( AccountPfcGestureDetector(
uname: sender.account.name, uname: sender.account.name,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: sender.account.profile.picture?.id, file: sender.account.profile.picture,
radius: 14, radius: 14,
), ),
), ),
@@ -69,7 +68,7 @@ class MessageSenderInfo extends StatelessWidget {
AccountPfcGestureDetector( AccountPfcGestureDetector(
uname: sender.account.name, uname: sender.account.name,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: sender.account.profile.picture?.id, file: sender.account.profile.picture,
radius: 14, radius: 14,
), ),
), ),
@@ -106,7 +105,7 @@ class MessageSenderInfo extends StatelessWidget {
AccountPfcGestureDetector( AccountPfcGestureDetector(
uname: sender.account.name, uname: sender.account.name,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: sender.account.profile.picture?.id, file: sender.account.profile.picture,
radius: 16, radius: 16,
), ),
), ),

View File

@@ -99,15 +99,15 @@ class PublicRoomPreview extends HookConsumerWidget {
SizedBox( SizedBox(
height: 26, height: 26,
width: 26, width: 26,
child: (room.type == 1 && room.picture?.id == null) child: (room.type == 1 && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: room.members! files: room.members!
.map((e) => e.account.profile.picture?.id) .map((e) => e.account.profile.picture)
.toList(), .toList(),
) )
: room.picture?.id != null : room.picture != null
? ProfilePictureWidget( ? ProfilePictureWidget(
fileId: room.picture?.id, file: room.picture,
fallbackIcon: Symbols.chat, fallbackIcon: Symbols.chat,
) )
: CircleAvatar( : CircleAvatar(
@@ -132,15 +132,15 @@ class PublicRoomPreview extends HookConsumerWidget {
SizedBox( SizedBox(
height: 26, height: 26,
width: 26, width: 26,
child: (room.type == 1 && room.picture?.id == null) child: (room.type == 1 && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: room.members! files: room.members!
.map((e) => e.account.profile.picture?.id) .map((e) => e.account.profile.picture)
.toList(), .toList(),
) )
: room.picture?.id != null : room.picture != null
? ProfilePictureWidget( ? ProfilePictureWidget(
fileId: room.picture?.id, file: room.picture,
fallbackIcon: Symbols.chat, fallbackIcon: Symbols.chat,
) )
: CircleAvatar( : CircleAvatar(

View File

@@ -22,15 +22,13 @@ class ChatRoomAvatar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final avatarChild = (isDirect && room.picture?.id == null) final avatarChild = (isDirect && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: validMembers files: validMembers.map((e) => e.account.profile.picture).toList(),
.map((e) => e.account.profile.picture?.id)
.toList(),
) )
: room.picture?.id == null : room.picture == null
? CircleAvatar(child: Text((room.name ?? 'DM')[0].toUpperCase())) ? CircleAvatar(child: Text((room.name ?? 'DM')[0].toUpperCase()))
: ProfilePictureWidget(fileId: room.picture?.id); : ProfilePictureWidget(file: room.picture);
final badgeChild = Badge( final badgeChild = Badge(
isLabelVisible: summary.when( isLabelVisible: summary.when(

View File

@@ -262,10 +262,7 @@ class CheckInActivityWidget extends StatelessWidget {
spacing: 12, spacing: 12,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ProfilePictureWidget( ProfilePictureWidget(file: result.account!.profile.picture, radius: 12),
fileId: result.account!.profile.picture?.id,
radius: 12,
),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@@ -577,6 +577,9 @@ class ProfilePictureWidget extends ConsumerWidget {
final serverUrl = ref.watch(serverUrlProvider); final serverUrl = ref.watch(serverUrlProvider);
final String? id = file?.id ?? fileId; final String? id = file?.id ?? fileId;
final meta = file?.fileMeta is Map ? (file!.fileMeta as Map) : const {};
final blurHash = meta['blur'] as String?;
final fallback = Icon( final fallback = Icon(
fallbackIcon ?? Symbols.account_circle, fallbackIcon ?? Symbols.account_circle,
size: radius, size: radius,
@@ -590,6 +593,7 @@ class ProfilePictureWidget extends ConsumerWidget {
placeholder: fallback, placeholder: fallback,
content: () => UniversalImage( content: () => UniversalImage(
uri: '$serverUrl/drive/files/$id', uri: '$serverUrl/drive/files/$id',
blurHash: blurHash,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
); );
@@ -625,14 +629,14 @@ class ProfilePictureWidget extends ConsumerWidget {
} }
class SplitAvatarWidget extends ConsumerWidget { class SplitAvatarWidget extends ConsumerWidget {
final List<String?> filesId; final List<SnCloudFile?> files;
final double radius; final double radius;
final IconData fallbackIcon; final IconData fallbackIcon;
final Color? fallbackColor; final Color? fallbackColor;
const SplitAvatarWidget({ const SplitAvatarWidget({
super.key, super.key,
required this.filesId, required this.files,
this.radius = 20, this.radius = 20,
this.fallbackIcon = Symbols.account_circle, this.fallbackIcon = Symbols.account_circle,
this.fallbackColor, this.fallbackColor,
@@ -640,17 +644,17 @@ class SplitAvatarWidget extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
if (filesId.isEmpty) { if (files.isEmpty) {
return ProfilePictureWidget( return ProfilePictureWidget(
fileId: null, file: null,
radius: radius, radius: radius,
fallbackIcon: fallbackIcon, fallbackIcon: fallbackIcon,
fallbackColor: fallbackColor, fallbackColor: fallbackColor,
); );
} }
if (filesId.length == 1) { if (files.length == 1) {
return ProfilePictureWidget( return ProfilePictureWidget(
fileId: filesId[0], file: files[0],
radius: radius, radius: radius,
fallbackIcon: fallbackIcon, fallbackIcon: fallbackIcon,
fallbackColor: fallbackColor, fallbackColor: fallbackColor,
@@ -665,32 +669,32 @@ class SplitAvatarWidget extends ConsumerWidget {
color: Theme.of(context).colorScheme.primaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
child: Stack( child: Stack(
children: [ children: [
if (filesId.length == 2) if (files.length == 2)
Row( Row(
children: [ children: [
Expanded( Expanded(
child: _buildQuadrant(context, filesId[0], ref, radius), child: _buildQuadrant(context, files[0], ref, radius),
), ),
Expanded( Expanded(
child: _buildQuadrant(context, filesId[1], ref, radius), child: _buildQuadrant(context, files[1], ref, radius),
), ),
], ],
) )
else if (filesId.length == 3) else if (files.length == 3)
Row( Row(
children: [ children: [
Column( Column(
children: [ children: [
Expanded( Expanded(
child: _buildQuadrant(context, filesId[0], ref, radius), child: _buildQuadrant(context, files[0], ref, radius),
), ),
Expanded( Expanded(
child: _buildQuadrant(context, filesId[1], ref, radius), child: _buildQuadrant(context, files[1], ref, radius),
), ),
], ],
), ),
Expanded( Expanded(
child: _buildQuadrant(context, filesId[2], ref, radius), child: _buildQuadrant(context, files[2], ref, radius),
), ),
], ],
) )
@@ -701,20 +705,10 @@ class SplitAvatarWidget extends ConsumerWidget {
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: _buildQuadrant( child: _buildQuadrant(context, files[0], ref, radius),
context,
filesId[0],
ref,
radius,
),
), ),
Expanded( Expanded(
child: _buildQuadrant( child: _buildQuadrant(context, files[1], ref, radius),
context,
filesId[1],
ref,
radius,
),
), ),
], ],
), ),
@@ -723,22 +717,17 @@ class SplitAvatarWidget extends ConsumerWidget {
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: _buildQuadrant( child: _buildQuadrant(context, files[2], ref, radius),
context,
filesId[2],
ref,
radius,
),
), ),
Expanded( Expanded(
child: filesId.length > 4 child: files.length > 4
? Container( ? Container(
color: Theme.of( color: Theme.of(
context, context,
).colorScheme.primaryContainer, ).colorScheme.primaryContainer,
child: Center( child: Center(
child: Text( child: Text(
'+${filesId.length - 3}', '+${files.length - 3}',
style: TextStyle( style: TextStyle(
fontSize: radius * 0.4, fontSize: radius * 0.4,
color: Theme.of( color: Theme.of(
@@ -748,12 +737,7 @@ class SplitAvatarWidget extends ConsumerWidget {
), ),
), ),
) )
: _buildQuadrant( : _buildQuadrant(context, files[3], ref, radius),
context,
filesId[3],
ref,
radius,
),
), ),
], ],
), ),
@@ -768,11 +752,11 @@ class SplitAvatarWidget extends ConsumerWidget {
Widget _buildQuadrant( Widget _buildQuadrant(
BuildContext context, BuildContext context,
String? fileId, SnCloudFile? file,
WidgetRef ref, WidgetRef ref,
double radius, double radius,
) { ) {
if (fileId == null) { if (file == null) {
return Container( return Container(
width: radius, width: radius,
height: radius, height: radius,
@@ -787,7 +771,7 @@ class SplitAvatarWidget extends ConsumerWidget {
} }
final serverUrl = ref.watch(serverUrlProvider); final serverUrl = ref.watch(serverUrlProvider);
final uri = '$serverUrl/drive/files/$fileId'; final uri = '$serverUrl/drive/files/${file.id}';
return SizedBox( return SizedBox(
width: radius, width: radius,

View File

@@ -1,9 +1,10 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
class UniversalImage extends StatelessWidget { class UniversalImage extends HookWidget {
final String uri; final String uri;
final String? blurHash; final String? blurHash;
final BoxFit fit; final BoxFit fit;
@@ -27,6 +28,7 @@ class UniversalImage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loaded = useState(false);
final isSvgImage = isSvg || uri.toLowerCase().endsWith('.svg'); final isSvgImage = isSvg || uri.toLowerCase().endsWith('.svg');
if (isSvgImage) { if (isSvgImage) {
@@ -35,8 +37,7 @@ class UniversalImage extends StatelessWidget {
fit: fit, fit: fit,
width: width, width: width,
height: height, height: height,
placeholderBuilder: placeholderBuilder: (BuildContext context) =>
(BuildContext context) =>
Center(child: CircularProgressIndicator()), Center(child: CircularProgressIndicator()),
); );
} }
@@ -46,8 +47,9 @@ class UniversalImage extends StatelessWidget {
if (width != null && height != null && !noCacheOptimization) { if (width != null && height != null && !noCacheOptimization) {
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
cacheWidth = width != null ? (width! * devicePixelRatio).round() : null; cacheWidth = width != null ? (width! * devicePixelRatio).round() : null;
cacheHeight = cacheHeight = height != null
height != null ? (height! * devicePixelRatio).round() : null; ? (height! * devicePixelRatio).round()
: null;
} }
return SizedBox( return SizedBox(
@@ -66,12 +68,26 @@ class UniversalImage extends StatelessWidget {
memCacheWidth: cacheWidth, memCacheWidth: cacheWidth,
progressIndicatorBuilder: (context, url, progress) { progressIndicatorBuilder: (context, url, progress) {
return Center( return Center(
child: CircularProgressIndicator(value: progress.progress), child: AnimatedCircularProgressIndicator(
value: progress.progress,
color: Colors.white.withOpacity(0.5),
),
); );
}, },
errorWidget: imageBuilder: (context, imageProvider) {
(context, url, error) => Future(() => loaded.value = true);
useFallbackImage return AnimatedOpacity(
opacity: loaded.value ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: Image(
image: imageProvider,
fit: fit,
width: width,
height: height,
),
);
},
errorWidget: (context, url, error) => useFallbackImage
? Image.asset( ? Image.asset(
'assets/images/media-offline.jpg', 'assets/images/media-offline.jpg',
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -84,3 +100,40 @@ class UniversalImage extends StatelessWidget {
); );
} }
} }
class AnimatedCircularProgressIndicator extends HookWidget {
final double? value;
final Color? color;
final double strokeWidth;
final Duration duration;
const AnimatedCircularProgressIndicator({
super.key,
this.value,
this.color,
this.strokeWidth = 4.0,
this.duration = const Duration(milliseconds: 200),
});
@override
Widget build(BuildContext context) {
final animationController = useAnimationController(duration: duration);
final animation = useAnimation(
Tween<double>(begin: 0.0, end: value ?? 0.0).animate(
CurvedAnimation(parent: animationController, curve: Curves.linear),
),
);
useEffect(() {
animationController.animateTo(value ?? 0.0);
return null;
}, [value]);
return CircularProgressIndicator(
value: animation,
color: color,
strokeWidth: strokeWidth,
backgroundColor: Colors.transparent,
);
}
}

View File

@@ -41,10 +41,9 @@ class ComposeFormFields extends HookConsumerWidget {
GestureDetector( GestureDetector(
onTap: onPublisherTap, onTap: onPublisherTap,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: state.currentPublisher.value?.picture?.id, file: state.currentPublisher.value?.picture,
radius: 20, radius: 20,
fallbackIcon: fallbackIcon: state.currentPublisher.value == null
state.currentPublisher.value == null
? Icons.question_mark ? Icons.question_mark
: null, : null,
), ),
@@ -98,8 +97,8 @@ class ComposeFormFields extends HookConsumerWidget {
), ),
), ),
style: theme.textTheme.titleMedium, style: theme.textTheme.titleMedium,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
// Description field // Description field
@@ -115,8 +114,8 @@ class ComposeFormFields extends HookConsumerWidget {
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
minLines: 1, minLines: 1,
maxLines: 3, maxLines: 3,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
// Content field // Content field
@@ -138,16 +137,17 @@ class ComposeFormFields extends HookConsumerWidget {
), ),
), ),
maxLines: null, maxLines: null,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
); );
}, },
suggestionsCallback: (pattern) async { suggestionsCallback: (pattern) async {
// Only trigger on @ or : // Only trigger on @ or :
final atIndex = pattern.lastIndexOf('@'); final atIndex = pattern.lastIndexOf('@');
final colonIndex = pattern.lastIndexOf(':'); final colonIndex = pattern.lastIndexOf(':');
final triggerIndex = final triggerIndex = atIndex > colonIndex
atIndex > colonIndex ? atIndex : colonIndex; ? atIndex
: colonIndex;
if (triggerIndex == -1) return []; if (triggerIndex == -1) return [];
final chopped = pattern.substring(triggerIndex); final chopped = pattern.substring(triggerIndex);
if (chopped.contains(' ')) return []; if (chopped.contains(' ')) return [];
@@ -202,7 +202,7 @@ class ComposeFormFields extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
width: 28, width: 28,
height: 28, height: 28,
child: CloudImageWidget(fileId: sticker.image.id), child: CloudImageWidget(file: sticker.image),
), ),
); );
break; break;
@@ -219,8 +219,9 @@ class ComposeFormFields extends HookConsumerWidget {
final text = state.contentController.text; final text = state.contentController.text;
final atIndex = text.lastIndexOf('@'); final atIndex = text.lastIndexOf('@');
final colonIndex = text.lastIndexOf(':'); final colonIndex = text.lastIndexOf(':');
final triggerIndex = final triggerIndex = atIndex > colonIndex
atIndex > colonIndex ? atIndex : colonIndex; ? atIndex
: colonIndex;
if (triggerIndex == -1) return; if (triggerIndex == -1) return;
final newText = text.replaceRange( final newText = text.replaceRange(
triggerIndex, triggerIndex,
@@ -281,8 +282,8 @@ class ArticleComposeFormFields extends StatelessWidget {
), ),
), ),
style: theme.textTheme.titleMedium, style: theme.textTheme.titleMedium,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
// Description field // Description field
@@ -297,8 +298,8 @@ class ArticleComposeFormFields extends StatelessWidget {
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
minLines: 1, minLines: 1,
maxLines: 3, maxLines: 3,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
// Content field (expanded) // Content field (expanded)
@@ -317,8 +318,8 @@ class ArticleComposeFormFields extends StatelessWidget {
maxLines: null, maxLines: null,
expands: true, expands: true,
textAlignVertical: TextAlignVertical.top, textAlignVertical: TextAlignVertical.top,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
], ],

View File

@@ -135,10 +135,7 @@ class CompactReferencePost extends StatelessWidget {
Widget _buildProfilePicture(BuildContext context) { Widget _buildProfilePicture(BuildContext context) {
// Handle publisher case // Handle publisher case
if (post.publisher != null) { if (post.publisher != null) {
return ProfilePictureWidget( return ProfilePictureWidget(file: post.publisher!.picture, radius: 16);
fileId: post.publisher!.picture?.id,
radius: 16,
);
} }
// Handle actor case // Handle actor case
if (post.actor != null) { if (post.actor != null) {
@@ -169,7 +166,7 @@ class CompactReferencePost extends StatelessWidget {
} }
} }
// Fallback // Fallback
return ProfilePictureWidget(fileId: null, radius: 16); return ProfilePictureWidget(file: null, radius: 16);
} }
String _getDisplayName() { String _getDisplayName() {

View File

@@ -412,7 +412,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
child: Row( child: Row(
children: [ children: [
ProfilePictureWidget( ProfilePictureWidget(
fileId: currentRealm.picture?.id, file: currentRealm.picture,
fallbackIcon: Symbols.workspaces, fallbackIcon: Symbols.workspaces,
radius: 16, radius: 16,
), ),
@@ -428,7 +428,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
child: Row( child: Row(
children: [ children: [
ProfilePictureWidget( ProfilePictureWidget(
fileId: realm.picture?.id, file: realm.picture,
fallbackIcon: Symbols.workspaces, fallbackIcon: Symbols.workspaces,
radius: 16, radius: 16,
), ),
@@ -454,7 +454,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
) )
else else
ProfilePictureWidget( ProfilePictureWidget(
fileId: currentRealm.picture?.id, file: currentRealm.picture,
fallbackIcon: Symbols.workspaces, fallbackIcon: Symbols.workspaces,
radius: 16, radius: 16,
), ),

View File

@@ -54,7 +54,7 @@ class PostAwardSheet extends HookConsumerWidget {
} }
} }
// Fallback // Fallback
return ProfilePictureWidget(fileId: null, radius: radius); return ProfilePictureWidget(file: null, radius: radius);
} }
String _getPublisherName() { String _getPublisherName() {

View File

@@ -73,8 +73,7 @@ class PostQuickReply extends HookConsumerWidget {
const kInputChipHeight = 54.0; const kInputChipHeight = 54.0;
return publishers.when( return publishers.when(
data: data: (data) => Material(
(data) => Material(
elevation: 2, elevation: 2,
color: Theme.of(context).colorScheme.surfaceContainerHighest, color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
@@ -86,7 +85,7 @@ class PostQuickReply extends HookConsumerWidget {
children: [ children: [
GestureDetector( GestureDetector(
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: currentPublisher.value?.picture?.id, file: currentPublisher.value?.picture,
radius: (kInputChipHeight * 0.5) - 6, radius: (kInputChipHeight * 0.5) - 6,
), ),
onTap: () { onTap: () {
@@ -118,8 +117,8 @@ class PostQuickReply extends HookConsumerWidget {
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
minLines: 1, minLines: 1,
maxLines: 5, maxLines: 5,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
const Gap(8), const Gap(8),
@@ -143,8 +142,7 @@ class PostQuickReply extends HookConsumerWidget {
), ),
), ),
IconButton( IconButton(
icon: icon: submitting.value
submitting.value
? SizedBox( ? SizedBox(
width: 28, width: 28,
height: 28, height: 28,

View File

@@ -148,7 +148,7 @@ class PostReplyPreview extends HookConsumerWidget {
return ActorPictureWidget(actor: post.actor!, radius: radius); return ActorPictureWidget(actor: post.actor!, radius: radius);
} }
// Fallback // Fallback
return ProfilePictureWidget(fileId: null, radius: radius); return ProfilePictureWidget(file: null, radius: radius);
} }
@override @override
@@ -448,7 +448,7 @@ class ReferencedPostWidget extends StatelessWidget {
// Handle publisher case // Handle publisher case
if (post.publisher != null) { if (post.publisher != null) {
return ProfilePictureWidget( return ProfilePictureWidget(
fileId: post.publisher!.picture?.id, file: post.publisher!.picture,
radius: radius, radius: radius,
); );
} }
@@ -457,7 +457,7 @@ class ReferencedPostWidget extends StatelessWidget {
return ActorPictureWidget(actor: post.actor!, radius: radius); return ActorPictureWidget(actor: post.actor!, radius: radius);
} }
// Fallback // Fallback
return ProfilePictureWidget(fileId: null, radius: radius); return ProfilePictureWidget(file: null, radius: radius);
} }
String _getDisplayName(SnPost post) { String _getDisplayName(SnPost post) {
@@ -701,7 +701,7 @@ class PostHeader extends HookConsumerWidget {
return ActorPictureWidget(actor: post.actor!, radius: radius); return ActorPictureWidget(actor: post.actor!, radius: radius);
} }
// Fallback // Fallback
return ProfilePictureWidget(fileId: null, radius: radius); return ProfilePictureWidget(file: null, radius: radius);
} }
String _getDisplayName(SnPost post) { String _getDisplayName(SnPost post) {

View File

@@ -21,13 +21,10 @@ class PublisherModal extends HookConsumerWidget {
children: [ children: [
Expanded( Expanded(
child: publishers.when( child: publishers.when(
data: data: (value) => value.isEmpty
(value) =>
value.isEmpty
? ConstrainedBox( ? ConstrainedBox(
constraints: BoxConstraints(maxWidth: 280), constraints: BoxConstraints(maxWidth: 280),
child: child: Column(
Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@@ -45,14 +42,11 @@ class PublisherModal extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) =>
(context) =>
const NewPublisherScreen(), const NewPublisherScreen(),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
ref.invalidate( ref.invalidate(publishersManagedProvider);
publishersManagedProvider,
);
} }
}); });
}, },
@@ -67,7 +61,7 @@ class PublisherModal extends HookConsumerWidget {
for (final publisher in value) for (final publisher in value)
ListTile( ListTile(
leading: ProfilePictureWidget( leading: ProfilePictureWidget(
fileId: publisher.picture?.id, file: publisher.picture,
), ),
title: Text(publisher.nick), title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'), subtitle: Text('@${publisher.name}'),

View File

@@ -30,8 +30,7 @@ class RealmListTile extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Container( child: Container(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: child: realm.background == null
realm.background == null
? const SizedBox.shrink() ? const SizedBox.shrink()
: CloudImageWidget(file: realm.background), : CloudImageWidget(file: realm.background),
), ),
@@ -40,7 +39,7 @@ class RealmListTile extends StatelessWidget {
bottom: -30, bottom: -30,
left: 18, left: 18,
child: ProfilePictureWidget( child: ProfilePictureWidget(
fileId: realm.picture?.id, file: realm.picture,
fallbackIcon: Symbols.group, fallbackIcon: Symbols.group,
radius: 24, radius: 24,
), ),

View File

@@ -49,7 +49,7 @@ class RealmSelectionDropdown extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
ProfilePictureWidget( ProfilePictureWidget(
fileId: realm.picture?.id, file: realm.picture,
fallbackIcon: Symbols.workspaces, fallbackIcon: Symbols.workspaces,
radius: 16, radius: 16,
), ),

View File

@@ -776,19 +776,19 @@ class _ChatRoomOption extends HookConsumerWidget {
color: Theme.of(context).colorScheme.primary.withOpacity(0.1), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: (isDirect && room.picture?.id == null) child: (isDirect && room.picture == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: validMembers files: validMembers
.map((e) => e.account.profile.picture?.id) .map((e) => e.account.profile.picture)
.toList(), .toList(),
radius: 16, radius: 16,
) )
: room.picture?.id == null : room.picture == null
? CircleAvatar( ? CircleAvatar(
radius: 16, radius: 16,
child: Text(room.name![0].toUpperCase()), child: Text(room.name![0].toUpperCase()),
) )
: ProfilePictureWidget(fileId: room.picture?.id, radius: 16), : ProfilePictureWidget(file: room.picture, radius: 16),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
// Chat room name // Chat room name

View File

@@ -75,14 +75,12 @@ class StickerPicker extends HookConsumerWidget {
}, },
); );
}, },
loading: loading: () => const SizedBox(
() => const SizedBox(
width: 320, width: 320,
height: 320, height: 320,
child: Center(child: CircularProgressIndicator()), child: Center(child: CircularProgressIndicator()),
), ),
error: error: (err, _) => SizedBox(
(err, _) => SizedBox(
width: 360, width: 360,
height: 200, height: 200,
child: Column( child: Column(
@@ -263,7 +261,7 @@ class _StickersGrid extends StatelessWidget {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: CloudImageWidget( child: CloudImageWidget(
fileId: sticker.image.id, file: sticker.image,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@@ -310,14 +308,12 @@ class StickerPickerEmbedded extends HookConsumerWidget {
}, },
); );
}, },
loading: loading: () => SizedBox(
() => SizedBox(
width: 320, width: 320,
height: height ?? 320, height: height ?? 320,
child: const Center(child: CircularProgressIndicator()), child: const Center(child: CircularProgressIndicator()),
), ),
error: error: (err, _) => SizedBox(
(err, _) => SizedBox(
width: 360, width: 360,
height: height ?? 200, height: height ?? 200,
child: Column( child: Column(
@@ -386,13 +382,11 @@ class _EmbeddedPackSwitcherState extends State<_EmbeddedPackSwitcher> {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: selected
selected
? Theme.of(context).colorScheme.primaryContainer ? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainer, : Theme.of(context).colorScheme.surfaceContainer,
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
border: border: selected
selected
? Border.all( ? Border.all(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
width: 4, width: 4,
@@ -458,8 +452,7 @@ Future<void> showStickerPickerPopover(
offset: offset, offset: offset,
alignment: alignment ?? Alignment.topLeft, alignment: alignment ?? Alignment.topLeft,
dimBackground: true, dimBackground: true,
builder: builder: (ctx) => SizedBox(
(ctx) => SizedBox(
width: math.min(480, MediaQuery.of(context).size.width * 0.9), width: math.min(480, MediaQuery.of(context).size.width * 0.9),
height: 480, height: 480,
child: ProviderScope( child: ProviderScope(