folder restructuring
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
|
||||
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
||||
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
||||
import '../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||
import '../../../state/app/modules/chatList/bloc/chat_list_state.dart';
|
||||
import '../../../notification/notifyUpdater.dart';
|
||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
import '../../../widget/confirmDialog.dart';
|
||||
import 'components/chatTile.dart';
|
||||
import 'components/splitViewPlaceholder.dart';
|
||||
import 'joinChat.dart';
|
||||
import 'searchChat.dart';
|
||||
|
||||
class ChatList extends StatelessWidget {
|
||||
const ChatList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => BlocModule<ChatListBloc, LoadableState<ChatListState>>(
|
||||
create: (_) => ChatListBloc(),
|
||||
child: (context, bloc, _) => const _ChatListView(),
|
||||
);
|
||||
}
|
||||
|
||||
class _ChatListView extends StatefulWidget {
|
||||
const _ChatListView();
|
||||
|
||||
@override
|
||||
State<_ChatListView> createState() => _ChatListViewState();
|
||||
}
|
||||
|
||||
class _ChatListViewState extends State<_ChatListView> {
|
||||
late final SettingsCubit _settings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_settings = context.read<SettingsCubit>();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _maybeAskForNotificationPermission());
|
||||
}
|
||||
|
||||
void _maybeAskForNotificationPermission() {
|
||||
final notificationSettings = _settings.val().notificationSettings;
|
||||
if (notificationSettings.enabled || notificationSettings.askUsageDismissed) return;
|
||||
|
||||
_settings.val(write: true).notificationSettings.askUsageDismissed = true;
|
||||
ConfirmDialog(
|
||||
icon: Icons.notifications_active_outlined,
|
||||
title: 'Benachrichtigungen aktivieren',
|
||||
content: 'Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.',
|
||||
confirmButton: 'Weiter',
|
||||
onConfirm: () {
|
||||
FirebaseMessaging.instance.requestPermission(provisional: false).then((value) {
|
||||
if (!mounted) return;
|
||||
switch (value.authorizationStatus) {
|
||||
case AuthorizationStatus.authorized:
|
||||
NotifyUpdater.enableAfterDisclaimer(_settings).asDialog(context);
|
||||
break;
|
||||
case AuthorizationStatus.denied:
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => const AlertDialog(
|
||||
content: Text('Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren.'),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
).asDialog(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = context.read<ChatListBloc>();
|
||||
return SplitView.material(
|
||||
placeholder: const SplitViewPlaceholder(),
|
||||
breakpoint: 1000,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Talk'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
final rooms = bloc.state.data?.rooms;
|
||||
if (rooms == null) return;
|
||||
showSearch(context: context, delegate: SearchChat(rooms.data.toList()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: 'createChat',
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
showSearch(context: context, delegate: JoinChat()).then((username) {
|
||||
if (username == null || !context.mounted) return;
|
||||
ConfirmDialog(
|
||||
title: 'Chat starten',
|
||||
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
|
||||
confirmButton: 'Chat starten',
|
||||
onConfirm: () {
|
||||
bloc.createDirectChat(username);
|
||||
},
|
||||
).asDialog(context);
|
||||
});
|
||||
},
|
||||
child: const Icon(Icons.add_comment_outlined),
|
||||
),
|
||||
body: LoadableStateConsumer<ChatListBloc, ChatListState>(
|
||||
child: (state, _) {
|
||||
final rooms = state.rooms;
|
||||
if (rooms == null) return const SizedBox.shrink();
|
||||
|
||||
final talkSettings = context.watch<SettingsCubit>().val().talkSettings;
|
||||
final sorted = rooms.sortBy(
|
||||
lastActivity: true,
|
||||
favoritesToTop: talkSettings.sortFavoritesToTop,
|
||||
unreadToTop: talkSettings.sortUnreadToTop,
|
||||
);
|
||||
|
||||
return ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: sorted.map((room) {
|
||||
final hasDraft = _settings.val().talkSettings.drafts.containsKey(room.token);
|
||||
return ChatTile(data: room, hasDraft: hasDraft);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||
|
||||
import '../../../routing/app_routes.dart';
|
||||
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
||||
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
||||
import '../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||
import '../../../state/app/modules/chatList/bloc/chat_list_state.dart';
|
||||
import '../../../notification/notify_updater.dart';
|
||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
import '../../../widget/confirm_dialog.dart';
|
||||
import 'widgets/chat_tile.dart';
|
||||
import 'widgets/split_view_placeholder.dart';
|
||||
import 'join_chat.dart';
|
||||
import 'search_chat.dart';
|
||||
|
||||
class ChatList extends StatelessWidget {
|
||||
const ChatList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => BlocModule<ChatListBloc, LoadableState<ChatListState>>(
|
||||
create: (_) => ChatListBloc(),
|
||||
child: (context, bloc, _) => const _ChatListView(),
|
||||
);
|
||||
}
|
||||
|
||||
class _ChatListView extends StatefulWidget {
|
||||
const _ChatListView();
|
||||
|
||||
@override
|
||||
State<_ChatListView> createState() => _ChatListViewState();
|
||||
}
|
||||
|
||||
class _ChatListViewState extends State<_ChatListView> {
|
||||
late final SettingsCubit _settings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_settings = context.read<SettingsCubit>();
|
||||
|
||||
AppRoutes.pendingChatToken.addListener(_maybeOpenPendingChat);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_maybeAskForNotificationPermission();
|
||||
_maybeOpenPendingChat();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
AppRoutes.pendingChatToken.removeListener(_maybeOpenPendingChat);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _maybeOpenPendingChat() {
|
||||
if (!mounted) return;
|
||||
final resolved = AppRoutes.resolvePendingChat(context);
|
||||
if (resolved == null) return;
|
||||
AppRoutes.pendingChatToken.value = null;
|
||||
|
||||
// Replace any chat already pushed on top of the chat list so a freshly
|
||||
// tapped notification doesn't stack indefinitely on previous chats.
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop()) {
|
||||
navigator.popUntil((route) => route.isFirst);
|
||||
}
|
||||
|
||||
AppRoutes.openChatView(
|
||||
context,
|
||||
room: resolved.room,
|
||||
selfId: resolved.selfId,
|
||||
avatar: resolved.avatar,
|
||||
overrideToSingleSubScreen: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _maybeAskForNotificationPermission() {
|
||||
final notificationSettings = _settings.val().notificationSettings;
|
||||
if (notificationSettings.enabled || notificationSettings.askUsageDismissed) return;
|
||||
|
||||
_settings.val(write: true).notificationSettings.askUsageDismissed = true;
|
||||
ConfirmDialog(
|
||||
icon: Icons.notifications_active_outlined,
|
||||
title: 'Benachrichtigungen aktivieren',
|
||||
content: 'Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.',
|
||||
confirmButton: 'Weiter',
|
||||
onConfirm: () {
|
||||
FirebaseMessaging.instance.requestPermission(provisional: false).then((value) {
|
||||
if (!mounted) return;
|
||||
switch (value.authorizationStatus) {
|
||||
case AuthorizationStatus.authorized:
|
||||
NotifyUpdater.enableAfterDisclaimer(_settings).asDialog(context);
|
||||
break;
|
||||
case AuthorizationStatus.denied:
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => const AlertDialog(
|
||||
content: Text('Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren.'),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
).asDialog(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = context.read<ChatListBloc>();
|
||||
return SplitView.material(
|
||||
placeholder: const SplitViewPlaceholder(),
|
||||
breakpoint: 1000,
|
||||
child: BlocListener<ChatListBloc, LoadableState<ChatListState>>(
|
||||
listener: (_, _) => _maybeOpenPendingChat(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Talk'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
final rooms = bloc.state.data?.rooms;
|
||||
if (rooms == null) return;
|
||||
showSearch(context: context, delegate: SearchChat(rooms.data.toList()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: 'createChat',
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
showSearch(context: context, delegate: JoinChat()).then((username) {
|
||||
if (username == null || !context.mounted) return;
|
||||
ConfirmDialog(
|
||||
title: 'Chat starten',
|
||||
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
|
||||
confirmButton: 'Chat starten',
|
||||
onConfirm: () {
|
||||
bloc.createDirectChat(username);
|
||||
},
|
||||
).asDialog(context);
|
||||
});
|
||||
},
|
||||
child: const Icon(Icons.add_comment_outlined),
|
||||
),
|
||||
body: LoadableStateConsumer<ChatListBloc, ChatListState>(
|
||||
child: (state, _) {
|
||||
final rooms = state.rooms;
|
||||
if (rooms == null) return const SizedBox.shrink();
|
||||
|
||||
final talkSettings = context.watch<SettingsCubit>().val().talkSettings;
|
||||
final sorted = rooms.sortBy(
|
||||
lastActivity: true,
|
||||
favoritesToTop: talkSettings.sortFavoritesToTop,
|
||||
unreadToTop: talkSettings.sortUnreadToTop,
|
||||
);
|
||||
|
||||
return ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: sorted.map((room) {
|
||||
final hasDraft = _settings.val().talkSettings.drafts.containsKey(room.token);
|
||||
return ChatTile(data: room, hasDraft: hasDraft);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,17 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||
import '../../../extensions/dateTime.dart';
|
||||
import '../../../extensions/date_time.dart';
|
||||
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||
import '../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../state/app/modules/chat/bloc/chat_state.dart';
|
||||
import '../../../theming/appTheme.dart';
|
||||
import '../../../widget/clickableAppBar.dart';
|
||||
import '../../../widget/userAvatar.dart';
|
||||
import 'chatDetails/chatInfo.dart';
|
||||
import 'components/chatBubble.dart';
|
||||
import 'components/chatTextfield.dart';
|
||||
import 'talkNavigator.dart';
|
||||
import '../../../theming/app_theme.dart';
|
||||
import '../../../widget/clickable_app_bar.dart';
|
||||
import '../../../widget/user_avatar.dart';
|
||||
import 'details/chat_info.dart';
|
||||
import 'widgets/chat_bubble.dart';
|
||||
import 'widgets/chat_textfield.dart';
|
||||
import 'talk_navigator.dart';
|
||||
|
||||
class ChatView extends StatefulWidget {
|
||||
final GetRoomResponseObject room;
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import 'package:bubble/bubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../theming/appTheme.dart';
|
||||
import '../../../../theming/app_theme.dart';
|
||||
|
||||
extension ColorExtensions on Color {
|
||||
Color invert() {
|
||||
+3
-3
@@ -5,9 +5,9 @@ import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
||||
import '../../../../model/accountData.dart';
|
||||
import '../../../../model/endpointData.dart';
|
||||
import '../../../../utils/UrlOpener.dart';
|
||||
import '../../../../model/account_data.dart';
|
||||
import '../../../../model/endpoint_data.dart';
|
||||
import '../../../../utils/url_opener.dart';
|
||||
|
||||
class ChatMessage {
|
||||
String originalMessage;
|
||||
+5
-5
@@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
||||
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsCache.dart';
|
||||
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||
import '../../../../widget/largeProfilePictureView.dart';
|
||||
import '../../../../widget/loadingSpinner.dart';
|
||||
import '../../../../widget/userAvatar.dart';
|
||||
import '../talkNavigator.dart';
|
||||
import 'participants/participantsListView.dart';
|
||||
import '../../../../widget/large_profile_picture_view.dart';
|
||||
import '../../../../widget/loading_spinner.dart';
|
||||
import '../../../../widget/user_avatar.dart';
|
||||
import '../talk_navigator.dart';
|
||||
import 'participants_list_view.dart';
|
||||
|
||||
class ChatInfo extends StatefulWidget {
|
||||
final GetRoomResponseObject room;
|
||||
+8
-8
@@ -1,14 +1,14 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../api/marianumcloud/talk/getReactions/getReactions.dart';
|
||||
import '../../../api/marianumcloud/talk/getReactions/getReactionsResponse.dart';
|
||||
import '../../../model/accountData.dart';
|
||||
import '../../../widget/centeredLeading.dart';
|
||||
import '../../../widget/loadingSpinner.dart';
|
||||
import '../../../widget/placeholderView.dart';
|
||||
import '../../../widget/unimplementedDialog.dart';
|
||||
import '../../../widget/userAvatar.dart';
|
||||
import '../../../../api/marianumcloud/talk/getReactions/getReactions.dart';
|
||||
import '../../../../api/marianumcloud/talk/getReactions/getReactionsResponse.dart';
|
||||
import '../../../../model/account_data.dart';
|
||||
import '../../../../widget/centered_leading.dart';
|
||||
import '../../../../widget/loading_spinner.dart';
|
||||
import '../../../../widget/placeholder_view.dart';
|
||||
import '../../../../widget/unimplemented_dialog.dart';
|
||||
import '../../../../widget/user_avatar.dart';
|
||||
|
||||
class MessageReactions extends StatefulWidget {
|
||||
final String token;
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
||||
import '../../../../../widget/userAvatar.dart';
|
||||
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
||||
import '../../../../widget/user_avatar.dart';
|
||||
|
||||
class ParticipantsListView extends StatelessWidget {
|
||||
final GetParticipantsResponse participantsResponse;
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../api/marianumcloud/autocomplete/autocompleteApi.dart';
|
||||
import '../../../api/marianumcloud/autocomplete/autocompleteResponse.dart';
|
||||
import '../../../model/endpointData.dart';
|
||||
import '../../../widget/placeholderView.dart';
|
||||
import '../../../model/endpoint_data.dart';
|
||||
import '../../../widget/placeholder_view.dart';
|
||||
|
||||
class JoinChat extends SearchDelegate<String> {
|
||||
CancelableOperation<AutocompleteResponse>? future;
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||
import 'components/chatTile.dart';
|
||||
import 'widgets/chat_tile.dart';
|
||||
|
||||
class SearchChat extends SearchDelegate {
|
||||
List<GetRoomResponseObject> chats;
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
||||
import 'chatBubbleStyles.dart';
|
||||
import '../data/chat_bubble_styles.dart';
|
||||
|
||||
class AnswerReference extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
+17
-185
@@ -1,31 +1,26 @@
|
||||
import 'package:bubble/bubble.dart';
|
||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
||||
import 'package:flowder/flowder.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import '../../../../api/marianumcloud/talk/getPoll/getPollState.dart';
|
||||
import '../../../../extensions/text.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../../../../api/marianumcloud/talk/deleteMessage/deleteMessage.dart';
|
||||
import '../../../../api/marianumcloud/talk/deleteReactMessage/deleteReactMessage.dart';
|
||||
import '../../../../api/marianumcloud/talk/deleteReactMessage/deleteReactMessageParams.dart';
|
||||
import '../../../../api/marianumcloud/talk/getPoll/getPollState.dart';
|
||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
|
||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||
import '../../../../extensions/text.dart';
|
||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../../widget/debug/debugTile.dart';
|
||||
import '../../../../widget/loadingSpinner.dart';
|
||||
import '../../files/fileElement.dart';
|
||||
import 'answerReference.dart';
|
||||
import 'chatBubbleStyles.dart';
|
||||
import 'chatMessage.dart';
|
||||
import '../messageReactions.dart';
|
||||
import 'pollOptionsList.dart';
|
||||
import '../../../../widget/loading_spinner.dart';
|
||||
import '../../files/widgets/file_element.dart';
|
||||
import '../data/chat_bubble_styles.dart';
|
||||
import '../data/chat_message.dart';
|
||||
import 'answer_reference.dart';
|
||||
import 'chat_message_options_dialog.dart';
|
||||
import 'poll_options_list.dart';
|
||||
|
||||
class ChatBubble extends StatefulWidget {
|
||||
final BuildContext context;
|
||||
@@ -77,176 +72,13 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
||||
}
|
||||
|
||||
void showOptionsDialog() {
|
||||
showDialog(context: context, builder: (context) {
|
||||
var commonReactions = <String>['👍', '👎', '😆', '❤️', '👀'];
|
||||
var canReact = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
|
||||
return SimpleDialog(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: canReact,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
...commonReactions.map((e) => TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40)
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ReactMessage(
|
||||
chatToken: widget.chatData.token,
|
||||
messageId: widget.bubbleData.id,
|
||||
params: ReactMessageParams(e),
|
||||
).run().then((value) => widget.refetch(renew: true));
|
||||
},
|
||||
child: Text(e),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(context: context, builder: (context) => AlertDialog(
|
||||
contentPadding: const EdgeInsets.all(15),
|
||||
titlePadding: const EdgeInsets.only(left: 6, top: 15),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Text('Reagieren'),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 256,
|
||||
height: 270,
|
||||
child: Column(
|
||||
children: [
|
||||
emojis.EmojiPicker(
|
||||
config: emojis.Config(
|
||||
height: 256,
|
||||
// swapCategoryAndBottomBar: true, // TODO this property is no longer supported, need to find an replacement
|
||||
emojiViewConfig: emojis.EmojiViewConfig(
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
recentsLimit: 67,
|
||||
emojiSizeMax: 25,
|
||||
noRecents: const Text('Keine zuletzt verwendeten Emojis'),
|
||||
columns: 7,
|
||||
),
|
||||
bottomActionBarConfig: const emojis.BottomActionBarConfig(
|
||||
enabled: false,
|
||||
),
|
||||
categoryViewConfig: emojis.CategoryViewConfig(
|
||||
backgroundColor: Theme.of(context).hoverColor,
|
||||
iconColorSelected: Theme.of(context).primaryColor,
|
||||
indicatorColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
searchViewConfig: emojis.SearchViewConfig(
|
||||
backgroundColor: Theme.of(context).dividerColor,
|
||||
// buttonColor: Theme.of(context).dividerColor, // TODO property no longer supported
|
||||
hintText: 'Suchen',
|
||||
buttonIconColor: Colors.white,
|
||||
),
|
||||
),
|
||||
onEmojiSelected: (emojis.Category? category, emojis.Emoji emoji) {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
ReactMessage(
|
||||
chatToken: widget.chatData.token,
|
||||
messageId: widget.bubbleData.id,
|
||||
params: ReactMessageParams(emoji.emoji),
|
||||
).run().then((value) => widget.refetch(renew: true));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
},
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40),
|
||||
),
|
||||
icon: const Icon(Icons.add_circle_outline_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: widget.bubbleData.isReplyable,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.reply_outlined),
|
||||
title: const Text('Antworten'),
|
||||
onTap: () {
|
||||
context.read<ChatBloc>().setReferenceMessageId(widget.bubbleData.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: canReact,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.emoji_emotions_outlined),
|
||||
title: const Text('Reaktionen'),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessageReactions(
|
||||
token: widget.chatData.token,
|
||||
messageId: widget.bubbleData.id,
|
||||
)));
|
||||
},
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: widget.bubbleData.message != '{file}',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: const Text('Nachricht kopieren'),
|
||||
onTap: () => {
|
||||
Clipboard.setData(ClipboardData(text: widget.bubbleData.message)),
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: !kReleaseMode && !widget.isSender && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.sms_outlined),
|
||||
title: Text("Private Nachricht an '${widget.bubbleData.actorDisplayName}'"),
|
||||
onTap: () => {
|
||||
Navigator.of(context).pop()
|
||||
},
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: widget.isSender && DateTime.fromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).add(const Duration(hours: 6)).isAfter(DateTime.now()),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
title: const Text('Nachricht löschen'),
|
||||
onTap: () {
|
||||
DeleteMessage(widget.chatData.token, widget.bubbleData.id).run().then((value) {
|
||||
if (!context.mounted) return;
|
||||
context.read<ChatBloc>().refresh();
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
DebugTile(context).jsonData(widget.bubbleData.toJson()),
|
||||
],
|
||||
);
|
||||
});
|
||||
showChatMessageOptionsDialog(
|
||||
context,
|
||||
chatData: widget.chatData,
|
||||
bubbleData: widget.bubbleData,
|
||||
isSender: widget.isSender,
|
||||
onRefetch: widget.refetch,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../../../../api/marianumcloud/talk/deleteMessage/deleteMessage.dart';
|
||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
|
||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
||||
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||
import '../../../../routing/app_routes.dart';
|
||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../../widget/debug/debug_tile.dart';
|
||||
|
||||
const _commonReactions = <String>['👍', '👎', '😆', '❤️', '👀'];
|
||||
|
||||
/// Long-press / double-tap options dialog for a single chat message bubble.
|
||||
/// The hosting [ChatBubble] keeps responsibility for rendering the bubble;
|
||||
/// this file owns the modal interactions (react, reply, copy, delete, ...).
|
||||
Future<void> showChatMessageOptionsDialog(
|
||||
BuildContext context, {
|
||||
required GetRoomResponseObject chatData,
|
||||
required GetChatResponseObject bubbleData,
|
||||
required bool isSender,
|
||||
required void Function({bool renew}) onRefetch,
|
||||
}) {
|
||||
final parentContext = context;
|
||||
final canReact = bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
|
||||
final canDelete = isSender &&
|
||||
DateTime.fromMillisecondsSinceEpoch(bubbleData.timestamp * 1000)
|
||||
.add(const Duration(hours: 6))
|
||||
.isAfter(DateTime.now());
|
||||
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (dialogCtx) => SimpleDialog(
|
||||
children: [
|
||||
if (canReact)
|
||||
_ReactionsRow(
|
||||
chatToken: chatData.token,
|
||||
messageId: bubbleData.id,
|
||||
onRefetch: onRefetch,
|
||||
dialogContext: dialogCtx,
|
||||
),
|
||||
if (bubbleData.isReplyable)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.reply_outlined),
|
||||
title: const Text('Antworten'),
|
||||
onTap: () {
|
||||
dialogCtx.read<ChatBloc>().setReferenceMessageId(bubbleData.id);
|
||||
Navigator.of(dialogCtx).pop();
|
||||
},
|
||||
),
|
||||
if (canReact)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.emoji_emotions_outlined),
|
||||
title: const Text('Reaktionen'),
|
||||
onTap: () {
|
||||
Navigator.of(dialogCtx).pop();
|
||||
if (!parentContext.mounted) return;
|
||||
AppRoutes.openMessageReactions(parentContext, chatData.token, bubbleData.id);
|
||||
},
|
||||
),
|
||||
if (bubbleData.message != '{file}')
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: const Text('Nachricht kopieren'),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: bubbleData.message));
|
||||
Navigator.of(dialogCtx).pop();
|
||||
},
|
||||
),
|
||||
if (!kReleaseMode && !isSender && chatData.type != GetRoomResponseObjectConversationType.oneToOne)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.sms_outlined),
|
||||
title: Text("Private Nachricht an '${bubbleData.actorDisplayName}'"),
|
||||
onTap: () => Navigator.of(dialogCtx).pop(),
|
||||
),
|
||||
if (canDelete)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
title: const Text('Nachricht löschen'),
|
||||
onTap: () async {
|
||||
await DeleteMessage(chatData.token, bubbleData.id).run();
|
||||
if (!dialogCtx.mounted) return;
|
||||
dialogCtx.read<ChatBloc>().refresh();
|
||||
Navigator.of(dialogCtx).pop();
|
||||
},
|
||||
),
|
||||
DebugTile(dialogCtx).jsonData(bubbleData.toJson()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ReactionsRow extends StatelessWidget {
|
||||
final String chatToken;
|
||||
final int messageId;
|
||||
final void Function({bool renew}) onRefetch;
|
||||
final BuildContext dialogContext;
|
||||
|
||||
const _ReactionsRow({
|
||||
required this.chatToken,
|
||||
required this.messageId,
|
||||
required this.onRefetch,
|
||||
required this.dialogContext,
|
||||
});
|
||||
|
||||
void _react(String emoji) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
ReactMessage(
|
||||
chatToken: chatToken,
|
||||
messageId: messageId,
|
||||
params: ReactMessageParams(emoji),
|
||||
).run().then((_) => onRefetch(renew: true));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
..._commonReactions.map(
|
||||
(emoji) => TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40),
|
||||
),
|
||||
onPressed: () => _react(emoji),
|
||||
child: Text(emoji),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _showEmojiPicker(context),
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40),
|
||||
),
|
||||
icon: const Icon(Icons.add_circle_outline_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
);
|
||||
|
||||
void _showEmojiPicker(BuildContext rowContext) {
|
||||
showDialog(
|
||||
context: rowContext,
|
||||
builder: (pickerCtx) => AlertDialog(
|
||||
contentPadding: const EdgeInsets.all(15),
|
||||
titlePadding: const EdgeInsets.only(left: 6, top: 15),
|
||||
title: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(pickerCtx).pop(),
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Text('Reagieren'),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 256,
|
||||
height: 270,
|
||||
child: emojis.EmojiPicker(
|
||||
config: emojis.Config(
|
||||
height: 256,
|
||||
emojiViewConfig: emojis.EmojiViewConfig(
|
||||
backgroundColor: Theme.of(pickerCtx).canvasColor,
|
||||
recentsLimit: 67,
|
||||
emojiSizeMax: 25,
|
||||
noRecents: const Text('Keine zuletzt verwendeten Emojis'),
|
||||
columns: 7,
|
||||
),
|
||||
bottomActionBarConfig: const emojis.BottomActionBarConfig(enabled: false),
|
||||
categoryViewConfig: emojis.CategoryViewConfig(
|
||||
backgroundColor: Theme.of(pickerCtx).hoverColor,
|
||||
iconColorSelected: Theme.of(pickerCtx).primaryColor,
|
||||
indicatorColor: Theme.of(pickerCtx).primaryColor,
|
||||
),
|
||||
searchViewConfig: emojis.SearchViewConfig(
|
||||
backgroundColor: Theme.of(pickerCtx).dividerColor,
|
||||
hintText: 'Suchen',
|
||||
buttonIconColor: Colors.white,
|
||||
),
|
||||
),
|
||||
onEmojiSelected: (_, emoji) {
|
||||
Navigator.of(pickerCtx).pop();
|
||||
_react(emoji.emoji);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -12,10 +12,10 @@ import '../../../../api/marianumcloud/talk/sendMessage/sendMessageParams.dart';
|
||||
import '../../../../api/marianumcloud/webdav/webdavApi.dart';
|
||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
import '../../../../widget/filePick.dart';
|
||||
import '../../../../widget/focusBehaviour.dart';
|
||||
import '../../files/filesUploadDialog.dart';
|
||||
import 'answerReference.dart';
|
||||
import '../../../../widget/file_pick.dart';
|
||||
import '../../../../widget/focus_behaviour.dart';
|
||||
import '../../files/files_upload_dialog.dart';
|
||||
import 'answer_reference.dart';
|
||||
|
||||
class ChatTextfield extends StatefulWidget {
|
||||
final String sendToToken;
|
||||
+6
-6
@@ -9,14 +9,14 @@ import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||
import '../../../../api/marianumcloud/talk/setFavorite/setFavorite.dart';
|
||||
import '../../../../api/marianumcloud/talk/setReadMarker/setReadMarker.dart';
|
||||
import '../../../../api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart';
|
||||
import '../../../../model/accountData.dart';
|
||||
import '../../../../model/account_data.dart';
|
||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||
import '../../../../widget/confirmDialog.dart';
|
||||
import '../../../../widget/debug/debugTile.dart';
|
||||
import '../../../../widget/userAvatar.dart';
|
||||
import '../chatView.dart';
|
||||
import '../talkNavigator.dart';
|
||||
import '../../../../widget/confirm_dialog.dart';
|
||||
import '../../../../widget/debug/debug_tile.dart';
|
||||
import '../../../../widget/user_avatar.dart';
|
||||
import '../chat_view.dart';
|
||||
import '../talk_navigator.dart';
|
||||
|
||||
class ChatTile extends StatefulWidget {
|
||||
final GetRoomResponseObject data;
|
||||
+1
-1
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/getPoll/getPollStateResponse.dart';
|
||||
import '../../../../utils/UrlOpener.dart';
|
||||
import '../../../../utils/url_opener.dart';
|
||||
|
||||
class PollOptionsList extends StatefulWidget {
|
||||
final GetPollStateResponseObject pollData;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../theming/appTheme.dart';
|
||||
import '../../../../theming/app_theme.dart';
|
||||
|
||||
class SplitViewPlaceholder extends StatelessWidget {
|
||||
const SplitViewPlaceholder({super.key});
|
||||
Reference in New Issue
Block a user