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 {
const UserListItem(
{required this.user, required this.firstUserListItemKey, super.key});
const UserListItem({
required this.user,
required this.firstUserListItemKey,
super.key,
});
final Contact user;
final GlobalKey? firstUserListItemKey;

View file

@ -256,187 +256,192 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return ContactView(widget.contact.userId);
}));
},
child: Row(
children: [
ContactAvatar(
contact: user,
fontSize: 19,
),
const SizedBox(width: 10),
Expanded(
child: ColoredBox(
color: Colors.transparent,
child: Row(
children: [
Text(getContactDisplayName(user)),
const SizedBox(width: 10),
VerifiedShield(key: verifyShieldKey, user),
],
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
title: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return ContactView(widget.contact.userId);
}));
},
child: Row(
children: [
ContactAvatar(
contact: user,
fontSize: 19,
),
const SizedBox(width: 10),
Expanded(
child: ColoredBox(
color: Colors.transparent,
child: Row(
children: [
Text(getContactDisplayName(user)),
const SizedBox(width: 10),
VerifiedShield(key: verifyShieldKey, user),
],
),
),
),
),
],
],
),
),
),
),
body: PieCanvas(
theme: getPieCanvasTheme(context),
child: SafeArea(
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: messages.length + 1,
reverse: true,
itemExtentBuilder: (index, dimensions) {
if (index == 0) return 10; // empty padding
index -= 1;
double size = 44;
if (messages[index].kind == MessageKind.textMessage) {
final content = TextMessageContent.fromJson(
jsonDecode(messages[index].contentJson!) as Map);
if (EmojiAnimation.supported(content.text)) {
size = 99;
} else {
size = 11 +
calculateNumberOfLines(
content.text,
MediaQuery.of(context).size.width * 0.8,
17) *
27;
}
}
if (messages[index].mediaStored) {
size = 271;
}
final reactions =
textReactionsToMessageId[messages[index].messageId];
if (reactions != null && reactions.isNotEmpty) {
for (final reaction in reactions) {
if (reaction.kind == MessageKind.textMessage) {
final content = TextMessageContent.fromJson(
jsonDecode(reaction.contentJson!) as Map);
size += calculateNumberOfLines(content.text,
MediaQuery.of(context).size.width * 0.5, 14) *
27;
body: PieCanvas(
theme: getPieCanvasTheme(context),
child: SafeArea(
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: messages.length + 1,
reverse: true,
itemExtentBuilder: (index, dimensions) {
if (index == 0) return 10; // empty padding
index -= 1;
double size = 44;
if (messages[index].kind == MessageKind.textMessage) {
final content = TextMessageContent.fromJson(
jsonDecode(messages[index].contentJson!) as Map);
if (EmojiAnimation.supported(content.text)) {
size = 99;
} else {
size = 11 +
calculateNumberOfLines(
content.text,
MediaQuery.of(context).size.width * 0.8,
17) *
27;
}
}
if (messages[index].mediaStored) {
size = 271;
}
final reactions =
textReactionsToMessageId[messages[index].messageId];
if (reactions != null && reactions.isNotEmpty) {
for (final reaction in reactions) {
if (reaction.kind == MessageKind.textMessage) {
final content = TextMessageContent.fromJson(
jsonDecode(reaction.contentJson!) as Map);
size += calculateNumberOfLines(
content.text,
MediaQuery.of(context).size.width * 0.5,
14) *
27;
}
}
}
}
if (!isLastMessageFromSameUser(messages, index)) {
size += 20;
}
return size;
},
itemBuilder: (context, i) {
if (i == 0) {
return Container(); // just a padding
}
i -= 1;
return ChatListEntry(
key: Key(messages[i].messageId.toString()),
messages[i],
user,
galleryItems,
isLastMessageFromSameUser(messages, i),
textReactionsToMessageId[messages[i].messageId] ?? [],
emojiReactionsToMessageId[messages[i].messageId] ?? [],
onResponseTriggered: (message) {
setState(() {
responseToMessage = message;
});
textFieldFocus.requestFocus();
},
);
},
if (!isLastMessageFromSameUser(messages, index)) {
size += 20;
}
return size;
},
itemBuilder: (context, i) {
if (i == 0) {
return Container(); // just a padding
}
i -= 1;
return ChatListEntry(
key: Key(messages[i].messageId.toString()),
messages[i],
user,
galleryItems,
isLastMessageFromSameUser(messages, i),
textReactionsToMessageId[messages[i].messageId] ?? [],
emojiReactionsToMessageId[messages[i].messageId] ?? [],
onResponseTriggered: (message) {
setState(() {
responseToMessage = message;
});
textFieldFocus.requestFocus();
},
);
},
),
),
),
if (responseToMessage != null && !user.deleted)
Container(
if (responseToMessage != null && !user.deleted)
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(
bottom: 30,
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,
),
)
],
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);
},
),
);
},
)
],
),
),
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.contactUsReasonOther,
];
return Scaffold(
appBar: AppBar(
title: Text(context.lang.settingsHelpContactUs),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
Text(context.lang.contactUsMessageTitle),
const SizedBox(height: 5),
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: const OutlineInputBorder(),
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
title: Text(context.lang.settingsHelpContactUs),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
Text(context.lang.contactUsMessageTitle),
const SizedBox(height: 5),
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: const OutlineInputBorder(),
),
minLines: 5,
maxLines: 10,
),
minLines: 5,
maxLines: 10,
),
const SizedBox(height: 5),
Text(
context.lang.contactUsMessage,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 10,
const SizedBox(height: 5),
Text(
context.lang.contactUsMessage,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 10,
),
),
),
const SizedBox(height: 20),
Text(context.lang.contactUsReason),
const SizedBox(height: 5),
DropdownButton<String>(
hint: Text(context.lang.contactUsSelectOption),
underline: const SizedBox.shrink(),
value: _selectedReason,
onChanged: (String? newValue) {
setState(() {
_selectedReason = newValue;
});
},
items: reasons.map<DropdownMenuItem<String>>((String reason) {
return DropdownMenuItem<String>(
value: reason,
child: Text(reason),
);
}).toList(),
),
const SizedBox(height: 20),
Text(context.lang.contactUsEmojis),
const SizedBox(height: 5),
FeedbackEmojiRow(
selectedFeedback: _selectedFeedback,
onFeedbackChanged: (int? newValue) {
setState(() {
_selectedFeedback = newValue;
});
},
),
const SizedBox(height: 20),
IncludeDebugLog(
isChecked: includeDebugLog,
onChanged: (value) {
setState(() {
includeDebugLog = value;
});
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
launchUrl(Uri.parse('https://twonly.eu/en/faq/'));
},
child: Text(
context.lang.contactUsFaq,
style: const TextStyle(
color: Colors.blue,
),
),
const SizedBox(height: 20),
Text(context.lang.contactUsReason),
const SizedBox(height: 5),
DropdownButton<String>(
hint: Text(context.lang.contactUsSelectOption),
underline: const SizedBox.shrink(),
value: _selectedReason,
onChanged: (String? newValue) {
setState(() {
_selectedReason = newValue;
});
},
items: reasons.map<DropdownMenuItem<String>>((String reason) {
return DropdownMenuItem<String>(
value: reason,
child: Text(reason),
);
}).toList(),
),
const SizedBox(height: 20),
Text(context.lang.contactUsEmojis),
const SizedBox(height: 5),
FeedbackEmojiRow(
selectedFeedback: _selectedFeedback,
onFeedbackChanged: (int? newValue) {
setState(() {
_selectedFeedback = newValue;
});
},
),
const SizedBox(height: 20),
IncludeDebugLog(
isChecked: includeDebugLog,
onChanged: (value) {
setState(() {
includeDebugLog = value;
});
},
),
],
),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
launchUrl(Uri.parse('https://twonly.eu/en/faq/'));
},
child: Text(
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
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(context.lang.settingsHelpContactUs),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
Text(
context.lang.contactUsLastWarning,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: const OutlineInputBorder(),
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
title: Text(context.lang.settingsHelpContactUs),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
Text(
context.lang.contactUsLastWarning,
textAlign: TextAlign.center,
),
minLines: 5,
maxLines: 20,
),
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),
),
],
const SizedBox(height: 10),
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: const OutlineInputBorder(),
),
minLines: 5,
maxLines: 20,
),
),
],
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),
),
],
),
),
],
),
),
),
);