Chat room layout

This commit is contained in:
LittleSheep 2025-05-03 02:01:25 +08:00
parent cce0be4fb2
commit 63ec82891f
8 changed files with 799 additions and 107 deletions

View File

@ -70,5 +70,10 @@
"name": "Name",
"description": "Description",
"slug": "Slug",
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe."
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
"createChatRoom": "Create a Room",
"editChatRoom": "Edit a Room",
"chat": "Chat",
"chatMessageHint": "Message in {}",
"chatDirectMessageHint": "Message to {}"
}

View File

@ -16,6 +16,7 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: ExploreRoute.page, path: 'explore'),
AutoRoute(page: AccountRoute.page, path: 'account'),
AutoRoute(page: RealmListRoute.page, path: 'realms'),
AutoRoute(page: ChatListRoute.page, path: 'chat'),
],
),
AutoRoute(page: LoginRoute.page, path: '/auth/login'),
@ -33,5 +34,8 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
AutoRoute(page: NewRealmRoute.page, path: '/realms/new'),
AutoRoute(page: EditRealmRoute.page, path: '/realms/:slug/edit'),
AutoRoute(page: NewChatRoute.page, path: '/chat/new'),
AutoRoute(page: EditChatRoute.page, path: '/chat/:id/edit'),
AutoRoute(page: ChatRoomRoute.page, path: '/chat/:id'),
];
}

View File

@ -9,30 +9,32 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i12;
import 'package:flutter/material.dart' as _i13;
import 'package:island/models/post.dart' as _i14;
import 'package:auto_route/auto_route.dart' as _i14;
import 'package:flutter/material.dart' as _i15;
import 'package:island/models/post.dart' as _i16;
import 'package:island/screens/account.dart' as _i1;
import 'package:island/screens/account/me.dart' as _i7;
import 'package:island/screens/account/me/publishers.dart' as _i3;
import 'package:island/screens/account/me/update.dart' as _i11;
import 'package:island/screens/auth/create_account.dart' as _i2;
import 'package:island/screens/auth/login.dart' as _i6;
import 'package:island/screens/auth/tabs.dart' as _i10;
import 'package:island/screens/explore.dart' as _i5;
import 'package:island/screens/posts/compose.dart' as _i8;
import 'package:island/screens/posts/detail.dart' as _i9;
import 'package:island/screens/realm/realms.dart' as _i4;
import 'package:island/screens/account/me.dart' as _i9;
import 'package:island/screens/account/me/publishers.dart' as _i5;
import 'package:island/screens/account/me/update.dart' as _i13;
import 'package:island/screens/auth/create_account.dart' as _i4;
import 'package:island/screens/auth/login.dart' as _i8;
import 'package:island/screens/auth/tabs.dart' as _i12;
import 'package:island/screens/chat/chat.dart' as _i2;
import 'package:island/screens/chat/room.dart' as _i3;
import 'package:island/screens/explore.dart' as _i7;
import 'package:island/screens/posts/compose.dart' as _i10;
import 'package:island/screens/posts/detail.dart' as _i11;
import 'package:island/screens/realm/realms.dart' as _i6;
/// generated route for
/// [_i1.AccountScreen]
class AccountRoute extends _i12.PageRouteInfo<void> {
const AccountRoute({List<_i12.PageRouteInfo>? children})
class AccountRoute extends _i14.PageRouteInfo<void> {
const AccountRoute({List<_i14.PageRouteInfo>? children})
: super(AccountRoute.name, initialChildren: children);
static const String name = 'AccountRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i1.AccountScreen();
@ -41,28 +43,123 @@ class AccountRoute extends _i12.PageRouteInfo<void> {
}
/// generated route for
/// [_i2.CreateAccountScreen]
class CreateAccountRoute extends _i12.PageRouteInfo<void> {
const CreateAccountRoute({List<_i12.PageRouteInfo>? children})
: super(CreateAccountRoute.name, initialChildren: children);
/// [_i2.ChatListScreen]
class ChatListRoute extends _i14.PageRouteInfo<void> {
const ChatListRoute({List<_i14.PageRouteInfo>? children})
: super(ChatListRoute.name, initialChildren: children);
static const String name = 'CreateAccountRoute';
static const String name = 'ChatListRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i2.CreateAccountScreen();
return const _i2.ChatListScreen();
},
);
}
/// generated route for
/// [_i3.EditPublisherScreen]
class EditPublisherRoute extends _i12.PageRouteInfo<EditPublisherRouteArgs> {
/// [_i3.ChatRoomScreen]
class ChatRoomRoute extends _i14.PageRouteInfo<ChatRoomRouteArgs> {
ChatRoomRoute({
_i15.Key? key,
required int id,
List<_i14.PageRouteInfo>? children,
}) : super(
ChatRoomRoute.name,
args: ChatRoomRouteArgs(key: key, id: id),
rawPathParams: {'id': id},
initialChildren: children,
);
static const String name = 'ChatRoomRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<ChatRoomRouteArgs>(
orElse: () => ChatRoomRouteArgs(id: pathParams.getInt('id')),
);
return _i3.ChatRoomScreen(key: args.key, id: args.id);
},
);
}
class ChatRoomRouteArgs {
const ChatRoomRouteArgs({this.key, required this.id});
final _i15.Key? key;
final int id;
@override
String toString() {
return 'ChatRoomRouteArgs{key: $key, id: $id}';
}
}
/// generated route for
/// [_i4.CreateAccountScreen]
class CreateAccountRoute extends _i14.PageRouteInfo<void> {
const CreateAccountRoute({List<_i14.PageRouteInfo>? children})
: super(CreateAccountRoute.name, initialChildren: children);
static const String name = 'CreateAccountRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i4.CreateAccountScreen();
},
);
}
/// generated route for
/// [_i2.EditChatScreen]
class EditChatRoute extends _i14.PageRouteInfo<EditChatRouteArgs> {
EditChatRoute({_i15.Key? key, int? id, List<_i14.PageRouteInfo>? children})
: super(
EditChatRoute.name,
args: EditChatRouteArgs(key: key, id: id),
rawPathParams: {'id': id},
initialChildren: children,
);
static const String name = 'EditChatRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<EditChatRouteArgs>(
orElse: () => EditChatRouteArgs(id: pathParams.optInt('id')),
);
return _i2.EditChatScreen(key: args.key, id: args.id);
},
);
}
class EditChatRouteArgs {
const EditChatRouteArgs({this.key, this.id});
final _i15.Key? key;
final int? id;
@override
String toString() {
return 'EditChatRouteArgs{key: $key, id: $id}';
}
}
/// generated route for
/// [_i5.EditPublisherScreen]
class EditPublisherRoute extends _i14.PageRouteInfo<EditPublisherRouteArgs> {
EditPublisherRoute({
_i13.Key? key,
_i15.Key? key,
String? name,
List<_i12.PageRouteInfo>? children,
List<_i14.PageRouteInfo>? children,
}) : super(
EditPublisherRoute.name,
args: EditPublisherRouteArgs(key: key, name: name),
@ -72,14 +169,14 @@ class EditPublisherRoute extends _i12.PageRouteInfo<EditPublisherRouteArgs> {
static const String name = 'EditPublisherRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<EditPublisherRouteArgs>(
orElse: () => EditPublisherRouteArgs(name: pathParams.optString('id')),
);
return _i3.EditPublisherScreen(key: args.key, name: args.name);
return _i5.EditPublisherScreen(key: args.key, name: args.name);
},
);
}
@ -87,7 +184,7 @@ class EditPublisherRoute extends _i12.PageRouteInfo<EditPublisherRouteArgs> {
class EditPublisherRouteArgs {
const EditPublisherRouteArgs({this.key, this.name});
final _i13.Key? key;
final _i15.Key? key;
final String? name;
@ -98,12 +195,12 @@ class EditPublisherRouteArgs {
}
/// generated route for
/// [_i4.EditRealmScreen]
class EditRealmRoute extends _i12.PageRouteInfo<EditRealmRouteArgs> {
/// [_i6.EditRealmScreen]
class EditRealmRoute extends _i14.PageRouteInfo<EditRealmRouteArgs> {
EditRealmRoute({
_i13.Key? key,
_i15.Key? key,
String? slug,
List<_i12.PageRouteInfo>? children,
List<_i14.PageRouteInfo>? children,
}) : super(
EditRealmRoute.name,
args: EditRealmRouteArgs(key: key, slug: slug),
@ -113,14 +210,14 @@ class EditRealmRoute extends _i12.PageRouteInfo<EditRealmRouteArgs> {
static const String name = 'EditRealmRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<EditRealmRouteArgs>(
orElse: () => EditRealmRouteArgs(slug: pathParams.optString('slug')),
);
return _i4.EditRealmScreen(key: args.key, slug: args.slug);
return _i6.EditRealmScreen(key: args.key, slug: args.slug);
},
);
}
@ -128,7 +225,7 @@ class EditRealmRoute extends _i12.PageRouteInfo<EditRealmRouteArgs> {
class EditRealmRouteArgs {
const EditRealmRouteArgs({this.key, this.slug});
final _i13.Key? key;
final _i15.Key? key;
final String? slug;
@ -139,108 +236,124 @@ class EditRealmRouteArgs {
}
/// generated route for
/// [_i5.ExploreScreen]
class ExploreRoute extends _i12.PageRouteInfo<void> {
const ExploreRoute({List<_i12.PageRouteInfo>? children})
/// [_i7.ExploreScreen]
class ExploreRoute extends _i14.PageRouteInfo<void> {
const ExploreRoute({List<_i14.PageRouteInfo>? children})
: super(ExploreRoute.name, initialChildren: children);
static const String name = 'ExploreRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i5.ExploreScreen();
return const _i7.ExploreScreen();
},
);
}
/// generated route for
/// [_i6.LoginScreen]
class LoginRoute extends _i12.PageRouteInfo<void> {
const LoginRoute({List<_i12.PageRouteInfo>? children})
/// [_i8.LoginScreen]
class LoginRoute extends _i14.PageRouteInfo<void> {
const LoginRoute({List<_i14.PageRouteInfo>? children})
: super(LoginRoute.name, initialChildren: children);
static const String name = 'LoginRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i6.LoginScreen();
return const _i8.LoginScreen();
},
);
}
/// generated route for
/// [_i3.ManagedPublisherScreen]
class ManagedPublisherRoute extends _i12.PageRouteInfo<void> {
const ManagedPublisherRoute({List<_i12.PageRouteInfo>? children})
/// [_i5.ManagedPublisherScreen]
class ManagedPublisherRoute extends _i14.PageRouteInfo<void> {
const ManagedPublisherRoute({List<_i14.PageRouteInfo>? children})
: super(ManagedPublisherRoute.name, initialChildren: children);
static const String name = 'ManagedPublisherRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i3.ManagedPublisherScreen();
return const _i5.ManagedPublisherScreen();
},
);
}
/// generated route for
/// [_i7.MyselfProfileScreen]
class MyselfProfileRoute extends _i12.PageRouteInfo<void> {
const MyselfProfileRoute({List<_i12.PageRouteInfo>? children})
/// [_i9.MyselfProfileScreen]
class MyselfProfileRoute extends _i14.PageRouteInfo<void> {
const MyselfProfileRoute({List<_i14.PageRouteInfo>? children})
: super(MyselfProfileRoute.name, initialChildren: children);
static const String name = 'MyselfProfileRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i7.MyselfProfileScreen();
return const _i9.MyselfProfileScreen();
},
);
}
/// generated route for
/// [_i3.NewPublisherScreen]
class NewPublisherRoute extends _i12.PageRouteInfo<void> {
const NewPublisherRoute({List<_i12.PageRouteInfo>? children})
/// [_i2.NewChatScreen]
class NewChatRoute extends _i14.PageRouteInfo<void> {
const NewChatRoute({List<_i14.PageRouteInfo>? children})
: super(NewChatRoute.name, initialChildren: children);
static const String name = 'NewChatRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i2.NewChatScreen();
},
);
}
/// generated route for
/// [_i5.NewPublisherScreen]
class NewPublisherRoute extends _i14.PageRouteInfo<void> {
const NewPublisherRoute({List<_i14.PageRouteInfo>? children})
: super(NewPublisherRoute.name, initialChildren: children);
static const String name = 'NewPublisherRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i3.NewPublisherScreen();
return const _i5.NewPublisherScreen();
},
);
}
/// generated route for
/// [_i4.NewRealmScreen]
class NewRealmRoute extends _i12.PageRouteInfo<void> {
const NewRealmRoute({List<_i12.PageRouteInfo>? children})
/// [_i6.NewRealmScreen]
class NewRealmRoute extends _i14.PageRouteInfo<void> {
const NewRealmRoute({List<_i14.PageRouteInfo>? children})
: super(NewRealmRoute.name, initialChildren: children);
static const String name = 'NewRealmRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i4.NewRealmScreen();
return const _i6.NewRealmScreen();
},
);
}
/// generated route for
/// [_i8.PostComposeScreen]
class PostComposeRoute extends _i12.PageRouteInfo<PostComposeRouteArgs> {
/// [_i10.PostComposeScreen]
class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> {
PostComposeRoute({
_i13.Key? key,
_i14.SnPost? originalPost,
List<_i12.PageRouteInfo>? children,
_i15.Key? key,
_i16.SnPost? originalPost,
List<_i14.PageRouteInfo>? children,
}) : super(
PostComposeRoute.name,
args: PostComposeRouteArgs(key: key, originalPost: originalPost),
@ -249,13 +362,13 @@ class PostComposeRoute extends _i12.PageRouteInfo<PostComposeRouteArgs> {
static const String name = 'PostComposeRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final args = data.argsAs<PostComposeRouteArgs>(
orElse: () => const PostComposeRouteArgs(),
);
return _i8.PostComposeScreen(
return _i10.PostComposeScreen(
key: args.key,
originalPost: args.originalPost,
);
@ -266,9 +379,9 @@ class PostComposeRoute extends _i12.PageRouteInfo<PostComposeRouteArgs> {
class PostComposeRouteArgs {
const PostComposeRouteArgs({this.key, this.originalPost});
final _i13.Key? key;
final _i15.Key? key;
final _i14.SnPost? originalPost;
final _i16.SnPost? originalPost;
@override
String toString() {
@ -277,12 +390,12 @@ class PostComposeRouteArgs {
}
/// generated route for
/// [_i9.PostDetailScreen]
class PostDetailRoute extends _i12.PageRouteInfo<PostDetailRouteArgs> {
/// [_i11.PostDetailScreen]
class PostDetailRoute extends _i14.PageRouteInfo<PostDetailRouteArgs> {
PostDetailRoute({
_i13.Key? key,
_i15.Key? key,
required int id,
List<_i12.PageRouteInfo>? children,
List<_i14.PageRouteInfo>? children,
}) : super(
PostDetailRoute.name,
args: PostDetailRouteArgs(key: key, id: id),
@ -292,14 +405,14 @@ class PostDetailRoute extends _i12.PageRouteInfo<PostDetailRouteArgs> {
static const String name = 'PostDetailRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<PostDetailRouteArgs>(
orElse: () => PostDetailRouteArgs(id: pathParams.getInt('id')),
);
return _i9.PostDetailScreen(key: args.key, id: args.id);
return _i11.PostDetailScreen(key: args.key, id: args.id);
},
);
}
@ -307,7 +420,7 @@ class PostDetailRoute extends _i12.PageRouteInfo<PostDetailRouteArgs> {
class PostDetailRouteArgs {
const PostDetailRouteArgs({this.key, required this.id});
final _i13.Key? key;
final _i15.Key? key;
final int id;
@ -318,12 +431,12 @@ class PostDetailRouteArgs {
}
/// generated route for
/// [_i8.PostEditScreen]
class PostEditRoute extends _i12.PageRouteInfo<PostEditRouteArgs> {
/// [_i10.PostEditScreen]
class PostEditRoute extends _i14.PageRouteInfo<PostEditRouteArgs> {
PostEditRoute({
_i13.Key? key,
_i15.Key? key,
required int id,
List<_i12.PageRouteInfo>? children,
List<_i14.PageRouteInfo>? children,
}) : super(
PostEditRoute.name,
args: PostEditRouteArgs(key: key, id: id),
@ -333,14 +446,14 @@ class PostEditRoute extends _i12.PageRouteInfo<PostEditRouteArgs> {
static const String name = 'PostEditRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<PostEditRouteArgs>(
orElse: () => PostEditRouteArgs(id: pathParams.getInt('id')),
);
return _i8.PostEditScreen(key: args.key, id: args.id);
return _i10.PostEditScreen(key: args.key, id: args.id);
},
);
}
@ -348,7 +461,7 @@ class PostEditRoute extends _i12.PageRouteInfo<PostEditRouteArgs> {
class PostEditRouteArgs {
const PostEditRouteArgs({this.key, required this.id});
final _i13.Key? key;
final _i15.Key? key;
final int id;
@ -359,49 +472,49 @@ class PostEditRouteArgs {
}
/// generated route for
/// [_i4.RealmListScreen]
class RealmListRoute extends _i12.PageRouteInfo<void> {
const RealmListRoute({List<_i12.PageRouteInfo>? children})
/// [_i6.RealmListScreen]
class RealmListRoute extends _i14.PageRouteInfo<void> {
const RealmListRoute({List<_i14.PageRouteInfo>? children})
: super(RealmListRoute.name, initialChildren: children);
static const String name = 'RealmListRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i4.RealmListScreen();
return const _i6.RealmListScreen();
},
);
}
/// generated route for
/// [_i10.TabsScreen]
class TabsRoute extends _i12.PageRouteInfo<void> {
const TabsRoute({List<_i12.PageRouteInfo>? children})
/// [_i12.TabsScreen]
class TabsRoute extends _i14.PageRouteInfo<void> {
const TabsRoute({List<_i14.PageRouteInfo>? children})
: super(TabsRoute.name, initialChildren: children);
static const String name = 'TabsRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i10.TabsScreen();
return const _i12.TabsScreen();
},
);
}
/// generated route for
/// [_i11.UpdateProfileScreen]
class UpdateProfileRoute extends _i12.PageRouteInfo<void> {
const UpdateProfileRoute({List<_i12.PageRouteInfo>? children})
/// [_i13.UpdateProfileScreen]
class UpdateProfileRoute extends _i14.PageRouteInfo<void> {
const UpdateProfileRoute({List<_i14.PageRouteInfo>? children})
: super(UpdateProfileRoute.name, initialChildren: children);
static const String name = 'UpdateProfileRoute';
static _i12.PageInfo page = _i12.PageInfo(
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i11.UpdateProfileScreen();
return const _i13.UpdateProfileScreen();
},
);
}

View File

@ -11,7 +11,12 @@ class TabsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AutoTabsRouter.pageView(
routes: const [ExploreRoute(), RealmListRoute(), AccountRoute()],
routes: const [
ExploreRoute(),
ChatListRoute(),
RealmListRoute(),
AccountRoute(),
],
builder: (context, child, _) {
final tabsRouter = AutoTabsRouter.of(context);
return Scaffold(
@ -26,6 +31,10 @@ class TabsScreen extends StatelessWidget {
label: 'explore'.tr(),
icon: const Icon(Symbols.explore),
),
NavigationDestination(
label: 'chat'.tr(),
icon: const Icon(Symbols.chat),
),
NavigationDestination(
label: 'realms'.tr(),
icon: const Icon(Symbols.workspaces),

276
lib/screens/chat/chat.dart Normal file
View File

@ -0,0 +1,276 @@
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_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:island/models/chat.dart';
import 'package:island/models/file.dart';
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/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
part 'chat.g.dart';
@riverpod
Future<List<SnChat>> 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();
}
@RoutePage()
class ChatListScreen extends HookConsumerWidget {
const ChatListScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final chats = ref.watch(chatroomsJoinedProvider);
return AppScaffold(
appBar: AppBar(title: Text('chat').tr()),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.pushRoute(NewChatRoute());
},
child: const Icon(Symbols.add),
),
body: chats.when(
data:
(items) => RefreshIndicator(
onRefresh:
() => Future.sync(() {
ref.invalidate(chatroomsJoinedProvider);
}),
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading:
item.picture == null
? CircleAvatar(
child: Text(item.name[0].toUpperCase()),
)
: ProfilePictureWidget(item: item.picture),
title: Text(item.name),
subtitle: Text(item.description),
onTap: () {
context.pushRoute(ChatRoomRoute(id: item.id));
},
);
},
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
);
}
}
@riverpod
Future<SnChat?> 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);
}
@RoutePage()
class NewChatScreen extends StatelessWidget {
const NewChatScreen({super.key});
@override
Widget build(BuildContext context) {
return EditChatScreen();
}
}
@RoutePage()
class EditChatScreen extends HookConsumerWidget {
final int? id;
const EditChatScreen({super.key, @PathParam("id") this.id});
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>(), []);
final submitting = useState(false);
final nameController = useTextEditingController();
final descriptionController = useTextEditingController();
final picture = useState<SnCloudFile?>(null);
final background = useState<SnCloudFile?>(null);
final chat = ref.watch(chatroomProvider(id));
useEffect(() {
if (chat.value != null) {
nameController.text = chat.value!.name;
descriptionController.text = chat.value!.description;
picture.value = chat.value!.picture;
background.value = chat.value!.background;
}
return;
}, [chat]);
void setPicture(String position) async {
final result = await ref
.read(imagePickerProvider)
.pickImage(source: ImageSource.gallery);
if (result == null) return;
submitting.value = true;
try {
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final cloudFile =
await putMediaToCloud(
fileData: result,
atk: atk,
baseUrl: baseUrl,
filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg',
).future;
if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...');
}
switch (position) {
case 'picture':
picture.value = cloudFile;
case 'background':
background.value = cloudFile;
}
} catch (err) {
showErrorAlert(err);
} finally {
submitting.value = false;
}
}
Future<void> performAction() async {
if (!formKey.currentState!.validate()) return;
submitting.value = true;
try {
final client = ref.watch(apiClientProvider);
final resp = await client.request(
id == null ? '/chat' : '/chat/$id',
data: {
'name': nameController.text,
'description': descriptionController.text,
'background_id': background.value?.id,
'picture_id': picture.value?.id,
},
options: Options(method: id == null ? 'POST' : 'PATCH'),
);
if (context.mounted) {
context.maybePop(SnChat.fromJson(resp.data));
}
} catch (err) {
showErrorAlert(err);
} finally {
submitting.value = false;
}
}
return AppScaffold(
appBar: AppBar(
title: Text(id == null ? 'createChatRoom' : 'editChatRoom').tr(),
leading: const PageBackButton(),
),
body: Column(
children: [
AspectRatio(
aspectRatio: 16 / 7,
child: Stack(
clipBehavior: Clip.none,
fit: StackFit.expand,
children: [
GestureDetector(
child: Container(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child:
background.value != null
? CloudFileWidget(
item: background.value!,
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
),
onTap: () {
setPicture('background');
},
),
Positioned(
left: 20,
bottom: -32,
child: GestureDetector(
child: ProfilePictureWidget(
item: picture.value,
radius: 40,
fallbackIcon: Symbols.group,
),
onTap: () {
setPicture('picture');
},
),
),
],
),
).padding(bottom: 32),
Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Name'),
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 16),
TextFormField(
controller: descriptionController,
decoration: const InputDecoration(labelText: 'Description'),
minLines: 3,
maxLines: null,
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 16),
Align(
alignment: Alignment.centerRight,
child: TextButton.icon(
onPressed: submitting.value ? null : performAction,
label: const Text('Save'),
icon: const Icon(Symbols.save),
),
),
],
).padding(all: 24),
),
],
),
);
}
}

View File

@ -0,0 +1,167 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$chatroomsJoinedHash() => r'3a2db4159663c54dfd7bc40519e2faa6df69b41f';
/// See also [chatroomsJoined].
@ProviderFor(chatroomsJoined)
final chatroomsJoinedProvider =
AutoDisposeFutureProvider<List<SnChat>>.internal(
chatroomsJoined,
name: r'chatroomsJoinedProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomsJoinedHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChat>>;
String _$chatroomHash() => r'27bd4cb49326bb2f2eac7d7db9db7f610e21afb2';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [chatroom].
@ProviderFor(chatroom)
const chatroomProvider = ChatroomFamily();
/// See also [chatroom].
class ChatroomFamily extends Family<AsyncValue<SnChat?>> {
/// See also [chatroom].
const ChatroomFamily();
/// See also [chatroom].
ChatroomProvider call(int? identifier) {
return ChatroomProvider(identifier);
}
@override
ChatroomProvider getProviderOverride(covariant ChatroomProvider provider) {
return call(provider.identifier);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatroomProvider';
}
/// See also [chatroom].
class ChatroomProvider extends AutoDisposeFutureProvider<SnChat?> {
/// See also [chatroom].
ChatroomProvider(int? identifier)
: this._internal(
(ref) => chatroom(ref as ChatroomRef, identifier),
from: chatroomProvider,
name: r'chatroomProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomHash,
dependencies: ChatroomFamily._dependencies,
allTransitiveDependencies: ChatroomFamily._allTransitiveDependencies,
identifier: identifier,
);
ChatroomProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.identifier,
}) : super.internal();
final int? identifier;
@override
Override overrideWith(
FutureOr<SnChat?> Function(ChatroomRef provider) create,
) {
return ProviderOverride(
origin: this,
override: ChatroomProvider._internal(
(ref) => create(ref as ChatroomRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
identifier: identifier,
),
);
}
@override
AutoDisposeFutureProviderElement<SnChat?> createElement() {
return _ChatroomProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ChatroomProvider && other.identifier == identifier;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, identifier.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ChatroomRef on AutoDisposeFutureProviderRef<SnChat?> {
/// The parameter `identifier` of this provider.
int? get identifier;
}
class _ChatroomProviderElement extends AutoDisposeFutureProviderElement<SnChat?>
with ChatroomRef {
_ChatroomProviderElement(super.provider);
@override
int? get identifier => (origin as ChatroomProvider).identifier;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

118
lib/screens/chat/room.dart Normal file
View File

@ -0,0 +1,118 @@
import 'package:auto_route/annotations.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:styled_widget/styled_widget.dart';
import 'chat.dart';
@RoutePage()
class ChatRoomScreen extends HookConsumerWidget {
final int id;
const ChatRoomScreen({super.key, @PathParam("id") required this.id});
@override
Widget build(BuildContext context, WidgetRef ref) {
final chatRoom = ref.watch(chatroomProvider(id));
final messageController = useTextEditingController();
return Scaffold(
appBar: AppBar(
title: chatRoom.when(
data:
(room) => Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 26,
width: 26,
child:
room?.picture != null
? ProfilePictureWidget(
item: room?.picture,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
child: Text(
room?.name[0].toUpperCase() ?? '',
style: const TextStyle(fontSize: 12),
),
),
),
Text(room?.name ?? 'unknown').fontSize(19).tr(),
],
),
loading: () => const Text('Loading...'),
error: (_, __) => const Text('Error'),
),
actions: [
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
const Gap(8),
],
),
body: Column(
children: [
Expanded(
child: chatRoom.when(
data: (room) => SizedBox.expand(),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
),
Material(
elevation: 2,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 3,
),
],
),
child: Row(
children: [
IconButton(icon: const Icon(Icons.add), onPressed: () {}),
Expanded(
child: TextField(
controller: messageController,
decoration: InputDecoration(
hintText: 'chatMessageHint'.tr(
args: [chatRoom.value?.name ?? 'unknown'.tr()],
),
border: OutlineInputBorder(),
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
maxLines: null,
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
const Gap(8),
IconButton(
icon: const Icon(Icons.send),
color: Theme.of(context).colorScheme.primary,
onPressed: () {},
),
],
).padding(bottom: MediaQuery.of(context).padding.bottom),
),
),
],
),
);
}
}