Compare commits
4 Commits
9a75228e38
...
67044148f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
67044148f1
|
|||
|
92bc43e4df
|
|||
|
a1a7b34c86
|
|||
|
40c0e052cf
|
@@ -30,7 +30,6 @@ import 'package:island/screens/account/me/profile_update.dart';
|
|||||||
import 'package:island/screens/account/leveling.dart';
|
import 'package:island/screens/account/leveling.dart';
|
||||||
import 'package:island/screens/account/me/account_settings.dart';
|
import 'package:island/screens/account/me/account_settings.dart';
|
||||||
import 'package:island/screens/chat/chat.dart';
|
import 'package:island/screens/chat/chat.dart';
|
||||||
import 'package:island/screens/chat/chat_form.dart';
|
|
||||||
import 'package:island/screens/chat/room.dart';
|
import 'package:island/screens/chat/room.dart';
|
||||||
import 'package:island/screens/chat/room_detail.dart';
|
import 'package:island/screens/chat/room_detail.dart';
|
||||||
import 'package:island/screens/chat/call.dart';
|
import 'package:island/screens/chat/call.dart';
|
||||||
@@ -265,11 +264,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
path: '/chat',
|
path: '/chat',
|
||||||
builder: (context, state) => const ChatListScreen(),
|
builder: (context, state) => const ChatListScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
name: 'chatNew',
|
|
||||||
path: '/chat/new',
|
|
||||||
builder: (context, state) => const NewChatScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'chatRoom',
|
name: 'chatRoom',
|
||||||
path: '/chat/:id',
|
path: '/chat/:id',
|
||||||
@@ -278,14 +272,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return ChatRoomScreen(id: id);
|
return ChatRoomScreen(id: id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
name: 'chatEdit',
|
|
||||||
path: '/chat/:id/edit',
|
|
||||||
builder: (context, state) {
|
|
||||||
final id = state.pathParameters['id']!;
|
|
||||||
return EditChatScreen(id: id);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'chatDetail',
|
name: 'chatDetail',
|
||||||
path: '/chat/:id/detail',
|
path: '/chat/:id/detail',
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import 'package:island/screens/realm/realms.dart';
|
|||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
import 'package:island/services/file_uploader.dart';
|
import 'package:island/services/file_uploader.dart';
|
||||||
import 'package:island/widgets/alert.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/content/cloud_files.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
|
||||||
class NewChatScreen extends StatelessWidget {
|
class NewChatScreen extends StatelessWidget {
|
||||||
const NewChatScreen({super.key});
|
const NewChatScreen({super.key});
|
||||||
@@ -151,12 +151,10 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return SheetScaffold(
|
||||||
appBar: AppBar(
|
titleText: (id == null ? 'createChatRoom' : 'editChatRoom').tr(),
|
||||||
title: Text(id == null ? 'createChatRoom' : 'editChatRoom').tr(),
|
onClose: () => context.pop(),
|
||||||
leading: const PageBackButton(),
|
child: SingleChildScrollView(
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
@@ -204,16 +202,24 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
decoration: const InputDecoration(labelText: 'Name'),
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Name',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: descriptionController,
|
controller: descriptionController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Description',
|
labelText: 'Description',
|
||||||
alignLabelWithHint: true,
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
@@ -223,7 +229,12 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DropdownButtonFormField<SnRealm>(
|
DropdownButtonFormField<SnRealm>(
|
||||||
value: currentRealm.value,
|
value: currentRealm.value,
|
||||||
decoration: InputDecoration(labelText: 'realm'.tr()),
|
decoration: InputDecoration(
|
||||||
|
labelText: 'realm'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem<SnRealm>(
|
DropdownMenuItem<SnRealm>(
|
||||||
value: null,
|
value: null,
|
||||||
|
|||||||
@@ -142,6 +142,25 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
final messageController = useTextEditingController();
|
final messageController = useTextEditingController();
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
|
|
||||||
|
// Input height measurement for dynamic padding
|
||||||
|
final inputKey = useMemoized(() => GlobalKey());
|
||||||
|
final inputHeight = useState<double>(80.0);
|
||||||
|
|
||||||
|
// Periodic height measurement for dynamic sizing
|
||||||
|
useEffect(() {
|
||||||
|
final timer = Timer.periodic(const Duration(milliseconds: 50), (_) {
|
||||||
|
final renderBox =
|
||||||
|
inputKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
if (renderBox != null) {
|
||||||
|
final newHeight = renderBox.size.height;
|
||||||
|
if (newHeight != inputHeight.value) {
|
||||||
|
inputHeight.value = newHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return timer.cancel;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Scroll animation notifiers
|
// Scroll animation notifiers
|
||||||
final bottomGradientNotifier = useState(ValueNotifier<double>(0.0));
|
final bottomGradientNotifier = useState(ValueNotifier<double>(0.0));
|
||||||
|
|
||||||
@@ -600,7 +619,8 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
top: 16,
|
top: 16,
|
||||||
bottom:
|
bottom:
|
||||||
MediaQuery.of(context).padding.bottom +
|
MediaQuery.of(context).padding.bottom +
|
||||||
80, // Leave space for chat input
|
8 +
|
||||||
|
inputHeight.value, // Leave space for chat input
|
||||||
),
|
),
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
reverse: true, // Show newest messages at the bottom
|
reverse: true, // Show newest messages at the bottom
|
||||||
@@ -964,6 +984,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
child: chatRoom.when(
|
child: chatRoom.when(
|
||||||
data:
|
data:
|
||||||
(room) => Column(
|
(room) => Column(
|
||||||
|
key: inputKey,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ChatInput(
|
ChatInput(
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:island/widgets/alert.dart';
|
|||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/screens/chat/chat_form.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@@ -447,10 +448,17 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
|||||||
if ((chatIdentity.value?.role ?? 0) >= 50)
|
if ((chatIdentity.value?.role ?? 0) >= 50)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushReplacementNamed(
|
showModalBottomSheet(
|
||||||
'chatEdit',
|
context: context,
|
||||||
pathParameters: {'id': id},
|
useRootNavigator: true,
|
||||||
);
|
isScrollControlled: true,
|
||||||
|
builder: (context) => EditChatScreen(id: id),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
// Invalidate to refresh room data after edit
|
||||||
|
ref.invalidate(chatroomProvider(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class ThoughtScreen extends HookConsumerWidget {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar('Failed to retry billing');
|
showSnackBar('Failed to retry billing');
|
||||||
}
|
}
|
||||||
hideLoadingModal(context);
|
if (context.mounted) hideLoadingModal(context);
|
||||||
},
|
},
|
||||||
[context, ref],
|
[context, ref],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar('Failed to retry billing');
|
showSnackBar('Failed to retry billing');
|
||||||
}
|
}
|
||||||
hideLoadingModal(context);
|
if (context.mounted) hideLoadingModal(context);
|
||||||
},
|
},
|
||||||
[context, ref],
|
[context, ref],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:island/services/responsive.dart';
|
|||||||
import 'package:island/widgets/account/account_picker.dart';
|
import 'package:island/widgets/account/account_picker.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/post/compose_sheet.dart';
|
import 'package:island/widgets/post/compose_sheet.dart';
|
||||||
|
import 'package:island/screens/chat/chat_form.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
enum FabMenuType { main, compose, chat, realm }
|
enum FabMenuType { main, compose, chat, realm }
|
||||||
@@ -135,8 +136,12 @@ class FabMenu extends HookConsumerWidget {
|
|||||||
leading: const Icon(Symbols.add),
|
leading: const Icon(Symbols.add),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
showModalBottomSheet(
|
||||||
context.pushNamed('chatNew').then((value) {
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => const EditChatScreen(),
|
||||||
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
eventBus.fire(const ChatRoomsRefreshEvent());
|
eventBus.fire(const ChatRoomsRefreshEvent());
|
||||||
}
|
}
|
||||||
@@ -148,7 +153,6 @@ class FabMenu extends HookConsumerWidget {
|
|||||||
leading: const Icon(Symbols.person),
|
leading: const Icon(Symbols.person),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
|
||||||
_createDirectMessage(context, ref);
|
_createDirectMessage(context, ref);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
@@ -401,6 +402,9 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final inputKey = useMemoized(() => GlobalKey());
|
||||||
|
final inputHeight = useState<double>(80.0);
|
||||||
|
|
||||||
final chatState = useThoughtChat(
|
final chatState = useThoughtChat(
|
||||||
ref,
|
ref,
|
||||||
initialThoughts: initialThoughts,
|
initialThoughts: initialThoughts,
|
||||||
@@ -409,6 +413,21 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Periodic height measurement for dynamic sizing
|
||||||
|
useEffect(() {
|
||||||
|
final timer = Timer.periodic(const Duration(milliseconds: 50), (_) {
|
||||||
|
final renderBox =
|
||||||
|
inputKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
if (renderBox != null) {
|
||||||
|
final newHeight = renderBox.size.height;
|
||||||
|
if (newHeight != inputHeight.value) {
|
||||||
|
inputHeight.value = newHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return timer.cancel;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
// Thoughts list
|
// Thoughts list
|
||||||
@@ -425,7 +444,8 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
top: 16,
|
top: 16,
|
||||||
bottom:
|
bottom:
|
||||||
MediaQuery.of(context).padding.bottom +
|
MediaQuery.of(context).padding.bottom +
|
||||||
80, // Leave space for thought input
|
56 +
|
||||||
|
inputHeight.value, // Leave space for thought input
|
||||||
),
|
),
|
||||||
reverse: true,
|
reverse: true,
|
||||||
itemCount:
|
itemCount:
|
||||||
@@ -492,6 +512,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: 640),
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
child: ThoughtInput(
|
child: ThoughtInput(
|
||||||
|
key: inputKey,
|
||||||
messageController: chatState.messageController,
|
messageController: chatState.messageController,
|
||||||
isStreaming: chatState.isStreaming.value,
|
isStreaming: chatState.isStreaming.value,
|
||||||
onSend: chatState.sendMessage,
|
onSend: chatState.sendMessage,
|
||||||
|
|||||||
Reference in New Issue
Block a user