dart format
This commit is contained in:
@@ -8,7 +8,12 @@ class AnswerReference extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
final GetChatResponseObject referenceMessage;
|
||||
final String? selfId;
|
||||
const AnswerReference({required this.context, required this.referenceMessage, required this.selfId, super.key});
|
||||
const AnswerReference({
|
||||
required this.context,
|
||||
required this.referenceMessage,
|
||||
required this.selfId,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -16,15 +21,25 @@ class AnswerReference extends StatelessWidget {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: referenceMessage.actorId == selfId
|
||||
? style.getSelfStyle(false).color!.withGreen(200).withValues(alpha: 0.2)
|
||||
: style.getRemoteStyle(false).color!.withWhite(200).withValues(alpha: 0.2),
|
||||
? style
|
||||
.getSelfStyle(false)
|
||||
.color!
|
||||
.withGreen(200)
|
||||
.withValues(alpha: 0.2)
|
||||
: style
|
||||
.getRemoteStyle(false)
|
||||
.color!
|
||||
.withWhite(200)
|
||||
.withValues(alpha: 0.2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
border: Border(left: BorderSide(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: referenceMessage.actorId == selfId
|
||||
? style.getSelfStyle(false).color!.withGreen(200)
|
||||
: style.getRemoteStyle(false).color!.withWhite(200),
|
||||
width: 5
|
||||
)),
|
||||
width: 5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)),
|
||||
@@ -43,7 +58,10 @@ class AnswerReference extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
RichObjectStringProcessor.parseToString(referenceMessage.message, referenceMessage.messageParameters),
|
||||
RichObjectStringProcessor.parseToString(
|
||||
referenceMessage.message,
|
||||
referenceMessage.messageParameters,
|
||||
),
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
@@ -3,12 +3,17 @@ import 'package:flutter/material.dart';
|
||||
enum BubbleNip { leftTop, rightBottom, none }
|
||||
|
||||
class BubbleEdges {
|
||||
const BubbleEdges.only({this.top = 0, this.bottom = 0, this.left = 0, this.right = 0});
|
||||
const BubbleEdges.only({
|
||||
this.top = 0,
|
||||
this.bottom = 0,
|
||||
this.left = 0,
|
||||
this.right = 0,
|
||||
});
|
||||
const BubbleEdges.all(double value)
|
||||
: top = value,
|
||||
bottom = value,
|
||||
left = value,
|
||||
right = value;
|
||||
: top = value,
|
||||
bottom = value,
|
||||
left = value,
|
||||
right = value;
|
||||
|
||||
final double top;
|
||||
final double bottom;
|
||||
@@ -53,9 +58,19 @@ class Bubble extends StatelessWidget {
|
||||
final flat = Radius.zero;
|
||||
switch (style.nip) {
|
||||
case BubbleNip.leftTop:
|
||||
return BorderRadius.only(topLeft: flat, topRight: r, bottomLeft: r, bottomRight: r);
|
||||
return BorderRadius.only(
|
||||
topLeft: flat,
|
||||
topRight: r,
|
||||
bottomLeft: r,
|
||||
bottomRight: r,
|
||||
);
|
||||
case BubbleNip.rightBottom:
|
||||
return BorderRadius.only(topLeft: r, topRight: r, bottomLeft: r, bottomRight: flat);
|
||||
return BorderRadius.only(
|
||||
topLeft: r,
|
||||
topRight: r,
|
||||
bottomLeft: r,
|
||||
bottomRight: flat,
|
||||
);
|
||||
case BubbleNip.none:
|
||||
return BorderRadius.all(r);
|
||||
}
|
||||
@@ -72,10 +87,19 @@ class Bubble extends StatelessWidget {
|
||||
color: style.color,
|
||||
borderRadius: radius,
|
||||
border: style.borderWidth > 0
|
||||
? Border.all(color: Theme.of(context).dividerColor, width: style.borderWidth)
|
||||
? Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: style.borderWidth,
|
||||
)
|
||||
: null,
|
||||
boxShadow: style.elevation > 0
|
||||
? [BoxShadow(color: Colors.black26, blurRadius: style.elevation * 2, offset: Offset(0, style.elevation))]
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: style.elevation * 2,
|
||||
offset: Offset(0, style.elevation),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
padding: style.padding.toEdgeInsets(),
|
||||
|
||||
@@ -40,13 +40,15 @@ class ChatBubble extends StatefulWidget {
|
||||
required this.refetch,
|
||||
this.isRead = false,
|
||||
this.selfId,
|
||||
super.key});
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChatBubble> createState() => _ChatBubbleState();
|
||||
}
|
||||
|
||||
class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateMixin {
|
||||
class _ChatBubbleState extends State<ChatBubble>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late ChatMessage message;
|
||||
DownloadJob? _job;
|
||||
|
||||
@@ -109,7 +111,10 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
||||
final file = message.file;
|
||||
final filePath = file?.path;
|
||||
if (file == null || filePath == null) return;
|
||||
final job = await DownloadManager.instance.start(remotePath: filePath, name: file.name);
|
||||
final job = await DownloadManager.instance.start(
|
||||
remotePath: filePath,
|
||||
name: file.name,
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (_job == job) return;
|
||||
_detachJob();
|
||||
@@ -129,19 +134,22 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
||||
|
||||
BubbleStyle _getStyle() {
|
||||
final styles = ChatBubbleStyles(context);
|
||||
if (widget.bubbleData.messageType != GetRoomResponseObjectMessageType.comment) {
|
||||
if (widget.bubbleData.messageType !=
|
||||
GetRoomResponseObjectMessageType.comment) {
|
||||
return styles.getSystemStyle();
|
||||
}
|
||||
return widget.isSender ? styles.getSelfStyle(false) : styles.getRemoteStyle(false);
|
||||
return widget.isSender
|
||||
? styles.getSelfStyle(false)
|
||||
: styles.getRemoteStyle(false);
|
||||
}
|
||||
|
||||
void _showOptionsDialog() => showChatMessageOptionsDialog(
|
||||
context,
|
||||
chatData: widget.chatData,
|
||||
bubbleData: widget.bubbleData,
|
||||
isSender: widget.isSender,
|
||||
onRefetch: widget.refetch,
|
||||
);
|
||||
context,
|
||||
chatData: widget.chatData,
|
||||
bubbleData: widget.bubbleData,
|
||||
isSender: widget.isSender,
|
||||
onRefetch: widget.refetch,
|
||||
);
|
||||
|
||||
void _onTap() {
|
||||
final obj = message.originalData?['object'];
|
||||
@@ -165,24 +173,40 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters);
|
||||
final showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment
|
||||
&& widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne;
|
||||
final showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system
|
||||
&& widget.bubbleData.messageType != GetRoomResponseObjectMessageType.deletedComment;
|
||||
message = ChatMessage(
|
||||
originalMessage: widget.bubbleData.message,
|
||||
originalData: widget.bubbleData.messageParameters,
|
||||
);
|
||||
final showActorDisplayName =
|
||||
widget.bubbleData.messageType ==
|
||||
GetRoomResponseObjectMessageType.comment &&
|
||||
widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne;
|
||||
final showBubbleTime =
|
||||
widget.bubbleData.messageType !=
|
||||
GetRoomResponseObjectMessageType.system &&
|
||||
widget.bubbleData.messageType !=
|
||||
GetRoomResponseObjectMessageType.deletedComment;
|
||||
|
||||
final parent = widget.bubbleData.parent;
|
||||
final actorText = Text(
|
||||
widget.bubbleData.actorDisplayName,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
|
||||
final timeText = Text(
|
||||
DateTime.fromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).formatHm(),
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
widget.bubbleData.timestamp * 1000,
|
||||
).formatHm(),
|
||||
textAlign: TextAlign.end,
|
||||
style: TextStyle(color: widget.timeIconColor, fontSize: widget.timeIconSize),
|
||||
style: TextStyle(
|
||||
color: widget.timeIconColor,
|
||||
fontSize: widget.timeIconSize,
|
||||
),
|
||||
);
|
||||
|
||||
return Column(
|
||||
@@ -206,7 +230,9 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
||||
final isAction = _position.dx.abs() > 50;
|
||||
setState(() => _position = Offset.zero);
|
||||
if (widget.bubbleData.isReplyable && isAction) {
|
||||
context.read<ChatBloc>().setReferenceMessageId(widget.bubbleData.id);
|
||||
context.read<ChatBloc>().setReferenceMessageId(
|
||||
widget.bubbleData.id,
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: _showOptionsDialog,
|
||||
@@ -281,67 +307,68 @@ class _BubbleContent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||
minWidth: showActorDisplayName
|
||||
? actorText.size.width
|
||||
: timeText.size.width + (isSender ? spacing + timeIconSize : 0) + 3,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||
minWidth: showActorDisplayName
|
||||
? actorText.size.width
|
||||
: timeText.size.width + (isSender ? spacing + timeIconSize : 0) + 3,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (showActorDisplayName) Positioned(top: 0, left: 0, child: actorText),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: showBubbleTime ? 18 : 0,
|
||||
top: showActorDisplayName ? 18 : 0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (parent != null &&
|
||||
bubbleData.messageType ==
|
||||
GetRoomResponseObjectMessageType.comment) ...[
|
||||
AnswerReference(
|
||||
context: context,
|
||||
referenceMessage: parent!,
|
||||
selfId: selfId,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
messageWidget,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (showActorDisplayName)
|
||||
Positioned(top: 0, left: 0, child: actorText),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: showBubbleTime ? 18 : 0,
|
||||
top: showActorDisplayName ? 18 : 0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (parent != null && bubbleData.messageType == GetRoomResponseObjectMessageType.comment) ...[
|
||||
AnswerReference(
|
||||
context: context,
|
||||
referenceMessage: parent!,
|
||||
selfId: selfId,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
messageWidget,
|
||||
if (showBubbleTime)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
timeText,
|
||||
if (isSender) ...[
|
||||
SizedBox(width: spacing),
|
||||
Icon(
|
||||
isRead ? Icons.done_all_outlined : Icons.done_outlined,
|
||||
size: timeIconSize,
|
||||
color: timeIconColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (showBubbleTime)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
timeText,
|
||||
if (isSender) ...[
|
||||
SizedBox(width: spacing),
|
||||
Icon(
|
||||
isRead ? Icons.done_all_outlined : Icons.done_outlined,
|
||||
size: timeIconSize,
|
||||
color: timeIconColor,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (downloadJob?.status.value is DownloadInProgress)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
child: LinearProgressIndicator(
|
||||
value: () {
|
||||
final s = downloadJob!.status.value as DownloadInProgress;
|
||||
return s.percent <= 0 ? null : s.percent / 100;
|
||||
}(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
if (downloadJob?.status.value is DownloadInProgress)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
child: LinearProgressIndicator(
|
||||
value: () {
|
||||
final s = downloadJob!.status.value as DownloadInProgress;
|
||||
return s.percent <= 0 ? null : s.percent / 100;
|
||||
}(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ void showChatBubblePollDialog(
|
||||
future: pollState,
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Column(mainAxisSize: MainAxisSize.min, children: [LoadingSpinner()]);
|
||||
return const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [LoadingSpinner()],
|
||||
);
|
||||
}
|
||||
final pollData = snapshot.data!.data;
|
||||
return SingleChildScrollView(
|
||||
child: PollOptionsList(
|
||||
pollData: pollData,
|
||||
chatToken: chatToken,
|
||||
),
|
||||
child: PollOptionsList(pollData: pollData, chatToken: chatToken),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -37,14 +37,20 @@ class ChatBubbleReactions extends StatelessWidget {
|
||||
alignment: isSender ? WrapAlignment.end : WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
children: reactions.entries.map<Widget>((e) {
|
||||
final hasSelfReacted = bubbleData.reactionsSelf?.contains(e.key) ?? false;
|
||||
final hasSelfReacted =
|
||||
bubbleData.reactionsSelf?.contains(e.key) ?? false;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 2.5, left: 2.5),
|
||||
child: ActionChip(
|
||||
label: Text('${e.key} ${e.value}'),
|
||||
visualDensity: const VisualDensity(vertical: VisualDensity.minimumDensity, horizontal: VisualDensity.minimumDensity),
|
||||
visualDensity: const VisualDensity(
|
||||
vertical: VisualDensity.minimumDensity,
|
||||
horizontal: VisualDensity.minimumDensity,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: hasSelfReacted ? Theme.of(context).primaryColor : null,
|
||||
backgroundColor: hasSelfReacted
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
onPressed: () {
|
||||
runWithErrorDialog(context, () async {
|
||||
if (hasSelfReacted) {
|
||||
|
||||
@@ -29,11 +29,13 @@ void showChatMessageOptionsDialog(
|
||||
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());
|
||||
final canReact =
|
||||
bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
|
||||
final canDelete =
|
||||
isSender &&
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
bubbleData.timestamp * 1000,
|
||||
).add(const Duration(hours: 6)).isAfter(DateTime.now());
|
||||
|
||||
showDetailsBottomSheet(
|
||||
context,
|
||||
@@ -61,7 +63,11 @@ void showChatMessageOptionsDialog(
|
||||
onTap: () {
|
||||
Navigator.of(sheetCtx).pop();
|
||||
if (!parentContext.mounted) return;
|
||||
AppRoutes.openMessageReactions(parentContext, chatData.token, bubbleData.id);
|
||||
AppRoutes.openMessageReactions(
|
||||
parentContext,
|
||||
chatData.token,
|
||||
bubbleData.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (bubbleData.message != '{file}')
|
||||
@@ -73,7 +79,9 @@ void showChatMessageOptionsDialog(
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
if (!kReleaseMode && !isSender && chatData.type != GetRoomResponseObjectConversationType.oneToOne)
|
||||
if (!kReleaseMode &&
|
||||
!isSender &&
|
||||
chatData.type != GetRoomResponseObjectConversationType.oneToOne)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.sms_outlined),
|
||||
title: Text("Private Nachricht an '${bubbleData.actorDisplayName}'"),
|
||||
@@ -136,54 +144,57 @@ class _ReactionsRowState extends State<_ReactionsRow> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, _) {
|
||||
final busy = _controller.busy;
|
||||
final err = _controller.error;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
animation: _controller,
|
||||
builder: (context, _) {
|
||||
final busy = _controller.busy;
|
||||
final err = _controller.error;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
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: busy ? null : () => _react(emoji),
|
||||
child: Text(emoji),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: busy ? null : () => _showEmojiPicker(context),
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40),
|
||||
),
|
||||
icon: busy
|
||||
? const AppProgressIndicator.small()
|
||||
: const Icon(Icons.add_circle_outline_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (err != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
err,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error, fontSize: 12),
|
||||
..._commonReactions.map(
|
||||
(emoji) => TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40),
|
||||
),
|
||||
onPressed: busy ? null : () => _react(emoji),
|
||||
child: Text(emoji),
|
||||
),
|
||||
const Divider(),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: busy ? null : () => _showEmojiPicker(context),
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size(40, 40),
|
||||
),
|
||||
icon: busy
|
||||
? const AppProgressIndicator.small()
|
||||
: const Icon(Icons.add_circle_outline_outlined),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (err != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Text(
|
||||
err,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
void _showEmojiPicker(BuildContext rowContext) {
|
||||
showDialog(
|
||||
@@ -214,7 +225,9 @@ class _ReactionsRowState extends State<_ReactionsRow> {
|
||||
noRecents: const Text('Keine zuletzt verwendeten Emojis'),
|
||||
columns: 7,
|
||||
),
|
||||
bottomActionBarConfig: const emojis.BottomActionBarConfig(enabled: false),
|
||||
bottomActionBarConfig: const emojis.BottomActionBarConfig(
|
||||
enabled: false,
|
||||
),
|
||||
categoryViewConfig: emojis.CategoryViewConfig(
|
||||
backgroundColor: Theme.of(pickerCtx).hoverColor,
|
||||
iconColorSelected: Theme.of(pickerCtx).primaryColor,
|
||||
|
||||
@@ -39,13 +39,17 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
void share(String shareFolder, List<String> filePaths) {
|
||||
for (final element in filePaths) {
|
||||
final fileName = element.split(Platform.pathSeparator).last;
|
||||
FileSharingApi().share(FileSharingApiParams(
|
||||
shareType: 10,
|
||||
shareWith: widget.sendToToken,
|
||||
path: '$shareFolder/$fileName',
|
||||
)).then((_) {
|
||||
if (mounted) context.read<ChatBloc>().refresh();
|
||||
});
|
||||
FileSharingApi()
|
||||
.share(
|
||||
FileSharingApiParams(
|
||||
shareType: 10,
|
||||
shareWith: widget.sendToToken,
|
||||
path: '$shareFolder/$fileName',
|
||||
),
|
||||
)
|
||||
.then((_) {
|
||||
if (mounted) context.read<ChatBloc>().refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,19 +57,25 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
if (paths == null) return;
|
||||
|
||||
const shareFolder = 'MarianumMobile';
|
||||
unawaited(WebdavApi.webdav.then((webdav) => webdav.mkcol(PathUri.parse('/$shareFolder'))));
|
||||
unawaited(
|
||||
WebdavApi.webdav.then(
|
||||
(webdav) => webdav.mkcol(PathUri.parse('/$shareFolder')),
|
||||
),
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
unawaited(pushScreen(
|
||||
context,
|
||||
withNavBar: false,
|
||||
screen: FilesUploadDialog(
|
||||
filePaths: paths,
|
||||
remotePath: shareFolder,
|
||||
onUploadFinished: (uploaded) => share(shareFolder, uploaded),
|
||||
uniqueNames: true,
|
||||
unawaited(
|
||||
pushScreen(
|
||||
context,
|
||||
withNavBar: false,
|
||||
screen: FilesUploadDialog(
|
||||
filePaths: paths,
|
||||
remotePath: shareFolder,
|
||||
onUploadFinished: (uploaded) => share(shareFolder, uploaded),
|
||||
uniqueNames: true,
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
void _setDraft(String text) {
|
||||
@@ -82,7 +92,9 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
if (messageId != null) {
|
||||
talkSettings.draftReplies[widget.sendToToken] = messageId;
|
||||
} else {
|
||||
talkSettings.draftReplies.removeWhere((key, _) => key == widget.sendToToken);
|
||||
talkSettings.draftReplies.removeWhere(
|
||||
(key, _) => key == widget.sendToToken,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +102,10 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
settings = context.read<SettingsCubit>();
|
||||
final draftReply = settings.val().talkSettings.draftReplies[widget.sendToToken];
|
||||
final draftReply = settings
|
||||
.val()
|
||||
.talkSettings
|
||||
.draftReplies[widget.sendToToken];
|
||||
if (draftReply != null) {
|
||||
context.read<ChatBloc>().setReferenceMessageId(draftReply);
|
||||
}
|
||||
@@ -121,16 +136,19 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_textBoxController.text = settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
|
||||
_textBoxController.text =
|
||||
settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
|
||||
final chatBloc = context.watch<ChatBloc>();
|
||||
final chatState = chatBloc.state.data;
|
||||
|
||||
Widget replyBanner = const SizedBox.shrink();
|
||||
if (chatState != null && chatState.referenceMessageId != null && chatState.chatResponse != null) {
|
||||
if (chatState != null &&
|
||||
chatState.referenceMessageId != null &&
|
||||
chatState.chatResponse != null) {
|
||||
try {
|
||||
final referenceMessage = chatState.chatResponse!.sortByTimestamp().firstWhere(
|
||||
(e) => e.id == chatState.referenceMessageId,
|
||||
);
|
||||
final referenceMessage = chatState.chatResponse!
|
||||
.sortByTimestamp()
|
||||
.firstWhere((e) => e.id == chatState.referenceMessageId);
|
||||
replyBanner = Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -150,120 +168,150 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
),
|
||||
],
|
||||
);
|
||||
} catch (_) {/* reference no longer in current chat data */}
|
||||
} catch (_) {
|
||||
/* reference no longer in current chat data */
|
||||
}
|
||||
}
|
||||
|
||||
return Stack(children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 10, bottom: 3, top: 3, right: 10),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
replyBanner,
|
||||
if (_sendError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
_sendError!,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error, fontSize: 12),
|
||||
),
|
||||
),
|
||||
Row(children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDetailsBottomSheet(
|
||||
context,
|
||||
children: (sheetCtx) => [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_open),
|
||||
title: const Text('Aus Dateien auswählen'),
|
||||
onTap: () {
|
||||
FilePick.documentPick().then(mediaUpload);
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.image),
|
||||
title: const Text('Aus Galerie auswählen'),
|
||||
onTap: () {
|
||||
FilePick.multipleGalleryPick().then((value) {
|
||||
if (value != null) mediaUpload(value.map((e) => e.path).toList());
|
||||
});
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt_outlined),
|
||||
title: const Text('Foto aufnehmen'),
|
||||
onTap: () {
|
||||
FilePick.cameraPick().then((image) {
|
||||
if (image != null) mediaUpload([image.path]);
|
||||
});
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Material(
|
||||
elevation: 5,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10,
|
||||
bottom: 3,
|
||||
top: 3,
|
||||
right: 10,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
replyBanner,
|
||||
if (_sendError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
_sendError!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
child: const Icon(Icons.attach_file_outlined, color: Colors.white, size: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
controller: _textBoxController,
|
||||
maxLines: 7,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Nachricht schreiben...',
|
||||
border: InputBorder.none,
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDetailsBottomSheet(
|
||||
context,
|
||||
children: (sheetCtx) => [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_open),
|
||||
title: const Text('Aus Dateien auswählen'),
|
||||
onTap: () {
|
||||
FilePick.documentPick().then(mediaUpload);
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.image),
|
||||
title: const Text('Aus Galerie auswählen'),
|
||||
onTap: () {
|
||||
FilePick.multipleGalleryPick().then((value) {
|
||||
if (value != null) {
|
||||
mediaUpload(
|
||||
value.map((e) => e.path).toList(),
|
||||
);
|
||||
}
|
||||
});
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt_outlined),
|
||||
title: const Text('Foto aufnehmen'),
|
||||
onTap: () {
|
||||
FilePick.cameraPick().then((image) {
|
||||
if (image != null) mediaUpload([image.path]);
|
||||
});
|
||||
Navigator.of(sheetCtx).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Material(
|
||||
elevation: 5,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.attach_file_outlined,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (text) {
|
||||
if (text.trim().toLowerCase() == 'marbot marbot marbot') {
|
||||
const newText = 'Roboter sind cool und so, aber Marbots sind besser!';
|
||||
_textBoxController.text = newText;
|
||||
text = newText;
|
||||
}
|
||||
_setDraft(text);
|
||||
},
|
||||
onTapOutside: (_) => FocusBehaviour.textFieldTapOutside(context),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
controller: _textBoxController,
|
||||
maxLines: 7,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Nachricht schreiben...',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (text) {
|
||||
if (text.trim().toLowerCase() ==
|
||||
'marbot marbot marbot') {
|
||||
const newText =
|
||||
'Roboter sind cool und so, aber Marbots sind besser!';
|
||||
_textBoxController.text = newText;
|
||||
text = newText;
|
||||
}
|
||||
_setDraft(text);
|
||||
},
|
||||
onTapOutside: (_) =>
|
||||
FocusBehaviour.textFieldTapOutside(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _textBoxController,
|
||||
builder: (context, value, _) => AsyncFab(
|
||||
mini: true,
|
||||
heroTag: 'chatSend_${widget.sendToToken}',
|
||||
icon: Icons.send,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
controller: _sendController,
|
||||
onPressed: value.text.trim().isEmpty
|
||||
? null
|
||||
: () => _sendMessage(chatBloc),
|
||||
onError: (message) =>
|
||||
setState(() => _sendError = message),
|
||||
onSuccess: () => setState(() => _sendError = null),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _textBoxController,
|
||||
builder: (context, value, _) => AsyncFab(
|
||||
mini: true,
|
||||
heroTag: 'chatSend_${widget.sendToToken}',
|
||||
icon: Icons.send,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
controller: _sendController,
|
||||
onPressed: value.text.trim().isEmpty ? null : () => _sendMessage(chatBloc),
|
||||
onError: (message) => setState(() => _sendError = message),
|
||||
onSuccess: () => setState(() => _sendError = null),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,12 @@ class ChatTile extends StatefulWidget {
|
||||
final bool disableContextActions;
|
||||
final bool hasDraft;
|
||||
|
||||
const ChatTile({super.key, required this.data, this.disableContextActions = false, this.hasDraft = false});
|
||||
const ChatTile({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.disableContextActions = false,
|
||||
this.hasDraft = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChatTile> createState() => _ChatTileState();
|
||||
@@ -39,7 +44,11 @@ class _ChatTileState extends State<ChatTile> {
|
||||
super.initState();
|
||||
AccountData().waitForPopulation().then((_) {
|
||||
if (!mounted) return;
|
||||
setState(() => selfUsername = AccountData().isPopulated() ? AccountData().getUsername() : null);
|
||||
setState(
|
||||
() => selfUsername = AccountData().isPopulated()
|
||||
? AccountData().getUsername()
|
||||
: null,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,7 +58,9 @@ class _ChatTileState extends State<ChatTile> {
|
||||
await SetReadMarker(
|
||||
widget.data.token,
|
||||
true,
|
||||
setReadMarkerParams: SetReadMarkerParams(lastReadMessage: widget.data.lastMessage.id),
|
||||
setReadMarkerParams: SetReadMarkerParams(
|
||||
lastReadMessage: widget.data.lastMessage.id,
|
||||
),
|
||||
).run();
|
||||
if (!mounted) return;
|
||||
_refreshList();
|
||||
@@ -58,12 +69,18 @@ class _ChatTileState extends State<ChatTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chatBloc = context.watch<ChatBloc>();
|
||||
final isGroup = widget.data.type != GetRoomResponseObjectConversationType.oneToOne;
|
||||
final circleAvatar = UserAvatar(id: isGroup ? widget.data.token : widget.data.name, isGroup: isGroup);
|
||||
final isGroup =
|
||||
widget.data.type != GetRoomResponseObjectConversationType.oneToOne;
|
||||
final circleAvatar = UserAvatar(
|
||||
id: isGroup ? widget.data.token : widget.data.name,
|
||||
isGroup: isGroup,
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
style: ListTileStyle.list,
|
||||
tileColor: chatBloc.state.data?.currentToken == widget.data.token && TalkNavigator.isSecondaryVisible(context)
|
||||
tileColor:
|
||||
chatBloc.state.data?.currentToken == widget.data.token &&
|
||||
TalkNavigator.isSecondaryVisible(context)
|
||||
? Theme.of(context).primaryColor.withAlpha(100)
|
||||
: null,
|
||||
leading: Stack(
|
||||
@@ -80,16 +97,25 @@ class _ChatTileState extends State<ChatTile> {
|
||||
color: Theme.of(context).primaryColor.withAlpha(200),
|
||||
borderRadius: BorderRadius.circular(90.0),
|
||||
),
|
||||
child: const Icon(Icons.star, color: Colors.amberAccent, size: 15),
|
||||
child: const Icon(
|
||||
Icons.star,
|
||||
color: Colors.amberAccent,
|
||||
size: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(child: Text(widget.data.displayName, overflow: TextOverflow.ellipsis)),
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.data.displayName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (widget.hasDraft) ...[
|
||||
const SizedBox(width: 5),
|
||||
const Icon(Icons.edit_outlined, size: 15),
|
||||
@@ -119,8 +145,16 @@ class _ChatTileState extends State<ChatTile> {
|
||||
onTap: () {
|
||||
if (selfUsername == null) return;
|
||||
unawaited(_setCurrentAsRead());
|
||||
final view = ChatView(room: widget.data, selfId: selfUsername!, avatar: circleAvatar);
|
||||
TalkNavigator.pushSplitView(context, view, overrideToSingleSubScreen: true);
|
||||
final view = ChatView(
|
||||
room: widget.data,
|
||||
selfId: selfUsername!,
|
||||
avatar: circleAvatar,
|
||||
);
|
||||
TalkNavigator.pushSplitView(
|
||||
context,
|
||||
view,
|
||||
overrideToSingleSubScreen: true,
|
||||
);
|
||||
context.read<ChatBloc>().setToken(widget.data.token);
|
||||
},
|
||||
onLongPress: () {
|
||||
@@ -168,7 +202,8 @@ class _ChatTileState extends State<ChatTile> {
|
||||
Navigator.of(sheetCtx).pop();
|
||||
ConfirmDialog(
|
||||
title: 'Chat verlassen',
|
||||
content: 'Du benötigst ggf. eine Einladung um erneut beizutreten.',
|
||||
content:
|
||||
'Du benötigst ggf. eine Einladung um erneut beizutreten.',
|
||||
confirmButton: 'Verlassen',
|
||||
onConfirmAsync: () async {
|
||||
await LeaveRoom(widget.data.token).run();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
@@ -8,7 +7,11 @@ import '../../../../utils/url_opener.dart';
|
||||
class PollOptionsList extends StatefulWidget {
|
||||
final GetPollStateResponseObject pollData;
|
||||
final String chatToken;
|
||||
const PollOptionsList({super.key, required this.pollData, required this.chatToken});
|
||||
const PollOptionsList({
|
||||
super.key,
|
||||
required this.pollData,
|
||||
required this.chatToken,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PollOptionsList> createState() => _PollOptionsListState();
|
||||
@@ -23,44 +26,48 @@ class _PollOptionsListState extends State<PollOptionsList> {
|
||||
var votedSelf = widget.pollData.votedSelf.contains(optionId);
|
||||
var portionsVisible = widget.pollData.votes is Map<String, dynamic>;
|
||||
var votes = portionsVisible
|
||||
? (widget.pollData.votes['option-$optionId'] as num?) ?? 0
|
||||
: 0;
|
||||
? (widget.pollData.votes['option-$optionId'] as num?) ?? 0
|
||||
: 0;
|
||||
var numVoters = widget.pollData.numVoters ?? 0;
|
||||
final portion = numVoters == 0 ? 0.0 : (votes / numVoters);
|
||||
|
||||
return ListTile(
|
||||
isThreeLine: portionsVisible,
|
||||
dense: true,
|
||||
title: Text(
|
||||
option,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
title: Text(option, style: Theme.of(context).textTheme.bodyLarge),
|
||||
leading: Icon(
|
||||
votedSelf ? Icons.check_circle_outlined : Icons.circle_outlined,
|
||||
color: votedSelf
|
||||
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.6)
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||
: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||
),
|
||||
subtitle: portionsVisible ? Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LinearProgressIndicator(value: portion.clamp(0.0, 1.0)),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 10),
|
||||
child: Text('${(portion * 100).round()}%'),
|
||||
),
|
||||
],
|
||||
) : null,
|
||||
subtitle: portionsVisible
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LinearProgressIndicator(
|
||||
value: portion.clamp(0.0, 1.0),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 10),
|
||||
child: Text('${(portion * 100).round()}%'),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Linkify(
|
||||
text: 'Wenn du abstimmen möchtest, verwende die Webversion unter https://cloud.marianum-fulda.de/call/${widget.chatToken}',
|
||||
text:
|
||||
'Wenn du abstimmen möchtest, verwende die Webversion unter https://cloud.marianum-fulda.de/call/${widget.chatToken}',
|
||||
onOpen: UrlOpener.onOpen,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,21 +7,25 @@ class SplitViewPlaceholder extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
invertColors: !AppTheme.isDarkMode(context),
|
||||
),
|
||||
child: Image.asset('assets/logo/icon.png', height: 200),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
const Text('Marianum Fulda\nTalk', textAlign: TextAlign.center, style: TextStyle(fontSize: 30)),
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
appBar: AppBar(),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
MediaQuery(
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(invertColors: !AppTheme.isDarkMode(context)),
|
||||
child: Image.asset('assets/logo/icon.png', height: 200),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
const Text(
|
||||
'Marianum Fulda\nTalk',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 30),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user