✨ Direct messages
This commit is contained in:
@ -1,7 +1,10 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -12,6 +15,7 @@ import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/route.gr.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/widgets/account/account_picker.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
@ -23,10 +27,13 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
part 'chat.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnChat>> chatroomsJoined(Ref ref) async {
|
||||
Future<List<SnChatRoom>> chatroomsJoined(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/chat');
|
||||
return resp.data.map((e) => SnChat.fromJson(e)).cast<SnChat>().toList();
|
||||
return resp.data
|
||||
.map((e) => SnChatRoom.fromJson(e))
|
||||
.cast<SnChatRoom>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@RoutePage()
|
||||
@ -37,6 +44,23 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final chats = ref.watch(chatroomsJoinedProvider);
|
||||
|
||||
final fabKey = useMemoized(() => GlobalKey<ExpandableFabState>(), []);
|
||||
|
||||
Future<void> createDirectMessage() async {
|
||||
final result = await showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AccountPickerSheet(),
|
||||
);
|
||||
if (result == null) return;
|
||||
final client = ref.read(apiClientProvider);
|
||||
try {
|
||||
await client.post('/chat/direct', data: {'related_user_id': result.id});
|
||||
ref.refresh(chatroomsJoinedProvider.future);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('chat').tr(),
|
||||
@ -53,12 +77,66 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: Key("chat-page-fab"),
|
||||
onPressed: () {
|
||||
context.pushRoute(NewChatRoute());
|
||||
},
|
||||
child: const Icon(Symbols.add),
|
||||
floatingActionButtonLocation: ExpandableFab.location,
|
||||
floatingActionButton: ExpandableFab(
|
||||
key: fabKey,
|
||||
distance: 75,
|
||||
type: ExpandableFabType.up,
|
||||
childrenAnimation: ExpandableFabAnimation.none,
|
||||
overlayStyle: ExpandableFabOverlayStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.surface.withAlpha((255 * 0.5).round()),
|
||||
),
|
||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||
child: const Icon(Symbols.add, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
foregroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
),
|
||||
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||
heroTag: Key("chat-page-fab"),
|
||||
child: const Icon(Symbols.close, size: 28),
|
||||
fabSize: ExpandableFabSize.regular,
|
||||
foregroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||
backgroundColor:
|
||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||
),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('createChatRoom').tr(),
|
||||
const Gap(20),
|
||||
FloatingActionButton(
|
||||
heroTag: null,
|
||||
tooltip: 'createChatRoom'.tr(),
|
||||
onPressed: () {
|
||||
context.pushRoute(NewChatRoute()).then((value) {
|
||||
if (value != null) {
|
||||
ref.refresh(chatroomsJoinedProvider.future);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: const Icon(Symbols.chat_add_on),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text('createDirectMessage').tr(),
|
||||
const Gap(20),
|
||||
FloatingActionButton(
|
||||
heroTag: null,
|
||||
tooltip: 'createDirectMessage'.tr(),
|
||||
onPressed: createDirectMessage,
|
||||
child: const Icon(Symbols.communication),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: chats.when(
|
||||
data:
|
||||
@ -72,6 +150,18 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
if (item.type == 1) {
|
||||
return ListTile(
|
||||
leading: ProfilePictureWidget(
|
||||
fileId: item.members!.first.account.profile.pictureId,
|
||||
),
|
||||
title: Text(item.members!.first.account.nick),
|
||||
subtitle: Text("An direct message"),
|
||||
onTap: () {
|
||||
context.pushRoute(ChatRoomRoute(id: item.id));
|
||||
},
|
||||
);
|
||||
}
|
||||
return ListTile(
|
||||
leading:
|
||||
item.pictureId == null
|
||||
@ -89,18 +179,24 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||
error:
|
||||
(error, stack) => GestureDetector(
|
||||
child: Center(child: Text('Error: $error')),
|
||||
onTap: () {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnChat?> chatroom(Ref ref, int? identifier) async {
|
||||
Future<SnChatRoom?> chatroom(Ref ref, int? identifier) async {
|
||||
if (identifier == null) return null;
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/chat/$identifier');
|
||||
return SnChat.fromJson(resp.data);
|
||||
return SnChatRoom.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
@ -208,7 +304,7 @@ class EditChatScreen extends HookConsumerWidget {
|
||||
options: Options(method: id == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.maybePop(SnChat.fromJson(resp.data));
|
||||
context.maybePop(SnChatRoom.fromJson(resp.data));
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
|
@ -6,12 +6,12 @@ part of 'chat.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatroomsJoinedHash() => r'3a2db4159663c54dfd7bc40519e2faa6df69b41f';
|
||||
String _$chatroomsJoinedHash() => r'0c93fd3cb8fe5c87626836ced4f244bfa7598582';
|
||||
|
||||
/// See also [chatroomsJoined].
|
||||
@ProviderFor(chatroomsJoined)
|
||||
final chatroomsJoinedProvider =
|
||||
AutoDisposeFutureProvider<List<SnChat>>.internal(
|
||||
AutoDisposeFutureProvider<List<SnChatRoom>>.internal(
|
||||
chatroomsJoined,
|
||||
name: r'chatroomsJoinedProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@ -24,8 +24,8 @@ final chatroomsJoinedProvider =
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChat>>;
|
||||
String _$chatroomHash() => r'27bd4cb49326bb2f2eac7d7db9db7f610e21afb2';
|
||||
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChatRoom>>;
|
||||
String _$chatroomHash() => r'3a945a61ea434f860fbeae9d40778fbfceddc5db';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@ -53,7 +53,7 @@ class _SystemHash {
|
||||
const chatroomProvider = ChatroomFamily();
|
||||
|
||||
/// See also [chatroom].
|
||||
class ChatroomFamily extends Family<AsyncValue<SnChat?>> {
|
||||
class ChatroomFamily extends Family<AsyncValue<SnChatRoom?>> {
|
||||
/// See also [chatroom].
|
||||
const ChatroomFamily();
|
||||
|
||||
@ -83,7 +83,7 @@ class ChatroomFamily extends Family<AsyncValue<SnChat?>> {
|
||||
}
|
||||
|
||||
/// See also [chatroom].
|
||||
class ChatroomProvider extends AutoDisposeFutureProvider<SnChat?> {
|
||||
class ChatroomProvider extends AutoDisposeFutureProvider<SnChatRoom?> {
|
||||
/// See also [chatroom].
|
||||
ChatroomProvider(int? identifier)
|
||||
: this._internal(
|
||||
@ -113,7 +113,7 @@ class ChatroomProvider extends AutoDisposeFutureProvider<SnChat?> {
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnChat?> Function(ChatroomRef provider) create,
|
||||
FutureOr<SnChatRoom?> Function(ChatroomRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
@ -130,7 +130,7 @@ class ChatroomProvider extends AutoDisposeFutureProvider<SnChat?> {
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnChat?> createElement() {
|
||||
AutoDisposeFutureProviderElement<SnChatRoom?> createElement() {
|
||||
return _ChatroomProviderElement(this);
|
||||
}
|
||||
|
||||
@ -150,12 +150,13 @@ class ChatroomProvider extends AutoDisposeFutureProvider<SnChat?> {
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ChatroomRef on AutoDisposeFutureProviderRef<SnChat?> {
|
||||
mixin ChatroomRef on AutoDisposeFutureProviderRef<SnChatRoom?> {
|
||||
/// The parameter `identifier` of this provider.
|
||||
int? get identifier;
|
||||
}
|
||||
|
||||
class _ChatroomProviderElement extends AutoDisposeFutureProviderElement<SnChat?>
|
||||
class _ChatroomProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnChatRoom?>
|
||||
with ChatroomRef {
|
||||
_ChatroomProviderElement(super.provider);
|
||||
|
||||
|
@ -443,19 +443,28 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
height: 26,
|
||||
width: 26,
|
||||
child:
|
||||
room?.pictureId != null
|
||||
room!.type == 1
|
||||
? ProfilePictureWidget(
|
||||
fileId: room?.pictureId,
|
||||
fileId:
|
||||
room.members!.first.account.profile.pictureId,
|
||||
)
|
||||
: room.pictureId != null
|
||||
? ProfilePictureWidget(
|
||||
fileId: room.pictureId,
|
||||
fallbackIcon: Symbols.chat,
|
||||
)
|
||||
: CircleAvatar(
|
||||
child: Text(
|
||||
room?.name[0].toUpperCase() ?? '',
|
||||
room.name[0].toUpperCase(),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(room?.name ?? 'unknown'.tr()).fontSize(19),
|
||||
Text(
|
||||
room!.type == 1
|
||||
? room.members!.first.account.nick
|
||||
: room.name,
|
||||
).fontSize(19),
|
||||
],
|
||||
),
|
||||
loading: () => const Text('Loading...'),
|
||||
@ -613,7 +622,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
|
||||
class _ChatInput extends StatelessWidget {
|
||||
final TextEditingController messageController;
|
||||
final SnChat chatRoom;
|
||||
final SnChatRoom chatRoom;
|
||||
final VoidCallback onSend;
|
||||
final VoidCallback onClear;
|
||||
final Function(bool isPhoto) onPickFile;
|
||||
@ -744,7 +753,12 @@ class _ChatInput extends StatelessWidget {
|
||||
child: TextField(
|
||||
controller: messageController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'chatMessageHint'.tr(args: [chatRoom.name]),
|
||||
hintText:
|
||||
chatRoom.type == 1
|
||||
? 'chatDirectMessageHint'.tr(
|
||||
args: [chatRoom.members!.first.account.nick],
|
||||
)
|
||||
: 'chatMessageHint'.tr(args: [chatRoom.name]),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
|
@ -55,9 +55,26 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
leading: PageBackButton(shadows: [iconShadow]),
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background:
|
||||
currentRoom?.backgroundId != null
|
||||
currentRoom!.type == 1 &&
|
||||
currentRoom
|
||||
.members!
|
||||
.first
|
||||
.account
|
||||
.profile
|
||||
.backgroundId !=
|
||||
null
|
||||
? CloudImageWidget(
|
||||
fileId: currentRoom!.backgroundId!,
|
||||
fileId:
|
||||
currentRoom
|
||||
.members!
|
||||
.first
|
||||
.account
|
||||
.profile
|
||||
.backgroundId!,
|
||||
)
|
||||
: currentRoom.backgroundId != null
|
||||
? CloudImageWidget(
|
||||
fileId: currentRoom.backgroundId!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Container(
|
||||
@ -65,7 +82,9 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
Theme.of(context).appBarTheme.backgroundColor,
|
||||
),
|
||||
title: Text(
|
||||
currentRoom?.name ?? 'unknown'.tr(),
|
||||
currentRoom.type == 1
|
||||
? currentRoom.members!.first.account.nick
|
||||
: currentRoom.name,
|
||||
).textColor(Theme.of(context).appBarTheme.foregroundColor),
|
||||
),
|
||||
actions: [
|
||||
|
Reference in New Issue
Block a user