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
61 changes: 59 additions & 2 deletions app/src/main/java/com/example/partymaker/PartyApplication.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.example.partymaker;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.example.partymaker.BuildConfig;
import com.example.partymaker.data.api.ConnectivityManager;
import com.example.partymaker.data.api.FirebaseServerClient;
import com.example.partymaker.data.api.NetworkManager;
Expand All @@ -16,10 +21,12 @@
/** Application class for PartyMaker. Initializes repositories and other app-wide components. */
public class PartyApplication extends Application {
private static final String TAG = "PartyApplication";
private static PartyApplication instance;

@Override
public void onCreate() {
super.onCreate();
instance = this;

// Apply saved theme preference
ServerSettingsActivity.applyThemeFromPreferences(this);
Expand Down Expand Up @@ -62,6 +69,14 @@ public void onCreate() {
// Initialize repositories
initializeRepositories();

// Initialize memory management
MemoryManager.getInstance();

// Setup memory monitoring in debug builds
if (BuildConfig.DEBUG) {
setupMemoryMonitoring();
}

// Log memory info
Log.d(TAG, "Initial memory usage: " + MemoryManager.getDetailedMemoryInfo());

Expand Down Expand Up @@ -107,13 +122,55 @@ public void onTerminate() {
Log.d(TAG, "Application terminated");
}

/**
* Gets the singleton instance of the application.
*/
public static PartyApplication getInstance() {
return instance;
}

/**
* Sets up memory monitoring for debug builds.
*/
private void setupMemoryMonitoring() {
// LeakCanary is automatically initialized

// Additional memory monitoring
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
Log.d("MemoryMonitor", "Activity created: " + activity.getClass().getSimpleName());
MemoryManager.getInstance().logMemoryStats();
}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {
Log.d("MemoryMonitor", "Activity destroyed: " + activity.getClass().getSimpleName());
MemoryManager.getInstance().logMemoryStats();
}

// Other lifecycle methods...
@Override public void onActivityStarted(@NonNull Activity activity) {}
@Override public void onActivityResumed(@NonNull Activity activity) {}
@Override public void onActivityPaused(@NonNull Activity activity) {}
@Override public void onActivityStopped(@NonNull Activity activity) {}
@Override public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {}
});
}

@Override
public void onLowMemory() {
super.onLowMemory();

// Perform memory cleanup
MemoryManager.performMemoryCleanup(this);
// Perform memory cleanup using enhanced memory manager
MemoryManager.getInstance().emergencyCleanup();

Log.d(TAG, "Low memory cleanup performed");
}

@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
MemoryManager.getInstance().emergencyCleanup();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.util.HashMap;
import java.util.Locale;
Expand All @@ -13,7 +14,14 @@
* Represents a chat message in the PartyMaker application. This class is annotated for Room
* database storage.
*/
@Entity(tableName = "chat_messages")
@Entity(tableName = "chat_messages",
indices = {
@Index(value = "groupKey", name = "idx_message_group"),
@Index(value = {"groupKey", "timestamp"}, name = "idx_message_group_time"),
@Index(value = "senderKey", name = "idx_message_sender"),
@Index(value = "timestamp", name = "idx_message_time"),
@Index(value = {"groupKey", "encrypted"}, name = "idx_message_group_encrypted")
})
public class ChatMessage {
/** The unique key for the message. */
@PrimaryKey
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/com/example/partymaker/data/model/Group.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.google.gson.annotations.SerializedName;
import java.util.HashMap;
Expand All @@ -12,7 +13,15 @@
* Represents a group (party) in the PartyMaker application. This class is annotated for Room
* database storage.
*/
@Entity(tableName = "groups")
@Entity(tableName = "groups",
indices = {
@Index(value = "created_at", name = "idx_group_created"),
@Index(value = "admin_key", name = "idx_group_admin"),
@Index(value = {"group_type", "created_at"}, name = "idx_group_type_created"),
@Index(value = "admin_key", name = "idx_group_user"),
@Index(value = {"admin_key", "group_type"}, name = "idx_group_user_type"),
@Index(value = "group_name", name = "idx_group_name") // For search
})
public class Group {

/** Constants for group types */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.google.firebase.database.PropertyName;
import java.util.HashMap;
Expand All @@ -14,7 +15,13 @@
* Represents a user in the PartyMaker application. This class is annotated for Room database
* storage.
*/
@Entity(tableName = "users")
@Entity(tableName = "users",
indices = {
@Index(value = "email", name = "idx_user_email", unique = true),
@Index(value = "username", name = "idx_user_name"),
@Index(value = "created_at", name = "idx_user_created"),
@Index(value = {"username", "email"}, name = "idx_user_search")
})
public class User {
/** The user's unique key. */
@PrimaryKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.example.partymaker.R;
import com.example.partymaker.data.model.ChatMessage;
Expand All @@ -15,36 +16,50 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

public class ChatRecyclerAdapter extends RecyclerView.Adapter<ChatRecyclerAdapter.MessageViewHolder> {

private final Context context;
private List<ChatMessage> messages;
private final List<ChatMessage> currentMessages = new ArrayList<>();
private String currentUserKey;

public ChatRecyclerAdapter(Context context) {
this.context = context;
this.messages = new ArrayList<>();
try {
currentUserKey = AuthenticationManager.getCurrentUserKey(context);
} catch (Exception e) {
currentUserKey = null;
}
}

public void updateMessages(List<ChatMessage> newMessages) {
List<ChatMessage> messages = newMessages != null ? newMessages : new ArrayList<>();
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new MessageDiffCallback(currentMessages, messages));
currentMessages.clear();
currentMessages.addAll(messages);
result.dispatchUpdatesTo(this);
}

/**
* @deprecated Use updateMessages() instead for better performance
*/
@Deprecated
public void setMessages(List<ChatMessage> messages) {
this.messages = messages != null ? messages : new ArrayList<>();
notifyDataSetChanged();
updateMessages(messages);
}

public void addMessage(ChatMessage message) {
messages.add(message);
notifyItemInserted(messages.size() - 1);
currentMessages.add(message);
notifyItemInserted(currentMessages.size() - 1);
}

public void clear() {
messages.clear();
notifyDataSetChanged();
int size = currentMessages.size();
if (size > 0) {
currentMessages.clear();
notifyItemRangeRemoved(0, size);
}
}

@NonNull
Expand All @@ -56,7 +71,7 @@ public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewT

@Override
public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
ChatMessage message = messages.get(position);
ChatMessage message = currentMessages.get(position);

// Get message text
String messageText = message.getMessage();
Expand Down Expand Up @@ -107,17 +122,77 @@ public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {

@Override
public int getItemCount() {
return messages.size();
return currentMessages.size();
}

@Override
public void onViewRecycled(@NonNull MessageViewHolder holder) {
super.onViewRecycled(holder);
// Clear text views to prevent showing old data during recycling
holder.clear();
}

/**
* DiffUtil callback for efficiently comparing chat messages
*/
private static class MessageDiffCallback extends DiffUtil.Callback {
private final List<ChatMessage> oldList;
private final List<ChatMessage> newList;

MessageDiffCallback(List<ChatMessage> oldList, List<ChatMessage> newList) {
this.oldList = oldList;
this.newList = newList;
}

@Override
public int getOldListSize() {
return oldList.size();
}

@Override
public int getNewListSize() {
return newList.size();
}

@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
ChatMessage oldMessage = oldList.get(oldItemPosition);
ChatMessage newMessage = newList.get(newItemPosition);

// Compare message IDs first
String oldId = oldMessage.getMessageKey();
String newId = newMessage.getMessageKey();
if (oldId != null && newId != null) {
return oldId.equals(newId);
}

// Fallback: compare timestamp and sender
return oldMessage.getTimestamp() == newMessage.getTimestamp() &&
Objects.equals(oldMessage.getSenderKey(), newMessage.getSenderKey());
}

@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
ChatMessage oldMessage = oldList.get(oldItemPosition);
ChatMessage newMessage = newList.get(newItemPosition);

return Objects.equals(oldMessage.getMessage(), newMessage.getMessage()) &&
Objects.equals(oldMessage.getMessageText(), newMessage.getMessageText()) &&
Objects.equals(oldMessage.getSenderName(), newMessage.getSenderName()) &&
Objects.equals(oldMessage.getMessageUser(), newMessage.getMessageUser()) &&
Objects.equals(oldMessage.getMessageTime(), newMessage.getMessageTime()) &&
oldMessage.getTimestamp() == newMessage.getTimestamp();
}
}

static class MessageViewHolder extends RecyclerView.ViewHolder {
View sentMessageLayout;
View receivedMessageLayout;
TextView tvSentMessage;
TextView tvSentTime;
TextView tvReceivedMessage;
TextView tvSenderName;
TextView tvReceivedTime;
final View sentMessageLayout;
final View receivedMessageLayout;
final TextView tvSentMessage;
final TextView tvSentTime;
final TextView tvReceivedMessage;
final TextView tvSenderName;
final TextView tvReceivedTime;

MessageViewHolder(@NonNull View itemView) {
super(itemView);
Expand All @@ -129,5 +204,18 @@ static class MessageViewHolder extends RecyclerView.ViewHolder {
tvSenderName = itemView.findViewById(R.id.tvSenderName);
tvReceivedTime = itemView.findViewById(R.id.tvReceivedTime);
}

/**
* Clears all text views to prevent showing old data during recycling
*/
void clear() {
tvSentMessage.setText("");
tvSentTime.setText("");
tvReceivedMessage.setText("");
tvSenderName.setText("");
tvReceivedTime.setText("");
sentMessageLayout.setVisibility(View.GONE);
receivedMessageLayout.setVisibility(View.GONE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public void btnSkipClick(View v) {
}

public void btnNextClick(View v) {
int current = getItem(PAGE_OFFSET);
int current = getItem();
if (current < SLIDE_LAYOUTS.length) {
viewPager.setCurrentItem(current);
} else {
Expand Down Expand Up @@ -180,11 +180,10 @@ private void setActiveDot(TextView[] dots, int currentPage) {
/**
* Returns the next item index for the ViewPager.
*
* @param i the offset
* @return the next item index
*/
private int getItem(int i) {
return viewPager.getCurrentItem() + i;
private int getItem() {
return viewPager.getCurrentItem() + IntroActivity.PAGE_OFFSET;
}

/** Launches the LoginActivity and finishes the intro. */
Expand Down
Loading
Loading