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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions app/src/org/commcare/adapters/ConnectMessageAdapter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.commcare.adapters;

import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.ViewGroup;

Expand All @@ -9,9 +10,9 @@
import org.commcare.dalvik.databinding.ItemChatLeftViewBinding;
import org.commcare.dalvik.databinding.ItemChatRightViewBinding;
import org.commcare.fragments.connectMessaging.ConnectMessageChatData;
import org.commcare.utils.MarkupUtil;
import org.javarosa.core.model.utils.DateUtils;

import java.util.ArrayList;
import java.util.List;

public class ConnectMessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Expand All @@ -38,7 +39,10 @@ public LeftViewHolder(ItemChatLeftViewBinding binding) {
}

public void bind(ConnectMessageChatData chat) {
binding.tvChatMessage.setText(chat.getMessage());
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(chat.getMessage());
MarkupUtil.setMarkdown(binding.tvChatMessage, builder, new SpannableStringBuilder());

binding.tvUserName.setText(DateUtils.formatDateTime(chat.getTimestamp(), DateUtils.FORMAT_HUMAN_READABLE_SHORT));
}
}
Expand All @@ -52,7 +56,10 @@ public RightViewHolder(ItemChatRightViewBinding binding) {
}

public void bind(ConnectMessageChatData chat) {
binding.tvChatMessage.setText(chat.getMessage());
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(chat.getMessage());
MarkupUtil.setMarkdown(binding.tvChatMessage, builder, new SpannableStringBuilder());

binding.tvUserName.setText(DateUtils.formatDateTime(chat.getTimestamp(), DateUtils.FORMAT_HUMAN_READABLE_SHORT));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
package org.commcare.fragments.connectMessaging;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import org.commcare.adapters.ChannelAdapter;
import org.commcare.android.database.connect.models.ConnectMessagingChannelRecord;
import org.commcare.connect.ConnectDatabaseHelper;
import org.commcare.connect.MessageManager;
import org.commcare.dalvik.databinding.FragmentChannelListBinding;
import org.commcare.services.CommCareFirebaseMessagingService;

import java.util.List;

public class ConnectMessageChannelListFragment extends Fragment {

public static boolean isActive;
private FragmentChannelListBinding binding;
private ChannelAdapter channelAdapter;

Expand Down Expand Up @@ -64,11 +70,30 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
@Override
public void onResume() {
super.onResume();
isActive = true;

LocalBroadcastManager.getInstance(requireContext()).registerReceiver(updateReceiver,
new IntentFilter(CommCareFirebaseMessagingService.MESSAGING_UPDATE_BROADCAST));

Comment on lines +73 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid duplicate message retrieval calls.

MessageManager.retrieveMessages() is called in both onCreateView and onResume. This could lead to unnecessary network calls and potential race conditions. Consider consolidating these calls or implementing a debounce mechanism.

 @Override
 public void onResume() {
     super.onResume();
     isActive = true;
     LocalBroadcastManager.getInstance(requireContext()).registerReceiver(updateReceiver,
             new IntentFilter(CommCareFirebaseMessagingService.MESSAGING_UPDATE_BROADCAST));
-    MessageManager.retrieveMessages(requireActivity(), success -> {
-        refreshUi();
-    });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isActive = true;
LocalBroadcastManager.getInstance(requireContext()).registerReceiver(updateReceiver,
new IntentFilter(CommCareFirebaseMessagingService.MESSAGING_UPDATE_BROADCAST));
isActive = true;
LocalBroadcastManager.getInstance(requireContext()).registerReceiver(updateReceiver,
new IntentFilter(CommCareFirebaseMessagingService.MESSAGING_UPDATE_BROADCAST));

MessageManager.retrieveMessages(requireActivity(), success -> {
refreshUi();
});
}

@Override
public void onPause() {
super.onPause();
isActive = false;
LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(updateReceiver);
}

private final BroadcastReceiver updateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refreshUi();
}
};

private void selectChannel(ConnectMessagingChannelRecord channel) {
NavDirections directions;
if(channel.getConsented()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package org.commcare.fragments.connectMessaging;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.RecyclerView;

import org.commcare.adapters.ConnectMessageAdapter;
Expand All @@ -20,14 +23,15 @@
import org.commcare.connect.ConnectDatabaseHelper;
import org.commcare.connect.MessageManager;
import org.commcare.dalvik.databinding.FragmentConnectMessageBinding;
import org.commcare.services.CommCareFirebaseMessagingService;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

public class ConnectMessageFragment extends Fragment {

public static String activeChannel;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider alternative to static variable for tracking active channel.

Using a static variable to track the active channel state could lead to memory leaks and issues during configuration changes. Consider using a ViewModel or other lifecycle-aware component to manage this state.

Here's a suggested implementation using ViewModel:

class ConnectMessageViewModel : ViewModel() {
    private val _activeChannel = MutableLiveData<String?>()
    val activeChannel: LiveData<String?> = _activeChannel

    fun setActiveChannel(channelId: String?) {
        _activeChannel.value = channelId
    }
}

Usage in fragment:

-public static String activeChannel;
+private ConnectMessageViewModel viewModel;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
+   viewModel = new ViewModelProvider(this).get(ConnectMessageViewModel.class);
}

private String channelId;
private FragmentConnectMessageBinding binding;
private ConnectMessageAdapter adapter;
Expand Down Expand Up @@ -63,17 +67,33 @@ public void run() {
@Override
public void onResume() {
super.onResume();
activeChannel = channelId;

LocalBroadcastManager.getInstance(requireContext()).registerReceiver(updateReceiver,
new IntentFilter(CommCareFirebaseMessagingService.MESSAGING_UPDATE_BROADCAST));

// Start periodic API calls
handler.post(apiCallRunnable);
}

@Override
public void onPause() {
super.onPause();
activeChannel = null;

LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(updateReceiver);

// Stop the periodic API calls when the screen is not active
handler.removeCallbacks(apiCallRunnable);
}

private final BroadcastReceiver updateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refreshUi();
}
};

private void makeApiCall() {
MessageManager.retrieveMessages(requireActivity(), success -> {
refreshUi();
Expand Down
132 changes: 78 additions & 54 deletions app/src/org/commcare/services/CommCareFirebaseMessagingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.os.Build;

import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
Expand All @@ -21,6 +22,8 @@
import org.commcare.connect.ConnectDatabaseHelper;
import org.commcare.connect.MessageManager;
import org.commcare.dalvik.R;
import org.commcare.fragments.connectMessaging.ConnectMessageChannelListFragment;
import org.commcare.fragments.connectMessaging.ConnectMessageFragment;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.sync.FirebaseMessagingDataSyncer;
import org.commcare.util.LogTypes;
Expand All @@ -37,6 +40,7 @@
public class CommCareFirebaseMessagingService extends FirebaseMessagingService {

private final static int FCM_NOTIFICATION = R.string.fcm_notification;
public static final String MESSAGING_UPDATE_BROADCAST = "com.dimagi.messaging.update";
public static final String OPPORTUNITY_ID = "opportunity_id";
public static final String PAYMENT_ID = "payment_id";
public static final String PAYMENT_STATUS = "payment_status";
Expand All @@ -62,7 +66,8 @@ enum ActionTypes {
*/
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Logger.log(LogTypes.TYPE_FCM, "CommCareFirebaseMessagingService Message received: " + remoteMessage.getData());
Logger.log(LogTypes.TYPE_FCM, "CommCareFirebaseMessagingService Message received: " +
remoteMessage.getData());
Map<String, String> payloadData = remoteMessage.getData();

// Check if the message contains a data object, there is no further action if not
Expand Down Expand Up @@ -96,9 +101,8 @@ public void onNewToken(String token) {
private void showNotification(Map<String, String> payloadData) {
String notificationTitle = payloadData.get("title");
String notificationText = payloadData.get("body");
NotificationManager mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

Intent intent;
Intent intent = null;
String action = payloadData.get("action");

if (hasCccAction(action)) {
Expand All @@ -112,14 +116,12 @@ private void showNotification(Map<String, String> payloadData) {
//On channels page (just update the page)
//On message page for the active channel

intent = new Intent(getApplicationContext(), ConnectMessagingActivity.class);
intent.putExtra("action", action);

int notificationTitleId;
String notificationMessage;
String channelId;
if(isMessage) {
ConnectMessagingMessageRecord message = MessageManager.handleReceivedMessage(this, payloadData);
ConnectMessagingMessageRecord message = MessageManager.handleReceivedMessage(this,
payloadData);

if(message == null) {
Logger.log(LogTypes.TYPE_FCM, "Ignoring message without known consented channel: " +
Expand All @@ -128,26 +130,40 @@ private void showNotification(Map<String, String> payloadData) {
return;
}

ConnectMessagingChannelRecord channel = ConnectDatabaseHelper.getMessagingChannel(this, message.getChannelId());
ConnectMessagingChannelRecord channel = ConnectDatabaseHelper.getMessagingChannel(this,
message.getChannelId());

notificationTitleId = R.string.connect_messaging_message_notification_title;
notificationMessage = getString(R.string.connect_messaging_message_notification_message, channel.getChannelName());
notificationMessage = getString(R.string.connect_messaging_message_notification_message,
channel.getChannelName());

channelId = message.getChannelId();
} else {
//Channel
ConnectMessagingChannelRecord channel = MessageManager.handleReceivedChannel(this, payloadData);
ConnectMessagingChannelRecord channel = MessageManager.handleReceivedChannel(this,
payloadData);

notificationTitleId = R.string.connect_messaging_channel_notification_title;
notificationMessage = getString(R.string.connect_messaging_channel_notification_message, channel.getChannelName());
notificationMessage = getString(R.string.connect_messaging_channel_notification_message,
channel.getChannelName());

channelId = channel.getChannelId();
}

notificationTitle = getString(notificationTitleId);
notificationText = notificationMessage;
if(ConnectMessageChannelListFragment.isActive ||
channelId.equals(ConnectMessageFragment.activeChannel)) {
//Notify active page to update instead of showing notification
Intent broadcastIntent = new Intent(MESSAGING_UPDATE_BROADCAST);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
} else {
Comment on lines +153 to +158
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include channel information in the broadcast intent.

The broadcast intent should include the channel ID to allow receivers to verify if they need to update their UI.

 if(ConnectMessageChannelListFragment.isActive ||
         channelId.equals(ConnectMessageFragment.activeChannel)) {
     //Notify active page to update instead of showing notification
     Intent broadcastIntent = new Intent(MESSAGING_UPDATE_BROADCAST);
+    broadcastIntent.putExtra("channel_id", channelId);
+    broadcastIntent.putExtra("update_type", isMessage ? "message" : "channel");
     LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
 }

Then update the receiver in ConnectMessageFragment:

 private final BroadcastReceiver updateReceiver = new BroadcastReceiver() {
     @Override
     public void onReceive(Context context, Intent intent) {
+        String updatedChannelId = intent.getStringExtra("channel_id");
+        if (updatedChannelId != null && updatedChannelId.equals(channelId)) {
             refreshUi();
+        }
     }
 };

Committable suggestion skipped: line range outside the PR's diff.

//Show push notification
notificationTitle = getString(notificationTitleId);
notificationText = notificationMessage;

intent.putExtra(ConnectMessagingMessageRecord.META_MESSAGE_CHANNEL_ID, channelId);
intent = new Intent(getApplicationContext(), ConnectMessagingActivity.class);
intent.putExtra("action", action);
intent.putExtra(ConnectMessagingMessageRecord.META_MESSAGE_CHANNEL_ID, channelId);
}
} else {
//Intent for ConnectActivity
intent = new Intent(getApplicationContext(), ConnectActivity.class);
Expand All @@ -158,54 +174,62 @@ private void showNotification(Map<String, String> payloadData) {
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
: PendingIntent.FLAG_UPDATE_CURRENT;

PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, flags);

NotificationCompat.Builder fcmNotification = new NotificationCompat.Builder(this,
CommCareNoficationManager.NOTIFICATION_CHANNEL_PUSH_NOTIFICATIONS_ID)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.commcare_actionbar_logo)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis());

// Check if the payload action is CCC_PAYMENTS
if (action.equals(ConnectConstants.CCC_DEST_PAYMENTS)) {
// Yes button intent with payment_id from payload
Intent yesIntent = new Intent(this, PaymentAcknowledgeReceiver.class);
yesIntent.putExtra(OPPORTUNITY_ID,payloadData.get(OPPORTUNITY_ID));
yesIntent.putExtra(PAYMENT_ID,payloadData.get(PAYMENT_ID));
yesIntent.putExtra(PAYMENT_STATUS,true);
PendingIntent yesPendingIntent = PendingIntent.getBroadcast(this, 1, yesIntent, flags);

// No button intent with payment_id from payload
Intent noIntent = new Intent(this, PaymentAcknowledgeReceiver.class);
noIntent.putExtra(OPPORTUNITY_ID,payloadData.get(OPPORTUNITY_ID));
noIntent.putExtra(PAYMENT_ID,payloadData.get(PAYMENT_ID));
noIntent.putExtra(PAYMENT_STATUS,false);
PendingIntent noPendingIntent = PendingIntent.getBroadcast(this, 2, noIntent, flags);

// Add Yes & No action button to the notification
fcmNotification.addAction(0, getString(R.string.connect_payment_acknowledge_notification_yes), yesPendingIntent);
fcmNotification.addAction(0, getString(R.string.connect_payment_acknowledge_notification_no), noPendingIntent);
}

mNM.notify(FCM_NOTIFICATION, fcmNotification.build());
if(intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP |
Intent.FLAG_ACTIVITY_NEW_TASK);

int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
: PendingIntent.FLAG_UPDATE_CURRENT;

PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, flags);

NotificationCompat.Builder fcmNotification = new NotificationCompat.Builder(this,
CommCareNoficationManager.NOTIFICATION_CHANNEL_PUSH_NOTIFICATIONS_ID)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.commcare_actionbar_logo)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis());

// Check if the payload action is CCC_PAYMENTS
if (action.equals(ConnectConstants.CCC_DEST_PAYMENTS)) {
// Yes button intent with payment_id from payload
Intent yesIntent = new Intent(this, PaymentAcknowledgeReceiver.class);
yesIntent.putExtra(OPPORTUNITY_ID, payloadData.get(OPPORTUNITY_ID));
yesIntent.putExtra(PAYMENT_ID, payloadData.get(PAYMENT_ID));
yesIntent.putExtra(PAYMENT_STATUS, true);
PendingIntent yesPendingIntent = PendingIntent.getBroadcast(this, 1,
yesIntent, flags);

// No button intent with payment_id from payload
Intent noIntent = new Intent(this, PaymentAcknowledgeReceiver.class);
noIntent.putExtra(OPPORTUNITY_ID, payloadData.get(OPPORTUNITY_ID));
noIntent.putExtra(PAYMENT_ID, payloadData.get(PAYMENT_ID));
noIntent.putExtra(PAYMENT_STATUS, false);
PendingIntent noPendingIntent = PendingIntent.getBroadcast(this, 2,
noIntent, flags);

// Add Yes & No action button to the notification
fcmNotification.addAction(0, getString(R.string.connect_payment_acknowledge_notification_yes), yesPendingIntent);
fcmNotification.addAction(0, getString(R.string.connect_payment_acknowledge_notification_no), noPendingIntent);
}

NotificationManager mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNM.notify(FCM_NOTIFICATION, fcmNotification.build());
}
}

private boolean hasCccAction(String action) {
return action != null && action.contains("ccc_");
}

public static void clearNotification(Context context){
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.cancel(R.string.fcm_notification);
}
Expand Down