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
15 changes: 15 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("com.diffplug.spotless") version "7.2.1"
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
id("com.getkeepsafe.dexcount") // For APK analysis
Expand Down Expand Up @@ -87,6 +88,11 @@ android {
// Disable Crashlytics in debug builds
buildConfigField("boolean", "ENABLE_CRASHLYTICS", "false")

// Disable Crashlytics collection for debug builds
// configure<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
// mappingFileUploadEnabled = false
// }

proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
Expand All @@ -103,6 +109,11 @@ android {
// Enable Crashlytics in release builds
buildConfigField("boolean", "ENABLE_CRASHLYTICS", "true")

// Enable Crashlytics collection for release builds
// configure<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
// mappingFileUploadEnabled = true
// }

// ProGuard/R8 configuration with optimization
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand Down Expand Up @@ -312,6 +323,10 @@ dependencies {
// Firebase App Check for security
implementation("com.google.firebase:firebase-appcheck-playintegrity:17.1.1")
implementation("com.google.firebase:firebase-appcheck-debug:17.1.1")

// Firebase Crashlytics for crash reporting (only in release builds)
implementation("com.google.firebase:firebase-crashlytics:19.0.3")
implementation("com.google.firebase:firebase-analytics:22.1.2")

// --- Google Services ---
implementation(libs.gms.play.services.auth)
Expand Down
99 changes: 99 additions & 0 deletions app/src/main/java/com/example/partymaker/PartyApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.google.firebase.appcheck.FirebaseAppCheck;
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory;
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.example.partymaker.utils.media.ImageOptimizationManager;

/** Application class for PartyMaker. Initializes repositories and other app-wide components. */
public class PartyApplication extends Application {
Expand Down Expand Up @@ -48,6 +50,9 @@ public void onCreate() {

// Initialize Firebase App Check for security
initializeAppCheck();

// Initialize Crashlytics
initializeCrashlytics();

// Initialize Firebase references
DBRef.init();
Expand Down Expand Up @@ -93,6 +98,13 @@ public void onCreate() {
// Log memory info
Log.d(TAG, "Initial memory usage: " + MemoryManager.getDetailedMemoryInfo());

// Setup global exception handler
setupGlobalExceptionHandler();

// Optimize image loading configuration
ImageOptimizationManager.optimizeGlideConfiguration(this);
Log.d(TAG, "Image loading optimized");

// End application initialization timing
PerformanceMonitor.trackMemoryUsage("Application.onCreate.end");
PerformanceMonitor.endTiming("Application.onCreate");
Expand Down Expand Up @@ -173,6 +185,91 @@ private void initializeAppCheck() {
}
}

/**
* Initializes Firebase Crashlytics for crash reporting.
*/
private void initializeCrashlytics() {
try {
FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();

// Enable/disable based on build type
crashlytics.setCrashlyticsCollectionEnabled(BuildConfig.ENABLE_CRASHLYTICS);

if (BuildConfig.ENABLE_CRASHLYTICS) {
Log.d(TAG, "Crashlytics enabled for crash reporting");

// Set user properties
crashlytics.setUserId("user_" + System.currentTimeMillis());
crashlytics.setCustomKey("app_version", BuildConfig.VERSION_NAME);
crashlytics.setCustomKey("debug_mode", BuildConfig.DEBUG);

// Test log (only in debug)
if (BuildConfig.DEBUG) {
crashlytics.log("PartyApplication initialized successfully");
}
} else {
Log.d(TAG, "Crashlytics disabled for debug builds");
}

} catch (Exception e) {
Log.e(TAG, "Failed to initialize Crashlytics", e);
}
}

/**
* Sets up global exception handler for uncaught exceptions.
*/
private void setupGlobalExceptionHandler() {
// Store the original handler before replacing it
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
Log.e(TAG, "Uncaught exception in thread: " + thread.getName(), throwable);

// Log crash information
Log.e(TAG, "Crash details - Message: " + throwable.getMessage());
Log.e(TAG, "Crash details - Cause: " + (throwable.getCause() != null ? throwable.getCause().toString() : "None"));

// Send to Crashlytics if enabled
try {
if (BuildConfig.ENABLE_CRASHLYTICS) {
FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
crashlytics.recordException(throwable);
crashlytics.log("Uncaught exception in thread: " + thread.getName());
}
} catch (Exception e) {
Log.e(TAG, "Failed to report crash to Crashlytics", e);
}

// In debug builds, show additional information
if (BuildConfig.DEBUG) {
Log.e(TAG, "Stack trace: ", throwable);
Log.e(TAG, "Thread state: " + thread.getState());
Log.e(TAG, "Memory info: " + MemoryManager.getDetailedMemoryInfo());
}

// Attempt graceful cleanup
try {
MemoryManager.getInstance().emergencyCleanup();
} catch (Exception e) {
Log.e(TAG, "Error during emergency cleanup", e);
}

// Call the original handler to terminate the app
if (originalHandler != null) {
originalHandler.uncaughtException(thread, throwable);
} else {
// Fallback termination
System.exit(1);
}
}
});

Log.d(TAG, "Global exception handler configured");
}

/**
* Sets up memory monitoring for debug builds.
*/
Expand Down Expand Up @@ -216,5 +313,7 @@ public void onLowMemory() {
public void onTrimMemory(int level) {
super.onTrimMemory(level);
MemoryManager.getInstance().emergencyCleanup();
// Also trim image cache memory
ImageOptimizationManager.trimMemory(this, level);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public GroupAdapter(Context context, OnGroupClickListener listener) {
@Override
public GroupViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_group, parent, false);
// Reduce overdraw by setting background only where needed
view.setWillNotDraw(false);
// Disable child clipping for better performance
if (view instanceof ViewGroup) {
((ViewGroup) view).setClipChildren(false);
((ViewGroup) view).setClipToPadding(false);
}
return new GroupViewHolder(view);
}

Expand All @@ -67,6 +74,28 @@ public void onBindViewHolder(@NonNull GroupViewHolder holder, int position) {
// Items will appear immediately without animation interference
}
}

@Override
public void onBindViewHolder(@NonNull GroupViewHolder holder, int position, @NonNull java.util.List<Object> payloads) {
if (payloads.isEmpty()) {
// Full bind
onBindViewHolder(holder, position);
} else {
// Partial bind for better performance - only update what changed
Group group = getItem(position);
if (group != null) {
for (Object payload : payloads) {
if ("name".equals(payload)) {
holder.setGroupName(group);
} else if ("date".equals(payload)) {
holder.setGroupDate(group);
} else if ("image".equals(payload)) {
holder.loadGroupImage(group.getGroupKey());
}
}
}
}
}

/**
* Ensures view is in completely normal state to prevent spacing issues
Expand Down Expand Up @@ -135,6 +164,21 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
groupNameTextView = itemView.findViewById(R.id.tvGroupName);
groupDateTextView = itemView.findViewById(R.id.tvGroupDate);
groupImageView = itemView.findViewById(R.id.imgGroupPicture);

// Optimize text views to reduce GPU operations
if (groupNameTextView != null) {
groupNameTextView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
groupNameTextView.setIncludeFontPadding(false);
}
if (groupDateTextView != null) {
groupDateTextView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
groupDateTextView.setIncludeFontPadding(false);
}
// Image view uses software layer for static images
if (groupImageView != null) {
groupImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

setupClickListeners();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import com.example.partymaker.ui.base.BaseActivity;
import androidx.lifecycle.ViewModelProvider;
import com.example.partymaker.R;
import com.example.partymaker.data.api.NetworkManager;
Expand Down Expand Up @@ -53,7 +53,7 @@
* @version 2.0
* @since 1.0
*/
public class LoginActivity extends AppCompatActivity {
public class LoginActivity extends BaseActivity {

private static final class Config {
static final String LOG_TAG = "LoginActivity";
Expand Down Expand Up @@ -612,4 +612,30 @@ void clearAnimations() {
}
}
}

@Override
protected void clearActivityReferences() {
// Clear all UI references
// Clear UI references
emailEditText = null;
passwordEditText = null;
loginButton = null;
registerButton = null;
resetPasswordButton = null;
googleSignInButton = null;
progressBar = null;
rememberMeCheckbox = null;
aboutButton = null;
rootView = null;

// Clear ViewModels and other objects
authViewModel = null;
firebaseAuth = null;

// Clear animation manager
if (animationManager != null) {
animationManager.clearAnimations();
animationManager = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import com.example.partymaker.ui.base.BaseActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
Expand All @@ -48,7 +48,7 @@
* Activity for user registration, including form validation, animations, and notifications. Handles
* user input, password strength, and registration logic.
*/
public class RegisterActivity extends AppCompatActivity {
public class RegisterActivity extends BaseActivity {
/** Notification channel ID. */
private static final String CHANNEL_ID = "registration_channel";

Expand Down Expand Up @@ -839,4 +839,29 @@ private void sendSuccessNotification(String username) {
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, builder.build());
}

@Override
protected void clearActivityReferences() {
// Clear UI references
tilEmail = null;
tilUsername = null;
tilPassword = null;
etEmail = null;
etUsername = null;
etPassword = null;
btnRegister = null;
btnPress = null;
imgRegister = null;
headerCard = null;
formCard = null;
celebrationLayout = null;
passwordStrengthBar = null;
passwordStrengthText = null;
formProgressText = null;
progressIndicator = null;
progressBar = null;

// Clear ViewModels
authViewModel = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import com.example.partymaker.ui.base.BaseActivity;
import androidx.lifecycle.ViewModelProvider;
import com.example.partymaker.R;
import com.example.partymaker.viewmodel.auth.ResetPasswordViewModel;
Expand All @@ -39,7 +39,7 @@
* @version 2.0
* @since 1.0
*/
public class ResetPasswordActivity extends AppCompatActivity {
public class ResetPasswordActivity extends BaseActivity {

private static final class Config {
static final String LOG_TAG = "ResetPasswordActivity";
Expand Down Expand Up @@ -261,6 +261,28 @@ private void cleanupResources() {
resetPasswordViewModel = null;
}

@Override
protected void clearActivityReferences() {
// Clear UI components
lightThemeButton = null;
darkThemeButton = null;
resetPasswordButton = null;
helpButton = null;
hideInstructionsButton = null;
emailInputField = null;
resetLayout = null;
forgotPasswordTextView = null;
helpTextView = null;
instructionsTextView = null;
hideInstructionsTextView = null;
cakeImageView = null;

// Clear dependencies and managers
resetPasswordViewModel = null;
themeManager = null;
emailValidator = null;
}

// Inner Classes and Text Watcher
private class EmailValidationTextWatcher implements TextWatcher {
@Override
Expand Down
Loading
Loading