This commit is contained in:
otsmr 2025-07-17 20:58:46 +02:00
parent 3fbf29b4e2
commit c89a14fc2d
4 changed files with 317 additions and 302 deletions

View file

@ -274,8 +274,11 @@ class _ChatListViewState extends State<ChatListView> {
} }
class UserListItem extends StatefulWidget { class UserListItem extends StatefulWidget {
const UserListItem( const UserListItem({
{required this.user, required this.firstUserListItemKey, super.key}); required this.user,
required this.firstUserListItemKey,
super.key,
});
final Contact user; final Contact user;
final GlobalKey? firstUserListItemKey; final GlobalKey? firstUserListItemKey;

View file

@ -256,187 +256,192 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return GestureDetector(
appBar: AppBar( onTap: () => FocusScope.of(context).unfocus(),
title: GestureDetector( child: Scaffold(
onTap: () { appBar: AppBar(
Navigator.push(context, MaterialPageRoute(builder: (context) { title: GestureDetector(
return ContactView(widget.contact.userId); onTap: () {
})); Navigator.push(context, MaterialPageRoute(builder: (context) {
}, return ContactView(widget.contact.userId);
child: Row( }));
children: [ },
ContactAvatar( child: Row(
contact: user, children: [
fontSize: 19, ContactAvatar(
), contact: user,
const SizedBox(width: 10), fontSize: 19,
Expanded( ),
child: ColoredBox( const SizedBox(width: 10),
color: Colors.transparent, Expanded(
child: Row( child: ColoredBox(
children: [ color: Colors.transparent,
Text(getContactDisplayName(user)), child: Row(
const SizedBox(width: 10), children: [
VerifiedShield(key: verifyShieldKey, user), Text(getContactDisplayName(user)),
], const SizedBox(width: 10),
VerifiedShield(key: verifyShieldKey, user),
],
),
), ),
), ),
), ],
], ),
), ),
), ),
), body: PieCanvas(
body: PieCanvas( theme: getPieCanvasTheme(context),
theme: getPieCanvasTheme(context), child: SafeArea(
child: SafeArea( child: Column(
child: Column( children: [
children: [ Expanded(
Expanded( child: ListView.builder(
child: ListView.builder( itemCount: messages.length + 1,
itemCount: messages.length + 1, reverse: true,
reverse: true, itemExtentBuilder: (index, dimensions) {
itemExtentBuilder: (index, dimensions) { if (index == 0) return 10; // empty padding
if (index == 0) return 10; // empty padding index -= 1;
index -= 1; double size = 44;
double size = 44; if (messages[index].kind == MessageKind.textMessage) {
if (messages[index].kind == MessageKind.textMessage) { final content = TextMessageContent.fromJson(
final content = TextMessageContent.fromJson( jsonDecode(messages[index].contentJson!) as Map);
jsonDecode(messages[index].contentJson!) as Map); if (EmojiAnimation.supported(content.text)) {
if (EmojiAnimation.supported(content.text)) { size = 99;
size = 99; } else {
} else { size = 11 +
size = 11 + calculateNumberOfLines(
calculateNumberOfLines( content.text,
content.text, MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.width * 0.8, 17) *
17) * 27;
27; }
} }
} if (messages[index].mediaStored) {
if (messages[index].mediaStored) { size = 271;
size = 271; }
} final reactions =
final reactions = textReactionsToMessageId[messages[index].messageId];
textReactionsToMessageId[messages[index].messageId]; if (reactions != null && reactions.isNotEmpty) {
if (reactions != null && reactions.isNotEmpty) { for (final reaction in reactions) {
for (final reaction in reactions) { if (reaction.kind == MessageKind.textMessage) {
if (reaction.kind == MessageKind.textMessage) { final content = TextMessageContent.fromJson(
final content = TextMessageContent.fromJson( jsonDecode(reaction.contentJson!) as Map);
jsonDecode(reaction.contentJson!) as Map); size += calculateNumberOfLines(
size += calculateNumberOfLines(content.text, content.text,
MediaQuery.of(context).size.width * 0.5, 14) * MediaQuery.of(context).size.width * 0.5,
27; 14) *
27;
}
} }
} }
}
if (!isLastMessageFromSameUser(messages, index)) { if (!isLastMessageFromSameUser(messages, index)) {
size += 20; size += 20;
} }
return size; return size;
}, },
itemBuilder: (context, i) { itemBuilder: (context, i) {
if (i == 0) { if (i == 0) {
return Container(); // just a padding return Container(); // just a padding
} }
i -= 1; i -= 1;
return ChatListEntry( return ChatListEntry(
key: Key(messages[i].messageId.toString()), key: Key(messages[i].messageId.toString()),
messages[i], messages[i],
user, user,
galleryItems, galleryItems,
isLastMessageFromSameUser(messages, i), isLastMessageFromSameUser(messages, i),
textReactionsToMessageId[messages[i].messageId] ?? [], textReactionsToMessageId[messages[i].messageId] ?? [],
emojiReactionsToMessageId[messages[i].messageId] ?? [], emojiReactionsToMessageId[messages[i].messageId] ?? [],
onResponseTriggered: (message) { onResponseTriggered: (message) {
setState(() { setState(() {
responseToMessage = message; responseToMessage = message;
}); });
textFieldFocus.requestFocus(); textFieldFocus.requestFocus();
}, },
); );
}, },
),
), ),
), if (responseToMessage != null && !user.deleted)
if (responseToMessage != null && !user.deleted) Container(
Container( padding: const EdgeInsets.only(
left: 20,
right: 20,
top: 10,
),
child: Row(
children: [
Expanded(child: getResponsePreview(responseToMessage!)),
IconButton(
onPressed: () {
setState(() {
responseToMessage = null;
});
},
icon: const FaIcon(
FontAwesomeIcons.xmark,
size: 16,
),
)
],
),
),
Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: 30,
left: 20, left: 20,
right: 20, right: 20,
top: 10, top: 10,
), ),
child: Row( child: Row(
children: [ children: (user.deleted)
Expanded(child: getResponsePreview(responseToMessage!)), ? []
IconButton( : [
onPressed: () { Expanded(
setState(() { child: TextField(
responseToMessage = null; controller: newMessageController,
}); focusNode: textFieldFocus,
}, keyboardType: TextInputType.multiline,
icon: const FaIcon( maxLines: 4,
FontAwesomeIcons.xmark, minLines: 1,
size: 16, onChanged: (value) {
), currentInputText = value;
) setState(() {});
], },
onSubmitted: (_) {
_sendMessage();
},
decoration: inputTextMessageDeco(context),
),
),
if (currentInputText != '')
IconButton(
padding: const EdgeInsets.all(15),
icon: const FaIcon(
FontAwesomeIcons.solidPaperPlane),
onPressed: _sendMessage,
)
else
IconButton(
icon: const FaIcon(FontAwesomeIcons.camera),
padding: const EdgeInsets.all(15),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.contact);
},
),
);
},
)
],
), ),
), ),
Padding( ],
padding: const EdgeInsets.only( ),
bottom: 30,
left: 20,
right: 20,
top: 10,
),
child: Row(
children: (user.deleted)
? []
: [
Expanded(
child: TextField(
controller: newMessageController,
focusNode: textFieldFocus,
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
onChanged: (value) {
currentInputText = value;
setState(() {});
},
onSubmitted: (_) {
_sendMessage();
},
decoration: inputTextMessageDeco(context),
),
),
if (currentInputText != '')
IconButton(
padding: const EdgeInsets.all(15),
icon: const FaIcon(
FontAwesomeIcons.solidPaperPlane),
onPressed: _sendMessage,
)
else
IconButton(
icon: const FaIcon(FontAwesomeIcons.camera),
padding: const EdgeInsets.all(15),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return CameraSendToView(widget.contact);
},
),
);
},
)
],
),
),
],
), ),
), ),
), ),

View file

@ -151,112 +151,115 @@ $debugLogToken
context.lang.contactUsReasonFeedback, context.lang.contactUsReasonFeedback,
context.lang.contactUsReasonOther, context.lang.contactUsReasonOther,
]; ];
return Scaffold( return GestureDetector(
appBar: AppBar( onTap: () => FocusScope.of(context).unfocus(),
title: Text(context.lang.settingsHelpContactUs), child: Scaffold(
), appBar: AppBar(
body: Padding( title: Text(context.lang.settingsHelpContactUs),
padding: const EdgeInsets.all(16), ),
child: ListView( body: Padding(
children: [ padding: const EdgeInsets.all(16),
Text(context.lang.contactUsMessageTitle), child: ListView(
const SizedBox(height: 5), children: [
TextField( Text(context.lang.contactUsMessageTitle),
controller: _controller, const SizedBox(height: 5),
decoration: InputDecoration( TextField(
hintText: context.lang.contactUsYourMessage, controller: _controller,
border: const OutlineInputBorder(), decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: const OutlineInputBorder(),
),
minLines: 5,
maxLines: 10,
), ),
minLines: 5, const SizedBox(height: 5),
maxLines: 10, Text(
), context.lang.contactUsMessage,
const SizedBox(height: 5), textAlign: TextAlign.left,
Text( style: const TextStyle(
context.lang.contactUsMessage, fontSize: 10,
textAlign: TextAlign.left, ),
style: const TextStyle(
fontSize: 10,
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20), Text(context.lang.contactUsReason),
Text(context.lang.contactUsReason), const SizedBox(height: 5),
const SizedBox(height: 5), DropdownButton<String>(
DropdownButton<String>( hint: Text(context.lang.contactUsSelectOption),
hint: Text(context.lang.contactUsSelectOption), underline: const SizedBox.shrink(),
underline: const SizedBox.shrink(), value: _selectedReason,
value: _selectedReason, onChanged: (String? newValue) {
onChanged: (String? newValue) { setState(() {
setState(() { _selectedReason = newValue;
_selectedReason = newValue; });
}); },
}, items: reasons.map<DropdownMenuItem<String>>((String reason) {
items: reasons.map<DropdownMenuItem<String>>((String reason) { return DropdownMenuItem<String>(
return DropdownMenuItem<String>( value: reason,
value: reason, child: Text(reason),
child: Text(reason), );
); }).toList(),
}).toList(), ),
), const SizedBox(height: 20),
const SizedBox(height: 20), Text(context.lang.contactUsEmojis),
Text(context.lang.contactUsEmojis), const SizedBox(height: 5),
const SizedBox(height: 5), FeedbackEmojiRow(
FeedbackEmojiRow( selectedFeedback: _selectedFeedback,
selectedFeedback: _selectedFeedback, onFeedbackChanged: (int? newValue) {
onFeedbackChanged: (int? newValue) { setState(() {
setState(() { _selectedFeedback = newValue;
_selectedFeedback = newValue; });
}); },
}, ),
), const SizedBox(height: 20),
const SizedBox(height: 20), IncludeDebugLog(
IncludeDebugLog( isChecked: includeDebugLog,
isChecked: includeDebugLog, onChanged: (value) {
onChanged: (value) { setState(() {
setState(() { includeDebugLog = value;
includeDebugLog = value; });
}); },
}, ),
), ],
Padding( ),
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40), ),
child: Row( bottomNavigationBar: Padding(
mainAxisAlignment: MainAxisAlignment.spaceBetween, padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
children: [ child: Row(
GestureDetector( mainAxisAlignment: MainAxisAlignment.spaceBetween,
onTap: () { children: [
launchUrl(Uri.parse('https://twonly.eu/en/faq/')); GestureDetector(
}, onTap: () {
child: Text( launchUrl(Uri.parse('https://twonly.eu/en/faq/'));
context.lang.contactUsFaq, },
style: const TextStyle( child: Text(
color: Colors.blue, context.lang.contactUsFaq,
), style: const TextStyle(
), color: Colors.blue,
), ),
ElevatedButton( ),
onPressed: isLoading
? null
: () async {
final fullMessage = await _getFeedbackText();
if (!context.mounted) return;
final feedbackSend = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return SubmitMessage(
fullMessage: fullMessage,
);
}));
if (feedbackSend == true && context.mounted) {
Navigator.pop(context);
}
},
child: Text(context.lang.next),
),
],
), ),
), ElevatedButton(
], onPressed: isLoading
? null
: () async {
final fullMessage = await _getFeedbackText();
if (!context.mounted) return;
final feedbackSend = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return SubmitMessage(
fullMessage: fullMessage,
);
}));
if (feedbackSend == true && context.mounted) {
Navigator.pop(context);
}
},
child: Text(context.lang.next),
),
],
),
), ),
), ),
); );

View file

@ -64,41 +64,45 @@ class _ContactUsState extends State<SubmitMessage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return GestureDetector(
appBar: AppBar( onTap: () => FocusScope.of(context).unfocus(),
title: Text(context.lang.settingsHelpContactUs), child: Scaffold(
), appBar: AppBar(
body: Padding( title: Text(context.lang.settingsHelpContactUs),
padding: const EdgeInsets.all(16), ),
child: ListView( body: Padding(
children: [ padding: const EdgeInsets.all(16),
Text( child: ListView(
context.lang.contactUsLastWarning, children: [
textAlign: TextAlign.center, Text(
), context.lang.contactUsLastWarning,
const SizedBox(height: 10), textAlign: TextAlign.center,
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: const OutlineInputBorder(),
), ),
minLines: 5, const SizedBox(height: 10),
maxLines: 20, TextField(
), controller: _controller,
Padding( decoration: InputDecoration(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40), hintText: context.lang.contactUsYourMessage,
child: Row( border: const OutlineInputBorder(),
mainAxisAlignment: MainAxisAlignment.center, ),
children: [ minLines: 5,
ElevatedButton( maxLines: 20,
onPressed: isLoading ? null : _submitFeedback,
child: Text(context.lang.submit),
),
],
), ),
), Padding(
], padding:
const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: isLoading ? null : _submitFeedback,
child: Text(context.lang.submit),
),
],
),
),
],
),
), ),
), ),
); );