From f160f141d6d5beeba4c29e741718ea36681ebbc8 Mon Sep 17 00:00:00 2001 From: null Date: Mon, 6 Jul 2026 21:41:54 -0500 Subject: [PATCH] chore(gitignore): ignore .code-workspace; cleanup Firebase data source dead code --- .gitignore | 1 + .../data/remote/FirebaseStorageDataSource.kt | 80 ++++++------------- .../remote/FirestoreDateSwipeDataSource.kt | 34 ++------ 3 files changed, 32 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index 5bc5eb1b..46993701 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,4 @@ Future.md docs/strategy/positioning-vs-paired.md ClaudeQAPlan.md ClaudeReport.md +relationship-app.code-workspace diff --git a/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt b/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt index bd92a8cd..6ef65f4c 100644 --- a/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt +++ b/app/src/main/java/app/closer/data/remote/FirebaseStorageDataSource.kt @@ -7,12 +7,10 @@ import android.net.Uri import com.google.firebase.storage.FirebaseStorage import com.google.firebase.storage.StorageMetadata import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await import java.io.IOException import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException @Singleton class FirebaseStorageDataSource @Inject constructor( @@ -31,42 +29,27 @@ class FirebaseStorageDataSource @Inject constructor( return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } - suspend fun uploadProfilePhoto(uid: String, uri: Uri): String = - suspendCancellableCoroutine { cont -> - val ref = storage.reference.child("users/$uid/profile.jpg") - val mimeType = context.contentResolver.getType(uri) - ?.takeIf { it.startsWith("image/") } - ?: "image/jpeg" - val metadata = StorageMetadata.Builder() - .setContentType(mimeType) - .build() - ref.putFile(uri, metadata) - .continueWithTask { ref.downloadUrl } - .addOnSuccessListener { cont.resume(it.toString()) } - .addOnFailureListener { cont.resumeWithException(it) } - } + suspend fun uploadProfilePhoto(uid: String, uri: Uri): String { + val ref = storage.reference.child("users/$uid/profile.jpg") + val mimeType = context.contentResolver.getType(uri) + ?.takeIf { it.startsWith("image/") } + ?: "image/jpeg" + val metadata = StorageMetadata.Builder().setContentType(mimeType).build() + return ref.putFile(uri, metadata).continueWithTask { ref.downloadUrl }.await().toString() + } /** * Uploads already-encrypted chat-media bytes under the author's own storage path (mirrors the * profile-photo ownership model) and returns the tokenized download URL. The bytes are * ciphertext, so Storage never holds anything readable. */ - suspend fun uploadEncryptedMedia(uid: String, encryptedBytes: ByteArray): String = - suspendCancellableCoroutine { cont -> - // Fail fast when clearly offline so the chat shows a retry instead of a stuck spinner. - if (!isOnline()) { - cont.resumeWithException(IOException("No internet connection")) - return@suspendCancellableCoroutine - } - val ref = storage.reference.child("users/$uid/chat_media/${java.util.UUID.randomUUID()}") - val metadata = StorageMetadata.Builder() - .setContentType("application/octet-stream") - .build() - ref.putBytes(encryptedBytes, metadata) - .continueWithTask { ref.downloadUrl } - .addOnSuccessListener { cont.resume(it.toString()) } - .addOnFailureListener { cont.resumeWithException(it) } - } + suspend fun uploadEncryptedMedia(uid: String, encryptedBytes: ByteArray): String { + // Fail fast when clearly offline so the chat shows a retry instead of a stuck spinner. + if (!isOnline()) throw IOException("No internet connection") + val ref = storage.reference.child("users/$uid/chat_media/${java.util.UUID.randomUUID()}") + val metadata = StorageMetadata.Builder().setContentType("application/octet-stream").build() + return ref.putBytes(encryptedBytes, metadata).continueWithTask { ref.downloadUrl }.await().toString() + } /** * Uploads an already-encrypted conversation-backup snapshot under the uploader's OWN path and @@ -74,30 +57,19 @@ class FirebaseStorageDataSource @Inject constructor( * can fetch it). The bytes are couple-key ciphertext, so Storage holds nothing readable. Living * under users/{uid}/ means the existing account-delete cleanup covers backups too. */ - suspend fun uploadBackupSnapshot(uid: String, snapshotId: String, encryptedBytes: ByteArray): String = - suspendCancellableCoroutine { cont -> - if (!isOnline()) { - cont.resumeWithException(IOException("No internet connection")) - return@suspendCancellableCoroutine - } - val ref = storage.reference.child("users/$uid/backups/$snapshotId") - val metadata = StorageMetadata.Builder() - .setContentType("application/octet-stream") - .build() - ref.putBytes(encryptedBytes, metadata) - .continueWithTask { ref.downloadUrl } - .addOnSuccessListener { cont.resume(it.toString()) } - .addOnFailureListener { cont.resumeWithException(it) } - } + suspend fun uploadBackupSnapshot(uid: String, snapshotId: String, encryptedBytes: ByteArray): String { + if (!isOnline()) throw IOException("No internet connection") + val ref = storage.reference.child("users/$uid/backups/$snapshotId") + val metadata = StorageMetadata.Builder().setContentType("application/octet-stream").build() + return ref.putBytes(encryptedBytes, metadata).continueWithTask { ref.downloadUrl }.await().toString() + } /** Best-effort delete of a previously-uploaded backup snapshot (post-compaction cleanup; succeeds * only for the uploader's own path, so a cross-partner stale blob is left for that owner/cleanup). */ - suspend fun deleteBackupSnapshot(uid: String, snapshotId: String): Unit = - suspendCancellableCoroutine { cont -> - storage.reference.child("users/$uid/backups/$snapshotId").delete() - .addOnSuccessListener { cont.resume(Unit) } - .addOnFailureListener { cont.resume(Unit) } // best-effort; a leftover blob is harmless ciphertext - } + suspend fun deleteBackupSnapshot(uid: String, snapshotId: String) { + // Best-effort: a leftover blob is harmless ciphertext, so swallow failures. + runCatching { storage.reference.child("users/$uid/backups/$snapshotId").delete().await() } + } /** * Downloads the raw (still-encrypted) bytes for a media message over HTTP using the tokenized diff --git a/app/src/main/java/app/closer/data/remote/FirestoreDateSwipeDataSource.kt b/app/src/main/java/app/closer/data/remote/FirestoreDateSwipeDataSource.kt index c43ba219..ca0c79e3 100644 --- a/app/src/main/java/app/closer/data/remote/FirestoreDateSwipeDataSource.kt +++ b/app/src/main/java/app/closer/data/remote/FirestoreDateSwipeDataSource.kt @@ -11,11 +11,9 @@ import com.google.firebase.firestore.SetOptions import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException /** * Firestore data source for per-couple, per-date swipe state. @@ -57,7 +55,7 @@ class FirestoreDateSwipeDataSource @Inject constructor( path.set( mapOf("actions" to mapOf(swipe.userId to entry)), SetOptions.merge() - ).voidAwait() + ).await() } /** Decrypt a stored action, tolerating legacy plaintext written before E2EE. */ @@ -72,7 +70,7 @@ class FirestoreDateSwipeDataSource @Inject constructor( suspend fun getSwipe(coupleId: String, dateIdeaId: String, userId: String): DateSwipe? { val aead = encryptionManager.aeadFor(coupleId) - val snap = swipesRef(coupleId).document(dateIdeaId).getDoc() + val snap = swipesRef(coupleId).document(dateIdeaId).get().await() return snap.toDateSwipe(dateIdeaId, userId, aead, coupleId) } @@ -103,20 +101,13 @@ class FirestoreDateSwipeDataSource @Inject constructor( @Suppress("UNCHECKED_CAST") private suspend fun getAllSwipesForUser(coupleId: String, userId: String): List { val aead = encryptionManager.aeadFor(coupleId) - val snap = swipesRef(coupleId).getQuery() + val snap = swipesRef(coupleId).get().await() return snap.documents.mapNotNull { it.toDateSwipe(it.id, userId, aead, coupleId) } } - private suspend fun com.google.firebase.firestore.CollectionReference.getQuery() = - suspendCancellableCoroutine { cont -> - get() - .addOnSuccessListener { cont.resume(it) } - .addOnFailureListener { cont.resumeWithException(it) } - } - suspend fun getAllSwipesForDate(coupleId: String, dateIdeaId: String): List { val aead = encryptionManager.aeadFor(coupleId) - val snap = swipesRef(coupleId).document(dateIdeaId).getDoc() + val snap = swipesRef(coupleId).document(dateIdeaId).get().await() @Suppress("UNCHECKED_CAST") val actions = snap.get("actions") as? Map> ?: emptyMap() return actions.map { (uid, data) -> @@ -129,21 +120,6 @@ class FirestoreDateSwipeDataSource @Inject constructor( } } - // ─── Coroutine helpers ─────────────────────────────────────────────────── - - private suspend fun com.google.firebase.firestore.DocumentReference.getDoc(): DocumentSnapshot = - suspendCancellableCoroutine { cont -> - get() - .addOnSuccessListener { cont.resume(it) } - .addOnFailureListener { cont.resumeWithException(it) } - } - - private suspend fun com.google.android.gms.tasks.Task.voidAwait() = - suspendCancellableCoroutine { cont -> - addOnSuccessListener { cont.resume(Unit) } - addOnFailureListener { cont.resumeWithException(it) } - } - // ─── Mappers ───────────────────────────────────────────────────────────── @Suppress("UNCHECKED_CAST")