Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

5 changes: 3 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ if (flutterVersionName == null) {
android {
namespace "com.bluebubbles.messaging"
compileSdk 36
ndkVersion "28.0.12433566"

lintOptions {
checkReleaseBuilds false
Expand All @@ -43,7 +44,7 @@ android {

defaultConfig {
applicationId "com.bluebubbles.messaging"
minSdkVersion 24
minSdkVersion 28
targetSdkVersion 36
versionCode 20002000 + flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down Expand Up @@ -121,7 +122,7 @@ android {
productFlavors.alpha.signingConfig signingConfigs.debug
productFlavors.beta.signingConfig signingConfigs.release
productFlavors.prod.signingConfig signingConfigs.release
productFlavors.alpha.signingConfig signingConfigs.release
productFlavors.alpha.signingConfig signingConfigs.debug
minifyEnabled false
shrinkResources false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.loader.ApplicationInfoLoader
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterCallbackInformation
import io.flutter.view.FlutterMain
import io.flutter.FlutterInjector
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
Expand All @@ -49,9 +49,9 @@ class DartWorker(context: Context, workerParams: WorkerParameters): ListenableWo
/// Code idea taken from https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
private suspend fun initNewEngine(applicationContext: Context) {
Log.d(Constants.logTag, "Ensuring Flutter is initialized before creating engine")
// We use the deprecated class here anyways, the new one doesn't work correctly using the same code
FlutterMain.startInitialization(applicationContext)
FlutterMain.ensureInitializationComplete(applicationContext, null)
val loader = FlutterInjector.instance().flutterLoader()
loader.startInitialization(applicationContext)
loader.ensureInitializationComplete(applicationContext, null)

Log.d(Constants.logTag, "Loading callback info")
val info = ApplicationInfoLoader.load(applicationContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,28 +82,11 @@ class InternalIntentReceiver: BroadcastReceiver() {
)
}

Log.d(Constants.logTag, "Creating sender and message object for the user-created reply")
val prefs = context.getSharedPreferences("FlutterSharedPreferences", 0)
val sender = Person.Builder()
.setName(prefs.getString("flutter.userName", "You"))
.setImportant(true)
val avatarPath = prefs.getString("flutter.userAvatarPath", "")
if (avatarPath!!.isNotEmpty()) {
val file = File(avatarPath)
val bytes = ByteArray(file.length().toInt())
try {
val bis = BufferedInputStream(FileInputStream(file))
val dis = DataInputStream(bis)
dis.readFully(bytes)
sender.setIcon(Utils.getAdaptiveIconFromByteArray(bytes).toIcon(context))
} catch (e: IOException) {
e.printStackTrace()
}
}
Log.d(Constants.logTag, "Creating message object for the user-created reply")
oldStyle.addMessage(Notification.MessagingStyle.Message(
replyText,
System.currentTimeMillis() / 1000,
sender.build()
System.currentTimeMillis(),
null as Person?
))

Log.d(Constants.logTag, "Posting the user-created reply")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class CreateIncomingMessageNotification: MethodCallHandlerImpl() {
val contactIcon: ByteArray? = call.argument("contact_avatar")
val contactBitmap = if ((contactIcon?.size ?: 0) == 0) null else Utils.getAdaptiveIconFromByteArray(contactIcon!!)
val chat_uri: String? = call.argument("contact_uri")
// attachment details
val attachmentPath: String? = call.argument("attachment_path")
val attachmentType: String? = call.argument("attachment_type")

val name = if (notifyAnyways) {
"Notify Anyways: $chatTitle"
Expand Down Expand Up @@ -120,11 +123,19 @@ class CreateIncomingMessageNotification: MethodCallHandlerImpl() {
style.conversationTitle = chatTitle
}
// add the new message to the style
style.addMessage(NotificationCompat.MessagingStyle.Message(
val notifMessage = NotificationCompat.MessagingStyle.Message(
messageText,
messageDate,
sender
))
if (messageIsFromMe) null else sender
)
if (attachmentPath != null && attachmentType != null) {
val file = java.io.File(attachmentPath)
if (file.exists()) {
val uri = androidx.core.content.FileProvider.getUriForFile(context, context.getString(R.string.file_provider), file)
notifMessage.setData(attachmentType, uri)
}
}
style.addMessage(notifMessage)

// create a bundle for extra info
val extras = Bundle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import io.flutter.embedding.engine.loader.ApplicationInfoLoader
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterCallbackInformation
import io.flutter.view.FlutterMain
import io.flutter.FlutterInjector
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

Expand Down Expand Up @@ -42,8 +42,9 @@ class NativeSyncIsolateHandler : MethodCallHandlerImpl() {
return
}

FlutterMain.startInitialization(context)
FlutterMain.ensureInitializationComplete(context, null)
val loader = FlutterInjector.instance().flutterLoader()
loader.startInitialization(context)
loader.ensureInitializationComplete(context, null)

Log.d(Constants.logTag, "Loading callback info")
val info = ApplicationInfoLoader.load(context)
Expand Down
23 changes: 22 additions & 1 deletion lib/helpers/types/helpers/message_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,30 @@ class MessageHelper {
// If there are attachments, return the number of attachments
if (message.realAttachments.isNotEmpty) {
int aCount = message.realAttachments.length;
String attachmentText = _getAttachmentText(message.realAttachments);

String? emoji;
if (attachmentText.contains("image")) {
emoji = "📷";
} else if (attachmentText.contains("movie")) {
emoji = "🎥";
} else if (attachmentText.contains("GIF")) {
emoji = "🖼️";
} else if (attachmentText.contains("audio")) {
emoji = "🎵";
}

if (emoji != null) {
if (aCount == 1 && attachmentText == "1 image") return "📷 Image";
if (aCount == 1 && attachmentText == "1 movie") return "🎥 Video";
if (aCount == 1 && attachmentText == "1 GIF") return "🖼️ GIF";
if (aCount == 1 && attachmentText == "1 audio") return "🎵 Audio";
return "$emoji $attachmentText";
}

// Build the attachment output by counting the attachments
String output = "Attachment${aCount > 1 ? "s" : ""}";
return "$output: ${_getAttachmentText(message.realAttachments)}";
return "$output: $attachmentText";
} else if (!isNullOrEmpty(message.associatedMessageGuid)) {
// It's a reaction message, get the sender
String sender = message.isFromMe! ? 'You' : (message.handle?.displayName ?? "Someone");
Expand Down
18 changes: 12 additions & 6 deletions lib/helpers/ui/ui_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -585,17 +585,23 @@ Future<void> paintAvatar(
}
}

Uint8List _clipIsolate(Map<String, dynamic> args) {
Uint8List data = args['data'];
int size = args['size'];
img.Image? _image = img.decodeImage(data);
if (_image != null) {
_image = img.copyResize(_image, width: size, height: size);
return img.encodePng(_image);
}
return data;
}

Future<Uint8List?> clip(Uint8List data, {required int size, required bool circle}) async {
ui.Image image;
Uint8List _data = data;

// Resize the image if it's the wrong size
img.Image? _image = img.decodeImage(data);
if (_image != null) {
_image = img.copyResize(_image, width: size, height: size);

_data = img.encodePng(_image);
}
_data = await compute(_clipIsolate, {'data': data, 'size': size});

image = await loadImage(_data);

Expand Down
3 changes: 3 additions & 0 deletions lib/services/backend/java_dart_interop/intents_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ class IntentsService extends GetxService {
if (!chatIsOpen) {
Logger.debug("Navigating to conversation view...", tag: "IntentsService");
await StartupTasks.waitForUI();
if (cm.activeChat != null) {
Navigator.of(Get.context!).popUntil((route) => route.isFirst);
}
await Future.delayed(const Duration(seconds: 1));
await ns.pushAndRemoveUntil(
Get.context!,
Expand Down
12 changes: 12 additions & 0 deletions lib/services/backend/notifications/notifications_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ class NotificationsService extends GetxService {
} else if (kIsDesktop) {
_lock.synchronized(() async => await showDesktopNotif(message, text, chat, guid, title, contactName, isGroup, isReaction));
} else {
String? attachmentPath;
String? attachmentType;
if (message.attachments.isNotEmpty) {
Attachment? attachment = message.attachments.firstWhereOrNull((e) => e?.mimeType?.startsWith("image/") ?? false);
if (attachment != null && attachment.existsOnDisk) {
attachmentPath = attachment.path;
attachmentType = attachment.mimeType;
}
}

await mcs.invokeMethod("create-incoming-message-notification", {
"channel_id": NEW_MESSAGE_CHANNEL,
"chat_id": chat.id,
Expand All @@ -249,6 +259,8 @@ class NotificationsService extends GetxService {
"message_text": text,
"message_date": message.dateCreated!.millisecondsSinceEpoch,
"message_is_from_me": false,
"attachment_path": attachmentPath,
"attachment_type": attachmentType,
});
}
}
Expand Down
4 changes: 3 additions & 1 deletion lib/services/ui/chat/chat_lifecycle_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class ChatLifecycleManager {
return Database.chats.get(chat.id!);
});
if (_chat != null) {
bool shouldSort = chat.latestMessage.dateCreated != _chat.latestMessage.dateCreated;
final newDate = Chat.getMessages(_chat, limit: 1, getDetails: false).firstOrNull?.dateCreated ?? DateTime.fromMillisecondsSinceEpoch(0);
bool shouldSort = chat.latestMessage.dateCreated != newDate;

chats.updateChat(_chat, shouldSort: shouldSort);
chat = _chat.merge(chat);
}
Expand Down
20 changes: 20 additions & 0 deletions lib/services/ui/chat/chats_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ class ChatsService extends GetxService {
chats.sort(Chat.sort);
}

Future<void> updateShareTarget(Chat c) async {
if (!Platform.isAndroid) return;
try {
final title = c.properTitle;
if (isNullOrEmpty(title)) return;
final icon = await avatarAsBytes(chat: c, quality: 256);
await mcs.invokeMethod("push-share-targets", {
"title": title,
"guid": c.guid,
"icon": icon,
});
} catch (ex) {
// ignore
}
}

bool updateChat(Chat updated, {bool shouldSort = false, bool override = false}) {
final index = chats.indexWhere((e) => updated.guid == e.guid);
if (index != -1) {
Expand All @@ -209,6 +225,9 @@ class ChatsService extends GetxService {
// ignore: invalid_use_of_protected_member
chats.value[index] = override ? updated : updated.merge(toUpdate);
if (shouldSort) sort();
if (updated.getTitle() != toUpdate.getTitle() || updated.customAvatarPath != toUpdate.customAvatarPath) {
updateShareTarget(updated);
}
}

return index != -1;
Expand All @@ -219,6 +238,7 @@ class ChatsService extends GetxService {
chats.add(toAdd);
cm.createChatController(toAdd);
sort();
updateShareTarget(toAdd);
}

void removeChat(Chat toRemove) {
Expand Down
Loading