👽 Support the latest API

This commit is contained in:
2025-05-24 01:34:20 +08:00
parent 942b62fbff
commit fc2520b8f8
16 changed files with 426 additions and 86 deletions

View File

@ -130,13 +130,13 @@ class AccountProfileScreen extends HookConsumerWidget {
SliverToBoxAdapter(
child: const Divider(height: 1).padding(bottom: 24),
),
if (data.profile.bio?.isNotEmpty ?? false)
if (data.profile.bio.isNotEmpty)
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('bio').tr().bold(),
Text(data.profile.bio!),
Text(data.profile.bio),
],
).padding(horizontal: 24),
),

View File

@ -96,7 +96,8 @@ class RelationshipListTile extends StatelessWidget {
relationship.status == 0 && relationship.relatedId == currentUserId;
final isWaiting =
relationship.status == 0 && relationship.accountId == currentUserId;
final isEstablished = relationship.status == 1 || relationship.status == 2;
final isEstablished =
relationship.status >= 100 || relationship.status <= -100;
return ListTile(
contentPadding: const EdgeInsets.only(left: 16, right: 12),
@ -105,13 +106,13 @@ class RelationshipListTile extends StatelessWidget {
spacing: 6,
children: [
Flexible(child: Text(account.nick)),
if (relationship.status == 1) // Friend
if (relationship.status >= 100) // Friend
Badge(
label: Text('relationshipStatusFriend').tr(),
backgroundColor: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
)
else if (relationship.status == 2) // Blocked
else if (relationship.status <= -100) // Blocked
Badge(
label: Text('relationshipStatusBlocked').tr(),
backgroundColor: Theme.of(context).colorScheme.error,
@ -171,7 +172,7 @@ class RelationshipListTile extends StatelessWidget {
icon: const Icon(Symbols.more_vert),
itemBuilder:
(context) => [
if (relationship.status == 1) // If friend
if (relationship.status >= 100) // If friend
PopupMenuItem(
child: ListTile(
leading: const Icon(Symbols.block),
@ -179,9 +180,12 @@ class RelationshipListTile extends StatelessWidget {
contentPadding: EdgeInsets.zero,
),
onTap:
() => onUpdateStatus?.call(relationship, 2),
() => onUpdateStatus?.call(
relationship,
-100,
),
)
else if (relationship.status == 2) // If blocked
else if (relationship.status <= -100) // If blocked
PopupMenuItem(
child: ListTile(
leading: const Icon(Symbols.person_add),
@ -189,7 +193,8 @@ class RelationshipListTile extends StatelessWidget {
contentPadding: EdgeInsets.zero,
),
onTap:
() => onUpdateStatus?.call(relationship, 1),
() =>
onUpdateStatus?.call(relationship, 100),
),
],
),

View File

@ -110,6 +110,7 @@ class TabsNavigationWidget extends HookConsumerWidget {
Gap(MediaQuery.of(context).padding.top + 8),
Expanded(
child: NavigationRail(
minExtendedWidth: 200,
extended: useExpandableLayout,
selectedIndex: activeIndex,
onDestinationSelected: (index) {

View File

@ -11,6 +11,7 @@ import 'package:image_picker/image_picker.dart';
import 'package:island/models/chat.dart';
import 'package:island/models/file.dart';
import 'package:island/models/realm.dart';
import 'package:island/pods/chat_summary.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
import 'package:island/route.gr.dart';
@ -24,12 +25,13 @@ import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/realms/selection_dropdown.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:relative_time/relative_time.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
part 'chat.g.dart';
class ChatRoomListTile extends StatelessWidget {
class ChatRoomListTile extends HookConsumerWidget {
final SnChatRoom room;
final bool isDirect;
final Widget? subtitle;
@ -46,7 +48,88 @@ class ChatRoomListTile extends StatelessWidget {
});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final summary = ref
.watch(chatSummaryProvider)
.whenData((summaries) => summaries[room.id]);
Widget buildSubtitle() {
if (subtitle != null) return subtitle!;
return summary.when(
data: (data) {
if (data == null) {
return isDirect && room.description == null
? Text(
room.members!.map((e) => '@${e.account.name}').join(', '),
maxLines: 1,
)
: Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (data.unreadCount > 0)
Text(
'unreadMessages'.plural(data.unreadCount),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
Row(
children: [
Text(
'${data.lastMessage.sender.account.name}: ',
style: Theme.of(context).textTheme.bodySmall,
),
Expanded(
child: Text(
data.lastMessage.content ?? 'messageNone'.tr(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Text(
RelativeTime(context).format(data.lastMessage.createdAt),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
);
},
loading: () => const SizedBox.shrink(),
error:
(_, __) =>
isDirect && room.description == null
? Text(
room.members!.map((e) => '@${e.account.name}').join(', '),
maxLines: 1,
)
: Text(
room.description ?? 'descriptionNone'.tr(),
maxLines: 1,
),
);
}
Widget buildTrailing() {
if (trailing != null) return trailing!;
return summary.when(
data: (data) {
if (data == null || data.unreadCount == 0) {
return const SizedBox.shrink();
}
return Badge(label: Text(data.unreadCount.toString()));
},
loading: () => const SizedBox.shrink(),
error: (_, __) => const SizedBox.shrink(),
);
}
return ListTile(
leading:
(isDirect && room.pictureId == null)
@ -62,19 +145,20 @@ class ChatRoomListTile extends StatelessWidget {
title: Text(
(isDirect && room.name == null)
? room.members!.map((e) => e.account.nick).join(', ')
: room.name!,
: room.name ?? '',
),
subtitle:
subtitle != null
? subtitle!
: (isDirect && room.description == null)
? Text(
room.members!.map((e) => '@${e.account.name}').join(', '),
maxLines: 1,
)
: Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1),
trailing: trailing,
onTap: onTap,
subtitle: buildSubtitle(),
trailing: buildTrailing(),
onTap: () async {
// Clear unread count if there are unread messages
final summary = await ref.read(chatSummaryProvider.future);
if ((summary[room.id]?.unreadCount ?? 0) > 0) {
await ref
.read(chatSummaryProvider.notifier)
.clearUnreadCount(room.id);
}
onTap?.call();
},
);
}
}
@ -100,9 +184,9 @@ class ChatShellScreen extends HookConsumerWidget {
if (isWide) {
return Row(
children: [
SizedBox(width: 320, child: ChatListScreen(isAside: true)),
Flexible(flex: 2, child: ChatListScreen(isAside: true)),
VerticalDivider(width: 1),
Expanded(child: AutoRouter()),
Flexible(flex: 4, child: AutoRouter()),
],
);
}

View File

@ -311,27 +311,14 @@ class ChatRoomScreen extends HookConsumerWidget {
final attachmentProgress = useState<Map<String, Map<int, double>>>({});
// Function to send read receipt
void sendReadReceipt(String messageId) async {
// Get message from repository to check read status
final repository = await ref.read(messageRepositoryProvider(id).future);
final message = await repository.getMessageById(messageId);
// Skip if message is already marked as read
if (message?.isRead ?? false) return;
void sendReadReceipt() async {
// Send websocket packet
final wsState = ref.read(websocketStateProvider.notifier);
wsState.sendMessage(
jsonEncode(
WebSocketPacket(
type: 'messages.read',
data: {'chat_room_id': id, 'message_id': messageId},
),
WebSocketPacket(type: 'messages.read', data: {'chat_room_id': id}),
),
);
// Mark as read in local database
await repository.markMessageAsRead(messageId);
}
// Add scroll listener for pagination
@ -357,7 +344,7 @@ class ChatRoomScreen extends HookConsumerWidget {
case 'messages.new':
messagesNotifier.receiveMessage(message);
// Send read receipt for new message
sendReadReceipt(message.id);
sendReadReceipt();
case 'messages.update':
messagesNotifier.receiveMessageUpdate(message);
case 'messages.delete':
@ -365,6 +352,7 @@ class ChatRoomScreen extends HookConsumerWidget {
}
}
sendReadReceipt();
final subscription = ws.dataStream.listen(onMessage);
return () => subscription.cancel();
}, [ws, chatRoom]);
@ -553,8 +541,6 @@ class ChatRoomScreen extends HookConsumerWidget {
nextMessage == null ||
nextMessage.senderId != message.senderId;
sendReadReceipt(message.id);
return chatIdentity.when(
skipError: true,
data:

View File

@ -285,7 +285,7 @@ class EditPublisherScreen extends HookConsumerWidget {
final user = ref.watch(userInfoProvider);
nameController.text = user.value!.name;
nickController.text = user.value!.nick;
bioController.text = user.value!.profile.bio ?? '';
bioController.text = user.value!.profile.bio;
picture.value = user.value!.profile.pictureId;
background.value = user.value!.profile.backgroundId;
} else {

View File

@ -120,13 +120,9 @@ class RealmListScreen extends HookConsumerWidget {
),
loading: () => const Center(child: CircularProgressIndicator()),
error:
(e, _) => GestureDetector(
child: Center(
child: Text('Error: $e', textAlign: TextAlign.center),
),
onTap: () {
ref.invalidate(realmsJoinedProvider);
},
(e, _) => ResponseErrorWidget(
error: e,
onRetry: () => ref.invalidate(realmsJoinedProvider),
),
),
onRefresh: () => ref.refresh(realmsJoinedProvider.future),