⚗️ Testing desktop layout

This commit is contained in:
LittleSheep 2025-05-18 23:50:50 +08:00
parent ad41de3674
commit 911439f3af
7 changed files with 342 additions and 168 deletions

View File

@ -16,7 +16,17 @@ 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: ChatShellRoute.page,
path: 'chat',
children: [
AutoRoute(page: ChatListRoute.page, path: ''),
AutoRoute(page: ChatRoomRoute.page, path: ':id'),
AutoRoute(page: NewChatRoute.page, path: 'new'),
AutoRoute(page: EditChatRoute.page, path: ':id/edit'),
AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'),
],
),
],
),
AutoRoute(page: WalletRoute.page, path: '/wallet'),
@ -47,10 +57,6 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: NewRealmRoute.page, path: '/realms/new'),
AutoRoute(page: RealmDetailRoute.page, path: '/realms/:slug'),
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'),
AutoRoute(page: ChatDetailRoute.page, path: '/chat/:id/detail'),
AutoRoute(page: CreatorHubRoute.page, path: '/creators'),
AutoRoute(page: StickersRoute.page, path: '/creators/:name/stickers'),
AutoRoute(

View File

@ -209,6 +209,22 @@ class ChatRoomRouteArgs {
}
}
/// generated route for
/// [_i5.ChatShellScreen]
class ChatShellRoute extends _i25.PageRouteInfo<void> {
const ChatShellRoute({List<_i25.PageRouteInfo>? children})
: super(ChatShellRoute.name, initialChildren: children);
static const String name = 'ChatShellRoute';
static _i25.PageInfo page = _i25.PageInfo(
name,
builder: (data) {
return const _i5.ChatShellScreen();
},
);
}
/// generated route for
/// [_i7.CreateAccountScreen]
class CreateAccountRoute extends _i25.PageRouteInfo<void> {

View File

@ -1,6 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:island/route.gr.dart';
import 'package:island/services/responsive.dart';
import 'package:material_symbols_icons/symbols.dart';
@ -11,46 +12,107 @@ class TabsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final useHorizontalLayout =
MediaQuery.of(context).size.width > kWideScreenWidth;
final useHorizontalLayout = isWideScreen(context);
final useExpandableLayout = isWidestScreen(context);
return AutoTabsRouter.pageView(
routes: const [
ExploreRoute(),
ChatListRoute(),
RealmListRoute(),
AccountRoute(),
],
final destinations = [
NavigationDestination(
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),
),
NavigationDestination(
label: 'account'.tr(),
icon: const Icon(Symbols.account_circle),
),
];
final routes = [
ExploreRoute(),
ChatListRoute(),
RealmListRoute(),
AccountRoute(),
];
return AutoTabsRouter.tabBar(
routes: routes,
scrollDirection: useHorizontalLayout ? Axis.vertical : Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
builder: (context, child, _) {
final tabsRouter = AutoTabsRouter.of(context);
// Check if current route is a tab route
final currentRoute = context.router.topRoute;
final isTabRoute = routes.any(
(route) => route.routeName == currentRoute.name,
);
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Colors.transparent,
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: tabsRouter.setActiveIndex,
destinations: [
NavigationDestination(
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),
),
NavigationDestination(
label: 'account'.tr(),
icon: const Icon(Symbols.account_circle),
),
],
),
body:
useHorizontalLayout
? Row(
children: [
if (isTabRoute)
Column(
children: [
Gap(MediaQuery.of(context).padding.top + 8),
if (useExpandableLayout)
Expanded(
child: NavigationDrawer(
backgroundColor: Colors.transparent,
children: [
for (final destination in destinations)
NavigationDrawerDestination(
label: Text(destination.label),
icon: destination.icon,
),
],
),
)
else
Expanded(
child: NavigationRail(
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected:
tabsRouter.setActiveIndex,
labelType: NavigationRailLabelType.all,
destinations:
destinations
.map(
(d) => NavigationRailDestination(
icon: d.icon,
label: Text(d.label),
),
)
.toList(),
),
),
Gap(MediaQuery.of(context).padding.bottom + 8),
],
),
if (isTabRoute)
VerticalDivider(
color: Theme.of(context).dividerColor,
width: 1 / MediaQuery.of(context).devicePixelRatio,
),
Expanded(child: child),
],
)
: child,
bottomNavigationBar:
!useHorizontalLayout && isTabRoute
? NavigationBar(
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: tabsRouter.setActiveIndex,
destinations: destinations,
)
: null,
);
},
);

View File

@ -16,11 +16,13 @@ import 'package:island/pods/network.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/realm/realms.dart';
import 'package:island/services/file.dart';
import 'package:island/services/responsive.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';
import 'package:island/widgets/realms/selection_dropdown.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
@ -69,11 +71,7 @@ class ChatRoomListTile extends StatelessWidget {
? Text(room.members!.map((e) => '@${e.account.name}').join(', '))
: Text(room.description ?? 'descriptionNone'.tr()),
trailing: trailing,
onTap:
onTap ??
() {
context.pushRoute(ChatRoomRoute(id: room.id));
},
onTap: onTap,
);
}
}
@ -88,9 +86,32 @@ Future<List<SnChatRoom>> chatroomsJoined(Ref ref) async {
.toList();
}
@RoutePage()
class ChatShellScreen extends HookConsumerWidget {
const ChatShellScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isWide = isWideScreen(context);
if (isWide) {
return Row(
children: [
SizedBox(width: 320, child: ChatListScreen(isAside: true)),
VerticalDivider(width: 1),
Expanded(child: AutoRouter()),
],
);
}
return AutoRouter();
}
}
@RoutePage()
class ChatListScreen extends HookConsumerWidget {
const ChatListScreen({super.key});
final bool isAside;
const ChatListScreen({super.key, this.isAside = false});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -112,6 +133,12 @@ class ChatListScreen extends HookConsumerWidget {
}
}
final isWide = isWideScreen(context);
if (isWide && !isAside) {
return SizedBox.shrink();
}
return AppScaffold(
appBar: AppBar(
title: Text('chat').tr(),
@ -191,15 +218,25 @@ class ChatListScreen extends HookConsumerWidget {
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ChatRoomListTile(room: item, isDirect: item.type == 1);
return ChatRoomListTile(
room: item,
isDirect: item.type == 1,
onTap: () {
if (isWide) {
context.router.replace(ChatRoomRoute(id: item.id));
} else {
context.router.push(ChatRoomRoute(id: item.id));
}
},
);
},
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error:
(error, stack) => GestureDetector(
child: Center(child: Text('Error: $error')),
onTap: () {
(error, stack) => ResponseErrorWidget(
error: error,
onRetry: () {
ref.invalidate(chatroomsJoinedProvider);
},
),

View File

@ -17,7 +17,9 @@ import 'package:island/pods/network.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/posts/compose.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/message_item.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/response.dart';
@ -415,46 +417,96 @@ class ChatRoomScreen extends HookConsumerWidget {
}
}
final compactHeader = isWideScreen(context);
return Scaffold(
appBar: AppBar(
toolbarHeight: 64,
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
automaticallyImplyLeading: false,
toolbarHeight: compactHeader ? null : 64,
title: chatRoom.when(
data:
(room) => Column(
spacing: 4,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 26,
width: 26,
child:
(room!.type == 1 && room.pictureId == null)
? SplitAvatarWidget(
filesId:
room.members!
.map((e) => e.account.profile.pictureId)
.toList(),
)
: room.pictureId != null
? ProfilePictureWidget(
fileId: room.pictureId,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
child: Text(
room.name![0].toUpperCase(),
style: const TextStyle(fontSize: 12),
),
),
),
Text(
(room.type == 1 && room.name == null)
? room.members!.map((e) => e.account.nick).join(', ')
: room.name!,
).fontSize(15),
],
),
(room) =>
compactHeader
? Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 26,
width: 26,
child:
(room!.type == 1 && room.pictureId == null)
? SplitAvatarWidget(
filesId:
room.members!
.map(
(e) =>
e.account.profile.pictureId,
)
.toList(),
)
: room.pictureId != null
? ProfilePictureWidget(
fileId: room.pictureId,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
child: Text(
room.name![0].toUpperCase(),
style: const TextStyle(fontSize: 12),
),
),
),
Text(
(room.type == 1 && room.name == null)
? room.members!
.map((e) => e.account.nick)
.join(', ')
: room.name!,
).fontSize(19),
],
)
: Column(
spacing: 4,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 26,
width: 26,
child:
(room!.type == 1 && room.pictureId == null)
? SplitAvatarWidget(
filesId:
room.members!
.map(
(e) =>
e.account.profile.pictureId,
)
.toList(),
)
: room.pictureId != null
? ProfilePictureWidget(
fileId: room.pictureId,
fallbackIcon: Symbols.chat,
)
: CircleAvatar(
child: Text(
room.name![0].toUpperCase(),
style: const TextStyle(fontSize: 12),
),
),
),
Text(
(room.type == 1 && room.name == null)
? room.members!
.map((e) => e.account.nick)
.join(', ')
: room.name!,
).fontSize(15),
],
),
loading: () => const Text('Loading...'),
error:
(err, __) => ResponseErrorWidget(

View File

@ -1 +1,17 @@
const kWideScreenWidth = 640;
import 'package:flutter/widgets.dart';
const kWideScreenWidth = 768;
const kWiderScreenWidth = 1024;
const kWidescreenWidth = 1280;
bool isWideScreen(BuildContext context) {
return MediaQuery.of(context).size.width > kWideScreenWidth;
}
bool isWiderScreen(BuildContext context) {
return MediaQuery.of(context).size.width > kWiderScreenWidth;
}
bool isWidestScreen(BuildContext context) {
return MediaQuery.of(context).size.width > kWidescreenWidth;
}

View File

@ -151,92 +151,77 @@ class MessageItem extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: containerColor,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (remoteMessage.repliedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (remoteMessage.repliedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: true,
),
if (remoteMessage.forwardedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: false,
),
if (remoteMessage.content?.isNotEmpty ?? false)
MarkdownTextContent(
content: remoteMessage.content!,
isSelectable: true,
linkStyle: TextStyle(color: linkColor),
textStyle: TextStyle(
color: textColor,
fontSize: 14,
),
if (remoteMessage.forwardedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: false,
),
if (remoteMessage.content?.isNotEmpty ?? false)
MarkdownTextContent(
content: remoteMessage.content!,
isSelectable: true,
linkStyle: TextStyle(color: linkColor),
textStyle: TextStyle(
color: textColor,
fontSize: 14,
),
),
if (remoteMessage.attachments.isNotEmpty)
CloudFileList(
files: remoteMessage.attachments,
maxWidth: MediaQuery.of(context).size.width * 0.8,
).padding(top: 4),
if (progress != null && progress!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 8,
children: [
if ((remoteMessage.content?.isNotEmpty ??
false))
const Gap(0),
for (var entry in progress!.entries)
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'fileUploadingProgress'.tr(
args: [
(entry.key + 1).toString(),
entry.value.toStringAsFixed(1),
],
),
style: TextStyle(
fontSize: 12,
color: textColor.withOpacity(0.8),
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value / 100,
backgroundColor:
Theme.of(
context,
).colorScheme.surfaceVariant,
valueColor:
AlwaysStoppedAnimation<Color>(
Theme.of(
context,
).colorScheme.primary,
),
),
],
),
),
if (remoteMessage.attachments.isNotEmpty)
CloudFileList(
files: remoteMessage.attachments,
maxWidth: MediaQuery.of(context).size.width * 0.8,
).padding(top: 4),
if (progress != null && progress!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 8,
children: [
if ((remoteMessage.content?.isNotEmpty ?? false))
const Gap(0),
],
),
],
),
),
for (var entry in progress!.entries)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'fileUploadingProgress'.tr(
args: [
(entry.key + 1).toString(),
entry.value.toStringAsFixed(1),
],
),
style: TextStyle(
fontSize: 12,
color: textColor.withOpacity(0.8),
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value / 100,
backgroundColor:
Theme.of(
context,
).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
],
),
const Gap(0),
],
),
],
).padding(left: 40),
),
_buildMessageIndicators(
context,