From ede7c70ea7762166794034fa3233ac10193e642d Mon Sep 17 00:00:00 2001 From: null Date: Wed, 17 Jun 2026 02:00:39 -0500 Subject: [PATCH] fix(release): date/time pickers, debug logging guards, RC_API_KEY comment, ExternalLinks TODOs (gap fixes) --- app/build.gradle.kts | 2 + .../closer/core/navigation/ExternalLinks.kt | 4 + .../data/questions/QuestionJsonParser.kt | 12 +- .../app/closer/ui/dates/DateBuilderScreen.kt | 86 +++- docs/qa/private-mvp-checklist.md | 443 +++++++++++++----- docs/release/internal-testing-checklist.md | 207 +++++--- docs/release/store-assets.md | 216 ++++++--- 7 files changed, 733 insertions(+), 237 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b6b80985..13cf4bec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,8 @@ android { versionName = "0.1.0" // RevenueCat API key is supplied via local.properties (RC_API_KEY) and never committed. + // TODO: Replace the PLACEHOLDER_RC_API_KEY fallback with a real key in local.properties before release. + // The app will not process purchases correctly while the placeholder is active. buildConfigField( "String", "RC_API_KEY", diff --git a/app/src/main/java/app/closer/core/navigation/ExternalLinks.kt b/app/src/main/java/app/closer/core/navigation/ExternalLinks.kt index 65009578..5026ed6b 100644 --- a/app/src/main/java/app/closer/core/navigation/ExternalLinks.kt +++ b/app/src/main/java/app/closer/core/navigation/ExternalLinks.kt @@ -6,9 +6,13 @@ import android.net.Uri import android.widget.Toast object ExternalLinks { + // TODO: Update placeholder URL before production. const val PRIVACY_POLICY = "https://closer.app/privacy" + // TODO: Update placeholder URL before production. const val TERMS_OF_SERVICE = "https://closer.app/terms" + // TODO: Update placeholder URL before production. const val SUBSCRIPTION_TERMS = "https://closer.app/subscription-terms" + // TODO: Update placeholder URL before production. const val SUPPORT = "https://couplesconnect.app/support" fun openUrl(context: Context, url: String) { diff --git a/app/src/main/java/app/closer/data/questions/QuestionJsonParser.kt b/app/src/main/java/app/closer/data/questions/QuestionJsonParser.kt index 61472fbb..62ded901 100644 --- a/app/src/main/java/app/closer/data/questions/QuestionJsonParser.kt +++ b/app/src/main/java/app/closer/data/questions/QuestionJsonParser.kt @@ -1,5 +1,7 @@ package app.closer.data.questions +import android.util.Log +import app.closer.BuildConfig import app.closer.domain.model.* import org.json.JSONArray import org.json.JSONObject @@ -26,6 +28,8 @@ import org.json.JSONObject */ object QuestionJsonParser { + private const val TAG = "QuestionJsonParser" + data class ParsedQuestionBundle( val category: QuestionCategory, val questions: List @@ -35,7 +39,9 @@ object QuestionJsonParser { val jsonContent = try { java.io.File(jsonFilePath).readText() } catch (e: Exception) { - android.util.Log.e("QuestionJsonParser", "Failed to read JSON file: ${e.message}") + if (BuildConfig.DEBUG) { + Log.e(TAG, "Failed to read JSON file: ${e.message}") + } return null } @@ -60,7 +66,9 @@ object QuestionJsonParser { ParsedQuestionBundle(category, questions) } catch (e: Exception) { - android.util.Log.e("QuestionJsonParser", "Failed to parse JSON: ${e.message}") + if (BuildConfig.DEBUG) { + Log.e(TAG, "Failed to parse JSON: ${e.message}") + } null } } diff --git a/app/src/main/java/app/closer/ui/dates/DateBuilderScreen.kt b/app/src/main/java/app/closer/ui/dates/DateBuilderScreen.kt index ed9b6232..7062ee1b 100644 --- a/app/src/main/java/app/closer/ui/dates/DateBuilderScreen.kt +++ b/app/src/main/java/app/closer/ui/dates/DateBuilderScreen.kt @@ -21,12 +21,19 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DisplayMode +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TimeInput +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.material3.rememberTimePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -146,6 +153,7 @@ private fun Header( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun InputCard( state: DateBuilderUiState, @@ -154,6 +162,9 @@ private fun InputCard( onBudgetChange: (Int) -> Unit, onDurationChange: (String) -> Unit ) { + var showDatePicker by remember { mutableStateOf(false) } + var showTimePicker by remember { mutableStateOf(false) } + Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(24.dp), @@ -169,9 +180,9 @@ private fun InputCard( DateTimeField( label = "When?", value = state.scheduledDate.takeIf { it > 0 }?.let { - java.text.SimpleDateFormat("MMMM dd, yyyy", java.util.Locale.getDefault()).format(java.util.Date(it)) + java.text.SimpleDateFormat("EEE, MMM dd", java.util.Locale.getDefault()).format(java.util.Date(it)) } ?: "Select a date", - onClick = { /* TODO: Date picker dialog */ }, + onClick = { showDatePicker = true }, modifier = Modifier.fillMaxWidth() ) @@ -179,7 +190,7 @@ private fun InputCard( DateTimeField( label = "What time?", value = state.scheduledTime.ifEmpty { "Select a time" }, - onClick = { /* TODO: Time picker dialog */ }, + onClick = { showTimePicker = true }, modifier = Modifier.fillMaxWidth() ) @@ -197,6 +208,71 @@ private fun InputCard( ) } } + + if (showDatePicker) { + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = state.scheduledDate.takeIf { it > 0 }, + initialDisplayMode = DisplayMode.Picker + ) + DatePickerDialog( + onDismissRequest = { showDatePicker = false }, + confirmButton = { + TextButton( + onClick = { + datePickerState.selectedDateMillis?.let { millis -> + onDateChange(millis) + } + showDatePicker = false + } + ) { + Text("OK") + } + }, + dismissButton = { + TextButton(onClick = { showDatePicker = false }) { + Text("Cancel") + } + } + ) { + DatePicker( + state = datePickerState, + showModeToggle = false + ) + } + } + + if (showTimePicker) { + val timePickerState = rememberTimePickerState() + AlertDialog( + onDismissRequest = { showTimePicker = false }, + confirmButton = { + TextButton( + onClick = { + val formattedTime = formatTime(timePickerState.hour, timePickerState.minute) + onTimeChange(formattedTime) + showTimePicker = false + } + ) { + Text("OK") + } + }, + dismissButton = { + TextButton(onClick = { showTimePicker = false }) { + Text("Cancel") + } + }, + title = { Text("Select time") }, + text = { TimeInput(state = timePickerState) } + ) + } +} + +private fun formatTime(hour: Int, minute: Int): String { + val calendar = java.util.Calendar.getInstance().apply { + set(java.util.Calendar.HOUR_OF_DAY, hour) + set(java.util.Calendar.MINUTE, minute) + } + return java.text.SimpleDateFormat("h:mm a", java.util.Locale.getDefault()).format(calendar.time) } @Composable diff --git a/docs/qa/private-mvp-checklist.md b/docs/qa/private-mvp-checklist.md index 3c6ec895..68c1c174 100644 --- a/docs/qa/private-mvp-checklist.md +++ b/docs/qa/private-mvp-checklist.md @@ -1,139 +1,366 @@ -# Private MVP QA Checklist +# Closer — Private MVP QA Checklist -Manual testing checklist for the Closer app private MVP. +> Manual testing checklist for the internal MVP build. Covers every top-level flow in the app and notes known gaps discovered during the 2025-06 QA pass. -## Onboarding +--- -- [ ] App launches to onboarding screen on fresh install -- [ ] Onboarding screens advance correctly (swipe/tap) -- [ ] "Get Started" leads to sign-up screen -- [ ] Sign-up: email + password creates account -- [ ] Sign-up: validation errors show (empty fields, weak password, existing email) -- [ ] Login: existing user can sign in -- [ ] Login: "Forgot password" navigates to reset screen -- [ ] Create Profile: name + avatar saved to Firestore -- [ ] Profile creation completes and navigates to home +## Environment & Setup -## Home Screen +- [ ] Clean install on test device (API 26+). +- [ ] Test device/emulator has network connectivity. +- [ ] Check non-network build: `./gradlew :app:compileDebugKotlin` passes. +- [ ] Verify `RC_API_KEY` in `local.properties` (or a placeholder) so billing build does not fail. +- [ ] Google-services JSON is present for the selected build variant. -- [ ] Home screen loads after onboarding -- [ ] Daily question card is visible -- [ ] Question pack sections display correctly -- [ ] Navigation between tabs (Home, Dates, Wheel, Settings) -- [ ] Pull-to-refresh reloads content -- [ ] Partner home view shows when coupled +--- -## Questions +## 1. Onboarding & First Launch -### Daily Question -- [ ] Today's question loads -- [ ] User can answer (select option or type response) -- [ ] Answer is saved to Firestore -- [ ] "Partner answered" notification flow works (if coupled) +### 1.1 Splash / App launch +- [ ] App launches to `MainActivity` without crash. +- [ ] `AppNavigation` starts at `AppRoute.ONBOARDING` on fresh install. +- [ ] Theme applies (`CloserTheme`) and status bar / navigation bar insets handled correctly. +- [ ] App name in launcher is "Closer". -### Question Packs -- [ ] Pack library screen lists available packs -- [ ] Premium packs show lock icon (if not subscribed) -- [ ] Free packs can be opened -- [ ] Category picker shows categories -- [ ] Selecting a category loads questions from that category +### 1.2 Onboarding screen (`OnboardingScreen`) +- [ ] Value proposition visible and not placeholder text. +- [ ] CTA navigates to `CREATE_PROFILE` or `LOGIN` as expected. +- [ ] Secondary sign-in link works. +- [ ] No dead-end: user can always reach either create profile or login. -### Question Detail & Thread -- [ ] Question detail screen loads with full text -- [ ] Answer reveal shows both partner answers (if both answered) -- [ ] Discussion thread loads messages -- [ ] User can send a message in thread -- [ ] Answer history screen shows past answers +### 1.3 Create profile (`CreateProfileScreen`) +- [ ] Name input accepts text and reflects in UI. +- [ ] Validation prevents empty submission. +- [ ] Profile creation succeeds and navigates forward (home or pairing flow). +- [ ] Loading / error states handled. -## Date Match +### 1.4 Login / Sign up / Forgot password +- [ ] `LoginScreen`: email/password fields, sign-in call, error shown on failure, success navigates to `HOME`. +- [ ] `SignUpScreen`: account creation, weak-password validation, navigates to profile creation. +- [ ] `ForgotPasswordScreen`: email reset flow, success/error messaging. +- [ ] Back navigation from auth screens returns to onboarding. -- [ ] Date match screen shows swipe UI -- [ ] Swipe right = "love", swipe left = "skip" -- [ ] "Maybe" button works -- [ ] Match reveal screen appears when both partners like same idea -- [ ] Date matches screen shows all mutual matches -- [ ] Empty state shows when no matches +### 1.5 Known onboarding gaps (from code scan) +- `AccountScreen` shows "Local profile" and disables "Sign in or create account" and "Export your data" rows. These need real wiring before public release. +- Home header text changes based on `partnerName` presence; confirm both states render. -## Date Builder +--- -- [ ] Date builder screen loads with suggestion fields -- [ ] Date and time picker buttons present (currently TODO placeholders) -- [ ] Save date plan works (if implemented) +## 2. Pairing -## Bucket List +### 2.1 Create invite (`CreateInviteScreen`) +- [ ] Generates 6-character code. +- [ ] Code displays with chunked formatting (e.g., "ABC – 123"). +- [ ] Copy button copies raw code to clipboard and shows snackbar. +- [ ] Share button opens system share sheet with correct message. +- [ ] "Partner already has a code? Accept instead" navigates to `ACCEPT_INVITE`. +- [ ] Code expiry note shown ("expires in 24 hours"). -- [ ] Bucket list screen shows saved date ideas -- [ ] Adding items works -- [ ] Completing/removing items works +### 2.2 Accept invite (`AcceptInviteScreen`) +- [ ] 6-character code entry field with visual chunking and uppercase auto-capitalization. +- [ ] Continue enabled only when code length == 6. +- [ ] Invalid/expired code shows error. +- [ ] Valid code navigates to `INVITE_CONFIRM/{inviteCode}`. +- [ ] "Need to create an invite instead?" link works. +- [ ] Keyboard Done triggers lookup. -## Wheel +### 2.3 Invite confirm (`InviteConfirmScreen`) +- [ ] Inviter name loaded and displayed. +- [ ] Pairing confirmation button shows loading spinner. +- [ ] Success navigates to home. +- [ ] "Not right — enter a different code" returns to accept screen. +- [ ] Error surfaced via snackbar. -- [ ] Spin wheel animates and selects a category -- [ ] Category picker shows available categories -- [ ] Wheel complete screen shows result -- [ ] Wheel history screen shows past spins +### 2.4 Email invite (`EmailInviteScreen`) +- [ ] This is currently a `PlaceholderScreen`. Verify it renders and that primary/secondary actions navigate correctly. +- [ ] **Risk**: placeholder actions reference a hardcoded invite code `ABC123`; replace before public release. -## Partner Pairing +### 2.5 Relationship settings +- [ ] `SettingsScreen` partner card opens `RELATIONSHIP_SETTINGS` when paired. +- [ ] Leave couple confirmation dialog shown. +- [ ] Leave action calls repository and navigates to `CREATE_INVITE` on success. +- [ ] Error shown on failure. -### Create Invite -- [ ] Invite code generates and displays -- [ ] Copy button copies code to clipboard -- [ ] Share button opens Android share sheet (Intent.ACTION_SEND) -- [ ] "Accept instead" link navigates to AcceptInvite +--- -### Accept Invite -- [ ] Code entry field works -- [ ] Back button navigates correctly -- [ ] Valid code proceeds to InviteConfirm +## 3. Home -### Invite Confirm -- [ ] Partner info displays correctly -- [ ] "Confirm" button links couple in Firestore -- [ ] "Wrong code" link returns to AcceptInvite -- [ ] Back button works +### 3.1 Home screen (`HomeScreen`) +- [ ] Loads without crash; loading, error, and success states all tested. +- [ ] Header shows correct subtitle for paired vs. unpaired user. +- [ ] Streak pill appears when `streakCount > 0`. +- [ ] Primary action card responds to tap. +- [ ] Secondary action feed renders and navigates. +- [ ] "More doorways" grid shows up to 2 categories; tap navigates to category. +- [ ] "All packs" button navigates to `QUESTION_PACKS`. +- [ ] Pull/refresh or retry on error works. -## Settings +### 3.2 Partner home (`PartnerHomeScreen`) +- [ ] Renders `PlaceholderScreen` with correct copy. +- [ ] Actions navigate to invite flow or home. +- [ ] **Gap**: screen is a placeholder; not a functional partner dashboard yet. -- [ ] Settings screen loads with all sections -- [ ] Profile row shows local profile state (not fake data) -- [ ] Partner row tappable, navigates to RelationshipSettings -- [ ] Account screen shows profile info -- [ ] Delete account screen requires checkbox acknowledgement -- [ ] Notification settings screen present -- [ ] Privacy screen present -- [ ] Subscription screen present -- [ ] Legal section: Privacy Policy link opens externally -- [ ] Legal section: Terms of Service link opens externally -- [ ] Sign out works +### 3.3 Moment cue / special dates section +- [ ] `SpecialDatesSection` previews render. +- [ ] Home moment cue card text not placeholder. +- [ ] Hardcoded names ("Jessica", "Mark") and dates in `SpecialDatesSection` must be replaced with real data before public release. -## Paywall +--- -- [ ] Paywall screen shows benefits list -- [ ] Product options display with prices -- [ ] Purchase button triggers RevenueCat flow -- [ ] Restore purchases link works -- [ ] Privacy Policy and Terms links at bottom open externally -- [ ] "Subscription terms apply" disclaimer visible -- [ ] Thank-you overlay appears on successful purchase -- [ ] Loading and error states display correctly +## 4. Daily Question -## Notifications +### 4.1 Daily question screen (`DailyQuestionScreen`) +- [ ] Question text loads. +- [ ] Answer input accepts text and respects private/public toggle. +- [ ] Save/submit works, shows feedback. +- [ ] Navigates to answer reveal or answer history appropriately. +- [ ] Discussion section handles un-revealed state gracefully. -- [ ] App requests POST_NOTIFICATIONS permission on Android 13+ -- [ ] FCM token obtained and stored in Firestore -- [ ] Quiet hours can be set in notification settings -- [ ] Notification channel "Reminders" created +### 4.2 Answer input components +- [ ] `QuestionAnswerInput`: empty-state handling, formatting, IME actions. +- [ ] `QuestionHelpExpandable`: expand/collapse works. +- [ ] `QuestionDiscussionThread`: disabled state copy is clear. +- [ ] `QuestionNavigationBar`: previous/next navigation and disabled states. -## Edge Cases & Crash Risks +### 4.3 Question thread (`QuestionThreadScreen`) +- [ ] Loads question and optional `prevId`/`nextId` args. +- [ ] Previous / next navigation works when ids provided. +- [ ] Both partner answers visible after reveal. +- [ ] Comments can be added (if unlocked). +- [ ] Back button returns to prior screen. -- [ ] App works offline (local cache) -- [ ] App handles no network gracefully (error states, not crashes) -- [ ] Rapid navigation doesn't crash (tap all tabs quickly) -- [ ] Back navigation from every screen works -- [ ] Rotation doesn't crash (if not locked to portrait) -- [ ] Empty states show for: no questions, no matches, no history, no partner +--- -## Known Issues (from code scan) +## 5. Question Packs & Categories -- **TODOs in code**: `DateBuilderScreen.kt` has TODO placeholders for date/time picker dialogs (lines 174, 182) -- **Debug logging**: `QuestionJsonParser.kt` uses `android.util.Log.e()` — should use Timber or remove before release \ No newline at end of file +### 5.1 Question pack library (`QuestionPackLibraryScreen`) +- [ ] Loading, error, empty states tested. +- [ ] Categories/packs list scrolls. +- [ ] Free and premium locks indicated. +- [ ] Tap on premium pack navigates to `PAYWALL`. +- [ ] Tap on free/mixed pack navigates to `QUESTION_CATEGORY/{categoryId}`. + +### 5.2 Question category (`QuestionCategoryScreen`) +- [ ] Loads questions for `categoryId`. +- [ ] Format and depth filters apply. +- [ ] Question cards navigate to `QUESTION_THREAD`. +- [ ] Locked premium packs route to paywall. +- [ ] Loading/error handled. + +### 5.3 Question composer (`QuestionComposerScreen`) +- [ ] User can create a custom question. +- [ ] Category/depth/format options available. +- [ ] Save creates question and returns. +- [ ] Validation prevents empty submission. + +--- + +## 6. Answers & Reveal + +### 6.1 Answer reveal (`AnswerRevealScreen`) +- [ ] Loads answer for `questionId`. +- [ ] Private answer shown only to author until reveal. +- [ ] Reveal action updates state and reveals both answers. +- [ ] Loading, error, empty states tested. +- [ ] Navigation to discussion or history works. + +### 6.2 Answer history (`AnswerHistoryScreen`) +- [ ] List of answered/revealed questions loads. +- [ ] Empty state shown when no answers. +- [ ] Remove action (if available) works and updates list. +- [ ] Tap navigates to thread/reveal. + +--- + +## 7. Spin Wheel + +### 7.1 Category picker (`CategoryPickerScreen`) +- [ ] Categories load; loading, error, empty states tested. +- [ ] Locked categories show lock icon and "Premium" pill. +- [ ] Locked tap routes to `PAYWALL`. +- [ ] Free tap routes to `SPIN_WHEEL/{categoryId}`. + +### 7.2 Spin wheel (`SpinWheelScreen`) +- [ ] Category name displayed. +- [ ] Spin action triggers selection of `SpinWheelViewModel.SESSION_SIZE` questions. +- [ ] Visual spinning animation runs. +- [ ] Error surfaced. +- [ ] Ready state shows count and enables "Start session". +- [ ] "Spin again" re-rolls selection. + +### 7.3 Wheel session (`WheelSessionScreen`) +- [ ] Loads session via `sessionId`. +- [ ] Progress indicator updates. +- [ ] Current question displays centered. +- [ ] Next / skip / end session buttons work. +- [ ] Empty session state shown when no active session. +- [ ] Last question button label reads "Finish". +- [ ] Navigates to `WHEEL_COMPLETE/{sessionId}` on finish. + +### 7.4 Wheel complete (`WheelCompleteScreen`) +- [ ] Shows category name and answered/total count. +- [ ] Saves session to repository on init. +- [ ] "Back home" navigates to `HOME`. +- [ ] "Spin again" navigates to `CATEGORY_PICKER`. +- [ ] Handles 0-question edge case gracefully. + +### 7.5 Wheel history (`WheelHistoryScreen`) +- [ ] Premium lock card shown for non-premium users. +- [ ] Premium users see list of completed sessions. +- [ ] Empty state with "Spin now" action. +- [ ] Error/retry works. +- [ ] Date formatting uses default locale. + +--- + +## 8. Dates + +### 8.1 Date match (`DateMatchScreen`) +- [ ] Swipe cards render. +- [ ] Match state shows correctly. +- [ ] Premium gating tested. +- [ ] Error/loading handled. + +### 8.2 Date matches (`DateMatchesScreen`) +- [ ] List of matched date ideas loads. +- [ ] Empty state shown. +- [ ] Tap navigates to detail/builder. + +### 8.3 Date builder (`DateBuilderScreen`) +- [ ] Fields render: date, time, budget, duration. +- [ ] **Gap**: date and time fields show "TODO: Date picker dialog" / "TODO: Time picker dialog"; they are not interactive yet. +- [ ] Budget input accepts digits only. +- [ ] Duration chips selectable. +- [ ] Save button calls view model. +- [ ] Back navigation works. + +### 8.4 Bucket list (`BucketListScreen`) +- [ ] Items load; empty state shown. +- [ ] Category filter chips scroll and filter list. +- [ ] Add item FAB opens dialog. +- [ ] Add dialog: title required, description optional, category selectable. +- [ ] Toggle complete updates card style. +- [ ] Delete removes item. +- [ ] Back navigation works. + +--- + +## 9. Settings + +### 9.1 Settings home (`SettingsScreen`) +- [ ] Profile card shows display name / email or local profile note. +- [ ] Partner card shows paired state and partner name, or invite prompt. +- [ ] Tapping profile card opens `ACCOUNT`. +- [ ] Tapping partner card opens `RELATIONSHIP_SETTINGS` (paired) or `CREATE_INVITE` (unpaired). +- [ ] Notifications, Subscription, Privacy & Terms rows open correct screens. +- [ ] Legal links open external URLs. +- [ ] Delete account row opens `DELETE_ACCOUNT`. +- [ ] Sign out button works and shows loading state. + +### 9.2 Account (`AccountScreen`) +- [ ] "Local profile" card shown for signed-out state. +- [ ] Disabled rows visually greyed out. +- [ ] Delete account row navigates to `DELETE_ACCOUNT`. +- [ ] Back navigation works. + +### 9.3 Notifications (`NotificationSettingsScreen`) +- [ ] All four toggles reflect persisted state. +- [ ] Toggling each calls repository and updates UI. +- [ ] Quiet hours description accurate (10 PM – 8 AM). + +### 9.4 Privacy (`PrivacyScreen`) +- [ ] External links open in browser. +- [ ] No browser available case handled via `ExternalLinks.openUrl` Toast fallback. +- [ ] Back navigation works. +- [ ] **Note**: Support URL is `https://couplesconnect.app/support` (legacy domain); should be migrated to `closer.app` before release. + +### 9.5 Subscription (`SubscriptionScreen`) +- [ ] Renders placeholder with paywall and settings actions. +- [ ] **Gap**: subscription management is placeholder; needs real UI before release. + +### 9.6 Relationship settings / Delete account +- [ ] See pairing section for leave-couple flow. +- [ ] Delete account confirmation dialog requires acknowledgment checkbox. +- [ ] Delete action calls `AuthRepository.deleteAccount()` and navigates to onboarding. +- [ ] Loading and error states shown. + +--- + +## 10. Paywall + +### 10.1 Paywall screen (`PaywallScreen`) +- [ ] Loads RevenueCat packages; loading/error handled. +- [ ] Benefits list accurate. +- [ ] Selecting plan updates selection state. +- [ ] Purchase button enabled only when plan selected. +- [ ] Purchase call uses current activity context. +- [ ] Restore purchases button works. +- [ ] Legal links open externally. +- [ ] Thank-you overlay shown on success; dismiss returns to previous screen. +- [ ] **Note**: Requires real RevenueCat API key and product configuration to fully test. + +--- + +## 11. Notifications + +### 11.1 FCM service (`AppMessagingService`) +- [ ] App receives push when in foreground and background. +- [ ] Quiet hours suppression works (10 PM – 8 AM). +- [ ] Notification channel created on Android O+. +- [ ] Tap action navigates to relevant screen. + +### 11.2 Token registrar +- [ ] Token updates sent to backend/user document. +- [ ] Handles sign-out / re-sign-in correctly. + +--- + +## 12. General UX / Edge Cases + +### 12.1 Navigation +- [ ] Bottom nav shows on top-level routes only. +- [ ] System back from top-level routes does not exit unexpectedly (navigate back within app). +- [ ] Shell top bar appears on `shellBackRoutes` and uses route title. +- [ ] Deep links / invite-confirm route handled: `invite_confirm/{inviteCode}`. + +### 12.2 Input / Accessibility +- [ ] Text fields have proper keyboard options (email, ASCII, capitalization). +- [ ] Labels / placeholders readable. +- [ ] Touch targets >= 48 dp for all clickable surfaces. +- [ ] Max lines and ellipses used to prevent overflow clipping. + +### 12.3 Theming +- [ ] Light theme renders consistently (current primary theme). +- [ ] Dark theme colors defined but not wired to system dark mode (`isSystemInDarkTheme()` not used). Verify this is intentional for MVP. +- [ ] No stale Next.js / React Native references in resources or manifests. + +### 12.4 Data / Offline +- [ ] App does not crash when offline (graceful error cards). +- [ ] Local answer repository persists data. +- [ ] Firebase calls fail cleanly and retry available where exposed. + +--- + +## 13. Release Blockers Logged from This QA Pass + +These findings came from the static review and should be fixed before public or store release. Do **not** block internal MVP on these unless explicitly required. + +| # | Area | Issue | Severity | +|---|------|-------|----------| +| 1 | Date builder | Date/time picker TODOs — fields are not interactive | High | +| 2 | Special dates | Hardcoded names/dates in `SpecialDatesSection` | High | +| 3 | Email invite | Placeholder screen with hardcoded `ABC123` code | Medium | +| 4 | Subscription | Placeholder screen, not real management | Medium | +| 5 | Partner home | Placeholder screen only | Medium | +| 6 | Settings | Account rows disabled ("Auth coming soon", "Export coming soon") | Medium | +| 7 | External links | Support URL points to `couplesconnect.app/support` | Low | +| 8 | Strings | 100+ hardcoded display strings; should move to `strings.xml` for localization | Low | +| 9 | Logging | `android.util.Log.e` used in `QuestionJsonParser` — acceptable for errors, but confirm no verbose/debug logs remain in release builds | Low | + +--- + +## Sign-off + +| Tester | Date | Build | Result | +|--------|------|-------|--------| +| | | | | diff --git a/docs/release/internal-testing-checklist.md b/docs/release/internal-testing-checklist.md index 96810f37..367fd119 100644 --- a/docs/release/internal-testing-checklist.md +++ b/docs/release/internal-testing-checklist.md @@ -1,74 +1,163 @@ -# Internal Testing Release Checklist +# Closer — Internal Testing Build Checklist -Steps to prepare and distribute a private MVP build for internal testing. +> Steps to prepare, sign, and distribute an internal testing build for the private MVP. Assumes Android Gradle Plugin 8.7.3, Kotlin 2.0.21, minSdk 26, targetSdk/compileSdk 35, and `app.closer` applicationId. -## 1. Signing Config +--- -- [ ] Generate or locate keystore for signing - - Keytool command: `keytool -genkeypair -v -keystore closer-release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias closer` -- [ ] Store keystore file securely (NOT in git) -- [ ] Add signing config to `app/build.gradle.kts`: - - `storeFile`, `storePassword`, `keyAlias`, `keyPassword` - - Read from `local.properties` or environment variables -- [ ] Verify debug and release build variants both compile +## 1. Pre-build Checklist -## 2. Build Configuration +### 1.1 Source readiness +- [ ] All feature branches for the MVP merged into the internal testing branch. +- [ ] No unintended `TODO` / `FIXME` blockers in release code (documented in `docs/qa/private-mvp-checklist.md`). +- [ ] No `com.couplesconnect` package references remain (verified via `grep -RIn` across `app/src`). +- [ ] No debug-only logging left in non-debug code paths (verbose/debug logs removed; error logging acceptable). +- [ ] Version matches private MVP: `versionName` should be `0.1.x` and `versionCode` incremented for each distributed build. +- [ ] `local.properties` contains required secrets but is not committed to git. -- [ ] Set `applicationId` to `app.closer` (verify not `com.couplesconnect`) -- [ ] Set versionCode and versionName in `app/build.gradle.kts` -- [ ] Set `minSdk`, `targetSdk` to appropriate values -- [ ] Verify `RC_API_KEY` in `local.properties` (or placeholder for testing) -- [ ] Verify `google-services.json` is present and points to correct Firebase project -- [ ] Remove or guard debug logging (`android.util.Log` calls) +### 1.2 Secrets and API keys +- [ ] `google-services.json` is present in `app/` and matches the internal testing package (`app.closer`). +- [ ] RevenueCat API key defined in `local.properties` as `RC_API_KEY`. +- [ ] Firebase App Check debug token removed or disabled for release. +- [ ] No hardcoded API keys, tokens, or credentials in Kotlin/XML source. -## 3. Firebase Configuration +### 1.3 Build configuration +- [ ] `build.gradle.kts` has `isMinifyEnabled = true` and `isShrinkResources = true` for `release`. +- [ ] ProGuard rules file (`proguard-rules.pro`) reviewed for Compose, Hilt, Room, Firebase, RevenueCat, and Navigation. +- [ ] `compileSdk = 35`, `targetSdk = 35`, `minSdk = 26`. +- [ ] `namespace = "app.closer"` and `applicationId = "app.closer"`. +- [ ] Kotlin `jvmTarget = "17"` and Java 17 compatibility set. +- [ ] `buildConfig = true` and `compose = true` enabled. -- [ ] Firebase project created (or confirmed existing) -- [ ] Authentication enabled: Email/Password sign-in method -- [ ] Firestore rules deployed and tested -- [ ] Cloud Functions deployed: - - [ ] `revenueCatWebhook` - - [ ] `syncEntitlement` - - [ ] `sendDailyQuestionReminder` - - [ ] `sendPartnerAnsweredNotification` - - [ ] `health` -- [ ] FCM enabled for the project -- [ ] RevenueCat project configured with Firebase integration (placeholder for now) +--- -## 4. Test Accounts +## 2. Signing Config Checklist -- [ ] Create 2 test accounts (couple A: user1@test.com / user2@test.com) -- [ ] Create 1 solo test account (single user: solo@test.com) -- [ ] Verify sign-up → profile creation → home flow on each -- [ ] Pair test couple via invite code -- [ ] Verify paired state persists across app restarts +### 2.1 Keystore +- [ ] Internal testing keystore generated or available (`.jks` / `.keystore`). +- [ ] Keystore password, key alias, and key password stored securely in CI or password manager — not in repo. +- [ ] Keystore used for signing is backed up in a secure location (1Password, team vault, hardware token). +- [ ] Key validity period covers expected lifetime of the app (Google Play requires validity past 22 Oct 2033 for app signing key uploads). -## 5. Android App Distribution - -- [ ] Install Firebase App Distribution Gradle plugin -- [ ] Add `app distribution` section to `app/build.gradle.kts` -- [ ] Build release APK: `./gradlew :app:assembleRelease` -- [ ] Distribute via Firebase App Distribution: - ```bash - firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk \ - --app \ - --groups internal-testers \ - --release-notes "Private MVP v0.1.x" +### 2.2 Gradle signing configuration +- [ ] `signingConfigs` block in `app/build.gradle.kts` configured: + ```kotlin + signingConfigs { + create("release") { + storeFile = file(System.getenv("KEYSTORE_PATH") ?: "release.keystore") + storePassword = System.getenv("KEYSTORE_PASSWORD") + keyAlias = System.getenv("KEY_ALIAS") + keyPassword = System.getenv("KEY_PASSWORD") + } + } ``` -- [ ] Add tester email addresses to Firebase App Distribution group -- [ ] Testers receive download link via email +- [ ] `buildTypes.release` references `signingConfigs.getByName("release")`. +- [ ] Local debug builds still use the default debug keystore. -## 6. Pre-Distribution Smoke Test +### 2.3 Verify signed APK/AAB +- [ ] Build release APK or AAB: `./gradlew :app:assembleRelease` or `:app:bundleRelease`. +- [ ] Confirm output is signed: `apksigner verify --verbose app-release.apk`. +- [ ] Check certificate fingerprints and expiration. +- [ ] Verify v1 (JAR) and v2/v3 APK signatures are present. -- [ ] Install release APK on a physical device -- [ ] Walk through the full QA checklist (`docs/qa/private-mvp-checklist.md`) -- [ ] Verify no crashes on cold start, warm start, or background restore -- [ ] Verify notification permissions prompt appears on Android 13+ -- [ ] Verify paywall loads (even with placeholder API key) +--- -## 7. Post-Distribution +## 3. Firebase / Production Environment Checklist -- [ ] Collect crash reports from Firebase Crashlytics -- [ ] Monitor Firestore usage in Firebase Console -- [ ] Monitor Cloud Function logs for errors -- [ ] Collect tester feedback via dedicated channel \ No newline at end of file +### 3.1 Firebase project +- [ ] Internal testing Firebase project created/separate from production. +- [ ] `app.closer` Android app registered with correct SHA-1/SHA-256 certificates. +- [ ] `google-services.json` downloaded and placed in `app/`. + +### 3.2 Firebase services +- [ ] **Authentication**: Email/password provider enabled (and any OAuth providers used in the app). +- [ ] **Firestore**: Security rules reviewed; internal testing rules should match expected MVP rules. +- [ ] **Cloud Messaging**: Server key / FCM v1 credentials available for backend/push testing. +- [ ] **Crashlytics**: Crashlytics plugin applied and mapping file upload enabled. +- [ ] **Analytics**: No PII logged; events reviewed. +- [ ] **Remote Config**: Default values in-app handle absent remote values. +- [ ] **App Check**: Play Integrity provider configured for release builds; debug provider only for debug/internal. + +### 3.3 Firebase secrets +- [ ] No Firebase API keys or FCM tokens committed. +- [ ] CI injects `google-services.json` or uses environment-based substitution. + +--- + +## 4. Test Account Setup + +### 4.1 Device / emulator accounts +- [ ] At least two test user accounts available (User A and User B) for couple pairing flows. +- [ ] Test accounts use `+` or tagged email addresses for isolation (e.g., `qa+a@closer.app`, `qa+b@closer.app`). +- [ ] Passwords stored in team vault; accounts enabled in Firebase Auth allowlist if applicable. + +### 4.2 Data seeding +- [ ] Seed questions and categories in Firestore/remote config for internal testing. +- [ ] Seed at least one premium category and one free category. +- [ ] Prepare test RevenueCat products with sandbox pricing. +- [ ] Ensure test accounts have predictable subscription states (free, premium, expired). + +### 4.3 Test scenarios matrix +| Scenario | User A | User B | +|----------|--------|--------| +| Unpaired new install | fresh account | fresh account | +| Paired couple | paired with B | paired with A | +| Premium unlock | subscribed | not subscribed | +| Different answer states | answered daily | not answered | +| Invite expiry | create invite | accept after expiry | + +--- + +## 5. Distribution Checklist + +### 5.1 Firebase App Distribution +- [ ] Firebase App Distribution Gradle plugin applied (optional but recommended). +- [ ] App Distribution configured with tester groups (e.g., `mvp-internal`, `qa-team`). +- [ ] Build variant selected: `release` (signed). +- [ ] Distribution command run: `./gradlew :app:assembleRelease :app:appDistributionUploadRelease`. +- [ ] Testers receive email/notification with download link. +- [ ] APK install succeeds and app launches. + +### 5.2 Alternative distribution +- [ ] If not using App Distribution, use internal Google Play track with closed testing. +- [ ] Signed AAB uploaded to Play Console internal testing track. +- [ ] Testers added to internal testing list. +- [ ] Play Console review status tracked (internal tests may be immediately available). + +### 5.3 Verification after install +- [ ] App installs and opens on target device. +- [ ] Onboarding flow reachable. +- [ ] Auth and pairing flows tested end-to-end on distributed build. +- [ ] Push notification received and quiet-hours suppression works. +- [ ] Paywall loads products in sandbox (where RevenueCat configured). +- [ ] Crashlytics reports non-fatal test event and appears in console. + +--- + +## 6. Build Commands Reference + +```bash +# Debug compile (fast smoke test) +./gradlew :app:compileDebugKotlin + +# Debug install +./gradlew :app:installDebug + +# Release APK (signed) +./gradlew :app:assembleRelease + +# Release AAB (signed) +./gradlew :app:bundleRelease + +# Firebase App Distribution upload +./gradlew :app:assembleRelease :app:appDistributionUploadRelease +``` + +--- + +## 7. Sign-off + +| Role | Name | Date | Notes | +|------|------|------|-------| +| Build engineer | | | | +| QA lead | | | | +| Product owner | | | | diff --git a/docs/release/store-assets.md b/docs/release/store-assets.md index f7ac5593..9f181f04 100644 --- a/docs/release/store-assets.md +++ b/docs/release/store-assets.md @@ -1,86 +1,176 @@ -# Store Assets Checklist +# Closer — Google Play Store Assets Checklist -Google Play Store asset preparation for Closer app release. +> Checklist of assets, metadata, and compliance items needed for a Google Play Store submission. Targeting the private MVP / internal launch phase. -## Required Assets +--- -### Screenshots (phone) -- [ ] 2–8 screenshots, minimum 320px, maximum 3840px -- [ ] Recommended: 1080x1920 or 1440x2560 (16:9 ratio) -- [ ] Required screenshots: - - [ ] Home screen with daily question - - [ ] Date Match swipe screen - - [ ] Date Matches (mutual likes) - - [ ] Question thread / answer reveal - - [ ] Spin Wheel - - [ ] Settings screen +## 1. Store Listing Metadata -### Feature Graphic -- [ ] 1024x500px JPEG or PNG -- [ ] Should convey app theme (couples, connection, questions) -- [ ] Must not contain device frames or text that duplicates store listing +### 1.1 App name +- [ ] App name finalized: **Closer**. +- [ ] App name matches `android:label` in `AndroidManifest.xml` (currently `@string/app_name`). +- [ ] App name is not trademark-conflicting in target categories/territories. -### App Icon -- [ ] 512x512 PNG, 32-bit color, no transparency -- [ ] Adaptive icon already defined in project — verify it renders well +### 1.2 Short description +- [ ] Short description <= 80 characters. +- [ ] Communicates core value proposition for couples. -## Store Listing Text +### 1.3 Full description +- [ ] Full description drafted and reviewed. +- [ ] Mentions key features: daily questions, question packs, spin wheel, date planning, bucket list, answer reveal. +- [ ] Includes value/privacy angle (answers stay private, built for two people). +- [ ] No misleading claims or guarantees about relationships. -### Title -- [ ] Max 30 characters: "Closer — Couple Questions" +### 1.4 Keywords / tags +- [ ] Primary category selected (e.g., Lifestyle, Dating, Health & Fitness). +- [ ] Relevant tags selected in Play Console. -### Short Description -- [ ] Max 80 characters -- [ ] Suggested: "Daily questions, date ideas, and deeper connection for couples." +--- -### Full Description -- [ ] Max 4000 characters -- [ ] Should cover: daily questions, date match, spin wheel, bucket list, premium features -- [ ] Include relevant keywords naturally +## 2. Graphic Assets -### Category -- [ ] Primary: Lifestyle -- [ ] Secondary: Social (if available) +### 2.1 App icon +- [ ] Launcher icon provided in all required densities (`mipmap-mdpi` through `mipmap-xxxhdpi`). +- [ ] Adaptive icon foreground + background layers present (`ic_launcher_foreground`, `ic_launcher_background`). +- [ ] Icon tested on light and dark wallpapers. +- [ ] Round icon variant provided (`ic_launcher_round`). -### Tags -- [ ] Couple, Relationship, Questions, Date Ideas, Connection +### 2.2 Feature graphic +- [ ] Feature graphic 1024 × 500 px (JPEG or PNG, no alpha). +- [ ] Brand name and key tagline legible at small sizes. +- [ ] Complies with Google Play policy (no device images, no price/calls-to-action like "Buy now"). -## Legal Links +### 2.3 Screenshots +- [ ] Phone screenshots: minimum 2, recommended up to 8. + - Suggested screens: + - Onboarding / welcome + - Home dashboard + - Daily question + answer input + - Question pack library + - Spin wheel / category picker + - Date builder or bucket list + - Answer reveal / history + - Settings / subscription +- [ ] Screenshot dimensions one of: 1080×1920, 1080×2160, 1080×2220, etc. (9:16 to 9:20 typical). +- [ ] 7-inch tablet screenshots if targeting tablets (optional for MVP). +- [ ] 10-inch tablet screenshots if targeting tablets (optional for MVP). +- [ ] Screenshots do not contain placeholder or hardcoded test data. +- [ ] Status bar cleaned (no low-battery warnings, demo time 9:41, etc.). -- [ ] Privacy Policy URL: `https://closer.app/privacy` (currently placeholder — must be live before submission) -- [ ] Terms of Service URL: `https://closer.app/terms` (currently placeholder — must be live before submission) -- [ ] Subscription terms: `https://closer.app/subscription-terms` (placeholder) +### 2.4 Promo video / preview +- [ ] Promo video optional but recommended; if used, YouTube URL added in Play Console. +- [ ] Video shows real in-app experience, no prohibited content. -## Content Rating +### 2.5 Other assets +- [ ] 512 × 512 px high-res icon uploaded in Play Console. +- [ ] 16:9 promo graphic if running store experiments. -- [ ] Complete IARC questionnaire -- [ ] Expected rating: PEGI 3 / ESRB Everyone (no violence, no gambling, no user-generated content moderation concerns) -- [ ] Note: If question packs include sensitive topics, may need higher rating +--- -## App Signing +## 3. Legal URLs -- [ ] Opt in to Google Play App Signing (recommended) -- [ ] Upload signing certificate to Play Console -- [ ] Keep local keystore backup in secure location -- [ ] DO NOT commit keystore to git +- [ ] Privacy policy URL defined and reachable: currently `https://closer.app/privacy`. +- [ ] Terms of service URL defined and reachable: currently `https://closer.app/terms`. +- [ ] Subscription terms URL defined and reachable: currently `https://closer.app/subscription-terms`. +- [ ] **Action needed**: Support URL currently points to `https://couplesconnect.app/support` in `ExternalLinks.kt`; migrate to `closer.app` domain and update store listing support/contact email. +- [ ] All legal pages load over HTTPS. +- [ ] Privacy policy accurately describes data collected (answers, FCM tokens, purchase data, couple pairing info). -## Pricing & Distribution +--- -- [ ] Free with in-app purchases (RevenueCat) -- [ ] Subscription tiers configured in Google Play Console: - - [ ] Monthly premium - - [ ] Annual premium (if offering) -- [ ] Distribution: All countries (or select markets for soft launch) +## 4. Content Rating -## Pre-Launch Report +### 4.1 Rating questionnaire +- [ ] Content rating questionnaire completed in Play Console. +- [ ] App declared as targeting couples / adults. +- [ ] No user-generated content that requires additional moderation declared. +- [ ] Question content reviewed for mature themes; mark appropriately if intimacy questions are explicit. -- [ ] Run Google Play pre-launch report (automated after upload) -- [ ] Address any accessibility issues flagged -- [ ] Address any performance issues flagged +### 4.2 Age-based considerations +- [ ] Determine if app should be rated Teen or Mature 17+ based on question language. +- [ ] Ensure ads (none currently) do not affect rating. +- [ ] In-app purchase disclosure accurate. -## Known Gaps (resolve before public release) +--- -- Date/Time pickers in DateBuilder are TODO placeholders -- RevenueCat API key is placeholder -- Cloud Functions are scaffolded but not deployed to Firebase -- Privacy/Terms URLs are placeholders — need real hosted pages \ No newline at end of file +## 5. App Signing Key Checklist + +### 5.1 Play App Signing +- [ ] Decision made: Play App Signing enabled (recommended) or opt-out (requires justification). +- [ ] If using Play App Signing, upload key generated separately from signing key. +- [ ] Upload signing key backed up securely; loss blocks future updates. +- [ ] App signing key (held by Google) fingerprint recorded. + +### 5.2 Key requirements +- [ ] Signing key algorithm: RSA, key size >= 2048 bits. +- [ ] Key validity period covers beyond 22 October 2033 (Google Play requirement). +- [ ] Keystore file, passwords, and alias stored in team password manager / HSM. +- [ ] CI pipeline injects signing credentials via environment variables, not hardcoded. + +### 5.3 Verification +- [ ] Signed AAB/APK uploads successfully to Play Console. +- [ ] Play Console reports signature valid. +- [ ] Same signing key used for all future updates. + +--- + +## 6. In-App Products / Subscriptions + +- [ ] Subscription products created in Play Console matching RevenueCat product IDs. +- [ ] Product titles/descriptions match paywall copy. +- [ ] Base plans and offers configured (free trial if offered). +- [ ] Subscription terms URL linked. +- [ ] RevenueCat dashboard product IDs match Play Console SKUs. +- [ ] Test licensing enabled for internal testers. + +--- + +## 7. Compliance & Policy + +- [ ] App complies with Google Play Families Policy (if applicable). +- [ ] No deceptive behavior, malware, or abuse of permissions. +- [ ] Permissions declared (`INTERNET`, `POST_NOTIFICATIONS`) are necessary and explained if requested. +- [ ] Data safety form completed and consistent with privacy policy. +- [ ] Accessibility considerations documented; minimum touch targets and labels reviewed. +- [ ] No restricted content in screenshots or description. + +--- + +## 8. Pre-Launch Checklist + +- [ ] Internal testing track tested end-to-end with closed testers. +- [ ] Pre-launch report reviewed for crashes, ANRs, security, and performance. +- [ ] Store listing content spell-checked and approved. +- [ ] Release notes / "What's new" text prepared for first submission. +- [ ] Rollout plan: staged release percentage and rollback plan defined. +- [ ] Monitoring: Crashlytics, Analytics, and RevenueCat dashboards ready. + +--- + +## 9. Asset Inventory + +| Asset | Spec | Status | Owner | +|-------|------|--------|-------| +| App name | ≤ 30 chars displayed | Draft | | +| Short description | ≤ 80 chars | Draft | | +| Full description | ≤ 4,000 chars | Draft | | +| Launcher icon | Adaptive, all densities | Ready/Not ready | | +| Feature graphic | 1024 × 500 | Ready/Not ready | | +| Phone screenshots | 2–8 images | Ready/Not ready | | +| Tablet screenshots | Optional | Ready/Not ready | | +| Promo video | Optional | Ready/Not ready | | +| Privacy policy URL | HTTPS | Ready/Not ready | | +| Terms URL | HTTPS | Ready/Not ready | | +| Subscription terms URL | HTTPS | Ready/Not ready | | +| Support URL | HTTPS | Needs migration | | + +--- + +## 10. Sign-off + +| Role | Name | Date | Notes | +|------|------|------|-------| +| Product | | | | +| Design | | | | +| Legal / Compliance | | | | +| Engineering | | | |