fix(release): date/time pickers, debug logging guards, RC_API_KEY comment, ExternalLinks TODOs (gap fixes)
This commit is contained in:
parent
160fb38c4f
commit
ede7c70ea7
|
|
@ -20,6 +20,8 @@ android {
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.0"
|
||||||
|
|
||||||
// RevenueCat API key is supplied via local.properties (RC_API_KEY) and never committed.
|
// 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(
|
buildConfigField(
|
||||||
"String",
|
"String",
|
||||||
"RC_API_KEY",
|
"RC_API_KEY",
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,13 @@ import android.net.Uri
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
||||||
object ExternalLinks {
|
object ExternalLinks {
|
||||||
|
// TODO: Update placeholder URL before production.
|
||||||
const val PRIVACY_POLICY = "https://closer.app/privacy"
|
const val PRIVACY_POLICY = "https://closer.app/privacy"
|
||||||
|
// TODO: Update placeholder URL before production.
|
||||||
const val TERMS_OF_SERVICE = "https://closer.app/terms"
|
const val TERMS_OF_SERVICE = "https://closer.app/terms"
|
||||||
|
// TODO: Update placeholder URL before production.
|
||||||
const val SUBSCRIPTION_TERMS = "https://closer.app/subscription-terms"
|
const val SUBSCRIPTION_TERMS = "https://closer.app/subscription-terms"
|
||||||
|
// TODO: Update placeholder URL before production.
|
||||||
const val SUPPORT = "https://couplesconnect.app/support"
|
const val SUPPORT = "https://couplesconnect.app/support"
|
||||||
|
|
||||||
fun openUrl(context: Context, url: String) {
|
fun openUrl(context: Context, url: String) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package app.closer.data.questions
|
package app.closer.data.questions
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import app.closer.BuildConfig
|
||||||
import app.closer.domain.model.*
|
import app.closer.domain.model.*
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
@ -26,6 +28,8 @@ import org.json.JSONObject
|
||||||
*/
|
*/
|
||||||
object QuestionJsonParser {
|
object QuestionJsonParser {
|
||||||
|
|
||||||
|
private const val TAG = "QuestionJsonParser"
|
||||||
|
|
||||||
data class ParsedQuestionBundle(
|
data class ParsedQuestionBundle(
|
||||||
val category: QuestionCategory,
|
val category: QuestionCategory,
|
||||||
val questions: List<Question>
|
val questions: List<Question>
|
||||||
|
|
@ -35,7 +39,9 @@ object QuestionJsonParser {
|
||||||
val jsonContent = try {
|
val jsonContent = try {
|
||||||
java.io.File(jsonFilePath).readText()
|
java.io.File(jsonFilePath).readText()
|
||||||
} catch (e: Exception) {
|
} 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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +66,9 @@ object QuestionJsonParser {
|
||||||
|
|
||||||
ParsedQuestionBundle(category, questions)
|
ParsedQuestionBundle(category, questions)
|
||||||
} catch (e: Exception) {
|
} 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
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,19 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.DatePicker
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.DatePickerDialog
|
||||||
|
import androidx.compose.material3.DisplayMode
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
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.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -146,6 +153,7 @@ private fun Header(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun InputCard(
|
private fun InputCard(
|
||||||
state: DateBuilderUiState,
|
state: DateBuilderUiState,
|
||||||
|
|
@ -154,6 +162,9 @@ private fun InputCard(
|
||||||
onBudgetChange: (Int) -> Unit,
|
onBudgetChange: (Int) -> Unit,
|
||||||
onDurationChange: (String) -> Unit
|
onDurationChange: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
var showDatePicker by remember { mutableStateOf(false) }
|
||||||
|
var showTimePicker by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(24.dp),
|
||||||
|
|
@ -169,9 +180,9 @@ private fun InputCard(
|
||||||
DateTimeField(
|
DateTimeField(
|
||||||
label = "When?",
|
label = "When?",
|
||||||
value = state.scheduledDate.takeIf { it > 0 }?.let {
|
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",
|
} ?: "Select a date",
|
||||||
onClick = { /* TODO: Date picker dialog */ },
|
onClick = { showDatePicker = true },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -179,7 +190,7 @@ private fun InputCard(
|
||||||
DateTimeField(
|
DateTimeField(
|
||||||
label = "What time?",
|
label = "What time?",
|
||||||
value = state.scheduledTime.ifEmpty { "Select a time" },
|
value = state.scheduledTime.ifEmpty { "Select a time" },
|
||||||
onClick = { /* TODO: Time picker dialog */ },
|
onClick = { showTimePicker = true },
|
||||||
modifier = Modifier.fillMaxWidth()
|
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
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -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
|
## Environment & Setup
|
||||||
- [ ] 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
|
|
||||||
|
|
||||||
## 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
|
### 1.1 Splash / App launch
|
||||||
- [ ] Today's question loads
|
- [ ] App launches to `MainActivity` without crash.
|
||||||
- [ ] User can answer (select option or type response)
|
- [ ] `AppNavigation` starts at `AppRoute.ONBOARDING` on fresh install.
|
||||||
- [ ] Answer is saved to Firestore
|
- [ ] Theme applies (`CloserTheme`) and status bar / navigation bar insets handled correctly.
|
||||||
- [ ] "Partner answered" notification flow works (if coupled)
|
- [ ] App name in launcher is "Closer".
|
||||||
|
|
||||||
### Question Packs
|
### 1.2 Onboarding screen (`OnboardingScreen`)
|
||||||
- [ ] Pack library screen lists available packs
|
- [ ] Value proposition visible and not placeholder text.
|
||||||
- [ ] Premium packs show lock icon (if not subscribed)
|
- [ ] CTA navigates to `CREATE_PROFILE` or `LOGIN` as expected.
|
||||||
- [ ] Free packs can be opened
|
- [ ] Secondary sign-in link works.
|
||||||
- [ ] Category picker shows categories
|
- [ ] No dead-end: user can always reach either create profile or login.
|
||||||
- [ ] Selecting a category loads questions from that category
|
|
||||||
|
|
||||||
### Question Detail & Thread
|
### 1.3 Create profile (`CreateProfileScreen`)
|
||||||
- [ ] Question detail screen loads with full text
|
- [ ] Name input accepts text and reflects in UI.
|
||||||
- [ ] Answer reveal shows both partner answers (if both answered)
|
- [ ] Validation prevents empty submission.
|
||||||
- [ ] Discussion thread loads messages
|
- [ ] Profile creation succeeds and navigates forward (home or pairing flow).
|
||||||
- [ ] User can send a message in thread
|
- [ ] Loading / error states handled.
|
||||||
- [ ] Answer history screen shows past answers
|
|
||||||
|
|
||||||
## 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
|
### 1.5 Known onboarding gaps (from code scan)
|
||||||
- [ ] Swipe right = "love", swipe left = "skip"
|
- `AccountScreen` shows "Local profile" and disables "Sign in or create account" and "Export your data" rows. These need real wiring before public release.
|
||||||
- [ ] "Maybe" button works
|
- Home header text changes based on `partnerName` presence; confirm both states render.
|
||||||
- [ ] Match reveal screen appears when both partners like same idea
|
|
||||||
- [ ] Date matches screen shows all mutual matches
|
|
||||||
- [ ] Empty state shows when no matches
|
|
||||||
|
|
||||||
## Date Builder
|
---
|
||||||
|
|
||||||
- [ ] Date builder screen loads with suggestion fields
|
## 2. Pairing
|
||||||
- [ ] Date and time picker buttons present (currently TODO placeholders)
|
|
||||||
- [ ] Save date plan works (if implemented)
|
|
||||||
|
|
||||||
## 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
|
### 2.2 Accept invite (`AcceptInviteScreen`)
|
||||||
- [ ] Adding items works
|
- [ ] 6-character code entry field with visual chunking and uppercase auto-capitalization.
|
||||||
- [ ] Completing/removing items works
|
- [ ] 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
|
### 2.4 Email invite (`EmailInviteScreen`)
|
||||||
- [ ] Category picker shows available categories
|
- [ ] This is currently a `PlaceholderScreen`. Verify it renders and that primary/secondary actions navigate correctly.
|
||||||
- [ ] Wheel complete screen shows result
|
- [ ] **Risk**: placeholder actions reference a hardcoded invite code `ABC123`; replace before public release.
|
||||||
- [ ] Wheel history screen shows past spins
|
|
||||||
|
|
||||||
## 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
|
## 3. Home
|
||||||
- [ ] Code entry field works
|
|
||||||
- [ ] Back button navigates correctly
|
|
||||||
- [ ] Valid code proceeds to InviteConfirm
|
|
||||||
|
|
||||||
### Invite Confirm
|
### 3.1 Home screen (`HomeScreen`)
|
||||||
- [ ] Partner info displays correctly
|
- [ ] Loads without crash; loading, error, and success states all tested.
|
||||||
- [ ] "Confirm" button links couple in Firestore
|
- [ ] Header shows correct subtitle for paired vs. unpaired user.
|
||||||
- [ ] "Wrong code" link returns to AcceptInvite
|
- [ ] Streak pill appears when `streakCount > 0`.
|
||||||
- [ ] Back button works
|
- [ ] 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
|
### 3.3 Moment cue / special dates section
|
||||||
- [ ] Profile row shows local profile state (not fake data)
|
- [ ] `SpecialDatesSection` previews render.
|
||||||
- [ ] Partner row tappable, navigates to RelationshipSettings
|
- [ ] Home moment cue card text not placeholder.
|
||||||
- [ ] Account screen shows profile info
|
- [ ] Hardcoded names ("Jessica", "Mark") and dates in `SpecialDatesSection` must be replaced with real data before public release.
|
||||||
- [ ] 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
|
|
||||||
|
|
||||||
## Paywall
|
---
|
||||||
|
|
||||||
- [ ] Paywall screen shows benefits list
|
## 4. Daily Question
|
||||||
- [ ] 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
|
|
||||||
|
|
||||||
## 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+
|
### 4.2 Answer input components
|
||||||
- [ ] FCM token obtained and stored in Firestore
|
- [ ] `QuestionAnswerInput`: empty-state handling, formatting, IME actions.
|
||||||
- [ ] Quiet hours can be set in notification settings
|
- [ ] `QuestionHelpExpandable`: expand/collapse works.
|
||||||
- [ ] Notification channel "Reminders" created
|
- [ ] `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)
|
### 5.1 Question pack library (`QuestionPackLibraryScreen`)
|
||||||
- **Debug logging**: `QuestionJsonParser.kt` uses `android.util.Log.e()` — should use Timber or remove before release
|
- [ ] 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 |
|
||||||
|
|--------|------|-------|--------|
|
||||||
|
| | | | |
|
||||||
|
|
|
||||||
|
|
@ -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
|
## 1. Pre-build Checklist
|
||||||
- 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
|
|
||||||
|
|
||||||
## 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`)
|
### 1.2 Secrets and API keys
|
||||||
- [ ] Set versionCode and versionName in `app/build.gradle.kts`
|
- [ ] `google-services.json` is present in `app/` and matches the internal testing package (`app.closer`).
|
||||||
- [ ] Set `minSdk`, `targetSdk` to appropriate values
|
- [ ] RevenueCat API key defined in `local.properties` as `RC_API_KEY`.
|
||||||
- [ ] Verify `RC_API_KEY` in `local.properties` (or placeholder for testing)
|
- [ ] Firebase App Check debug token removed or disabled for release.
|
||||||
- [ ] Verify `google-services.json` is present and points to correct Firebase project
|
- [ ] No hardcoded API keys, tokens, or credentials in Kotlin/XML source.
|
||||||
- [ ] Remove or guard debug logging (`android.util.Log` calls)
|
|
||||||
|
|
||||||
## 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)
|
### 2.1 Keystore
|
||||||
- [ ] Create 1 solo test account (single user: solo@test.com)
|
- [ ] Internal testing keystore generated or available (`.jks` / `.keystore`).
|
||||||
- [ ] Verify sign-up → profile creation → home flow on each
|
- [ ] Keystore password, key alias, and key password stored securely in CI or password manager — not in repo.
|
||||||
- [ ] Pair test couple via invite code
|
- [ ] Keystore used for signing is backed up in a secure location (1Password, team vault, hardware token).
|
||||||
- [ ] Verify paired state persists across app restarts
|
- [ ] 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
|
### 2.2 Gradle signing configuration
|
||||||
|
- [ ] `signingConfigs` block in `app/build.gradle.kts` configured:
|
||||||
- [ ] Install Firebase App Distribution Gradle plugin
|
```kotlin
|
||||||
- [ ] Add `app distribution` section to `app/build.gradle.kts`
|
signingConfigs {
|
||||||
- [ ] Build release APK: `./gradlew :app:assembleRelease`
|
create("release") {
|
||||||
- [ ] Distribute via Firebase App Distribution:
|
storeFile = file(System.getenv("KEYSTORE_PATH") ?: "release.keystore")
|
||||||
```bash
|
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||||
firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk \
|
keyAlias = System.getenv("KEY_ALIAS")
|
||||||
--app <FIREBASE_APP_ID> \
|
keyPassword = System.getenv("KEY_PASSWORD")
|
||||||
--groups internal-testers \
|
}
|
||||||
--release-notes "Private MVP v0.1.x"
|
}
|
||||||
```
|
```
|
||||||
- [ ] Add tester email addresses to Firebase App Distribution group
|
- [ ] `buildTypes.release` references `signingConfigs.getByName("release")`.
|
||||||
- [ ] Testers receive download link via email
|
- [ ] 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
|
### 3.1 Firebase project
|
||||||
- [ ] Monitor Firestore usage in Firebase Console
|
- [ ] Internal testing Firebase project created/separate from production.
|
||||||
- [ ] Monitor Cloud Function logs for errors
|
- [ ] `app.closer` Android app registered with correct SHA-1/SHA-256 certificates.
|
||||||
- [ ] Collect tester feedback via dedicated channel
|
- [ ] `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 | | | |
|
||||||
|
|
|
||||||
|
|
@ -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)
|
## 1. Store Listing Metadata
|
||||||
- [ ] 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
|
|
||||||
|
|
||||||
### Feature Graphic
|
### 1.1 App name
|
||||||
- [ ] 1024x500px JPEG or PNG
|
- [ ] App name finalized: **Closer**.
|
||||||
- [ ] Should convey app theme (couples, connection, questions)
|
- [ ] App name matches `android:label` in `AndroidManifest.xml` (currently `@string/app_name`).
|
||||||
- [ ] Must not contain device frames or text that duplicates store listing
|
- [ ] App name is not trademark-conflicting in target categories/territories.
|
||||||
|
|
||||||
### App Icon
|
### 1.2 Short description
|
||||||
- [ ] 512x512 PNG, 32-bit color, no transparency
|
- [ ] Short description <= 80 characters.
|
||||||
- [ ] Adaptive icon already defined in project — verify it renders well
|
- [ ] 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
|
### 1.4 Keywords / tags
|
||||||
- [ ] Max 30 characters: "Closer — Couple Questions"
|
- [ ] 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
|
## 2. Graphic Assets
|
||||||
- [ ] Max 4000 characters
|
|
||||||
- [ ] Should cover: daily questions, date match, spin wheel, bucket list, premium features
|
|
||||||
- [ ] Include relevant keywords naturally
|
|
||||||
|
|
||||||
### Category
|
### 2.1 App icon
|
||||||
- [ ] Primary: Lifestyle
|
- [ ] Launcher icon provided in all required densities (`mipmap-mdpi` through `mipmap-xxxhdpi`).
|
||||||
- [ ] Secondary: Social (if available)
|
- [ ] 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
|
### 2.2 Feature graphic
|
||||||
- [ ] Couple, Relationship, Questions, Date Ideas, Connection
|
- [ ] 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)
|
### 2.4 Promo video / preview
|
||||||
- [ ] Terms of Service URL: `https://closer.app/terms` (currently placeholder — must be live before submission)
|
- [ ] Promo video optional but recommended; if used, YouTube URL added in Play Console.
|
||||||
- [ ] Subscription terms: `https://closer.app/subscription-terms` (placeholder)
|
- [ ] 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)
|
- [ ] Privacy policy URL defined and reachable: currently `https://closer.app/privacy`.
|
||||||
- [ ] Upload signing certificate to Play Console
|
- [ ] Terms of service URL defined and reachable: currently `https://closer.app/terms`.
|
||||||
- [ ] Keep local keystore backup in secure location
|
- [ ] Subscription terms URL defined and reachable: currently `https://closer.app/subscription-terms`.
|
||||||
- [ ] DO NOT commit keystore to git
|
- [ ] **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)
|
## 4. Content Rating
|
||||||
- [ ] Subscription tiers configured in Google Play Console:
|
|
||||||
- [ ] Monthly premium
|
|
||||||
- [ ] Annual premium (if offering)
|
|
||||||
- [ ] Distribution: All countries (or select markets for soft launch)
|
|
||||||
|
|
||||||
## 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)
|
### 4.2 Age-based considerations
|
||||||
- [ ] Address any accessibility issues flagged
|
- [ ] Determine if app should be rated Teen or Mature 17+ based on question language.
|
||||||
- [ ] Address any performance issues flagged
|
- [ ] 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
|
## 5. App Signing Key Checklist
|
||||||
- RevenueCat API key is placeholder
|
|
||||||
- Cloud Functions are scaffolded but not deployed to Firebase
|
### 5.1 Play App Signing
|
||||||
- Privacy/Terms URLs are placeholders — need real hosted pages
|
- [ ] 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 | | | |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue