diff --git a/android/app/build.gradle b/android/app/build.gradle
index b8e413b83..64c0dcd65 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -103,6 +103,8 @@ dependencies {
//for location sending
implementation "com.google.android.gms:play-services-location:18.0.0"
+ // for album art color
+ implementation 'androidx.palette:palette:1.0.0'
// Add the SDK for Firebase Cloud Messaging
implementation 'com.google.firebase:firebase-messaging:22.0.0'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 271608473..7b0921c7a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -27,6 +27,7 @@
+
+
+
+
+
+
+
-
+ android:resource="@xml/automotive_app_desc"/>
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/MainActivity.java b/android/app/src/main/java/com/bluebubbles/messaging/MainActivity.java
index 1c857a8d2..513a331a5 100644
--- a/android/app/src/main/java/com/bluebubbles/messaging/MainActivity.java
+++ b/android/app/src/main/java/com/bluebubbles/messaging/MainActivity.java
@@ -53,6 +53,7 @@ public class MainActivity extends FlutterFragmentActivity {
public static int PICK_IMAGE = 1000;
public static int OPEN_CAMERA = 2000;
+ public static int NOTIFICATION_SETTINGS = 3000;
public MethodChannel.Result result = null;
@@ -128,6 +129,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d("OPEN_CAMERA", "Something went wrong");
result.success(null);
}
+ } else if (requestCode == NOTIFICATION_SETTINGS) {
+ result.success(null);
}
}
@@ -304,6 +307,7 @@ protected void onStart() {
@Override
protected void onDestroy() {
Log.d("MainActivity", "Removing Activity from memory");
+ new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), CHANNEL).invokeMethod("remove-sendPort", null);
engine = null;
super.onDestroy();
}
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/MethodCallHandler.java b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/MethodCallHandler.java
index 8132f0caf..ee34a8950 100644
--- a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/MethodCallHandler.java
+++ b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/MethodCallHandler.java
@@ -4,6 +4,8 @@
import android.app.AlarmManager;
import android.content.Context;
import android.os.Build;
+import android.content.Intent;
+import android.provider.Settings;
import androidx.annotation.RequiresApi;
@@ -20,6 +22,7 @@
import com.bluebubbles.messaging.method_call_handler.handlers.GetLastLocation;
import com.bluebubbles.messaging.method_call_handler.handlers.GetServerUrl;
import com.bluebubbles.messaging.method_call_handler.handlers.InitializeBackgroundHandle;
+import com.bluebubbles.messaging.method_call_handler.handlers.MediaSessionListener;
import com.bluebubbles.messaging.method_call_handler.handlers.NewMessageNotification;
import com.bluebubbles.messaging.method_call_handler.handlers.OpenCamera;
import com.bluebubbles.messaging.method_call_handler.handlers.OpenFile;
@@ -32,6 +35,7 @@
import com.bluebubbles.messaging.method_call_handler.handlers.OpenContactForm;
import com.bluebubbles.messaging.method_call_handler.handlers.ViewContactForm;
import com.bluebubbles.messaging.workers.DartWorker;
+import static com.bluebubbles.messaging.MainActivity.engine;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@@ -96,6 +100,21 @@ public static void methodCallHandler(MethodCall call, MethodChannel.Result resul
new FailedToSend(context, call, result).Handle();
} else if (call.method.equals(ClearFailedToSend.TAG)) {
new ClearFailedToSend(context, call, result).Handle();
+ } else if (call.method.equals("start-notif-listener")) {
+ if (Settings.Secure.getString(context.getContentResolver(),"enabled_notification_listeners").contains(context.getPackageName()) && engine != null) {
+ new MediaSessionListener(context, call, result).Handle();
+ } else {
+ result.error("could_not_initialize", "Failed to initialize, permission not granted", "");
+ }
+ } else if (call.method.equals("request-notif-permission")) {
+ if (Settings.Secure.getString(context.getContentResolver(),"enabled_notification_listeners").contains(context.getPackageName())) {
+ result.success("");
+ } else {
+ MainActivity activity = (MainActivity) context;
+ activity.result = result;
+ Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
+ activity.startActivityForResult(intent, 3000);
+ }
} else {
result.notImplemented();
}
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/CreateNotificationChannel.java b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/CreateNotificationChannel.java
index a9341445c..a7c014567 100644
--- a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/CreateNotificationChannel.java
+++ b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/CreateNotificationChannel.java
@@ -3,7 +3,10 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
+import android.media.AudioAttributes;
import android.os.Build;
+import android.net.Uri;
+import android.util.Log;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@@ -23,11 +26,11 @@ public CreateNotificationChannel(Context context, MethodCall call, MethodChanne
@Override
public void Handle() {
- createNotificationChannel(call.argument("channel_name"), call.argument("channel_description"), call.argument("CHANNEL_ID"), context);
+ createNotificationChannel(call.argument("channel_name"), call.argument("channel_description"), call.argument("CHANNEL_ID"), call.argument("sound"), context);
result.success("");
}
- public static void createNotificationChannel(String channel_name, String channel_description, String CHANNEL_ID, Context context) {
+ public static void createNotificationChannel(String channel_name, String channel_description, String CHANNEL_ID, String soundPath, Context context) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -38,6 +41,14 @@ public static void createNotificationChannel(String channel_name, String channel
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
+ if (soundPath != null) {
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build();
+ int soundResourceId = context.getResources().getIdentifier(soundPath, "raw", context.getPackageName());
+ channel.setSound(Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId), audioAttributes);
+ }
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/MediaSessionListener.java b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/MediaSessionListener.java
new file mode 100644
index 000000000..da2274b39
--- /dev/null
+++ b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/MediaSessionListener.java
@@ -0,0 +1,150 @@
+package com.bluebubbles.messaging.method_call_handler.handlers;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.media.session.MediaController;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.os.Build;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.io.ByteArrayOutputStream;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AlertDialog;
+import androidx.palette.graphics.Palette;
+import com.bluebubbles.messaging.services.CustomNotificationListenerService;
+
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import static com.bluebubbles.messaging.MainActivity.engine;
+import static com.bluebubbles.messaging.MainActivity.CHANNEL;
+
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class MediaSessionListener implements OnActiveSessionsChangedListener, Handler {
+
+ private Context context;
+ private MethodCall call;
+ private MethodChannel.Result result;
+ private List oldControllers;
+ private MediaController.Callback callback;
+ private MethodChannel backgroundChannel;
+
+ public MediaSessionListener(Context context, MethodCall call, MethodChannel.Result result) {
+ this.context = context;
+ this.call = call;
+ this.result = result;
+ }
+
+
+ @Override
+ public void Handle() {
+ backgroundChannel = new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), CHANNEL);
+ MediaSessionManager manager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ if (null == manager) {
+ result.error("could_not_initialize", "Failed to initialize, manager == null", "");
+ }
+ callback = new MediaController.Callback() {
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ String title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ if (title == null) {
+ title = "Unknown";
+ }
+ Bitmap icon = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ Bitmap icon2 = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ Log.d("BlueBubblesApp", "Getting media metadata for media " + title);
+ HashMap input = new HashMap<>();
+ Palette p = null;
+ if (icon != null) {
+ p = Palette.from(icon).generate();
+ } else if (icon2 != null) {
+ p = Palette.from(icon2).generate();
+ }
+ if (p != null) {
+ int lightBg = p.getLightVibrantColor(Color.WHITE);
+ int darkBg = p.getDarkMutedColor(Color.BLACK);
+ int primary;
+ double lightBgPercent = 0.5;
+ double darkBgPercent = 0.5;
+ double primaryPercent = 0.5;
+ String primaryFrom = "none";
+ if (p.getVibrantColor(0xFF2196F3) != 0xFF2196F3) {
+ primary = p.getVibrantColor(0xFF2196F3);
+ primaryFrom = "vibrant";
+ } else if (p.getMutedColor(0xFF2196F3) != 0xFF2196F3) {
+ primary = p.getMutedColor(0xFF2196F3);
+ primaryFrom = "muted";
+ } else if (p.getLightMutedColor(0xFF2196F3) != 0xFF2196F3) {
+ primary = p.getLightMutedColor(0xFF2196F3);
+ primaryFrom = "lightMuted";
+ } else {
+ primary = 0xFF2196F3;
+ primaryFrom = "none";
+ }
+ if (p.getLightVibrantSwatch() != null) {
+ lightBgPercent = p.getLightVibrantSwatch().getPopulation();
+ }
+ if (p.getDarkMutedSwatch() != null) {
+ darkBgPercent = p.getDarkMutedSwatch().getPopulation();
+ }
+ if (primaryFrom == "vibrant" && p.getVibrantSwatch() != null) {
+ primaryPercent = p.getVibrantSwatch().getPopulation();
+ } else if (primaryFrom == "muted" && p.getMutedSwatch() != null) {
+ primaryPercent = p.getMutedSwatch().getPopulation();
+ } else if (primaryFrom == "lightMuted" && p.getLightMutedSwatch() != null) {
+ primaryPercent = p.getLightMutedSwatch().getPopulation();
+ }
+ Log.d("BlueBubblesApp", "Dominant color found (for debugging only): " + Integer.toString(p.getDominantColor(Color.BLACK)));
+ input.put("lightBg", lightBg);
+ input.put("darkBg", darkBg);
+ input.put("primary", primary);
+ input.put("lightBgPercent", lightBgPercent);
+ input.put("darkBgPercent", darkBgPercent);
+ input.put("primaryPercent", primaryPercent);
+ Log.d("BlueBubblesApp", "Sending media metadata for media " + title);
+ backgroundChannel.invokeMethod("media-colors", input);
+ }
+ }
+ };
+ List controllers = manager.getActiveSessions(new ComponentName(context, CustomNotificationListenerService.class));
+ oldControllers = controllers;
+ if (null != controllers && controllers.size() != 0) {
+ for (MediaController controller : controllers) {
+ controller.registerCallback(callback);
+ }
+ }
+ manager.addOnActiveSessionsChangedListener(MediaSessionListener.this, new ComponentName(context, CustomNotificationListenerService.class));
+ result.success("");
+ }
+
+ @Override
+ public void onActiveSessionsChanged(@Nullable List controllers) {
+
+ if (null == controllers || controllers.size() == 0) {
+ return;
+ }
+
+ for (MediaController controller : oldControllers) {
+ controller.unregisterCallback(callback);
+ }
+ oldControllers = controllers;
+ for (MediaController controller : controllers) {
+ controller.registerCallback(callback);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/NewMessageNotification.java b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/NewMessageNotification.java
index 813b18989..6e83c86cb 100644
--- a/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/NewMessageNotification.java
+++ b/android/app/src/main/java/com/bluebubbles/messaging/method_call_handler/handlers/NewMessageNotification.java
@@ -19,6 +19,7 @@
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
+import android.net.Uri;
import android.service.notification.StatusBarNotification;
import androidx.annotation.RequiresApi;
@@ -81,6 +82,7 @@ public void Handle() {
Integer notificationVisibility = (Integer) call.argument("visibility");
Integer notificationId = (Integer) call.argument("notificationId");
Integer summaryId = (Integer) call.argument("summaryId");
+ String soundPath = (String) call.argument("sound");
// Find any notifications that already exist for the chat
NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
@@ -283,6 +285,12 @@ public void Handle() {
// Set the color. This is the blue primary color
.setColor(4888294);
+ // Set the sound of the notification (Android 7 and below)
+ if (soundPath != "default") {
+ int soundResourceId = context.getResources().getIdentifier(soundPath, "raw", context.getPackageName());
+ notificationBuilder.setSound(Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId));
+ }
+
// Disable the alert if it's from you
notificationBuilder.setOnlyAlertOnce(messageIsFromMe);
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/services/CustomNotificationListenerService.java b/android/app/src/main/java/com/bluebubbles/messaging/services/CustomNotificationListenerService.java
new file mode 100644
index 000000000..2188610dd
--- /dev/null
+++ b/android/app/src/main/java/com/bluebubbles/messaging/services/CustomNotificationListenerService.java
@@ -0,0 +1,13 @@
+package com.bluebubbles.messaging.services;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+public class CustomNotificationListenerService extends NotificationListenerService {
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/bluebubbles/messaging/services/FlutterFirebaseMessagingBackgroundExecutor.java b/android/app/src/main/java/com/bluebubbles/messaging/services/FlutterFirebaseMessagingBackgroundExecutor.java
index dc9c4f753..55eba3e28 100644
--- a/android/app/src/main/java/com/bluebubbles/messaging/services/FlutterFirebaseMessagingBackgroundExecutor.java
+++ b/android/app/src/main/java/com/bluebubbles/messaging/services/FlutterFirebaseMessagingBackgroundExecutor.java
@@ -184,8 +184,8 @@ public void onMethodCall(MethodCall call, @NonNull Result result) {
*/
public void startBackgroundIsolate() {
if (isNotRunning()) {
- long callbackHandle = getPluginCallbackHandle();
- if (callbackHandle != 0) {
+ Long callbackHandle = getPluginCallbackHandle();
+ if (callbackHandle != null && callbackHandle != 0) {
startBackgroundIsolate(callbackHandle, null);
}
}
diff --git a/android/app/src/main/res/drawable/ic_stat_icon.png b/android/app/src/main/res/drawable/ic_stat_icon.png
new file mode 100644
index 000000000..3cf130bd9
Binary files /dev/null and b/android/app/src/main/res/drawable/ic_stat_icon.png differ
diff --git a/android/app/src/main/res/raw/raspberry.wav b/android/app/src/main/res/raw/raspberry.wav
new file mode 100644
index 000000000..249e7d6f9
Binary files /dev/null and b/android/app/src/main/res/raw/raspberry.wav differ
diff --git a/android/app/src/main/res/raw/sugarfree.wav b/android/app/src/main/res/raw/sugarfree.wav
new file mode 100644
index 000000000..f4d5cb54b
Binary files /dev/null and b/android/app/src/main/res/raw/sugarfree.wav differ
diff --git a/android/app/src/main/res/raw/twig.wav b/android/app/src/main/res/raw/twig.wav
new file mode 100644
index 000000000..6fd1a0f7b
Binary files /dev/null and b/android/app/src/main/res/raw/twig.wav differ
diff --git a/android/app/src/main/res/raw/walrus.wav b/android/app/src/main/res/raw/walrus.wav
new file mode 100644
index 000000000..0d45d3f2e
Binary files /dev/null and b/android/app/src/main/res/raw/walrus.wav differ
diff --git a/assets/changelog/changelog.md b/assets/changelog/changelog.md
index a1fdfa920..f39829a43 100644
--- a/assets/changelog/changelog.md
+++ b/assets/changelog/changelog.md
@@ -2,10 +2,62 @@
Below are the last few BlueBubbles App release changelogs
-## v1.4.1
+## v1.5.0
-### Bug Fixes
+## Changes
+
+### The Big Stuff
+* New notification options
+* New theming options
+ - Ability to dynamically theme the app based on the current song's album cover
+ - Ability to copy and save those dynamic themes
+* Tablet mode
+* Unknown senders tab option
+ - Senders with no associated contact info will be separated
+* Other new features, UI, and UX improvements
+
+### The Nitty Gritty
+
+#### New Features
+
+- **New Notification Options**
+ - Added the option to schedule a reminder for a message by long-pressing the message
+ - Added new notification settings page
+ - Added the ability to change the notification sound
+ - Added the option to disable notifying reactions
+ - Added the ability to set global text detection (only notify when a text contains certain words or phrases)
+ - Added the ability to mute certain individuals in a chat
+ - Added the ability to mute a chat temporarily
+ - Added the ability to set text detection on a specific chat - only notify when a text from the specified chat contains certain words or phrases
+- **New Theming Options**
+ - Added the ability to get background and primary color from album art (requires full notification access)
+ - Added the ability to set an animated gradient background for the chat list (gradient is created between background and primary color)
+- **Other New Features**
+ - Added a better logging mechanism to make it easier to send bug reports to the developers
+ - Added ability to add a camera button above the chat creator button like Signal
+ - Added "Unknown Senders" tab
+#### Bug Fixes
+
+- **UI bugs**
+ - Fixed custom bubble color getting reset for new messages
+ - Fixed 24hr time not working properly
+ - Improved smart reply padding in Material theme
+- **UX bugs**
+ - Fixed some bugs with the fullscreen photo viewer
+
+#### Improvements
+
+- **UI Improvements**
+ - Move pinned chat typing indicator to the avatar so the latest message bubble can always be seen
+ - Completely revamped icons for iOS theme to match iOS-style
+ - Improved URL preview
+ - Removed Camera preview from share menu to reduce lag. Replaced by 2 buttons, camera and video
+- **UX Improvements**
+ - Added pagination to incremental sync (messages should load faster)
+ - Increased chat page size to reduce visible "lag" when resuming the app from the background
+
+## v1.4.1
### Enhancements
* Increases message preview to 2 lines (max)
diff --git a/lib/action_handler.dart b/lib/action_handler.dart
index b352e5649..9839cfbd1 100644
--- a/lib/action_handler.dart
+++ b/lib/action_handler.dart
@@ -7,6 +7,7 @@ import 'package:bluebubbles/helpers/attachment_downloader.dart';
import 'package:bluebubbles/helpers/attachment_helper.dart';
import 'package:bluebubbles/helpers/attachment_sender.dart';
import 'package:bluebubbles/helpers/darty.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/message_helper.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/current_chat.dart';
@@ -19,6 +20,7 @@ import 'package:bluebubbles/managers/settings_manager.dart';
import 'package:bluebubbles/repository/database.dart';
import 'package:bluebubbles/repository/models/attachment.dart';
import 'package:bluebubbles/repository/models/chat.dart';
+import 'package:bluebubbles/repository/models/handle.dart';
import 'package:bluebubbles/repository/models/message.dart';
import 'package:bluebubbles/socket_manager.dart';
import 'package:collection/collection.dart';
@@ -252,7 +254,7 @@ class ActionHandler {
// If there is an error, replace the temp value with an error
if (response['status'] != 200) {
- debugPrint("FAILED TO SEND REACTION " + response['error']['message']);
+ Logger.error("FAILED TO SEND REACTION " + response['error']['message']);
}
completer.complete();
@@ -361,7 +363,7 @@ class ActionHandler {
[chat.id]);
// If there are no messages, return
- debugPrint("Deleting ${items.length} messages");
+ Logger.info("Deleting ${items.length} messages");
if (isNullOrEmpty(items)!) return;
Batch batch = db.batch();
@@ -409,7 +411,7 @@ class ActionHandler {
if (updatedMessage.isFromMe!) {
await Future.delayed(Duration(milliseconds: 200));
- debugPrint("(Message status) -> handleUpdatedMessage: " + updatedMessage.text!);
+ Logger.info("Handling message update: " + updatedMessage.text!, tag: "Actions-UpdatedMessage");
}
updatedMessage = await Message.replaceMessage(updatedMessage.guid, updatedMessage) ?? updatedMessage;
@@ -463,7 +465,7 @@ class ActionHandler {
// Save the new chat only if current chat isn't found
if (currentChat == null) {
- debugPrint("(Handle Chat) Chat did not exist. Saving.");
+ Logger.info("Chat did not exist. Saving.", tag: "Actions-HandleChat");
await newChat.save();
}
@@ -476,7 +478,7 @@ class ActionHandler {
if (newChat == null) return;
await ChatBloc().updateChatPosition(newChat);
} catch (ex) {
- debugPrint(ex.toString());
+ Logger.error(ex.toString());
}
}
@@ -502,7 +504,8 @@ class ActionHandler {
// If the GUID exists already, delete the temporary entry
// Otherwise, replace the temp message
if (existing != null) {
- debugPrint("(Message status) -> Deleting message: [${data["text"]}] - ${data["guid"]} - ${data["tempGuid"]}");
+ Logger.info("Deleting message: [${data["text"]}] - ${data["guid"]} - ${data["tempGuid"]}",
+ tag: "MessageStatus");
await Message.delete({'guid': data['tempGuid']});
NewMessageManager().removeMessage(chats.first, data['tempGuid']);
} else {
@@ -515,18 +518,18 @@ class ActionHandler {
try {
await Attachment.replaceAttachment(data["tempGuid"], file);
} catch (ex) {
- debugPrint("Attachment's Old GUID doesn't exist. Skipping");
+ Logger.warn("Attachment's Old GUID doesn't exist. Skipping");
}
message.attachments!.add(file);
}
- debugPrint("(Message status) -> Message match: [${data["text"]}] - ${data["guid"]} - ${data["tempGuid"]}");
+ Logger.info("Message match: [${data["text"]}] - ${data["guid"]} - ${data["tempGuid"]}", tag: "MessageStatus");
if (!isHeadless) NewMessageManager().updateMessage(chats.first, data['tempGuid'], message);
}
} else if (forceProcess || !NotificationManager().hasProcessed(data["guid"])) {
// Add the message to the chats
for (int i = 0; i < chats.length; i++) {
- debugPrint("Client received new message " + chats[i].guid!);
+ Logger.info("Client received new message " + chats[i].guid!);
// Gets the chat from the chat bloc
Chat? chat = await ChatBloc().getChat(chats[i].guid);
@@ -535,11 +538,18 @@ class ActionHandler {
chat = chats[i];
}
+ Handle? handle = chat.participants.firstWhereOrNull((e) => e.address == message.handle?.address);
+
+ if (handle != null) {
+ message.handle?.color = handle.color;
+ message.handle?.defaultPhone = handle.defaultPhone;
+ }
+
await chat.getParticipants();
// Handle the notification based on the message and chat
await MessageHelper.handleNotification(message, chat);
- debugPrint("(Message status) New message: [${message.text}] - [${message.guid}]");
+ Logger.info("New message: [${message.text}] - [${message.guid}]", tag: "Actions-HandleMessage");
await chat.addMessage(message);
if (message.itemType == 2 && message.groupTitle != null) {
diff --git a/lib/blocs/chat_bloc.dart b/lib/blocs/chat_bloc.dart
index fb37e5973..4c02b39ab 100644
--- a/lib/blocs/chat_bloc.dart
+++ b/lib/blocs/chat_bloc.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:bluebubbles/helpers/constants.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/attachment_info_bloc.dart';
import 'package:bluebubbles/managers/contact_manager.dart';
@@ -9,8 +10,8 @@ import 'package:bluebubbles/managers/method_channel_interface.dart';
import 'package:bluebubbles/managers/new_message_manager.dart';
import 'package:bluebubbles/managers/notification_manager.dart';
import 'package:bluebubbles/managers/settings_manager.dart';
+import 'package:bluebubbles/repository/models/message.dart';
import 'package:contacts_service/contacts_service.dart';
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../repository/models/chat.dart';
@@ -34,6 +35,7 @@ class ChatBloc {
}
Completer? chatRequest;
+ int lastFetch = 0;
static final ChatBloc _chatBloc = ChatBloc._internal();
@@ -74,20 +76,45 @@ class ChatBloc {
}
chatRequest = new Completer();
-
- debugPrint("[ChatBloc] -> Fetching chats (${force ? 'forced' : 'normal'})...");
+ Logger.info("Fetching chats (${force ? 'forced' : 'normal'})...", tag: "ChatBloc");
// Get the contacts in case we haven't
- await ContactManager().getContacts();
+ if (ContactManager().contacts.isEmpty) await ContactManager().getContacts();
if (_messageSubscription == null) {
_messageSubscription = setupMessageListener();
}
+ // Store the last time we fetched
+ lastFetch = DateTime.now().toUtc().millisecondsSinceEpoch;
+
// Fetch the first x chats
getChatBatches();
}
+ Future resumeRefresh() async {
+ Logger.info('Performing ChatBloc resume request...', tag: 'ChatBloc-Resume');
+
+ // Get the last message date
+ DateTime? lastMsgDate = await Message.lastMessageDate();
+
+ // If there is no last message, don't do anything
+ if (lastMsgDate == null) {
+ Logger.debug("No last message date found! Not doing anything...", tag: 'ChatBloc-Resume');
+ return;
+ }
+
+ // If the last message date is >= the last fetch, let's refetch
+ int lastMs = lastMsgDate.millisecondsSinceEpoch;
+ if (lastMs >= lastFetch) {
+ Logger.info('New messages detected! Refreshing the ChatBloc', tag: 'ChatBloc-Resume');
+ Logger.debug("$lastMs >= $lastFetch", tag: 'ChatBloc-Resume');
+ await this.refreshChats();
+ } else {
+ Logger.info('No new messages detected. Not refreshing the ChatBloc', tag: 'ChatBloc-Resume');
+ }
+ }
+
/// Inserts a [chat] into the chat bloc based on the lastMessage data
Future updateChatPosition(Chat chat) async {
if (isNullOrEmpty(_chats)!) {
@@ -204,7 +231,7 @@ class ChatBloc {
icon = NotificationManager().defaultAvatar;
}
} catch (ex) {
- debugPrint("Failed to load contact avatar: ${ex.toString()}");
+ Logger.error("Failed to load contact avatar: ${ex.toString()}");
}
// If we don't have a title, try to get it
@@ -245,7 +272,7 @@ class ChatBloc {
return NewMessageManager().stream.listen(handleMessageAction);
}
- Future getChatBatches({int batchSize = 10}) async {
+ Future getChatBatches({int batchSize = 15}) async {
int count = (await Chat.count()) ?? 0;
if (count == 0) {
hasChats.value = false;
@@ -263,13 +290,9 @@ class ChatBloc {
for (Chat chat in chats) {
newChats.add(chat);
-
await initTileValsForChat(chat);
- }
-
- for (int i = 0; i < newChats.length; i++) {
- if (isNullOrEmpty(newChats[i].participants)!) {
- await newChats[i].getParticipants();
+ if (isNullOrEmpty(chat.participants)!) {
+ await chat.getParticipants();
}
}
@@ -279,7 +302,7 @@ class ChatBloc {
}
}
- debugPrint("[ChatBloc] -> Finished fetching chats (${_chats.length}).");
+ Logger.info("Finished fetching chats (${_chats.length}).", tag: "ChatBloc");
await updateAllShareTargets();
if (chatRequest != null && !chatRequest!.isCompleted) {
@@ -321,12 +344,18 @@ class ChatBloc {
final item = _chats.bigPinHelper(true)[oldIndex];
if (newIndex > oldIndex) {
newIndex = newIndex - 1;
- _chats.bigPinHelper(true).where((p0) => p0.pinIndex.value != null && p0.pinIndex.value! <= newIndex).forEach((element) {
+ _chats
+ .bigPinHelper(true)
+ .where((p0) => p0.pinIndex.value != null && p0.pinIndex.value! <= newIndex)
+ .forEach((element) {
element.pinIndex.value = element.pinIndex.value! - 1;
});
item.pinIndex.value = newIndex;
} else {
- _chats.bigPinHelper(true).where((p0) => p0.pinIndex.value != null && p0.pinIndex.value! >= newIndex).forEach((element) {
+ _chats
+ .bigPinHelper(true)
+ .where((p0) => p0.pinIndex.value != null && p0.pinIndex.value! >= newIndex)
+ .forEach((element) {
element.pinIndex.value = element.pinIndex.value! + 1;
});
item.pinIndex.value = newIndex;
@@ -413,4 +442,21 @@ extension Helpers on RxList {
.toList()
.obs;
}
+
+ RxList unknownSendersHelper(bool unknown) {
+ if (!SettingsManager().settings.filterUnknownSenders.value) return this;
+ if (unknown)
+ return this
+ .where(
+ (e) => e.participants.length == 1 && ContactManager().handleToContact[e.participants[0].address] == null)
+ .toList()
+ .obs;
+ else
+ return this
+ .where((e) =>
+ e.participants.length > 1 ||
+ (e.participants.length == 1 && ContactManager().handleToContact[e.participants[0].address] != null))
+ .toList()
+ .obs;
+ }
}
diff --git a/lib/blocs/message_bloc.dart b/lib/blocs/message_bloc.dart
index 4ed44bc78..c393ca091 100644
--- a/lib/blocs/message_bloc.dart
+++ b/lib/blocs/message_bloc.dart
@@ -1,14 +1,13 @@
import 'dart:async';
import 'dart:collection';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/message_helper.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/current_chat.dart';
import 'package:bluebubbles/managers/new_message_manager.dart';
import 'package:bluebubbles/repository/models/chat.dart';
import 'package:bluebubbles/repository/models/message.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../socket_manager.dart';
@@ -143,9 +142,10 @@ class MessageBloc {
List messages = _allMessages.values.toList();
for (int i = 0; i < messages.length; i++) {
//if _allMessages[i] dateCreated is earlier than the new message, insert at that index
- if (message.guid != null && (messages[i]!.originalROWID != null &&
- message.originalROWID != null &&
- message.originalROWID! > messages[i]!.originalROWID!) ||
+ if (message.guid != null &&
+ (messages[i]!.originalROWID != null &&
+ message.originalROWID != null &&
+ message.originalROWID! > messages[i]!.originalROWID!) ||
((messages[i]!.originalROWID == null || message.originalROWID == null) &&
messages[i]!.dateCreated!.compareTo(message.dateCreated!) < 0)) {
_allMessages = linkedHashMapInsert(_allMessages, i, message.guid!, message);
@@ -227,25 +227,25 @@ class MessageBloc {
_allMessages.addAll({message.guid!: message});
}
- // print("ITEMS OG");
+ // Logger.instance.log("ITEMS OG");
// for (var i in _allMessages.values.toList()) {
- // print(i.guid);
+ // Logger.instance.log(i.guid);
// }
// for (var i in res ?? []) {
// Message tmp = Message.fromMap(i);
- // print("ADDING: ${tmp.guid}");
+ // Logger.instance.log("ADDING: ${tmp.guid}");
// if (!_allMessages.containsKey(tmp.guid)) {
// _allMessages.addAll({tmp.guid: message});
// }
// }
- // print("ITEMS AFTER");
+ // Logger.instance.log("ITEMS AFTER");
// for (var i in _allMessages.values.toList()) {
- // print("TEXT: ${i.text}");
+ // Logger.instance.log("TEXT: ${i.text}");
// }
- // print(_allMessages.length);
+ // Logger.instance.log(_allMessages.length);
this.emitLoaded();
}
@@ -278,10 +278,10 @@ class MessageBloc {
// Handle the messages
if (isNullOrEmpty(_messages)!) {
- debugPrint("(CHUNK) No message chunks left from server");
+ Logger.info("No message chunks left from server", tag: "MessageBloc");
completer.complete(LoadMessageResult.RETREIVED_NO_MESSAGES);
} else {
- debugPrint("(CHUNK) Received ${_messages.length} messages from socket");
+ Logger.info("Received ${_messages.length} messages from socket", tag: "MessageBloc");
messages = await MessageHelper.bulkAddMessages(_currentChat, _messages,
notifyMessageManager: false, notifyForNewMessage: false, checkForLatestMessageText: false);
@@ -293,14 +293,14 @@ class MessageBloc {
}
}
} catch (ex) {
- debugPrint("(CHUNK) Failed to load message chunk!");
- debugPrint(ex.toString());
+ Logger.error("Failed to load message chunk!", tag: "MessageBloc");
+ Logger.error(ex.toString());
completer.complete(LoadMessageResult.FAILED_TO_RETREIVE);
}
}
// Save the messages to the bloc
- debugPrint("(CHUNK) Emitting ${messages.length} messages to listeners");
+ Logger.info("Emitting ${messages.length} messages to listeners", tag: "MessageBloc");
for (Message element in messages) {
if (element.associatedMessageGuid == null && element.guid != null) {
_allMessages.addAll({element.guid!: element});
@@ -323,7 +323,7 @@ class MessageBloc {
completer.complete(LoadMessageResult.RETREIVED_MESSAGES);
}
} else {
- debugPrint("(CHUNK) Failed to load message chunk! Unknown chat!");
+ Logger.error(" Failed to load message chunk! Unknown chat!", tag: "MessageBloc");
completer.complete(LoadMessageResult.FAILED_TO_RETREIVE);
}
diff --git a/lib/blocs/setup_bloc.dart b/lib/blocs/setup_bloc.dart
index 67af85ff7..039b1b74b 100644
--- a/lib/blocs/setup_bloc.dart
+++ b/lib/blocs/setup_bloc.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:bluebubbles/blocs/chat_bloc.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/message_helper.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/contact_manager.dart';
@@ -9,7 +10,6 @@ import 'package:bluebubbles/repository/models/chat.dart';
import 'package:bluebubbles/repository/models/fcm_data.dart';
import 'package:bluebubbles/repository/models/settings.dart';
import 'package:bluebubbles/socket_manager.dart';
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
enum SetupOutputType { ERROR, LOG }
@@ -56,7 +56,7 @@ class SetupBloc {
Future connectToServer(FCMData data, String serverURL, String password) async {
Settings settingsCopy = SettingsManager().settings;
if (SocketManager().state.value == SocketState.CONNECTED && settingsCopy.serverAddress.value == serverURL) {
- debugPrint("Not reconnecting to server we are already connected to!");
+ Logger.warn("Not reconnecting to server we are already connected to!");
return;
}
@@ -143,13 +143,26 @@ class SetupBloc {
} else {
try {
if (!(chat.chatIdentifier ?? "").startsWith("urn:biz")) {
- await chat.save();
-
- // Re-match the handles with the contacts
- await ContactManager().matchHandles();
-
- await syncChat(chat);
- addOutput("Finished syncing chat, '${chat.chatIdentifier}'", SetupOutputType.LOG);
+ Map params = Map();
+ params["identifier"] = chat.guid;
+ params["withBlurhash"] = false;
+ params["limit"] = numberOfMessagesPerPage.round();
+ params["where"] = [
+ {"statement": "message.service = 'iMessage'", "args": null}
+ ];
+ List messages = await SocketManager().getChatMessages(params)!;
+ addOutput("Received ${messages.length} messages for chat, '${chat.chatIdentifier}'!", SetupOutputType.LOG);
+ if (!skipEmptyChats || (skipEmptyChats && messages.length > 0)) {
+ await chat.save();
+
+ // Re-match the handles with the contacts
+ await ContactManager().matchHandles();
+
+ await syncChat(chat, messages);
+ addOutput("Finished syncing chat, '${chat.chatIdentifier}'", SetupOutputType.LOG);
+ } else {
+ addOutput("Skipping syncing chat (empty chat), '${chat.chatIdentifier}'", SetupOutputType.LOG);
+ }
} else {
addOutput("Skipping syncing chat, '${chat.chatIdentifier}'", SetupOutputType.LOG);
}
@@ -184,18 +197,7 @@ class SetupBloc {
this.startIncrementalSync(settings);
}
- Future syncChat(Chat chat) async {
- Map params = Map();
- params["identifier"] = chat.guid;
- params["withBlurhash"] = false;
- params["limit"] = numberOfMessagesPerPage.round();
- params["where"] = [
- {"statement": "message.service = 'iMessage'", "args": null}
- ];
-
- List messages = await SocketManager().getChatMessages(params)!;
- addOutput("Received ${messages.length} messages for chat, '${chat.chatIdentifier}'!", SetupOutputType.LOG);
-
+ Future syncChat(Chat chat, List messages) async {
// Since we got the messages in desc order, we want to reverse it.
// Reversing it will add older messages before newer one. This should help fix
// issues with associated message GUIDs
@@ -226,7 +228,7 @@ class SetupBloc {
}
void addOutput(String _output, SetupOutputType type) {
- debugPrint('[Setup] -> $_output');
+ Logger.info(_output, tag: "Setup");
output.add(SetupOutputData(_output, type));
data.value = SetupData(_progress, output);
}
@@ -235,7 +237,8 @@ class SetupBloc {
{String? chatGuid, bool saveDate = true, Function? onConnectionError, Function? onComplete}) async {
// If we are already syncing, don't sync again
// Or, if we haven't finished setup, or we aren't connected, don't sync
- if (isSyncing.value || !settings.finishedSetup.value || SocketManager().state.value != SocketState.CONNECTED) return;
+ if (isSyncing.value || !settings.finishedSetup.value || SocketManager().state.value != SocketState.CONNECTED)
+ return;
// Reset the progress
_progress = 0;
@@ -252,39 +255,45 @@ class SetupBloc {
int syncStart = DateTime.now().millisecondsSinceEpoch;
await Future.delayed(Duration(seconds: 3));
- // Build request params. We want all details on the messages
- Map params = Map();
- if (chatGuid != null) {
- params["chatGuid"] = chatGuid;
- }
+ // only get up to 1000 messages (arbitrary limit)
+ int batches = 10;
+ for (int i = 0; i < batches; i++) {
+ // Build request params. We want all details on the messages
+ Map params = Map();
+ if (chatGuid != null) {
+ params["chatGuid"] = chatGuid;
+ }
- params["withBlurhash"] = false; // Maybe we want it?
- params["limit"] = 1000; // This is arbitrary, hopefully there aren't more messages
- params["after"] = settings.lastIncrementalSync.value; // Get everything since the last sync
- params["withChats"] = true; // We want the chats too so we can save them correctly
- params["withAttachments"] = true; // We want the attachment data
- params["withHandle"] = true; // We want to know who sent it
- params["sort"] = "DESC"; // Sort my DESC so we receive the newest messages first
- params["where"] = [
- {"statement": "message.service = 'iMessage'", "args": null}
- ];
-
- List messages = await SocketManager().getMessages(params)!;
- if (messages.isEmpty) {
- addOutput("No new messages found during incremental sync", SetupOutputType.LOG);
- } else {
- addOutput("Incremental sync found ${messages.length} messages. Syncing...", SetupOutputType.LOG);
- }
+ params["withBlurhash"] = false; // Maybe we want it?
+ params["limit"] = 100;
+ params["offset"] = i * batches;
+ params["after"] = settings.lastIncrementalSync.value; // Get everything since the last sync
+ params["withChats"] = true; // We want the chats too so we can save them correctly
+ params["withAttachments"] = true; // We want the attachment data
+ params["withHandle"] = true; // We want to know who sent it
+ params["sort"] = "DESC"; // Sort my DESC so we receive the newest messages first
+ params["where"] = [
+ {"statement": "message.service = 'iMessage'", "args": null}
+ ];
+
+ List messages = await SocketManager().getMessages(params)!;
+ if (messages.isEmpty) {
+ addOutput("No more new messages found during incremental sync", SetupOutputType.LOG);
+ break;
+ } else {
+ addOutput("Incremental sync found ${messages.length} messages. Syncing...", SetupOutputType.LOG);
+ }
- if (messages.length > 0) {
- await MessageHelper.bulkAddMessages(null, messages, onProgress: (progress, total) {
- _progress = (progress / total) * 100;
- data.value = SetupData(_progress, output);
- });
+ if (messages.length > 0) {
+ await MessageHelper.bulkAddMessages(null, messages, onProgress: (progress, total) {
+ _progress = (progress / total) * 100;
+ data.value = SetupData(_progress, output);
+ });
- // If we want to download the attachments, do it, and wait for them to finish before continuing
- if (downloadAttachments) {
- await MessageHelper.bulkDownloadAttachments(null, messages.reversed.toList());
+ // If we want to download the attachments, do it, and wait for them to finish before continuing
+ if (downloadAttachments) {
+ await MessageHelper.bulkDownloadAttachments(null, messages.reversed.toList());
+ }
}
}
diff --git a/lib/helpers/attachment_downloader.dart b/lib/helpers/attachment_downloader.dart
index 8c4be15ab..9fe619c02 100644
--- a/lib/helpers/attachment_downloader.dart
+++ b/lib/helpers/attachment_downloader.dart
@@ -1,11 +1,11 @@
import 'dart:io';
import 'package:bluebubbles/helpers/attachment_helper.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/managers/settings_manager.dart';
import 'package:bluebubbles/repository/models/attachment.dart';
import 'package:bluebubbles/socket_manager.dart';
import 'package:collection/collection.dart';
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AttachmentDownloadService extends GetxService {
@@ -59,7 +59,7 @@ class AttachmentDownloadController extends GetxController {
if (attachment.guid == null) return;
isFetching = true;
int numOfChunks = (attachment.totalBytes! / chunkSize).ceil();
- debugPrint("Fetching $numOfChunks attachment chunks");
+ Logger.info("Fetching $numOfChunks attachment chunks");
stopwatch.start();
getChunkRecursive(attachment.guid!, 0, numOfChunks, []);
}
@@ -91,7 +91,7 @@ class AttachmentDownloadController extends GetxController {
if (numBytes == chunkSize) {
// Calculate some stats
double progress = ((index + 1) / total).clamp(0, 1).toDouble();
- debugPrint("Progress: ${(progress * 100).round()}% of the attachment");
+ Logger.info("Progress: ${(progress * 100).round()}% of the attachment");
// Update the progress in stream
setProgress(progress);
@@ -99,9 +99,9 @@ class AttachmentDownloadController extends GetxController {
// Get the next chunk
getChunkRecursive(guid, index + 1, total, currentBytes);
} else {
- debugPrint("Finished fetching attachment");
+ Logger.info("Finished fetching attachment");
stopwatch.stop();
- debugPrint("Attachment downloaded in ${stopwatch.elapsedMilliseconds} ms");
+ Logger.info("Attachment downloaded in ${stopwatch.elapsedMilliseconds} ms");
try {
// Compress the attachment
diff --git a/lib/helpers/attachment_helper.dart b/lib/helpers/attachment_helper.dart
index 5cb263374..8f22ba08e 100644
--- a/lib/helpers/attachment_helper.dart
+++ b/lib/helpers/attachment_helper.dart
@@ -3,16 +3,19 @@ import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui';
+import 'package:bluebubbles/helpers/constants.dart';
+import 'package:bluebubbles/helpers/logger.dart';
+import 'package:bluebubbles/helpers/navigator.dart';
import 'package:bluebubbles/helpers/simple_vcard_parser.dart';
import 'package:contacts_service/contacts_service.dart';
import 'package:exif/exif.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter_native_image/flutter_native_image.dart';
import 'package:get/get.dart';
import 'package:bluebubbles/helpers/attachment_downloader.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/settings_manager.dart';
import 'package:bluebubbles/repository/models/attachment.dart';
-import 'package:bluebubbles/socket_manager.dart';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;
@@ -82,8 +85,8 @@ class AttachmentHelper {
latitude: double.tryParse(query.split(",")[1]), longitude: double.tryParse(query.split(",")[0]));
}
} catch (ex) {
- debugPrint("Failed to parse location!");
- debugPrint(ex.toString());
+ Logger.error("Failed to parse location!");
+ Logger.error(ex.toString());
return AppleLocation(latitude: null, longitude: null);
}
}
@@ -102,7 +105,7 @@ class AttachmentHelper {
// Parse emails from results
for (dynamic email in _contact.typedEmail) {
String label = "HOME";
- if (email.length > 1 && email[1].length > 0 && email[1][1] != null) {
+ if (email.length > 1 && email[1].length > 1 && email[1][1] != null) {
label = email[1][1] ?? label;
}
@@ -112,7 +115,7 @@ class AttachmentHelper {
// Parse phone numbers from results
for (dynamic phone in _contact.typedTelephone) {
String label = "HOME";
- if (phone.length > 1 && phone[1].length > 0 && phone[1][1] != null) {
+ if (phone.length > 1 && phone[1].length > 1 && phone[1][1] != null) {
label = phone[1][1] ?? label;
}
@@ -127,7 +130,7 @@ class AttachmentHelper {
String country = address[0].length > 3 ? address[0][3] : '';
String label = "HOME";
- if (address.length > 1 && address[1].length > 0 && address[1][1] != null) {
+ if (address.length > 1 && address[1].length > 1 && address[1][1] != null) {
label = address[1][1] ?? label;
}
@@ -166,7 +169,7 @@ class AttachmentHelper {
double width = attachment.width?.toDouble() ?? 0.0;
double factor = attachment.height?.toDouble() ?? 0.0;
if (attachment.width == null || attachment.width == 0 || attachment.height == null || attachment.height == 0) {
- width = context.width;
+ width = CustomNavigator.width(context);
factor = 2;
}
@@ -234,21 +237,21 @@ class AttachmentHelper {
}
static IconData getIcon(String mimeType) {
- if (mimeType.isEmpty) return Icons.open_in_new;
+ if (mimeType.isEmpty) return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.arrow_up_right_square : Icons.open_in_new;
if (mimeType == "application/pdf") {
- return Icons.picture_as_pdf;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.doc_on_doc : Icons.picture_as_pdf;
} else if (mimeType == "application/zip") {
- return Icons.folder;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.folder : Icons.folder;
} else if (mimeType.startsWith("audio")) {
- return Icons.music_note;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.music_note : Icons.music_note;
} else if (mimeType.startsWith("image")) {
- return Icons.photo;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.photo : Icons.photo;
} else if (mimeType.startsWith("video")) {
- return Icons.videocam;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.videocam : Icons.videocam;
} else if (mimeType.startsWith("text")) {
- return Icons.note;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.doc_text : Icons.note;
}
- return Icons.open_in_new;
+ return SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.arrow_up_right_square : Icons.open_in_new;
}
static Future canAutoDownload() async {
@@ -276,7 +279,8 @@ class AttachmentHelper {
if (cExists) compressedFile.deleteSync();
// Redownload the attachment
- Get.put(AttachmentDownloadController(attachment: attachment, onComplete: onComplete, onError: onError), tag: attachment.guid);
+ Get.put(AttachmentDownloadController(attachment: attachment, onComplete: onComplete, onError: onError),
+ tag: attachment.guid);
}
static Future getVideoThumbnail(String filePath) async {
@@ -396,7 +400,7 @@ class AttachmentHelper {
attachment.height = size.height.toInt();
}
} catch (ex) {
- debugPrint('Failed to get GIF dimensions! Error: ${ex.toString()}');
+ Logger.error('Failed to get GIF dimensions! Error: ${ex.toString()}');
}
} else if (mimeStart == "image") {
// For images, load properties
@@ -415,7 +419,7 @@ class AttachmentHelper {
attachment.metadata!['orientation'] = 'portrait';
}
} catch (ex) {
- debugPrint('Failed to get Image Properties! Error: ${ex.toString()}');
+ Logger.error('Failed to get Image Properties! Error: ${ex.toString()}');
}
} else if (mimeStart == "video") {
// For videos, load the thumbnail
@@ -427,7 +431,7 @@ class AttachmentHelper {
attachment.height = size.height.toInt();
}
} catch (ex) {
- debugPrint('Failed to get video thumbnail! Error: ${ex.toString()}');
+ Logger.error('Failed to get video thumbnail! Error: ${ex.toString()}');
}
}
@@ -438,14 +442,14 @@ class AttachmentHelper {
attachment.metadata![item.key] = item.value.printable;
}
} catch (ex) {
- debugPrint('Failed to read EXIF data: ${ex.toString()}');
+ Logger.error('Failed to read EXIF data: ${ex.toString()}');
}
bool usedFallback = false;
// If the preview data is null, compress the file
if (previewData == null) {
// Compress the file
ReceivePort receivePort = ReceivePort();
- debugPrint("Spawning isolate...");
+ Logger.info("Spawning isolate...");
// if we don't have a valid width use the max image width
// if the image width is less than the max width already don't bother
// compressing it because it is already low quality
@@ -463,13 +467,13 @@ class AttachmentHelper {
}
}
await Isolate.spawn(
- resizeIsolate,
- ResizeArgs(filePath, receivePort.sendPort, compressWidth),
- errorsAreFatal: false,
+ resizeIsolate,
+ ResizeArgs(filePath, receivePort.sendPort, compressWidth),
+ errorsAreFatal: false,
);
var received = await receivePort.first;
- debugPrint("Compressing via ${received is String ? "FlutterNativeImage" : "image"} plugin");
+ Logger.info("Compressing via ${received is String ? "FlutterNativeImage" : "image"} plugin");
if (received is String) {
File compressedFile = await FlutterNativeImage.compressImage(filePath,
quality: quality,
@@ -483,7 +487,7 @@ class AttachmentHelper {
try {
previewData = Uint8List.fromList(img.encodeNamedImage(received as img.Image, filePath.split("/").last) ?? []);
} catch (e) {
- debugPrint("Compression via image plugin failed, using fallback...");
+ Logger.info("Compression via image plugin failed, using fallback...");
File compressedFile = await FlutterNativeImage.compressImage(filePath,
quality: quality,
targetWidth: attachment.width == null ? 0 : attachment.width!,
@@ -496,7 +500,7 @@ class AttachmentHelper {
}
}
if (previewData.isEmpty && !usedFallback) {
- debugPrint("Compression via image plugin failed, using fallback...");
+ Logger.info("Compression via image plugin failed, using fallback...");
File compressedFile = await FlutterNativeImage.compressImage(filePath,
quality: quality,
targetWidth: attachment.width == null ? 0 : attachment.width!,
@@ -506,7 +510,7 @@ class AttachmentHelper {
previewData = await compressedFile.readAsBytes();
usedFallback = true;
}
- debugPrint("Got previewData: ${previewData.isNotEmpty}");
+ Logger.info("Got previewData: ${previewData.isNotEmpty}");
// As long as we have preview data now, save it
cachedFile.writeAsBytes(previewData);
@@ -519,9 +523,9 @@ class AttachmentHelper {
static void resizeIsolate(ResizeArgs args) {
try {
- debugPrint("Decoding image...");
+ Logger.info("Decoding image...");
img.Image image = img.decodeImage(File(args.path).readAsBytesSync())!;
- debugPrint("Resizing image...");
+ Logger.info("Resizing image...");
img.Image resized = img.copyResize(image, width: args.width);
args.sendPort.send(resized);
} catch (e) {
diff --git a/lib/helpers/attachment_sender.dart b/lib/helpers/attachment_sender.dart
index b3aab5a83..7b6983b74 100644
--- a/lib/helpers/attachment_sender.dart
+++ b/lib/helpers/attachment_sender.dart
@@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:bluebubbles/helpers/attachment_helper.dart';
import 'package:bluebubbles/helpers/darty.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/new_message_manager.dart';
import 'package:bluebubbles/managers/settings_manager.dart';
@@ -12,7 +13,6 @@ import 'package:bluebubbles/repository/models/attachment.dart';
import 'package:bluebubbles/repository/models/chat.dart';
import 'package:bluebubbles/repository/models/message.dart';
import 'package:bluebubbles/socket_manager.dart';
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mime_type/mime_type.dart';
import 'package:path/path.dart';
@@ -53,7 +53,7 @@ class AttachmentSender {
}
// resumeChunkingAfterDisconnect() {
- // debugPrint("restarting chunking");
+ // Logger.instance.log("restarting chunking");
// sendChunkRecursive(_guid, _currentchunk, _totalchunks, _currentbytes,
// _chunksize * 1024, _cb);
// }
@@ -74,10 +74,7 @@ class AttachmentSender {
params["hasMore"] = index + _chunkSize < _imageBytes.length;
params["attachmentName"] = _attachmentName;
params["attachmentData"] = base64Encode(chunk);
- debugPrint(chunk.length.toString() + "/" + _imageBytes.length.toString());
- if (index == 0) {
- debugPrint("(Sigabrt) Before sending first chunk");
- }
+ Logger.info(chunk.length.toString() + "/" + _imageBytes.length.toString());
SocketManager().sendMessage("send-message-chunk", params, (data) async {
Map response = data;
if (response['status'] == 200) {
@@ -91,7 +88,8 @@ class AttachmentSender {
SocketManager().finishSender(_attachmentGuid);
}
} else {
- debugPrint("failed to send");
+ Logger.error("Failed to sendattachment");
+
String? tempGuid = sentMessage!.guid;
sentMessage!.guid = sentMessage!.guid!.replaceAll("temp", "error-${response['error']['message']}");
sentMessage!.error.value =
@@ -157,10 +155,8 @@ class AttachmentSender {
// Save the attachment to device
String appDocPath = SettingsManager().appDocDir.path;
String pathName = "$appDocPath/attachments/${messageAttachment!.guid}/$_attachmentName";
- debugPrint("(Sigabrt) Before saving to device");
File file = await new File(pathName).create(recursive: true);
await file.writeAsBytes(Uint8List.fromList(_imageBytes));
- debugPrint("(Sigabrt) After saving to device");
// Add the message to the chat.
// This will save the message, attachments, and chat
@@ -175,7 +171,6 @@ class AttachmentSender {
_totalChunks = numOfChunks;
SocketManager().addAttachmentSender(this);
- debugPrint("(Sigabrt) Before sending first chunk");
sendChunkRecursive(0, _totalChunks, messageWithText == null ? "temp-${randomString(8)}" : messageWithText!.guid);
}
}
diff --git a/lib/helpers/logger.dart b/lib/helpers/logger.dart
new file mode 100644
index 000000000..ec74d5de3
--- /dev/null
+++ b/lib/helpers/logger.dart
@@ -0,0 +1,126 @@
+import 'dart:io';
+
+import 'package:bluebubbles/helpers/share.dart';
+import 'package:bluebubbles/helpers/utils.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+// ignore: non_constant_identifier_names
+BaseLogger Logger = Get.isRegistered() ? Get.find() : Get.put(BaseLogger());
+
+enum LogLevel { INFO, WARN, ERROR, DEBUG }
+
+extension LogLevelExtension on LogLevel {
+ String get value {
+ String self = this.toString();
+ return self.substring(self.indexOf('.') + 1).toUpperCase();
+ }
+}
+
+class BaseLogger extends GetxService {
+ final RxBool saveLogs = false.obs;
+ final int lineLimit = 5000;
+ List logs = [];
+ List enabledLevels = [LogLevel.INFO, LogLevel.WARN, LogLevel.DEBUG, LogLevel.ERROR];
+
+ String get logPath {
+ String directoryPath = "/storage/emulated/0/Download/BlueBubbles-log-";
+ DateTime now = DateTime.now().toLocal();
+ return directoryPath + "${now.year}${now.month}${now.day}_${now.hour}${now.minute}${now.second}" + ".txt";
+ }
+
+ set setEnabledLevels(List levels) => this.enabledLevels = levels;
+
+ void startSavingLogs() {
+ this.saveLogs.value = true;
+ }
+
+ Future stopSavingLogs() async {
+ this.saveLogs.value = false;
+
+ // Write the log to a file so the user can view/share it
+ await this.writeLogToFile();
+
+ // Clear the logs
+ this.logs.clear();
+ }
+
+ Future writeLogToFile() async {
+ // Create the log file and write to it
+ String filePath = this.logPath;
+ File file = File(filePath);
+ await file.create(recursive: true);
+ await file.writeAsString(logs.join('\n'));
+
+ // Show the snackbar when finished
+ showSnackbar(
+ "Success",
+ "Logs exported successfully to downloads folder",
+ durationMs: 2500,
+ button: TextButton(
+ style: TextButton.styleFrom(
+ backgroundColor: Get.theme.accentColor,
+ ),
+ onPressed: () {
+ Share.file("BlueBubbles Logs", filePath);
+ },
+ child: Text("SHARE", style: TextStyle(color: Theme.of(Get.context!).primaryColor)),
+ ),
+ );
+ }
+
+ void info(dynamic log, {String? tag}) => this._log(LogLevel.INFO, log, tag: tag);
+ void warn(dynamic log, {String? tag}) => this._log(LogLevel.WARN, log, tag: tag);
+ void debug(dynamic log, {String? tag}) => this._log(LogLevel.DEBUG, log, tag: tag);
+ void error(dynamic log, {String? tag}) => this._log(LogLevel.ERROR, log, tag: tag);
+
+ void _log(LogLevel level, dynamic log, {String name = "BlueBubblesApp", String? tag}) {
+ if (!this.enabledLevels.contains(level)) return;
+
+ try {
+ // Example: [BlueBubblesApp][INFO][2021-01-01 01:01:01.000] (Some Tag) ->
+ String theLog = this._buildLog(level, name, tag, log);
+
+ // Log the data normally
+ debugPrint(theLog);
+
+ // If we aren't saving logs, return here
+ if (!this.saveLogs.value) return;
+
+ // Otherwise, add the log to the list
+ logs.add(theLog);
+
+ // Make sure we concatenate to our limit
+ if (this.logs.length >= this.lineLimit) {
+ // Be safe with it. Make sure we don't go negative or the ranges max < min
+ int min = this.logs.length - this.lineLimit;
+ int max = this.logs.length;
+ if (min < 0) min = 0;
+ if (max < min) max = min;
+
+ // Take the last x amount of logs (based on the line limit)
+ this.logs = this.logs.sublist(min, max);
+ }
+ } catch (ex, stacktrace) {
+ debugPrint("Failed to write log! ${ex.toString()}");
+ debugPrint(stacktrace.toString());
+ }
+ }
+
+ String _buildLog(LogLevel level, String name, String? tag, dynamic log) {
+ final time = DateTime.now().toLocal().toString();
+ String theLog = "[$time][${level.value}]";
+
+ // If we have a name, add the name
+ if (name.isNotEmpty) {
+ theLog = "[$name]$theLog";
+ }
+
+ // If we have a tag, add it before the log string
+ if (tag != null && tag.isNotEmpty) {
+ theLog = "$theLog ($tag) ->";
+ }
+
+ return "$theLog ${log.toString()}";
+ }
+}
diff --git a/lib/helpers/message_helper.dart b/lib/helpers/message_helper.dart
index f1d4ec6f5..a264f0a4c 100644
--- a/lib/helpers/message_helper.dart
+++ b/lib/helpers/message_helper.dart
@@ -1,7 +1,7 @@
import 'dart:async';
-import 'dart:math';
import 'package:bluebubbles/helpers/attachment_downloader.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/contact_manager.dart';
import 'package:bluebubbles/managers/current_chat.dart';
@@ -104,9 +104,9 @@ class MessageHelper {
// Every 50 messages synced, who a message
index += 1;
if (index % 50 == 0) {
- debugPrint('[Bulk Ingest] Saved $index of ${messages.length} messages');
+ Logger.info('Saved $index of ${messages.length} messages', tag: "BulkIngest");
} else if (index == messages.length) {
- debugPrint('[Bulk Ingest] Saved ${messages.length} messages');
+ Logger.info('Saved ${messages.length} messages', tag: "BulkIngest");
}
}
@@ -212,12 +212,14 @@ class MessageHelper {
// Handle all the cases that would mean we don't show the notification
if (!SettingsManager().settings.finishedSetup.value) return; // Don't notify if not fully setup
if (existingMessage != null) return;
- if (chat.isMuted!) return; // Don''t notify if the chat is muted
+ if (await chat.shouldMuteNotification(message)) return; // Don''t notify if the chat is muted
if (message.isFromMe! || message.handle == null) return; // Don't notify if the text is from me
CurrentChat? currChat = CurrentChat.activeChat;
if (LifeCycleManager().isAlive &&
- ((!SettingsManager().settings.notifyOnChatList.value && currChat == null) ||
+ ((!SettingsManager().settings.notifyOnChatList.value &&
+ currChat == null &&
+ !Get.currentRoute.contains("settings")) ||
currChat?.chat.guid == chat.guid)) {
// Don't notify if the the chat is the active chat
return;
diff --git a/lib/helpers/metadata_helper.dart b/lib/helpers/metadata_helper.dart
index c26d8c924..462d15ca6 100644
--- a/lib/helpers/metadata_helper.dart
+++ b/lib/helpers/metadata_helper.dart
@@ -2,9 +2,9 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/repository/models/message.dart';
-import 'package:flutter/cupertino.dart';
import 'package:html/dom.dart';
import 'package:html/parser.dart' as parser;
import 'package:http/http.dart' as http;
@@ -136,7 +136,7 @@ class MetadataHelper {
try {
data = await MetadataFetch.extract(url);
} catch (ex) {
- debugPrint('An error occurred while fetching URL Preview Metadata: ${ex.toString()}');
+ Logger.error('An error occurred while fetching URL Preview Metadata: ${ex.toString()}');
}
}
@@ -202,7 +202,7 @@ class MetadataHelper {
document = parser.parse(response.body.toString());
document.requestUrl = response.request!.url.toString();
} catch (err) {
- debugPrint("Error parsing HTML document: ${err.toString()}");
+ Logger.error("Error parsing HTML document: ${err.toString()}");
return document;
}
@@ -240,7 +240,7 @@ class MetadataHelper {
meta.title = 'Invalid SSL Certificate';
meta.description = ex.message;
} catch (ex) {
- debugPrint('Failed to manually get metadata: ${ex.toString()}');
+ Logger.error('Failed to manually get metadata: ${ex.toString()}');
}
return meta;
diff --git a/lib/helpers/navigator.dart b/lib/helpers/navigator.dart
new file mode 100644
index 000000000..0288eb653
--- /dev/null
+++ b/lib/helpers/navigator.dart
@@ -0,0 +1,108 @@
+import 'package:bluebubbles/layouts/widgets/theme_switcher/theme_switcher.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+// ignore: non_constant_identifier_names
+BaseNavigator CustomNavigator = Get.isRegistered() ? Get.find() : Get.put(BaseNavigator());
+
+/// Handles navigation for the app
+class BaseNavigator extends GetxService {
+ /// width of left side of split screen view
+ double? _widthChatListLeft;
+ /// width of right side of split screen view
+ double? _widthChatListRight;
+ /// width of settings right side split screen
+ double? _widthSettings;
+
+ set maxWidthLeft(double w) => _widthChatListLeft = w;
+ set maxWidthRight(double w) => _widthChatListRight = w;
+ set maxWidthSettings(double w) => _widthSettings = w;
+
+ /// grab the available screen width, returning the split screen width if applicable
+ /// this should *always* be used in place of context.width or similar
+ double width(BuildContext context) {
+ if (Navigator.of(context).widget.key?.toString().contains("Getx nested key: 1") ?? false) {
+ return _widthChatListLeft ?? context.width;
+ } else if (Navigator.of(context).widget.key?.toString().contains("Getx nested key: 2") ?? false) {
+ return _widthChatListRight ?? context.width;
+ } else if (Navigator.of(context).widget.key?.toString().contains("Getx nested key: 3") ?? false) {
+ return _widthSettings ?? context.width;
+ }
+ return context.width;
+ }
+
+ /// Push a new route onto the chat list right side navigator
+ void push(BuildContext context, Widget widget) {
+ if (Get.keys.containsKey(2) && (!context.isPhone || context.isLandscape)) {
+ Get.to(() => widget, transition: Transition.rightToLeft, id: 2);
+ } else {
+ Navigator.of(context).push(ThemeSwitcher.buildPageRoute(
+ builder: (BuildContext context) => widget,
+ ));
+ }
+ }
+
+ /// Push a new route onto the chat list left side navigator
+ void pushLeft(BuildContext context, Widget widget) {
+ if (Get.keys.containsKey(1) && (!context.isPhone || context.isLandscape)) {
+ Get.to(() => widget, transition: Transition.leftToRight, id: 1);
+ } else {
+ Navigator.of(context).push(ThemeSwitcher.buildPageRoute(
+ builder: (BuildContext context) => widget,
+ ));
+ }
+ }
+
+ /// Push a new route onto the settings navigator
+ void pushSettings(BuildContext context, Widget widget, {Bindings? binding}) {
+ if (Get.keys.containsKey(3) && (!context.isPhone || context.isLandscape)) {
+ Get.to(() => widget, transition: Transition.rightToLeft, id: 3, binding: binding);
+ } else {
+ binding?.dependencies();
+ Navigator.of(context).push(ThemeSwitcher.buildPageRoute(
+ builder: (BuildContext context) => widget,
+ ));
+ }
+ }
+
+ /// Push a new route, popping all previous routes, on the chat list right side navigator
+ void pushAndRemoveUntil(BuildContext context, Widget widget, bool Function(Route) predicate) {
+ if (Get.keys.containsKey(2) && (!context.isPhone || context.isLandscape)) {
+ Get.offUntil(GetPageRoute(
+ page: () => widget,
+ transition: Transition.noTransition
+ ), predicate, id: 2);
+ } else {
+ Navigator.of(context).pushAndRemoveUntil(ThemeSwitcher.buildPageRoute(
+ builder: (BuildContext context) => widget,
+ ), predicate);
+ }
+ }
+
+ /// Push a new route, popping all previous routes, on the settings navigator
+ void pushAndRemoveSettingsUntil(BuildContext context, Widget widget, bool Function(Route) predicate, {Bindings? binding}) {
+ if (Get.keys.containsKey(3) && (!context.isPhone || context.isLandscape)) {
+ // we only want to offUntil when in landscape, otherwise when the user presses back, the previous page will be the chat list
+ Get.offUntil(GetPageRoute(
+ page: () => widget,
+ binding: binding,
+ transition: Transition.noTransition
+ ), predicate, id: 3);
+ } else {
+ binding?.dependencies();
+ // only push here because we don't want to remove underlying routes when in portrait
+ Navigator.of(context).push(ThemeSwitcher.buildPageRoute(
+ builder: (BuildContext context) => widget,
+ ));
+ }
+ }
+
+ void backSettingsCloseOverlays(BuildContext context) {
+ if (Get.keys.containsKey(3) && (!context.isPhone || context.isLandscape)) {
+ Get.back(closeOverlays: true, id: 3);
+ } else {
+ Get.back(closeOverlays: true);
+ }
+ }
+}
diff --git a/lib/helpers/reaction.dart b/lib/helpers/reaction.dart
index f49af7a4e..2c18592c1 100644
--- a/lib/helpers/reaction.dart
+++ b/lib/helpers/reaction.dart
@@ -104,8 +104,8 @@ class Reaction {
reactionList.add(
Padding(
padding: EdgeInsets.fromLTRB(
- (this.messages[i].isFromMe! && isReactionPicker ? 5.0 : 0.0) + i.toDouble() * 10.0,
- bigPin ? 0 : 1.0,
+ (this.messages[i].isFromMe! && !isReactionPicker ? 5.0 : 0.0) + i.toDouble() * 10.0,
+ bigPin || isReactionPicker ? 0 : 1.0,
0,
0,
),
diff --git a/lib/helpers/share.dart b/lib/helpers/share.dart
index 6117a1449..1418ca493 100644
--- a/lib/helpers/share.dart
+++ b/lib/helpers/share.dart
@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:bluebubbles/helpers/attachment_helper.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/managers/method_channel_interface.dart';
import 'package:bluebubbles/managers/new_message_manager.dart';
@@ -10,7 +11,6 @@ import 'package:bluebubbles/repository/models/attachment.dart';
import 'package:bluebubbles/repository/models/chat.dart';
import 'package:bluebubbles/repository/models/message.dart';
import 'package:bluebubbles/socket_manager.dart';
-import 'package:flutter/widgets.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart' as sp;
@@ -33,7 +33,7 @@ class Share {
final result = await MethodChannelInterface().invokeMethod("get-last-location");
if (result == null) {
- debugPrint("Failed to load last location!");
+ Logger.error("Failed to load last location!");
return;
}
diff --git a/lib/helpers/simple_vcard_parser.dart b/lib/helpers/simple_vcard_parser.dart
index 3de1932e5..40d2809ef 100644
--- a/lib/helpers/simple_vcard_parser.dart
+++ b/lib/helpers/simple_vcard_parser.dart
@@ -1,5 +1,7 @@
import 'dart:convert';
+import 'package:bluebubbles/helpers/logger.dart';
+
class VCard {
String? _vCardString;
late List lines;
@@ -33,10 +35,10 @@ class VCard {
void printLines() {
String s;
- print('lines #${lines.length}');
+ Logger.debug('lines #${lines.length}');
for (var i = 0; i < lines.length; i++) {
s = i.toString().padLeft(2, '0');
- print('$s | ${lines[i]}');
+ Logger.debug('$s | ${lines[i]}');
}
}
diff --git a/lib/helpers/themes.dart b/lib/helpers/themes.dart
index 6f83894dd..2c195cf2e 100644
--- a/lib/helpers/themes.dart
+++ b/lib/helpers/themes.dart
@@ -2,6 +2,7 @@ import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:bluebubbles/helpers/hex_color.dart';
import 'package:bluebubbles/repository/models/theme_object.dart';
import 'package:flutter/material.dart';
+import 'package:collection/src/iterable_extensions.dart';
enum DarkThemes {
OLED,
@@ -14,15 +15,16 @@ enum LightThemes {
class Themes {
static List get themes => [
- ThemeObject.fromData(oledDarkTheme, "OLED Dark", isPreset: true),
- ThemeObject.fromData(whiteLightTheme, "Bright White", isPreset: true),
- ThemeObject.fromData(nordDarkTheme, "Nord Theme", isPreset: true),
+ ThemeObject.fromData(oledDarkTheme, "OLED Dark"),
+ ThemeObject.fromData(whiteLightTheme, "Bright White"),
+ ThemeObject.fromData(nordDarkTheme, "Nord Theme"),
+ ThemeObject.fromData(whiteLightTheme, "Music Theme (Light)", gradientBg: true),
+ ThemeObject.fromData(oledDarkTheme, "Music Theme (Dark)", gradientBg: true),
];
}
bool isEqual(ThemeData one, ThemeData two) {
- return one.accentColor == two.accentColor
- && one.backgroundColor == two.backgroundColor;
+ return one.accentColor == two.accentColor && one.backgroundColor == two.backgroundColor;
}
ThemeData oledDarkTheme = ThemeData(
@@ -160,3 +162,33 @@ Future loadTheme(BuildContext? context, {ThemeObject? lightOverride, Theme
dark: dark.themeData,
);
}
+
+Future revertToPreviousDarkTheme() async {
+ List allThemes = await ThemeObject.getThemes();
+ ThemeObject? previous = allThemes.firstWhereOrNull((e) => e.previousDarkTheme);
+
+ if (previous == null) {
+ previous = Themes.themes.firstWhereOrNull((element) => element.name == "OLED Dark");
+ }
+
+ // Remove the previous flags
+ previous!.previousDarkTheme = false;
+
+ // Save the theme and set it accordingly
+ return await previous.save();
+}
+
+Future revertToPreviousLightTheme() async {
+ List allThemes = await ThemeObject.getThemes();
+ ThemeObject? previous = allThemes.firstWhereOrNull((e) => e.previousDarkTheme);
+
+ if (previous == null) {
+ previous = Themes.themes.firstWhereOrNull((element) => element.name == "Bright White");
+ }
+
+ // Remove the previous flags
+ previous!.previousDarkTheme = false;
+
+ // Save the theme and set it accordingly
+ return await previous.save();
+}
diff --git a/lib/helpers/ui_helpers.dart b/lib/helpers/ui_helpers.dart
index b3a409f67..a64507c86 100644
--- a/lib/helpers/ui_helpers.dart
+++ b/lib/helpers/ui_helpers.dart
@@ -12,14 +12,17 @@ Widget buildBackButton(BuildContext context,
padding: padding,
width: 25,
child: IconButton(
- iconSize: iconSize ?? 24,
+ iconSize: iconSize ?? (SettingsManager().settings.skin.value == Skins.iOS ? 30 : 24),
icon: skin != null
- ? Icon(skin == Skins.iOS ? Icons.arrow_back_ios : Icons.arrow_back, color: Theme.of(context).primaryColor)
- : Obx(() => Icon(SettingsManager().settings.skin.value == Skins.iOS ? Icons.arrow_back_ios : Icons.arrow_back,
+ ? Icon(skin == Skins.iOS ? CupertinoIcons.back : Icons.arrow_back, color: Theme.of(context).primaryColor)
+ : Obx(() => Icon(SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.back : Icons.arrow_back,
color: Theme.of(context).primaryColor)),
onPressed: () {
callback?.call();
- Get.back(closeOverlays: true);
+ while (Get.isOverlaysOpen) {
+ Get.back();
+ }
+ Navigator.of(context).pop();
},
),
);
diff --git a/lib/helpers/utils.dart b/lib/helpers/utils.dart
index 9fa6df9ed..a958968a6 100644
--- a/lib/helpers/utils.dart
+++ b/lib/helpers/utils.dart
@@ -9,6 +9,7 @@ import 'package:bluebubbles/helpers/attachment_helper.dart';
import 'package:bluebubbles/helpers/constants.dart';
import 'package:bluebubbles/helpers/country_codes.dart';
import 'package:bluebubbles/helpers/hex_color.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/layouts/conversation_view/conversation_view_mixin.dart';
import 'package:bluebubbles/layouts/widgets/message_widget/message_content/media_players/video_widget.dart';
import 'package:bluebubbles/managers/contact_manager.dart';
@@ -22,7 +23,7 @@ import 'package:collection/collection.dart';
import 'package:contacts_service/contacts_service.dart';
import 'package:convert/convert.dart';
import 'package:device_info/device_info.dart';
-import 'package:flutter/foundation.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_libphonenumber/flutter_libphonenumber.dart';
@@ -148,8 +149,8 @@ bool sameAddress(List options, String? compared) {
String getInitials(Contact contact) {
// Set default initials
- String initials = (contact.givenName!.isNotEmpty == true ? contact.givenName![0] : "") +
- (contact.familyName!.isNotEmpty == true ? contact.familyName![0] : "");
+ String initials = ((contact.givenName ?? "").isNotEmpty == true ? contact.givenName![0] : "") +
+ ((contact.familyName ?? "").isNotEmpty == true ? contact.familyName![0] : "");
// If the initials are empty, get them from the display name
if (initials.trim().isEmpty) {
@@ -238,7 +239,6 @@ String buildDate(DateTime? dateTime) {
}
String buildTime(DateTime? dateTime) {
- SettingsManager().settings.use24HrFormat.value = MediaQuery.of(Get.context!).alwaysUse24HourFormat;
if (dateTime == null || dateTime.millisecondsSinceEpoch == 0) return "";
String time = SettingsManager().settings.use24HrFormat.value
? intl.DateFormat.Hm().format(dateTime)
@@ -350,6 +350,8 @@ Future getGroupEventText(Message message) async {
text = "$handle left the conversation";
} else if (message.itemType == 2 && message.groupTitle != null) {
text = "$handle named the conversation \"${message.groupTitle}\"";
+ } else if (message.itemType == 6) {
+ text = "$handle started a FaceTime call";
}
return text;
@@ -455,8 +457,8 @@ Size getGifDimensions(Uint8List bytes) {
hexString += hex.encode(bytes.sublist(8, 9));
int height = int.parse(hexString, radix: 16);
- debugPrint("GIF width: $width");
- debugPrint("GIF height: $height");
+ Logger.debug("GIF width: $width");
+ Logger.debug("GIF height: $height");
Size size = new Size(width.toDouble(), height.toDouble());
return size;
}
@@ -509,8 +511,8 @@ Future getDeviceName() async {
deviceName = items.join("_").toLowerCase();
}
} catch (ex) {
- debugPrint("Failed to get device name! Defaulting to 'android-client'");
- debugPrint(ex.toString());
+ Logger.error("Failed to get device name! Defaulting to 'android-client'");
+ Logger.error(ex.toString());
}
// Fallback for if it happens to be empty or null, somehow... idk
diff --git a/lib/layouts/conversation_details/attachment_details_card.dart b/lib/layouts/conversation_details/attachment_details_card.dart
index c02cc3173..ee7db9aeb 100644
--- a/lib/layouts/conversation_details/attachment_details_card.dart
+++ b/lib/layouts/conversation_details/attachment_details_card.dart
@@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
+import 'package:bluebubbles/helpers/constants.dart';
+import 'package:bluebubbles/helpers/navigator.dart';
import 'package:get/get.dart';
import 'package:bluebubbles/helpers/attachment_downloader.dart';
import 'package:bluebubbles/helpers/attachment_helper.dart';
@@ -12,11 +14,9 @@ import 'package:bluebubbles/layouts/widgets/theme_switcher/theme_switcher.dart';
import 'package:bluebubbles/managers/current_chat.dart';
import 'package:bluebubbles/managers/settings_manager.dart';
import 'package:bluebubbles/repository/models/attachment.dart';
-import 'package:bluebubbles/socket_manager.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart';
-import 'package:tuple/tuple.dart';
class AttachmentDetailsCard extends StatefulWidget {
AttachmentDetailsCard({Key? key, required this.attachment, required this.allAttachments}) : super(key: key);
@@ -81,7 +81,7 @@ class _AttachmentDetailsCardState extends State {
widget.attachment.getFriendlySize(),
style: Theme.of(context).textTheme.bodyText1,
),
- Icon(Icons.cloud_download, size: 28.0),
+ Icon(SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.cloud_download : Icons.cloud_download, size: 28.0),
(widget.attachment.mimeType != null)
? Text(
basename(this.attachmentFile.path),
@@ -94,7 +94,7 @@ class _AttachmentDetailsCardState extends State {
}
Widget buildPreview(BuildContext context) => SizedBox(
- width: context.width / 2,
+ width: CustomNavigator.width(context) / 2,
child: _buildPreview(this.attachmentFile, context),
);
@@ -195,8 +195,8 @@ class _AttachmentDetailsCardState extends State {
alignment: Alignment.center,
)
: Container()),
- width: context.width / 2,
- height: context.width / 2,
+ width: CustomNavigator.width(context) / 2,
+ height: CustomNavigator.width(context) / 2,
),
Material(
color: Colors.transparent,
@@ -234,8 +234,8 @@ class _AttachmentDetailsCardState extends State {
)
: Container(),
),
- width: context.width / 2,
- height: context.width / 2,
+ width: CustomNavigator.width(context) / 2,
+ height: CustomNavigator.width(context) / 2,
),
Material(
color: Colors.transparent,
@@ -255,7 +255,7 @@ class _AttachmentDetailsCardState extends State {
Align(
alignment: Alignment.bottomRight,
child: Icon(
- Icons.play_arrow,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.play : Icons.play_arrow,
color: Colors.white,
),
),
diff --git a/lib/layouts/conversation_details/contact_tile.dart b/lib/layouts/conversation_details/contact_tile.dart
index a565a2521..225f6a73a 100644
--- a/lib/layouts/conversation_details/contact_tile.dart
+++ b/lib/layouts/conversation_details/contact_tile.dart
@@ -1,7 +1,10 @@
import 'dart:ui';
+import 'package:bluebubbles/helpers/constants.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/managers/method_channel_interface.dart';
import 'package:contacts_service/contacts_service.dart';
+import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:bluebubbles/blocs/chat_bloc.dart';
import 'package:bluebubbles/helpers/redacted_helper.dart';
@@ -184,7 +187,7 @@ class _ContactTileState extends State {
onPressed: () {
startEmail(widget.handle.address);
},
- child: Icon(Icons.email, color: Theme.of(context).primaryColor, size: 20),
+ child: Icon(SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.mail : Icons.email, color: Theme.of(context).primaryColor, size: 20),
),
),
((contact == null && !isEmail) || (contact?.phones?.length ?? 0) > 0)
@@ -197,7 +200,7 @@ class _ContactTileState extends State {
),
onLongPress: () => onPressContactTrailing(longPressed: true),
onPressed: () => onPressContactTrailing(),
- child: Icon(Icons.call, color: Theme.of(context).primaryColor, size: 20),
+ child: Icon(SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.phone : Icons.call, color: Theme.of(context).primaryColor, size: 20),
),
)
: Container()
@@ -293,7 +296,7 @@ class _ContactTileState extends State {
IconSlideAction(
caption: 'Remove',
color: Colors.red,
- icon: Icons.delete,
+ icon: SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.trash : Icons.delete,
onTap: () async {
showDialog(
context: context,
@@ -312,13 +315,15 @@ class _ContactTileState extends State {
params["identifier"] = widget.chat.guid;
params["address"] = widget.handle.address;
SocketManager().sendMessage("remove-participant", params, (response) async {
- debugPrint("removed participant participant " + response.toString());
+ Logger.info("Removed participant participant " + response.toString());
+
if (response["status"] == 200) {
Chat updatedChat = Chat.fromMap(response["data"]);
await updatedChat.save();
await ChatBloc().updateChatPosition(updatedChat);
Chat chatWithParticipants = await updatedChat.getParticipants();
- debugPrint("updating chat with ${chatWithParticipants.participants.length} participants");
+
+ Logger.info("Updating chat with ${chatWithParticipants.participants.length} participants");
widget.updateChat(chatWithParticipants);
Navigator.of(context).pop();
}
diff --git a/lib/layouts/conversation_details/conversation_details.dart b/lib/layouts/conversation_details/conversation_details.dart
index 9bbfb10c6..e0d0d8340 100644
--- a/lib/layouts/conversation_details/conversation_details.dart
+++ b/lib/layouts/conversation_details/conversation_details.dart
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:bluebubbles/blocs/chat_bloc.dart';
import 'package:bluebubbles/blocs/message_bloc.dart';
import 'package:bluebubbles/helpers/constants.dart';
+import 'package:bluebubbles/helpers/logger.dart';
import 'package:bluebubbles/helpers/message_helper.dart';
import 'package:bluebubbles/helpers/ui_helpers.dart';
import 'package:bluebubbles/layouts/conversation_details/attachment_details_card.dart';
@@ -80,7 +81,7 @@ class _ConversationDetailsState extends State {
await chat.getParticipants();
readOnly = !(chat.participants.length > 1);
- debugPrint("updated readonly $readOnly");
+ Logger.info("Updated readonly $readOnly");
if (this.mounted) setState(() {});
}
@@ -211,7 +212,7 @@ class _ConversationDetailsState extends State {
);
},
child: Icon(
- Icons.info_outline,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.info : Icons.info_outline,
color: Theme.of(context).primaryColor,
))),
if (chat.displayName!.isEmpty)
@@ -284,7 +285,7 @@ class _ConversationDetailsState extends State {
trailing: Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
- Icons.more_horiz,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.ellipsis : Icons.more_horiz,
color: Theme.of(context).primaryColor,
),
),
@@ -318,15 +319,13 @@ class _ConversationDetailsState extends State {
return AlertDialog(
backgroundColor: Theme.of(context).accentColor,
title: new Text("Custom Avatar",
- style:
- TextStyle(color: Theme.of(context).textTheme.bodyText1!.color)),
+ style: TextStyle(color: Theme.of(context).textTheme.bodyText1!.color)),
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(
- "You have already set a custom avatar for this chat. What would you like to do?",
+ Text("You have already set a custom avatar for this chat. What would you like to do?",
style: Theme.of(context).textTheme.bodyText1),
],
),
@@ -380,7 +379,7 @@ class _ConversationDetailsState extends State {
trailing: Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
- Icons.person,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.person : Icons.person,
color: Theme.of(context).primaryColor,
),
),
@@ -408,7 +407,7 @@ class _ConversationDetailsState extends State {
trailing: Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
- Icons.file_download,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.cloud_download : Icons.file_download,
color: Theme.of(context).primaryColor,
),
),
@@ -433,7 +432,7 @@ class _ConversationDetailsState extends State {
trailing: Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
- Icons.replay,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.arrow_counterclockwise : Icons.replay,
color: Theme.of(context).primaryColor,
),
),
@@ -464,7 +463,7 @@ class _ConversationDetailsState extends State {
color: Theme.of(context).primaryColor,
)),
trailing: Switch(
- value: widget.chat.isMuted!,
+ value: widget.chat.muteType == "mute",
activeColor: Theme.of(context).primaryColor,
activeTrackColor: Theme.of(context).primaryColor.withAlpha(200),
inactiveTrackColor: Theme.of(context).accentColor.withOpacity(0.6),
@@ -536,11 +535,11 @@ class _ConversationDetailsState extends State {
)
: (isCleared)
? Icon(
- Icons.done,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.checkmark : Icons.done,
color: Theme.of(context).primaryColor,
)
: Icon(
- Icons.delete_forever,
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.trash : Icons.delete_forever,
color: Theme.of(context).primaryColor,
),
),
diff --git a/lib/layouts/conversation_list/conversation_list.dart b/lib/layouts/conversation_list/conversation_list.dart
index 3b97a5e4a..9af1ff0cd 100644
--- a/lib/layouts/conversation_list/conversation_list.dart
+++ b/lib/layouts/conversation_list/conversation_list.dart
@@ -1,39 +1,40 @@
import 'dart:async';
-import 'dart:math';
+import 'dart:io';
import 'dart:ui';
import 'package:bluebubbles/blocs/chat_bloc.dart';
import 'package:bluebubbles/blocs/setup_bloc.dart';
import 'package:bluebubbles/helpers/constants.dart';
+import 'package:bluebubbles/helpers/logger.dart';
+import 'package:bluebubbles/helpers/navigator.dart';
import 'package:bluebubbles/helpers/ui_helpers.dart';
import 'package:bluebubbles/helpers/utils.dart';
-import 'package:bluebubbles/layouts/conversation_list/conversation_tile.dart';
-import 'package:bluebubbles/layouts/conversation_list/pinned_conversation_tile.dart';
+import 'package:bluebubbles/layouts/conversation_list/cupertino_conversation_list.dart';
+import 'package:bluebubbles/layouts/conversation_list/material_conversation_list.dart';
+import 'package:bluebubbles/layouts/conversation_list/samsung_conversation_list.dart';
import 'package:bluebubbles/layouts/conversation_view/conversation_view.dart';
-import 'package:bluebubbles/layouts/search/search_view.dart';
import 'package:bluebubbles/layouts/settings/settings_panel.dart';
import 'package:bluebubbles/layouts/widgets/theme_switcher/theme_switcher.dart';
import 'package:bluebubbles/managers/event_dispatcher.dart';
+import 'package:bluebubbles/managers/method_channel_interface.dart';
import 'package:bluebubbles/managers/settings_manager.dart';
-import 'package:bluebubbles/managers/theme_manager.dart';
-import 'package:bluebubbles/repository/models/chat.dart';
import 'package:bluebubbles/socket_manager.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
-import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class ConversationList extends StatefulWidget {
- ConversationList({Key? key, required this.showArchivedChats}) : super(key: key);
+ ConversationList({Key? key, required this.showArchivedChats, required this.showUnknownSenders}) : super(key: key);
final bool showArchivedChats;
+ final bool showUnknownSenders;
@override
- _ConversationListState createState() => _ConversationListState();
+ ConversationListState createState() => ConversationListState();
}
-class _ConversationListState extends State {
+class ConversationListState extends State {
Color? currentHeaderColor;
bool hasPinnedChats = false;
@@ -50,7 +51,7 @@ class _ConversationListState extends State {
}
SystemChannels.textInput.invokeMethod('TextInput.hide').catchError((e) {
- debugPrint("Error caught while hiding keyboard: ${e.toString()}");
+ Logger.error("Error caught while hiding keyboard: ${e.toString()}");
});
}
@@ -65,7 +66,9 @@ class _ConversationListState extends State {
@override
void initState() {
super.initState();
- ChatBloc().refreshChats();
+ if (!widget.showUnknownSenders) {
+ ChatBloc().refreshChats();
+ }
scrollController = ScrollController()..addListener(scrollListener);
// Listen for any incoming events
@@ -98,7 +101,7 @@ class _ConversationListState extends State {
TextStyle? style = context.textTheme.headline1;
if (size != null) style = style!.copyWith(fontSize: size);
- return [Text(widget.showArchivedChats ? "Archive" : "Messages", style: style), Container(width: 10)];
+ return [Text(widget.showArchivedChats ? "Archive" : widget.showUnknownSenders ? "Unknown Senders" : "Messages", style: style), Container(width: 10)];
}
Widget getSyncIndicatorWidget() {
@@ -109,24 +112,22 @@ class _ConversationListState extends State {
});
}
- void openNewChatCreator() async {
+ void openNewChatCreator({List? existing}) async {
bool shouldShowSnackbar = (await SettingsManager().getMacOSVersion())! >= 11;
- Navigator.of(context).push(
- CupertinoPageRoute(
- builder: (BuildContext context) {
- return ConversationView(
- isCreator: true,
- showSnackbar: shouldShowSnackbar,
- );
- },
+ CustomNavigator.pushAndRemoveUntil(
+ context,
+ ConversationView(
+ isCreator: true,
+ showSnackbar: shouldShowSnackbar,
+ existingAttachments: existing ?? [],
),
+ (route) => route.isFirst,
);
}
void sortChats() {
ChatBloc().chats.sort((a, b) {
- if (a.pinIndex.value != null && b.pinIndex.value != null)
- return a.pinIndex.value!.compareTo(b.pinIndex.value!);
+ if (a.pinIndex.value != null && b.pinIndex.value != null) return a.pinIndex.value!.compareTo(b.pinIndex.value!);
if (b.pinIndex.value != null) return 1;
if (a.pinIndex.value != null) return -1;
if (!a.isPinned! && b.isPinned!) return 1;
@@ -138,19 +139,19 @@ class _ConversationListState extends State {
});
}
- Widget buildSettingsButton() => !widget.showArchivedChats
+ Widget buildSettingsButton() => !widget.showArchivedChats && !widget.showUnknownSenders
? PopupMenuButton(
color: context.theme.accentColor,
onSelected: (dynamic value) {
if (value == 0) {
ChatBloc().markAllAsRead();
} else if (value == 1) {
- Navigator.of(context).push(
- ThemeSwitcher.buildPageRoute(
- builder: (context) => ConversationList(
- showArchivedChats: true,
- ),
- ),
+ CustomNavigator.pushLeft(
+ context,
+ ConversationList(
+ showArchivedChats: true,
+ showUnknownSenders: false,
+ )
);
} else if (value == 2) {
Navigator.of(context).push(
@@ -160,6 +161,14 @@ class _ConversationListState extends State {
},
),
);
+ } else if (value == 3) {
+ CustomNavigator.pushLeft(
+ context,
+ ConversationList(
+ showArchivedChats: false,
+ showUnknownSenders: true,
+ )
+ );
}
},
itemBuilder: (context) {
@@ -178,6 +187,14 @@ class _ConversationListState extends State {
style: context.textTheme.bodyText1,
),
),
+ if (SettingsManager().settings.filterUnknownSenders.value)
+ PopupMenuItem(
+ value: 3,
+ child: Text(
+ 'Unknown Senders',
+ style: context.textTheme.bodyText1,
+ ),
+ ),
PopupMenuItem(
value: 2,
child: Text(
@@ -215,1556 +232,66 @@ class _ConversationListState extends State {
)
: Container();
- FloatingActionButton buildFloatingActionButton() {
- return FloatingActionButton(
- backgroundColor: context.theme.primaryColor,
- child: Icon(Icons.message, color: Colors.white, size: 25),
- onPressed: openNewChatCreator);
- }
-
- List getConnectionIndicatorWidgets() {
- if (!SettingsManager().settings.showConnectionIndicator.value) return [];
-
- return [Obx(() => getIndicatorIcon(SocketManager().state.value, size: 12)), Container(width: 10.0)];
- }
-
- @override
- Widget build(BuildContext context) {
- return ThemeSwitcher(
- iOSSkin: _Cupertino(parent: this),
- materialSkin: _Material(parent: this),
- samsungSkin: _Samsung(parent: this),
- );
- }
-}
-
-class _Cupertino extends StatelessWidget {
- const _Cupertino({Key? key, required this.parent}) : super(key: key);
-
- final _ConversationListState parent;
-
- @override
- Widget build(BuildContext context) {
- bool showArchived = parent.widget.showArchivedChats;
- Brightness brightness = ThemeData.estimateBrightnessForColor(context.theme.backgroundColor);
- return AnnotatedRegion(
- value: SystemUiOverlayStyle(
- systemNavigationBarColor: context.theme.backgroundColor, // navigation bar color
- systemNavigationBarIconBrightness:
- context.theme.backgroundColor.computeLuminance() > 0.5 ? Brightness.dark : Brightness.light,
- statusBarColor: Colors.transparent, // status bar color
- ),
- child: Obx(
- () => Scaffold(
- appBar: PreferredSize(
- preferredSize: Size(
- context.width,
- SettingsManager().settings.reducedForehead.value ? 10 : 40,
+ Column buildFloatingActionButton() {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ if (SettingsManager().settings.cameraFAB.value)
+ ConstrainedBox(
+ constraints: BoxConstraints(
+ maxWidth: 45,
+ maxHeight: 45,
),
- child: ClipRRect(
- child: BackdropFilter(
- filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
- child: StreamBuilder(
- stream: parent.headerColorStream.stream,
- builder: (context, snapshot) {
- return AnimatedCrossFade(
- crossFadeState:
- parent.theme == Colors.transparent ? CrossFadeState.showFirst : CrossFadeState.showSecond,
- duration: Duration(milliseconds: 250),
- secondChild: AppBar(
- iconTheme: IconThemeData(color: context.theme.primaryColor),
- elevation: 0,
- backgroundColor: parent.theme,
- centerTitle: true,
- brightness: brightness,
- title: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- Text(
- showArchived ? "Archive" : "Messages",
- style: context.textTheme.bodyText1,
- ),
- ],
- ),
- ),
- firstChild: AppBar(
- leading: new Container(),
- elevation: 0,
- brightness: brightness,
- backgroundColor: context.theme.backgroundColor,
- ),
- );
- },
- ),
- ),
- ),
- ),
- backgroundColor: context.theme.backgroundColor,
- extendBodyBehindAppBar: true,
- body: CustomScrollView(
- controller: parent.scrollController,
- physics: ThemeManager().scrollPhysics,
- slivers: [
- SliverAppBar(
- leading: ((SettingsManager().settings.skin.value == Skins.iOS && showArchived) ||
- (SettingsManager().settings.skin.value == Skins.Material ||
- SettingsManager().settings.skin.value == Skins.Samsung) &&
- !showArchived)
- ? IconButton(
- icon: Icon(
- (SettingsManager().settings.skin.value == Skins.iOS && showArchived)
- ? Icons.arrow_back_ios
- : Icons.arrow_back,
- color: context.theme.primaryColor),
- onPressed: () {
- Navigator.of(context).pop();
- },
- )
- : new Container(),
- stretch: true,
- expandedHeight: (!showArchived) ? 80 : 50,
- backgroundColor: Colors.transparent,
- pinned: false,
- flexibleSpace: FlexibleSpaceBar(
- stretchModes: [StretchMode.zoomBackground],
- background: Stack(
- fit: StackFit.expand,
- ),
- centerTitle: true,
- title: Column(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Container(height: 20),
- Container(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Container(width: (!showArchived) ? 20 : 50),
- Row(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- ...parent.getHeaderTextWidgets(),
- ...parent.getConnectionIndicatorWidgets(),
- parent.getSyncIndicatorWidget(),
- ],
- ),
- Spacer(
- flex: 25,
- ),
- if (!showArchived)
- ClipOval(
- child: Material(
- color: context.theme.accentColor, // button color
- child: InkWell(
- child: SizedBox(
- width: 20,
- height: 20,
- child: Icon(Icons.search, color: context.theme.primaryColor, size: 12)),
- onTap: () async {
- Navigator.of(context).push(
- CupertinoPageRoute(
- builder: (context) => SearchView(),
- ),
- );
- },
- ),
- ),
- ),
- if (!showArchived) Container(width: 10.0),
- if (SettingsManager().settings.moveChatCreatorToHeader.value && !showArchived)
- ClipOval(
- child: Material(
- color: context.theme.accentColor, // button color
- child: InkWell(
- child: SizedBox(
- width: 20,
- height: 20,
- child: Icon(Icons.create, color: context.theme.primaryColor, size: 12),
- ),
- onTap: this.parent.openNewChatCreator,
- ),
- ),
- ),
- if (SettingsManager().settings.moveChatCreatorToHeader.value) Container(width: 10.0),
- parent.buildSettingsButton(),
- Spacer(
- flex: 3,
- ),
- ],
- ),
- ),
- ],
- ),
- ),
+ child: FloatingActionButton(
+ child: Icon(
+ SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.camera : Icons.photo_camera,
+ size: 20,
),
- // SliverToBoxAdapter(
- // child: Container(
- // padding: EdgeInsets.symmetric(horizontal: 30, vertical: 5),
- // child: GestureDetector(
- // onTap: () {
- // Navigator.of(context).push(
- // MaterialPageRoute(
- // builder: (context) => SearchView(),
- // ),
- // );
- // },
- // child: AbsorbPointer(
- // child: SearchTextBox(),
- // ),
- // ),
- // ),
- // ),
- Obx(() {
- if (ChatBloc().chats.archivedHelper(showArchived).bigPinHelper(true).isEmpty) {
- return SliverToBoxAdapter(child: Container());
+ onPressed: () async {
+ String appDocPath = SettingsManager().appDocDir.path;
+ String ext = ".png";
+ File file = new File("$appDocPath/attachments/" + randomString(16) + ext);
+ await file.create(recursive: true);
+
+ // Take the picture after opening the camera
+ await MethodChannelInterface().invokeMethod("open-camera", {"path": file.path, "type": "camera"});
+
+ // If we don't get data back, return outta here
+ if (!file.existsSync()) return;
+ if (file.statSync().size == 0) {
+ file.deleteSync();
+ return;
}
- ChatBloc().chats.archivedHelper(showArchived).sort(Chat.sort);
-
- int rowCount = context.mediaQuery.orientation == Orientation.portrait
- ? SettingsManager().settings.pinRowsPortrait.value
- : SettingsManager().settings.pinRowsLandscape.value;
- int colCount = SettingsManager().settings.pinColumnsPortrait.value;
- if (context.mediaQuery.orientation != Orientation.portrait) {
- colCount = (colCount / context.mediaQuerySize.height * context.mediaQuerySize.width).floor();
- }
- int pinCount = ChatBloc().chats.archivedHelper(showArchived).bigPinHelper(true).length;
- int usedRowCount = min((pinCount / colCount).ceil(), rowCount);
- int maxOnPage = rowCount * colCount;
- PageController _controller = PageController();
- int _pageCount = (pinCount / maxOnPage).ceil();
- int _filledPageCount = (pinCount / maxOnPage).floor();
-
- return SliverPadding(
- padding: EdgeInsets.only(
- top: 10,
- bottom: 10,
- ),
- sliver: SliverToBoxAdapter(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- maxHeight: (context.mediaQuerySize.width + 30) / colCount * usedRowCount,
- ),
- child: Stack(
- alignment: Alignment.bottomCenter,
- children: [
- PageView.builder(
- physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
- scrollDirection: Axis.horizontal,
- controller: _controller,
- itemBuilder: (context, index) {
- return Wrap(
- crossAxisAlignment: WrapCrossAlignment.center,
- alignment: _pageCount > 1 ? WrapAlignment.start : WrapAlignment.center,
- children: List.generate(
- index < _filledPageCount
- ? maxOnPage
- : ChatBloc().chats.archivedHelper(showArchived).bigPinHelper(true).length %
- maxOnPage,
- (_index) {
- return PinnedConversationTile(
- key: Key(ChatBloc()
- .chats
- .archivedHelper(showArchived)
- .bigPinHelper(true)[index * maxOnPage + _index]
- .guid
- .toString()),
- chat: ChatBloc()
- .chats
- .archivedHelper(showArchived)
- .bigPinHelper(true)[index * maxOnPage + _index],
- );
- },
- ),
- );
- },
- itemCount: _pageCount,
- ),
- if (_pageCount > 1)
- SmoothPageIndicator(
- controller: _controller,
- count: _pageCount,
- effect: ScaleEffect(
- dotHeight: 5.0,
- dotWidth: 5.0,
- spacing: 5.0,
- radius: 5.0,
- scale: 1.5,
- activeDotColor: context.theme.primaryColor,
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }),
- Obx(() {
- ChatBloc().chats.archivedHelper(showArchived).sort(Chat.sort);
- if (!ChatBloc().hasChats.value) {
- return SliverToBoxAdapter(
- child: Center(
- child: Container(
- padding: EdgeInsets.only(top: 50.0),
- child: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- "Loading chats...",
- style: Theme.of(context).textTheme.subtitle1,
- ),
- ),
- buildProgressIndicator(context, size: 15),
- ],
- ),
- ),
- ),
- );
- }
- if (!ChatBloc().hasChats.value) {
- return SliverToBoxAdapter(
- child: Center(
- child: Container(
- padding: EdgeInsets.only(top: 50.0),
- child: Text(
- showArchived ? "You have no archived chats :(" : "You have no chats :(",
- style: Theme.of(context).textTheme.subtitle1,
- ),
- ),
- ),
- );
- }
-
- return SliverList(
- delegate: SliverChildBuilderDelegate(
- (context, index) {
- return ConversationTile(
- key: Key(
- ChatBloc().chats.archivedHelper(showArchived).bigPinHelper(false)[index].guid.toString()),
- chat: ChatBloc().chats.archivedHelper(showArchived).bigPinHelper(false)[index],
- );
- },
- childCount: ChatBloc().chats.archivedHelper(showArchived).bigPinHelper(false).length,
- ),
- );
- }),
- ],
- ),
- floatingActionButton:
- !SettingsManager().settings.moveChatCreatorToHeader.value ? parent.buildFloatingActionButton() : null,
- ),
- ),
- );
- }
-}
-
-class _Material extends StatefulWidget {
- _Material({Key? key, required this.parent}) : super(key: key);
-
- final _ConversationListState parent;
-
- @override
- __MaterialState createState() => __MaterialState();
-}
-
-class __MaterialState extends State<_Material> {
- List selected = [];
-
- bool hasPinnedChat() {
- for (var i = 0; i < ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats).length; i++) {
- if (ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats)[i].isPinned!) {
- widget.parent.hasPinnedChats = true;
- return true;
- } else {
- return false;
- }
- }
- return false;
- }
-
- bool hasNormalChats() {
- int counter = 0;
- for (var i = 0; i < ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats).length; i++) {
- if (ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats)[i].isPinned!) {
- counter++;
- } else {}
- }
- if (counter == ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats).length) {
- return false;
- } else {
- return true;
- }
- }
-
- Widget slideLeftBackground(Chat chat) {
- return Container(
- color: SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin
- ? Colors.yellow[800]
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.alerts
- ? Colors.purple
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.delete
- ? Colors.red
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.mark_read
- ? Colors.blue
- : Colors.red,
- child: Align(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Icon(
- SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? Icons.star_outline : Icons.star)
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? Icons.notifications_active : Icons.notifications_off)
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.delete
- ? Icons.delete_forever
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? Icons.mark_chat_read : Icons.mark_chat_unread)
- : (chat.isArchived! ? Icons.unarchive : Icons.archive),
- color: Colors.white,
- ),
- Text(
- SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? " Unpin" : " Pin")
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? ' Show Alerts' : ' Hide Alerts')
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.delete
- ? " Delete"
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? ' Mark Read' : ' Mark Unread')
- : (chat.isArchived! ? ' UnArchive' : ' Archive'),
- style: TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.w700,
- ),
- textAlign: TextAlign.right,
- ),
- SizedBox(
- width: 20,
- ),
- ],
- ),
- alignment: Alignment.centerRight,
- ),
- );
- }
-
- Widget slideRightBackground(Chat chat) {
- return Container(
- color: SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin
- ? Colors.yellow[800]
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.alerts
- ? Colors.purple
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.delete
- ? Colors.red
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.mark_read
- ? Colors.blue
- : Colors.red,
- child: Align(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- SizedBox(
- width: 20,
- ),
- Icon(
- SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? Icons.star_outline : Icons.star)
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? Icons.notifications_active : Icons.notifications_off)
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.delete
- ? Icons.delete_forever
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? Icons.mark_chat_read : Icons.mark_chat_unread)
- : (chat.isArchived! ? Icons.unarchive : Icons.archive),
- color: Colors.white,
- ),
- Text(
- SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? " Unpin" : " Pin")
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? ' Show Alerts' : ' Hide Alerts')
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.delete
- ? " Delete"
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? ' Mark Read' : ' Mark Unread')
- : (chat.isArchived! ? ' UnArchive' : ' Archive'),
- style: TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.w700,
- ),
- textAlign: TextAlign.left,
- ),
- ],
- ),
- alignment: Alignment.centerLeft,
- ),
- );
- }
- @override
- Widget build(BuildContext context) {
- hasPinnedChat();
- bool showArchived = widget.parent.widget.showArchivedChats;
- return AnnotatedRegion(
- value: SystemUiOverlayStyle(
- systemNavigationBarColor: context.theme.backgroundColor, // navigation bar color
- systemNavigationBarIconBrightness:
- context.theme.backgroundColor.computeLuminance() > 0.5 ? Brightness.dark : Brightness.light,
- statusBarColor: Colors.transparent, // status bar color
- ),
- child: Obx(
- () => WillPopScope(
- onWillPop: () async {
- if (selected.isNotEmpty) {
- selected = [];
- setState(() {});
- return false;
- }
- return true;
- },
- child: Scaffold(
- appBar: PreferredSize(
- preferredSize: Size.fromHeight(60),
- child: AnimatedSwitcher(
- duration: Duration(milliseconds: 500),
- child: selected.isEmpty
- ? AppBar(
- iconTheme: IconThemeData(color: context.theme.primaryColor),
- brightness: ThemeData.estimateBrightnessForColor(context.theme.backgroundColor),
- bottom: PreferredSize(
- child: Container(
- color: context.theme.dividerColor,
- height: 0,
- ),
- preferredSize: Size.fromHeight(0.5),
- ),
- title: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- ...widget.parent.getHeaderTextWidgets(size: 20),
- ...widget.parent.getConnectionIndicatorWidgets(),
- widget.parent.getSyncIndicatorWidget(),
- ],
- ),
- actions: [
- (!showArchived)
- ? GestureDetector(
- onTap: () async {
- Navigator.of(context).push(
- CupertinoPageRoute(
- builder: (context) => SearchView(),
- ),
- );
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.search,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- )
- : Container(),
- (SettingsManager().settings.moveChatCreatorToHeader.value && !showArchived)
- ? GestureDetector(
- onTap: () {
- Navigator.of(context).push(
- ThemeSwitcher.buildPageRoute(
- builder: (BuildContext context) {
- return ConversationView(
- isCreator: true,
- );
- },
- ),
- );
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.create,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- )
- : Container(),
- Padding(
- padding: EdgeInsets.only(right: 20),
- child: Padding(
- padding: EdgeInsets.symmetric(vertical: 15.5),
- child: Container(
- width: 40,
- child: widget.parent.buildSettingsButton(),
- ),
- ),
- ),
- ],
- backgroundColor: context.theme.backgroundColor,
- )
- : Padding(
- padding: const EdgeInsets.all(20.0),
- child: Column(
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- if (([0, selected.length])
- .contains(selected.where((element) => element.hasUnreadMessage!).length))
- GestureDetector(
- onTap: () {
- selected.forEach((element) async {
- await element.toggleHasUnread(!element.hasUnreadMessage!);
- });
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- selected[0].hasUnreadMessage! ? Icons.mark_chat_read : Icons.mark_chat_unread,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- if (([0, selected.length])
- .contains(selected.where((element) => element.isMuted!).length))
- GestureDetector(
- onTap: () {
- selected.forEach((element) async {
- await element.toggleMute(!element.isMuted!);
- });
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- selected[0].isMuted! ? Icons.notifications_active : Icons.notifications_off,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- if (([0, selected.length])
- .contains(selected.where((element) => element.isPinned!).length))
- GestureDetector(
- onTap: () {
- selected.forEach((element) {
- element.togglePin(!element.isPinned!);
- });
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- selected[0].isPinned! ? Icons.star_outline : Icons.star,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- GestureDetector(
- onTap: () {
- selected.forEach((element) {
- if (element.isArchived!) {
- ChatBloc().unArchiveChat(element);
- } else {
- ChatBloc().archiveChat(element);
- }
- });
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- showArchived ? Icons.unarchive : Icons.archive,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- if (selected[0].isArchived!)
- GestureDetector(
- onTap: () {
- selected.forEach((element) {
- ChatBloc().deleteChat(element);
- Chat.deleteChat(element);
- });
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.delete_forever,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- ),
- backgroundColor: context.theme.backgroundColor,
- body: Obx(
- () {
- if (!ChatBloc().hasChats.value) {
- return Center(
- child: Container(
- padding: EdgeInsets.only(top: 50.0),
- child: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- "Loading chats...",
- style: Theme.of(context).textTheme.subtitle1,
- ),
- ),
- buildProgressIndicator(context, size: 15),
- ],
- ),
- ),
- );
- }
- if (ChatBloc().chats.archivedHelper(showArchived).isEmpty) {
- return Center(
- child: Container(
- padding: EdgeInsets.only(top: 50.0),
- child: Text(
- "You have no archived chats :(",
- style: context.textTheme.subtitle1,
- ),
- ),
- );
- }
- return ListView.builder(
- physics: ThemeSwitcher.getScrollPhysics(),
- itemBuilder: (context, index) {
- return Obx(() {
- if (SettingsManager().settings.swipableConversationTiles.value) {
- return Dismissible(
- background:
- Obx(() => slideRightBackground(ChatBloc().chats.archivedHelper(showArchived)[index])),
- secondaryBackground:
- Obx(() => slideLeftBackground(ChatBloc().chats.archivedHelper(showArchived)[index])),
- // Each Dismissible must contain a Key. Keys allow Flutter to
- // uniquely identify widgets.
- key: UniqueKey(),
- // Provide a function that tells the app
- // what to do after an item has been swiped away.
- onDismissed: (direction) async {
- if (direction == DismissDirection.endToStart) {
- if (SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .togglePin(!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!);
- EventDispatcher().emit("refresh", null);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.alerts) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .toggleMute(!ChatBloc().chats.archivedHelper(showArchived)[index].isMuted!);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.delete) {
- ChatBloc().deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- Chat.deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.mark_read) {
- ChatBloc().toggleChatUnread(ChatBloc().chats.archivedHelper(showArchived)[index],
- !ChatBloc().chats.archivedHelper(showArchived)[index].hasUnreadMessage!);
- } else {
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!) {
- ChatBloc().unArchiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- ChatBloc().archiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- }
- }
- } else {
- if (SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .togglePin(!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!);
- EventDispatcher().emit("refresh", null);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.alerts) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .toggleMute(!ChatBloc().chats.archivedHelper(showArchived)[index].isMuted!);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.delete) {
- ChatBloc().deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- Chat.deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.mark_read) {
- ChatBloc().toggleChatUnread(ChatBloc().chats.archivedHelper(showArchived)[index],
- !ChatBloc().chats.archivedHelper(showArchived)[index].hasUnreadMessage!);
- } else {
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!) {
- ChatBloc().unArchiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- ChatBloc().archiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- }
- }
- }
- },
- child: (!showArchived && ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- ? Container()
- : (showArchived && !ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- ? Container()
- : ConversationTile(
- key: UniqueKey(),
- chat: ChatBloc().chats.archivedHelper(showArchived)[index],
- inSelectMode: selected.isNotEmpty,
- selected: selected,
- onSelect: (bool selected) {
- if (selected) {
- this.selected.add(ChatBloc().chats.archivedHelper(showArchived)[index]);
- setState(() {});
- } else {
- this.selected.removeWhere((element) =>
- element.guid ==
- ChatBloc().chats.archivedHelper(showArchived)[index].guid);
- setState(() {});
- }
- },
- ));
- } else {
- return ConversationTile(
- key: UniqueKey(),
- chat: ChatBloc().chats.archivedHelper(showArchived)[index],
- inSelectMode: selected.isNotEmpty,
- selected: selected,
- onSelect: (bool selected) {
- if (selected) {
- this.selected.add(ChatBloc().chats.archivedHelper(showArchived)[index]);
- setState(() {});
- } else {
- this.selected.removeWhere((element) =>
- element.guid == ChatBloc().chats.archivedHelper(showArchived)[index].guid);
- setState(() {});
- }
- },
- );
- }
- });
- },
- itemCount: ChatBloc().chats.archivedHelper(showArchived).length,
- );
+ openNewChatCreator(existing: [file]);
},
+ heroTag: null,
),
- floatingActionButton: selected.isEmpty && !SettingsManager().settings.moveChatCreatorToHeader.value
- ? widget.parent.buildFloatingActionButton()
- : null,
),
- ),
- ),
+ if (SettingsManager().settings.cameraFAB.value)
+ SizedBox(
+ height: 10,
+ ),
+ FloatingActionButton(
+ backgroundColor: context.theme.primaryColor,
+ child: Icon(SettingsManager().settings.skin.value == Skins.iOS ? CupertinoIcons.pencil : Icons.message, color: Colors.white, size: 25),
+ onPressed: openNewChatCreator),
+ ],
);
}
-}
-
-class _Samsung extends StatefulWidget {
- _Samsung({Key? key, required this.parent}) : super(key: key);
-
- final _ConversationListState parent;
-
- @override
- _SamsungState createState() => _SamsungState();
-}
-
-class _SamsungState extends State<_Samsung> {
- List selected = [];
-
- bool hasPinnedChat() {
- for (var i = 0; i < ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats).length; i++) {
- if (ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats)[i].isPinned!) {
- widget.parent.hasPinnedChats = true;
- return true;
- } else {
- return false;
- }
- }
- return false;
- }
-
- bool hasNormalChats() {
- int counter = 0;
- for (var i = 0; i < ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats).length; i++) {
- if (ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats)[i].isPinned!) {
- counter++;
- } else {}
- }
- if (counter == ChatBloc().chats.archivedHelper(widget.parent.widget.showArchivedChats).length) {
- return false;
- } else {
- return true;
- }
- }
- Widget slideLeftBackground(Chat chat) {
- return Container(
- color: SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin
- ? Colors.yellow[800]
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.alerts
- ? Colors.purple
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.delete
- ? Colors.red
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.mark_read
- ? Colors.blue
- : Colors.red,
- child: Align(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Icon(
- SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? Icons.star_outline : Icons.star)
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? Icons.notifications_active : Icons.notifications_off)
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.delete
- ? Icons.delete_forever
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? Icons.mark_chat_read : Icons.mark_chat_unread)
- : (chat.isArchived! ? Icons.unarchive : Icons.archive),
- color: Colors.white,
- ),
- Text(
- SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? " Unpin" : " Pin")
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? ' Show Alerts' : ' Hide Alerts')
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.delete
- ? " Delete"
- : SettingsManager().settings.materialLeftAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? ' Mark Read' : ' Mark Unread')
- : (chat.isArchived! ? ' UnArchive' : ' Archive'),
- style: TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.w700,
- ),
- textAlign: TextAlign.right,
- ),
- SizedBox(
- width: 20,
- ),
- ],
- ),
- alignment: Alignment.centerRight,
- ),
- );
- }
+ List getConnectionIndicatorWidgets() {
+ if (!SettingsManager().settings.showConnectionIndicator.value) return [];
- Widget slideRightBackground(Chat chat) {
- return Container(
- color: SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin
- ? Colors.yellow[800]
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.alerts
- ? Colors.purple
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.delete
- ? Colors.red
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.mark_read
- ? Colors.blue
- : Colors.red,
- child: Align(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- SizedBox(
- width: 20,
- ),
- Icon(
- SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? Icons.star_outline : Icons.star)
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? Icons.notifications_active : Icons.notifications_off)
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.delete
- ? Icons.delete_forever
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? Icons.mark_chat_read : Icons.mark_chat_unread)
- : (chat.isArchived! ? Icons.unarchive : Icons.archive),
- color: Colors.white,
- ),
- Text(
- SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.pin
- ? (chat.isPinned! ? " Unpin" : " Pin")
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.alerts
- ? (chat.isMuted! ? ' Show Alerts' : ' Hide Alerts')
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.delete
- ? " Delete"
- : SettingsManager().settings.materialRightAction.value == MaterialSwipeAction.mark_read
- ? (chat.hasUnreadMessage! ? ' Mark Read' : ' Mark Unread')
- : (chat.isArchived! ? ' UnArchive' : ' Archive'),
- style: TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.w700,
- ),
- textAlign: TextAlign.left,
- ),
- ],
- ),
- alignment: Alignment.centerLeft,
- ),
- );
+ return [Obx(() => getIndicatorIcon(SocketManager().state.value, size: 12)), Container(width: 10.0)];
}
@override
Widget build(BuildContext context) {
- bool showArchived = widget.parent.widget.showArchivedChats;
- return AnnotatedRegion(
- value: SystemUiOverlayStyle(
- systemNavigationBarColor: context.theme.backgroundColor, // navigation bar color
- systemNavigationBarIconBrightness:
- context.theme.backgroundColor.computeLuminance() > 0.5 ? Brightness.dark : Brightness.light,
- statusBarColor: Colors.transparent, // status bar color
- ),
- child: Obx(
- () => WillPopScope(
- onWillPop: () async {
- if (selected.isNotEmpty) {
- selected = [];
- setState(() {});
- return false;
- }
- return true;
- },
- child: Scaffold(
- appBar: PreferredSize(
- preferredSize: Size.fromHeight(60),
- child: AnimatedSwitcher(
- duration: Duration(milliseconds: 500),
- child: selected.isEmpty
- ? AppBar(
- shadowColor: Colors.transparent,
- iconTheme: IconThemeData(color: context.theme.primaryColor),
- brightness: ThemeData.estimateBrightnessForColor(context.theme.backgroundColor),
- bottom: PreferredSize(
- child: Container(
- color: context.theme.dividerColor,
- height: 0,
- ),
- preferredSize: Size.fromHeight(0.5),
- ),
- title: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- ...widget.parent.getHeaderTextWidgets(size: 20),
- ...widget.parent.getConnectionIndicatorWidgets(),
- widget.parent.getSyncIndicatorWidget(),
- ],
- ),
- actions: [
- (!showArchived)
- ? GestureDetector(
- onTap: () async {
- Navigator.of(context).push(
- CupertinoPageRoute(
- builder: (context) => SearchView(),
- ),
- );
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.search,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- )
- : Container(),
- (SettingsManager().settings.moveChatCreatorToHeader.value && !showArchived
- ? GestureDetector(
- onTap: () {
- Navigator.of(context).push(
- ThemeSwitcher.buildPageRoute(
- builder: (BuildContext context) {
- return ConversationView(
- isCreator: true,
- );
- },
- ),
- );
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.create,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- )
- : Container()),
- Padding(
- padding: EdgeInsets.only(right: 20),
- child: Padding(
- padding: EdgeInsets.symmetric(vertical: 15.5),
- child: Container(
- width: 40,
- child: widget.parent.buildSettingsButton(),
- ),
- ),
- ),
- ],
- backgroundColor: context.theme.backgroundColor,
- )
- : Padding(
- padding: const EdgeInsets.all(20.0),
- child: Column(
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- if (selected.length <= 1)
- GestureDetector(
- onTap: () {
- selected.forEach((element) async {
- await element.toggleMute(!element.isMuted!);
- });
-
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.notifications_off,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- GestureDetector(
- onTap: () {
- selected.forEach((element) {
- if (element.isArchived!) {
- ChatBloc().unArchiveChat(element);
- } else {
- ChatBloc().archiveChat(element);
- }
- });
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- showArchived ? Icons.unarchive : Icons.archive,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- GestureDetector(
- onTap: () {
- selected.forEach((element) async {
- await element.togglePin(!element.isPinned!);
- });
-
- selected = [];
- if (this.mounted) setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Icon(
- Icons.star,
- color: context.textTheme.bodyText1!.color,
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- ),
- backgroundColor: context.theme.backgroundColor,
- body: Obx(() {
- if (!ChatBloc().hasChats.value) {
- return Center(
- child: Container(
- padding: EdgeInsets.only(top: 50.0),
- child: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- "Loading chats...",
- style: Theme.of(context).textTheme.subtitle1,
- ),
- ),
- buildProgressIndicator(context, size: 15),
- ],
- ),
- ),
- );
- }
- if (ChatBloc().chats.archivedHelper(showArchived).isEmpty) {
- return Center(
- child: Container(
- padding: EdgeInsets.only(top: 50.0),
- child: Text(
- "You have no archived chats :(",
- style: context.textTheme.subtitle1,
- ),
- ),
- );
- }
-
- bool hasPinned = hasPinnedChat();
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- if (hasPinned)
- Container(
- height: 20.0,
- decoration: BoxDecoration(
- border: Border.all(
- color: Colors.transparent,
- ),
- borderRadius: BorderRadius.all(Radius.circular(20)),
- ),
- ),
- if (hasPinned)
- Container(
- padding: EdgeInsets.all(6.0),
- decoration: new BoxDecoration(
- color: context.theme.accentColor,
- borderRadius: BorderRadius.circular(20),
- ),
- child: ListView.builder(
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- itemBuilder: (context, index) {
- return Obx(() {
- if (SettingsManager().settings.swipableConversationTiles.value) {
- return Dismissible(
- background: Obx(
- () => slideRightBackground(ChatBloc().chats.archivedHelper(showArchived)[index])),
- secondaryBackground: Obx(
- () => slideLeftBackground(ChatBloc().chats.archivedHelper(showArchived)[index])),
- // Each Dismissible must contain a Key. Keys allow Flutter to
- // uniquely identify widgets.
- key: UniqueKey(),
- // Provide a function that tells the app
- // what to do after an item has been swiped away.
- onDismissed: (direction) async {
- if (direction == DismissDirection.endToStart) {
- if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.pin) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .togglePin(!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!);
- EventDispatcher().emit("refresh", null);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.alerts) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .toggleMute(!ChatBloc().chats.archivedHelper(showArchived)[index].isMuted!);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.delete) {
- ChatBloc().deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- Chat.deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.mark_read) {
- ChatBloc().toggleChatUnread(
- ChatBloc().chats.archivedHelper(showArchived)[index],
- !ChatBloc().chats.archivedHelper(showArchived)[index].hasUnreadMessage!);
- } else {
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!) {
- ChatBloc()
- .unArchiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- ChatBloc().archiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- }
- }
- } else {
- if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.pin) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .togglePin(!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!);
- EventDispatcher().emit("refresh", null);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.alerts) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .toggleMute(!ChatBloc().chats.archivedHelper(showArchived)[index].isMuted!);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.delete) {
- ChatBloc().deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- Chat.deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.mark_read) {
- ChatBloc().toggleChatUnread(
- ChatBloc().chats.archivedHelper(showArchived)[index],
- !ChatBloc().chats.archivedHelper(showArchived)[index].hasUnreadMessage!);
- } else {
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!) {
- ChatBloc()
- .unArchiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- ChatBloc().archiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- }
- }
- }
- },
- child: (!showArchived &&
- ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- ? Container()
- : (showArchived &&
- !ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- ? Container()
- : ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!
- ? ConversationTile(
- key: UniqueKey(),
- chat: ChatBloc().chats.archivedHelper(showArchived)[index],
- inSelectMode: selected.isNotEmpty,
- selected: selected,
- onSelect: (bool selected) {
- if (selected) {
- this
- .selected
- .add(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- this.selected.removeWhere((element) =>
- element.guid ==
- ChatBloc().chats.archivedHelper(showArchived)[index].guid);
- }
-
- if (this.mounted) setState(() {});
- },
- )
- : Container(),
- );
- } else {
- if (!showArchived && ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- return Container();
- if (showArchived && !ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- return Container();
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!) {
- return ConversationTile(
- key: UniqueKey(),
- chat: ChatBloc().chats.archivedHelper(showArchived)[index],
- inSelectMode: selected.isNotEmpty,
- selected: selected,
- onSelect: (bool selected) {
- if (selected) {
- this.selected.add(ChatBloc().chats.archivedHelper(showArchived)[index]);
- if (this.mounted) setState(() {});
- } else {
- this.selected.removeWhere((element) =>
- element.guid == ChatBloc().chats.archivedHelper(showArchived)[index].guid);
- if (this.mounted) setState(() {});
- }
- },
- );
- }
- return Container();
- }
- });
- },
- itemCount: ChatBloc().chats.archivedHelper(showArchived).length,
- ),
- ),
- if (hasNormalChats())
- Container(
- height: 20.0,
- decoration: BoxDecoration(
- border: Border.all(
- color: Colors.transparent,
- ),
- borderRadius: BorderRadius.all(Radius.circular(20))),
- ),
- if (hasNormalChats())
- Container(
- padding: const EdgeInsets.all(6.0),
- decoration: new BoxDecoration(
- color: context.theme.accentColor,
- borderRadius: new BorderRadius.only(
- topLeft: const Radius.circular(20.0),
- topRight: const Radius.circular(20.0),
- bottomLeft: const Radius.circular(20.0),
- bottomRight: const Radius.circular(20.0),
- )),
- child: ListView.builder(
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- itemBuilder: (context, index) {
- return Obx(() {
- if (SettingsManager().settings.swipableConversationTiles.value) {
- return Dismissible(
- background: Obx(
- () => slideRightBackground(ChatBloc().chats.archivedHelper(showArchived)[index])),
- secondaryBackground: Obx(
- () => slideLeftBackground(ChatBloc().chats.archivedHelper(showArchived)[index])),
- // Each Dismissible must contain a Key. Keys allow Flutter to
- // uniquely identify widgets.
- key: UniqueKey(),
- // Provide a function that tells the app
- // what to do after an item has been swiped away.
- onDismissed: (direction) async {
- if (direction == DismissDirection.endToStart) {
- if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.pin) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .togglePin(!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!);
- EventDispatcher().emit("refresh", null);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.alerts) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .toggleMute(!ChatBloc().chats.archivedHelper(showArchived)[index].isMuted!);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.delete) {
- ChatBloc().deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- Chat.deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else if (SettingsManager().settings.materialLeftAction.value ==
- MaterialSwipeAction.mark_read) {
- ChatBloc().toggleChatUnread(
- ChatBloc().chats.archivedHelper(showArchived)[index],
- !ChatBloc().chats.archivedHelper(showArchived)[index].hasUnreadMessage!);
- } else {
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!) {
- ChatBloc()
- .unArchiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- ChatBloc().archiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- }
- }
- } else {
- if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.pin) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .togglePin(!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!);
- EventDispatcher().emit("refresh", null);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.alerts) {
- await ChatBloc()
- .chats
- .archivedHelper(showArchived)[index]
- .toggleMute(!ChatBloc().chats.archivedHelper(showArchived)[index].isMuted!);
- if (this.mounted) setState(() {});
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.delete) {
- ChatBloc().deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- Chat.deleteChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else if (SettingsManager().settings.materialRightAction.value ==
- MaterialSwipeAction.mark_read) {
- ChatBloc().toggleChatUnread(
- ChatBloc().chats.archivedHelper(showArchived)[index],
- !ChatBloc().chats.archivedHelper(showArchived)[index].hasUnreadMessage!);
- } else {
- if (ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!) {
- ChatBloc()
- .unArchiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- ChatBloc().archiveChat(ChatBloc().chats.archivedHelper(showArchived)[index]);
- }
- }
- }
- },
- child: (!showArchived &&
- ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- ? Container()
- : (showArchived &&
- !ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- ? Container()
- : (!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!)
- ? ConversationTile(
- key: UniqueKey(),
- chat: ChatBloc().chats.archivedHelper(showArchived)[index],
- inSelectMode: selected.isNotEmpty,
- selected: selected,
- onSelect: (bool selected) {
- if (selected) {
- this
- .selected
- .add(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- this.selected.removeWhere((element) =>
- element.guid ==
- ChatBloc().chats.archivedHelper(showArchived)[index].guid);
- }
-
- if (this.mounted) setState(() {});
- },
- )
- : Container(),
- );
- } else {
- if (!showArchived && ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- return Container();
- if (showArchived && !ChatBloc().chats.archivedHelper(showArchived)[index].isArchived!)
- return Container();
- if (!ChatBloc().chats.archivedHelper(showArchived)[index].isPinned!) {
- return ConversationTile(
- key: UniqueKey(),
- chat: ChatBloc().chats.archivedHelper(showArchived)[index],
- inSelectMode: selected.isNotEmpty,
- selected: selected,
- onSelect: (bool selected) {
- if (selected) {
- this.selected.add(ChatBloc().chats.archivedHelper(showArchived)[index]);
- } else {
- this.selected.removeWhere((element) =>
- element.guid == ChatBloc().chats.archivedHelper(showArchived)[index].guid);
- }
-
- if (this.mounted) setState(() {});
- },
- );
- }
- return Container();
- }
- });
- },
- itemCount: ChatBloc().chats.archivedHelper(showArchived).length,
- ),
- )
- ],
- ),
- );
- }),
- floatingActionButton: selected.isEmpty && !SettingsManager().settings.moveChatCreatorToHeader.value
- ? widget.parent.buildFloatingActionButton()
- : null,
- ),
- ),
- ),
+ return ThemeSwitcher(
+ iOSSkin: CupertinoConversationList(parent: this),
+ materialSkin: MaterialConversationList(parent: this),
+ samsungSkin: SamsungConversationList(parent: this),
);
}
}
diff --git a/lib/layouts/conversation_list/conversation_tile.dart b/lib/layouts/conversation_list/conversation_tile.dart
index 49626b8b2..0e57b76c7 100644
--- a/lib/layouts/conversation_list/conversation_tile.dart
+++ b/lib/layouts/conversation_list/conversation_tile.dart
@@ -6,6 +6,8 @@ import 'package:assorted_layout_widgets/assorted_layout_widgets.dart';
import 'package:bluebubbles/blocs/chat_bloc.dart';
import 'package:bluebubbles/helpers/constants.dart';
import 'package:bluebubbles/helpers/hex_color.dart';
+import 'package:bluebubbles/helpers/logger.dart';
+import 'package:bluebubbles/helpers/navigator.dart';
import 'package:bluebubbles/helpers/socket_singletons.dart';
import 'package:bluebubbles/helpers/utils.dart';
import 'package:bluebubbles/layouts/conversation_view/conversation_view.dart';
@@ -34,6 +36,7 @@ class ConversationTile extends StatefulWidget {
final Function(bool)? onSelect;
final bool inSelectMode;
final List selected;
+ final Widget? subtitle;
ConversationTile({
Key? key,
@@ -43,6 +46,7 @@ class ConversationTile extends StatefulWidget {
this.onSelect,
this.inSelectMode = false,
this.selected = const [],
+ this.subtitle,
}) : super(key: key);
@override
@@ -94,7 +98,7 @@ class _ConversationTileState extends State with AutomaticKeepA
try {
await fetchChatSingleton(widget.chat.guid!);
} catch (ex) {
- debugPrint(ex.toString());
+ Logger.error(ex.toString());
}
this.setNewChatData(forceUpdate: true);
@@ -135,16 +139,14 @@ class _ConversationTileState extends State with AutomaticKeepA
if (widget.inSelectMode && widget.onSelect != null) {
onSelect();
} else {
- Navigator.of(context).push(
- CupertinoPageRoute(
- builder: (BuildContext context) {
- return ConversationView(
- chat: widget.chat,
- existingAttachments: widget.existingAttachments,
- existingText: widget.existingText,
- );
- },
+ CustomNavigator.pushAndRemoveUntil(
+ context,
+ ConversationView(
+ chat: widget.chat,
+ existingAttachments: widget.existingAttachments,
+ existingText: widget.existingText,
),
+ (route) => route.isFirst,
);
}
}
@@ -163,7 +165,7 @@ class _ConversationTileState extends State with AutomaticKeepA
caption: widget.chat.isPinned! ? 'Unpin' : 'Pin',
color: Colors.yellow[800],
foregroundColor: Colors.white,
- icon: widget.chat.isPinned! ? Icons.star_outline : Icons.star,
+ icon: widget.chat.isPinned! ? CupertinoIcons.pin_slash : CupertinoIcons.pin,
onTap: () async {
await widget.chat.togglePin(!widget.chat.isPinned!);
EventDispatcher().emit("refresh", null);
@@ -174,11 +176,11 @@ class _ConversationTileState extends State with AutomaticKeepA
secondaryActions: [
if (!widget.chat.isArchived! && SettingsManager().settings.iosShowAlert.value)
IconSlideAction(
- caption: widget.chat.isMuted! ? 'Show Alerts' : 'Hide Alerts',
+ caption: widget.chat.muteType == "mute" ? 'Show Alerts' : 'Hide Alerts',
color: Colors.purple[700],
- icon: widget.chat.isMuted! ? Icons.notifications_active : Icons.notifications_off,
+ icon: widget.chat.muteType == "mute" ? CupertinoIcons.bell : CupertinoIcons.bell_slash,
onTap: () async {
- await widget.chat.toggleMute(!widget.chat.isMuted!);
+ await widget.chat.toggleMute(widget.chat.muteType != "mute");
if (this.mounted) setState(() {});
},
),
@@ -186,7 +188,7 @@ class _ConversationTileState extends State with AutomaticKeepA
IconSlideAction(
caption: "Delete",
color: Colors.red,
- icon: Icons.delete_forever,
+ icon: CupertinoIcons.trash,
onTap: () async {
ChatBloc().deleteChat(widget.chat);
Chat.deleteChat(widget.chat);
@@ -196,7 +198,7 @@ class _ConversationTileState extends State with AutomaticKeepA
IconSlideAction(
caption: widget.chat.hasUnreadMessage! ? 'Mark Read' : 'Mark Unread',
color: Colors.blue,
- icon: widget.chat.hasUnreadMessage! ? Icons.mark_chat_read : Icons.mark_chat_unread,
+ icon: widget.chat.hasUnreadMessage! ? CupertinoIcons.person_crop_circle_badge_checkmark : CupertinoIcons.person_crop_circle_badge_exclam,
onTap: () {
ChatBloc().toggleChatUnread(widget.chat, !widget.chat.hasUnreadMessage!);
},
@@ -205,7 +207,7 @@ class _ConversationTileState extends State with AutomaticKeepA
IconSlideAction(
caption: widget.chat.isArchived! ? 'UnArchive' : 'Archive',
color: widget.chat.isArchived! ? Colors.blue : Colors.red,
- icon: widget.chat.isArchived! ? Icons.unarchive : Icons.archive,
+ icon: widget.chat.isArchived! ? CupertinoIcons.tray_arrow_up : CupertinoIcons.tray_arrow_down,
onTap: () {
if (widget.chat.isArchived!) {
ChatBloc().unArchiveChat(widget.chat);
@@ -235,89 +237,88 @@ class _ConversationTileState extends State with AutomaticKeepA
}
Widget buildSubtitle() {
- return StreamBuilder