Note to self feature addition #256

Merged
PapaTutuWawa merged 28 commits from :feature_note_to_self into master 2023-03-20 11:52:27 +00:00
26 changed files with 303 additions and 161 deletions

View File

@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true

View File

@ -135,6 +135,7 @@
"conversations": {
"speeddialNewChat": "New chat",
"speeddialJoinGroupchat": "Join groupchat",
"speeddialAddNoteToSelf": "Note to self",
"overlaySettings": "Settings",
"noOpenChats": "You have no open chats",
"startChat": "Start a chat",

View File

@ -135,6 +135,7 @@
"conversations": {
"speeddialNewChat": "Neuer chat",
"speeddialJoinGroupchat": "Gruppenchat beitreten",
"speeddialAddNoteToSelf": "Notiz an mich",
"overlaySettings": "Einstellungen",
"noOpenChats": "Du hast keine offenen chats",
"startChat": "Einen chat anfangen",

View File

@ -303,6 +303,7 @@ files:
lastMessageBody: String
avatarUrl: String
jid: String
conversationType: String
- name: SetOpenConversationCommand
extends: BackgroundCommand
implements:

View File

@ -140,6 +140,7 @@ class ConversationService {
Future<Conversation> addConversationFromData(
String title,
Message? lastMessage,
ConversationType type,
String avatarUrl,
String jid,
int unreadCounter,
@ -156,6 +157,7 @@ class ConversationService {
await GetIt.I.get<DatabaseService>().addConversationFromData(
title,
lastMessage,
type,
avatarUrl,
jid,
unreadCounter,

View File

@ -71,6 +71,7 @@ Future<void> createDatabase(Database db, int version) async {
jid TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL,
avatarUrl TEXT NOT NULL,
type TEXT NOT NULL,
lastChangeTimestamp INTEGER NOT NULL,
unreadCounter INTEGER NOT NULL,
open INTEGER NOT NULL,

View File

@ -34,6 +34,7 @@ import 'package:moxxyv2/service/database/migrations/0000_stickers_privacy.dart';
import 'package:moxxyv2/service/database/migrations/0000_xmpp_state.dart';
import 'package:moxxyv2/service/database/migrations/0001_conversation_media_amount.dart';
import 'package:moxxyv2/service/database/migrations/0001_conversation_primary_key.dart';
import 'package:moxxyv2/service/database/migrations/0001_conversations_type.dart';
import 'package:moxxyv2/service/database/migrations/0001_debug_menu.dart';
import 'package:moxxyv2/service/database/migrations/0001_remove_auto_accept_subscriptions.dart';
import 'package:moxxyv2/service/database/migrations/0001_subscriptions.dart';
@ -121,7 +122,7 @@ class DatabaseService {
_db = await openDatabase(
dbPath,
password: key,
version: 30,
version: 31,
onCreate: createDatabase,
onConfigure: (db) async {
// In order to do schema changes during database upgrades, we disable foreign
@ -250,6 +251,10 @@ class DatabaseService {
_log.finest('Running migration for database version 30');
await upgradeFromV29ToV30(db);
}
if (oldVersion < 31) {
_log.finest('Running migration for database version 31');
await upgradeFromV30ToV31(db);
}
},
);
@ -472,6 +477,7 @@ class DatabaseService {
Future<Conversation> addConversationFromData(
String title,
Message? lastMessage,
ConversationType type,
String avatarUrl,
String jid,
int unreadCounter,
@ -492,6 +498,7 @@ class DatabaseService {
avatarUrl,
jid,
unreadCounter,
type,
lastChangeTimestamp,
<SharedMedium>[],
open,
@ -576,6 +583,8 @@ class DatabaseService {
String? stickerHashKey,
int? pseudoMessageType,
Map<String, dynamic>? pseudoMessageData,
bool received = false,
bool displayed = false,
}) async {
var m = Message(
sender,
@ -599,8 +608,8 @@ class DatabaseService {
mediaWidth: mediaWidth,
mediaHeight: mediaHeight,
srcUrl: srcUrl,
received: false,
displayed: false,
received: received,
displayed: displayed,
acked: false,
originId: originId,
filename: filename,

View File

@ -1,3 +1,5 @@
import 'package:moxxyv2/shared/models/conversation.dart';
/// Conversion helpers for bool <-> int as sqlite has no "real" booleans
int boolToInt(bool b) => b ? 1 : 0;
bool intToBool(int i) => i == 0 ? false : true;
@ -7,3 +9,29 @@ bool stringToBool(String s) => s == 'true' ? true : false;
String intToString(int i) => '$i';
int stringToInt(String s) => int.parse(s);
String conversationTypeToString(ConversationType type) {
switch (type) {
case ConversationType.chat:
{
return 'chat';
}
case ConversationType.note:
{
return 'note';
}
}
}
ConversationType stringToConversationType(String type) {
switch (type) {
case 'chat':
{
return ConversationType.chat;
}
default:
{
return ConversationType.note;
}
}
}

View File

@ -0,0 +1,9 @@
import 'package:moxxyv2/service/database/constants.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
Future<void> upgradeFromV30ToV31(Database db) async {
await db.execute(
'ALTER TABLE $conversationsTable ADD COLUMN type TEXT NOT NULL DEFAULT "chat";',
);
}

View File

@ -10,6 +10,7 @@ import 'package:moxxyv2/service/blocking.dart';
import 'package:moxxyv2/service/contacts.dart';
import 'package:moxxyv2/service/conversation.dart';
import 'package:moxxyv2/service/database/database.dart';
import 'package:moxxyv2/service/database/helpers.dart';
import 'package:moxxyv2/service/helpers.dart';
import 'package:moxxyv2/service/httpfiletransfer/helpers.dart';
import 'package:moxxyv2/service/httpfiletransfer/httpfiletransfer.dart';
@ -30,6 +31,7 @@ import 'package:moxxyv2/shared/commands.dart';
import 'package:moxxyv2/shared/eventhandler.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/shared/models/preferences.dart';
import 'package:moxxyv2/shared/models/reaction.dart';
import 'package:moxxyv2/shared/models/sticker.dart' as sticker;
@ -233,6 +235,7 @@ Future<void> performAddConversation(
final newConversation = await cs.addConversationFromData(
command.title,
null,
stringToConversationType(command.conversationType),
command.avatarUrl,
command.jid,
0,
@ -289,7 +292,8 @@ Future<void> performSetOpenConversation(
await GetIt.I.get<XmppService>().setCurrentlyOpenedChatJid(command.jid ?? '');
// Null just means that the chat has been closed
if (command.jid != null) {
// Empty string JID for notes to self
if (command.jid != null && command.jid != '') {
await GetIt.I
.get<NotificationsService>()
.dismissNotificationsByJid(command.jid!);
@ -462,6 +466,7 @@ Future<void> performAddContact(
final newConversation = await cs.addConversationFromData(
jid.split('@')[0],
null,
ConversationType.chat,
'',
jid,
0,
@ -654,9 +659,12 @@ Future<void> performSendChatState(
if (!prefs.sendChatMarkers) return;
final conn = GetIt.I.get<XmppConnection>();
conn
.getManagerById<ChatStateManager>(chatStateManager)!
.sendChatState(chatStateFromString(command.state), command.jid);
if (command.jid != '') {
conn
.getManagerById<ChatStateManager>(chatStateManager)!
.sendChatState(chatStateFromString(command.state), command.jid);
}
}
Future<void> performGetFeatures(
@ -847,19 +855,20 @@ Future<void> performMessageRetraction(
'',
true,
);
// Send the retraction
(GetIt.I.get<XmppConnection>().getManagerById(messageManager)!
as MessageManager)
.sendMessage(
MessageDetails(
to: command.conversationJid,
messageRetraction: MessageRetractionData(
command.originId,
t.messages.retractedFallback,
if (command.conversationJid != '') {
(GetIt.I
.get<XmppConnection>()
.getManagerById<MessageManager>(messageManager)!)
.sendMessage(
MessageDetails(
to: command.conversationJid,
messageRetraction: MessageRetractionData(
command.originId,
t.messages.retractedFallback,
),
),
),
);
);
}
}
Future<void> performMarkConversationAsRead(
@ -945,19 +954,21 @@ Future<void> performAddMessageReaction(
final ownReactions =
reactions.where((r) => r.reactedBySelf).map((r) => r.emoji).toList();
// Send the reaction
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: command.conversationJid,
messageReactions: MessageReactions(
msg.originId ?? msg.sid,
ownReactions,
if (command.conversationJid != '') {
// Send the reaction
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: command.conversationJid,
messageReactions: MessageReactions(
msg.originId ?? msg.sid,
ownReactions,
),
requestChatMarkers: false,
messageProcessingHints:
!msg.containsNoStore ? [MessageProcessingHint.store] : null,
),
requestChatMarkers: false,
messageProcessingHints:
!msg.containsNoStore ? [MessageProcessingHint.store] : null,
),
);
);
}
}
Future<void> performRemoveMessageReaction(
@ -985,19 +996,21 @@ Future<void> performRemoveMessageReaction(
final ownReactions =
reactions.where((r) => r.reactedBySelf).map((r) => r.emoji).toList();
// Send the reaction
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: command.conversationJid,
messageReactions: MessageReactions(
msg.originId ?? msg.sid,
ownReactions,
if (command.conversationJid != '') {
// Send the reaction
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: command.conversationJid,
messageReactions: MessageReactions(
msg.originId ?? msg.sid,
ownReactions,
),
requestChatMarkers: false,
messageProcessingHints:
!msg.containsNoStore ? [MessageProcessingHint.store] : null,
),
requestChatMarkers: false,
messageProcessingHints:
!msg.containsNoStore ? [MessageProcessingHint.store] : null,
),
);
);
}
}
Future<void> performMarkDeviceVerified(

View File

@ -91,6 +91,8 @@ class MessageService {
String? stickerHashKey,
int? pseudoMessageType,
Map<String, dynamic>? pseudoMessageData,
bool received = false,
bool displayed = false,
}) async {
final msg = await GetIt.I.get<DatabaseService>().addMessageFromData(
body,
@ -125,6 +127,8 @@ class MessageService {
stickerHashKey: stickerHashKey,
pseudoMessageType: pseudoMessageType,
pseudoMessageData: pseudoMessageData,
received: received,
displayed: displayed,
);
await _cacheLock.synchronized(() {

View File

@ -34,6 +34,7 @@ import 'package:moxxyv2/shared/error_types.dart';
import 'package:moxxyv2/shared/eventhandler.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/shared/models/media.dart';
import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/shared/models/reaction.dart';
@ -182,15 +183,17 @@ class XmppService {
sendEvent(ConversationUpdatedEvent(conversation: conversation));
}
// Send the correction
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: recipient,
body: newBody,
lastMessageCorrectionId: oldId,
chatState: chatState,
),
);
if (conversation?.type != ConversationType.note) {
// Send the correction
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: recipient,
body: newBody,
lastMessageCorrectionId: oldId,
chatState: chatState,
),
);
}
}
/// Sends a message to JIDs in [recipients] with the body of [body].
@ -224,7 +227,7 @@ class XmppService {
sticker != null,
sid,
false,
c.encrypted,
c.type == ConversationType.note ? true : c.encrypted,
// TODO(Unknown): Maybe make this depend on some setting
false,
originId: originId,
@ -233,6 +236,8 @@ class XmppService {
stickerHashKey: sticker?.hashKey,
srcUrl: sticker?.urlSources.first,
mediaType: sticker?.mediaType,
received: c.type == ConversationType.note ? true : false,
displayed: c.type == ConversationType.note ? true : false,
);
final newConversation = await cs.updateConversation(
@ -256,42 +261,44 @@ class XmppService {
);
}
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: recipient,
body: body,
requestDeliveryReceipt: true,
id: sid,
originId: originId,
quoteBody: createFallbackBodyForQuotedMessage(quotedMessage),
quoteFrom: quotedMessage?.sender,
quoteId: quotedMessage?.sid,
chatState: chatState,
shouldEncrypt: conversation!.encrypted,
stickerPackId: sticker?.stickerPackId,
sfs: sticker == null
? null
: StatelessFileSharingData(
FileMetadataData(
mediaType: sticker.mediaType,
width: sticker.width,
height: sticker.height,
desc: sticker.desc,
size: sticker.size,
thumbnails: [],
hashes: sticker.hashes,
if (conversation?.type == ConversationType.chat) {
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: recipient,
body: body,
requestDeliveryReceipt: true,
id: sid,
originId: originId,
quoteBody: createFallbackBodyForQuotedMessage(quotedMessage),
quoteFrom: quotedMessage?.sender,
quoteId: quotedMessage?.sid,
chatState: chatState,
shouldEncrypt: conversation!.encrypted,
stickerPackId: sticker?.stickerPackId,
sfs: sticker == null
? null
: StatelessFileSharingData(
FileMetadataData(
mediaType: sticker.mediaType,
width: sticker.width,
height: sticker.height,
desc: sticker.desc,
size: sticker.size,
thumbnails: [],
hashes: sticker.hashes,
),
sticker.urlSources
// ignore: unnecessary_lambdas
.map((s) => StatelessFileSharingUrlSource(s))
.toList(),
),
sticker.urlSources
// ignore: unnecessary_lambdas
.map((s) => StatelessFileSharingUrlSource(s))
.toList(),
),
setOOBFallbackBody: sticker != null ? false : true,
),
);
setOOBFallbackBody: sticker != null ? false : true,
),
);
}
sendEvent(
ConversationUpdatedEvent(conversation: conversation),
ConversationUpdatedEvent(conversation: conversation!),
);
}
}
@ -537,7 +544,9 @@ class XmppService {
true,
conn.generateId(),
false,
encrypt[recipient]!,
conversation?.type == ConversationType.note
? true
: encrypt[recipient]!,
// TODO(Unknown): Maybe make this depend on some setting
false,
mediaUrl: path,
@ -546,7 +555,10 @@ class XmppService {
mediaWidth: dimensions[path]?.width.toInt(),
mediaHeight: dimensions[path]?.height.toInt(),
filename: pathlib.basename(path),
isUploading: true,
isUploading:
conversation?.type != ConversationType.note ? true : false,
received: conversation?.type == ConversationType.note ? true : false,
displayed: conversation?.type == ConversationType.note ? true : false,
);
if (messages.containsKey(path)) {
messages[path]![recipient] = msg;
@ -578,6 +590,7 @@ class XmppService {
// TODO(Unknown): Should we use the JID parser?
rosterItem?.title ?? recipient.split('@').first,
lastMessages[recipient],
ConversationType.chat,
rosterItem?.avatarUrl ?? '',
recipient,
0,
@ -665,37 +678,40 @@ class XmppService {
}
}
}
// Send an upload notification
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: recipient,
id: messages[path]![recipient]!.sid,
fun: FileMetadataData(
// TODO(Unknown): Maybe add media type specific metadata
mediaType: lookupMimeType(path),
name: pathlib.basename(path),
size: File(path).statSync().size,
thumbnails: thumbnails[path] ?? [],
if (recipient != '') {
conn.getManagerById<MessageManager>(messageManager)!.sendMessage(
MessageDetails(
to: recipient,
id: messages[path]![recipient]!.sid,
fun: FileMetadataData(
// TODO(Unknown): Maybe add media type specific metadata
mediaType: lookupMimeType(path),
name: pathlib.basename(path),
size: File(path).statSync().size,
thumbnails: thumbnails[path] ?? [],
),
shouldEncrypt: encrypt[recipient]!,
),
shouldEncrypt: encrypt[recipient]!,
),
);
);
}
}
await hfts.uploadFile(
FileUploadJob(
recipients,
path,
pathMime,
encrypt,
messages[path]!,
thumbnails[path] ?? [],
),
);
}
recipients.remove('');
_log.finest('File upload submitted');
if (recipients.isNotEmpty) {
await hfts.uploadFile(
FileUploadJob(
recipients,
path,
pathMime,
encrypt,
messages[path]!,
thumbnails[path] ?? [],
),
);
_log.finest('File upload submitted');
}
}
}
Future<void> _initializeOmemoService(String jid) async {
@ -1420,6 +1436,7 @@ class XmppService {
final newConversation = await cs.addConversationFromData(
rosterItem?.title ?? conversationJid.split('@')[0],
message,
ConversationType.chat,
rosterItem?.avatarUrl ?? '',
conversationJid,
sent ? 0 : 1,

View File

@ -40,6 +40,13 @@ class ConversationMessageConverter
};
}
enum ConversationType {
@JsonValue('chat')
chat,
@JsonValue('note')
note
}
@freezed
class Conversation with _$Conversation {
factory Conversation(
@ -48,6 +55,7 @@ class Conversation with _$Conversation {
String avatarUrl,
String jid,
int unreadCounter,
ConversationType type,
// NOTE: In milliseconds since Epoch or -1 if none has ever happened
int lastChangeTimestamp,
List<SharedMedium> sharedMedia,

View File

@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
import 'package:moxlib/moxlib.dart';
import 'package:moxplatform/moxplatform.dart';
import 'package:moxxyv2/service/database/helpers.dart';
import 'package:moxxyv2/shared/commands.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
@ -63,6 +64,7 @@ class NewConversationBloc
jid: event.jid,
avatarUrl: event.avatarUrl,
lastMessageBody: '',
conversationType: conversationTypeToString(event.type),
),
);

View File

@ -11,10 +11,11 @@ class NewConversationInitEvent extends NewConversationEvent {
/// Triggered when a new conversation has been added by the UI
class NewConversationAddedEvent extends NewConversationEvent {
NewConversationAddedEvent(this.jid, this.title, this.avatarUrl);
NewConversationAddedEvent(this.jid, this.title, this.avatarUrl, this.type);
final String jid;
final String title;
final String avatarUrl;
final ConversationType type;
}
/// Triggered when a roster item has been removed by the UI

View File

@ -10,6 +10,7 @@ import 'package:grouped_list/grouped_list.dart';
import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/shared/error_types.dart';
import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/shared/warning_types.dart';
import 'package:moxxyv2/ui/bloc/conversation_bloc.dart';
@ -322,7 +323,7 @@ class ConversationPageState extends State<ConversationPage>
},
),
if (item.isQuotable)
if (item.isQuotable && item.conversationJid != '')
OverviewMenuItem(
icon: Icons.forward,
text: t.pages.conversation.forward,
@ -488,7 +489,8 @@ class ConversationPageState extends State<ConversationPage>
prev.conversation?.inRoster !=
next.conversation?.inRoster,
builder: (context, state) {
if (state.conversation?.inRoster ?? false) {
if ((state.conversation?.inRoster ?? false) ||
state.conversation?.type == ConversationType.note) {
return Container();
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/ui/bloc/conversation_bloc.dart';
import 'package:moxxyv2/ui/bloc/conversations_bloc.dart';
import 'package:moxxyv2/ui/bloc/navigation_bloc.dart';
@ -171,37 +172,38 @@ class ConversationTopbar extends StatelessWidget
),
),
),
// ignore: implicit_dynamic_type
PopupMenuButton(
onSelected: (result) {
if (result == EncryptionOption.omemo &&
state.conversation!.encrypted == false) {
context
.read<ConversationBloc>()
.add(OmemoSetEvent(true));
} else if (result == EncryptionOption.none &&
state.conversation!.encrypted == true) {
context
.read<ConversationBloc>()
.add(OmemoSetEvent(false));
}
},
icon: (state.conversation?.encrypted ?? false)
? const Icon(Icons.lock)
: const Icon(Icons.lock_open),
itemBuilder: (BuildContext c) => [
popupItemWithIcon(
EncryptionOption.none,
t.pages.conversation.unencrypted,
Icons.lock_open,
),
popupItemWithIcon(
EncryptionOption.omemo,
t.pages.conversation.encrypted,
Icons.lock,
),
],
),
if (state.conversation?.type != ConversationType.note)
// ignore: implicit_dynamic_type
PopupMenuButton(
onSelected: (result) {
if (result == EncryptionOption.omemo &&
state.conversation!.encrypted == false) {
context
.read<ConversationBloc>()
.add(OmemoSetEvent(true));
} else if (result == EncryptionOption.none &&
state.conversation!.encrypted == true) {
context
.read<ConversationBloc>()
.add(OmemoSetEvent(false));
}
},
icon: (state.conversation?.encrypted ?? false)
? const Icon(Icons.lock)
: const Icon(Icons.lock_open),
itemBuilder: (BuildContext c) => [
popupItemWithIcon(
EncryptionOption.none,
t.pages.conversation.unencrypted,
Icons.lock_open,
),
popupItemWithIcon(
EncryptionOption.omemo,
t.pages.conversation.encrypted,
Icons.lock,
),
],
),
// ignore: implicit_dynamic_type
PopupMenuButton(
onSelected: (result) async {
@ -244,11 +246,12 @@ class ConversationTopbar extends StatelessWidget
t.pages.conversation.closeChat,
Icons.close,
),
popupItemWithIcon(
ConversationOption.block,
t.pages.conversation.blockUser,
Icons.block,
)
if (state.conversation?.type != ConversationType.note)
popupItemWithIcon(
ConversationOption.block,
t.pages.conversation.blockUser,
Icons.block,
)
],
),
],

View File

@ -7,6 +7,7 @@ import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/ui/bloc/conversation_bloc.dart';
import 'package:moxxyv2/ui/bloc/conversations_bloc.dart';
import 'package:moxxyv2/ui/bloc/newconversation_bloc.dart';
import 'package:moxxyv2/ui/bloc/profile_bloc.dart' as profile;
import 'package:moxxyv2/ui/constants.dart';
import 'package:moxxyv2/ui/helpers.dart';
@ -285,6 +286,23 @@ class ConversationsPageState extends State<ConversationsPage>
backgroundColor: primaryColor,
foregroundColor: Colors.white,
children: [
SpeedDialChild(
child: const Icon(Icons.notes),
onTap: () {
context.read<NewConversationBloc>().add(
NewConversationAddedEvent(
ikjot-2605 marked this conversation as resolved

I think we might be able to add a new event in the NewConversationBloc that creates a Conversation with the type of Notes. Or we adapt the event so that we can tell it what type of chat we want. The latter might be the cleaner or the two solutions.

I think we might be able to add a new event in the NewConversationBloc that creates a Conversation with the type of `Notes`. Or we adapt the event so that we can tell it what type of chat we want. The latter might be the cleaner or the two solutions.

Done.

Done.
'',
t.pages.conversations.speeddialAddNoteToSelf,
'',
ConversationType.note,
),
);
},
backgroundColor: primaryColor,
// TODO(Unknown): Theme dependent?
foregroundColor: Colors.white,
label: t.pages.conversations.speeddialAddNoteToSelf,
),
SpeedDialChild(
child: const Icon(Icons.group),
onTap: () => showNotImplementedDialog('groupchat', context),

View File

@ -93,6 +93,7 @@ class NewConversationPage extends StatelessWidget {
item.jid,
item.title,
item.avatarUrl,
ConversationType.chat,
),
),
child: ConversationsListRow(
@ -114,6 +115,7 @@ class NewConversationPage extends StatelessWidget {
item.avatarUrl,
item.jid,
0,
ConversationType.chat,
0,
[],
true,

View File

@ -88,6 +88,7 @@ class ShareSelectionPage extends StatelessWidget {
item.avatarPath,
item.jid,
0,
ConversationType.chat,
0,
[],
true,

View File

@ -239,7 +239,10 @@ class AudioChatState extends State<AudioChatWidget> {
_position,
widget.message.id,
),
MessageBubbleBottom(widget.message, widget.sent),
MessageBubbleBottom(
widget.message,
widget.sent,
),
widget.radius,
gradient: false,
);

View File

@ -90,7 +90,10 @@ class FileChatBaseWidget extends StatelessWidget {
],
),
),
MessageBubbleBottom(message, sent),
MessageBubbleBottom(
message,
sent,
),
radius,
gradient: false,
//extra: extra,

View File

@ -90,7 +90,10 @@ class ImageChatWidget extends StatelessWidget {
return MediaBaseChatWidget(
image,
MessageBubbleBottom(message, sent),
MessageBubbleBottom(
message,
sent,
),
radius,
onTap: () => openFile(message.mediaUrl!),
);

View File

@ -111,7 +111,11 @@ class StickerChatWidget extends StatelessWidget {
),
child: Padding(
padding: const EdgeInsets.all(8),
child: MessageBubbleBottom(message, sent, shrink: true),
child: MessageBubbleBottom(
message,
sent,
shrink: true,
),
),
),
),

View File

@ -77,7 +77,10 @@ class TextChatWidget extends StatelessWidget {
padding: topWidget != null
? const EdgeInsets.only(left: 8, right: 8, bottom: 8)
: EdgeInsets.zero,
child: MessageBubbleBottom(message, sent),
child: MessageBubbleBottom(
message,
sent,
),
)
],
),

View File

@ -89,7 +89,10 @@ class VideoChatWidget extends StatelessWidget {
),
borderRadius: radius,
),
MessageBubbleBottom(message, sent),
MessageBubbleBottom(
message,
sent,
),
radius,
onTap: () => openFile(message.mediaUrl!),
extra: const PlayButton(),