Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 30 additions & 24 deletions app/src/main/java/to/bitkit/models/BitrefillCategory.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package to.bitkit.models

import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CardGiftcard
import androidx.compose.material.icons.filled.Checkroom
Expand All @@ -22,39 +23,44 @@ import androidx.compose.material.icons.filled.Storefront
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material.icons.filled.VideogameAsset
import androidx.compose.ui.graphics.vector.ImageVector
import to.bitkit.R

/**
* Represents the categories for purchases.
*
* @property title The display name of the category.
* @property titleRes The string resource of the category display name.
* @property route The navigation route associated with the category.
* @property icon The visual icon for the category.
*/
enum class BitrefillCategory(
val title: String,
@StringRes val titleRes: Int,
val route: String,
val icon: ImageVector
) {
APPAREL("Apparel", "buy/apparel", Icons.Filled.Checkroom),
AUTOMOBILES("Automobiles", "buy/automobiles", Icons.Filled.DirectionsCar),
CRUISES("Cruises", "buy/cruises", Icons.Filled.DirectionsBoat),
ECOMMERCE("Ecommerce", "buy/ecommerce", Icons.Filled.ShoppingCart),
ELECTRONICS("Electronics", "buy/electronics", Icons.Filled.Print),
ENTERTAINMENT("Entertainment", "buy/entertainment", Icons.Filled.Headphones),
EXPERIENCES("Experiences", "buy/experiences", Icons.Filled.Public),
FLIGHTS("Flights", "buy/flights", Icons.Filled.Flight),
FOOD("Food", "buy/food", Icons.Filled.Storefront),
FOOD_DELIVERY("Food Delivery", "buy/food-delivery", Icons.Filled.DeliveryDining),
GAMES("Games", "buy/games", Icons.Filled.VideogameAsset),
GIFTS("Gifts", "buy/gifts", Icons.Filled.CardGiftcard),
GROCERIES("Groceries", "buy/groceries", Icons.Filled.ShoppingBag),
HEALTH_AND_BEAUTY("Health & Beauty", "buy/health-beauty", Icons.Filled.FavoriteBorder),
HOME("Home", "buy/home", Icons.Filled.Home),
MULTI_BRAND("Multi-Brand", "buy/multi-brand", Icons.Filled.Layers),
PETS("Pets", "buy/pets", Icons.Filled.Pets),
RESTAURANTS("Restaurants", "buy/restaurants", Icons.Filled.Restaurant),
RETAIL("Retail", "buy/retail", Icons.Filled.Storefront),
STREAMING("Streaming", "buy/streaming", Icons.Filled.Videocam),
TRAVEL("Travel", "buy/travel", Icons.Filled.Flight),
VOIP("VoIP", "buy/voip", Icons.Filled.Phone)
APPAREL(R.string.other__shop__categories__apparel, "buy/apparel", Icons.Filled.Checkroom),
Comment thread
jvsena42 marked this conversation as resolved.
AUTOMOBILES(R.string.other__shop__categories__automobiles, "buy/automobiles", Icons.Filled.DirectionsCar),
CRUISES(R.string.other__shop__categories__cruises, "buy/cruises", Icons.Filled.DirectionsBoat),
ECOMMERCE(R.string.other__shop__categories__ecommerce, "buy/ecommerce", Icons.Filled.ShoppingCart),
ELECTRONICS(R.string.other__shop__categories__electronics, "buy/electronics", Icons.Filled.Print),
ENTERTAINMENT(R.string.other__shop__categories__entertainment, "buy/entertainment", Icons.Filled.Headphones),
EXPERIENCES(R.string.other__shop__categories__experiences, "buy/experiences", Icons.Filled.Public),
FLIGHTS(R.string.other__shop__categories__flights, "buy/flights", Icons.Filled.Flight),
FOOD(R.string.other__shop__categories__food, "buy/food", Icons.Filled.Storefront),
FOOD_DELIVERY(R.string.other__shop__categories__food_delivery, "buy/food-delivery", Icons.Filled.DeliveryDining),
GAMES(R.string.other__shop__categories__games, "buy/games", Icons.Filled.VideogameAsset),
GIFTS(R.string.other__shop__categories__gifts, "buy/gifts", Icons.Filled.CardGiftcard),
GROCERIES(R.string.other__shop__categories__groceries, "buy/groceries", Icons.Filled.ShoppingBag),
HEALTH_AND_BEAUTY(
R.string.other__shop__categories__health_and_beauty,
"buy/health-beauty",
Icons.Filled.FavoriteBorder,
),
HOME(R.string.other__shop__categories__home, "buy/home", Icons.Filled.Home),
MULTI_BRAND(R.string.other__shop__categories__multi_brand, "buy/multi-brand", Icons.Filled.Layers),
PETS(R.string.other__shop__categories__pets, "buy/pets", Icons.Filled.Pets),
RESTAURANTS(R.string.other__shop__categories__restaurants, "buy/restaurants", Icons.Filled.Restaurant),
RETAIL(R.string.other__shop__categories__retail, "buy/retail", Icons.Filled.Storefront),
STREAMING(R.string.other__shop__categories__streaming, "buy/streaming", Icons.Filled.Videocam),
TRAVEL(R.string.other__shop__categories__travel, "buy/travel", Icons.Filled.Flight),
VOIP(R.string.other__shop__categories__voip, "buy/voip", Icons.Filled.Phone)
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,13 @@ private fun ShopTabContent(
}

items(items = BitrefillCategory.entries.toList(), key = { it.name }) { item ->
val title = stringResource(item.titleRes)
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickableAlpha {
navigateWebView(item.route, item.title)
navigateWebView(item.route, title)
}
.padding(top = 8.5.dp, bottom = 10.5.dp)
) {
Expand All @@ -218,7 +219,7 @@ private fun ShopTabContent(
)
}
BodyM(
text = item.title,
text = title,
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp)
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,28 @@
<string name="other__scan_err_decoding">Decoding Error</string>
<string name="other__scan_err_interpret_title">Unable To Interpret Provided Data</string>
<string name="other__share_qr">Share QR Code Via</string>
<string name="other__shop__categories__apparel">Apparel</string>
<string name="other__shop__categories__automobiles">Automobiles</string>
<string name="other__shop__categories__cruises">Cruises</string>
<string name="other__shop__categories__ecommerce">Ecommerce</string>
<string name="other__shop__categories__electronics">Electronics</string>
<string name="other__shop__categories__entertainment">Entertainment</string>
<string name="other__shop__categories__experiences">Experiences</string>
<string name="other__shop__categories__flights">Flights</string>
<string name="other__shop__categories__food">Food</string>
<string name="other__shop__categories__food_delivery">Food Delivery</string>
<string name="other__shop__categories__games">Games</string>
<string name="other__shop__categories__gifts">Gifts</string>
<string name="other__shop__categories__groceries">Groceries</string>
<string name="other__shop__categories__health_and_beauty">Health &amp; Beauty</string>
<string name="other__shop__categories__home">Home</string>
<string name="other__shop__categories__multi_brand">Multi-Brand</string>
<string name="other__shop__categories__pets">Pets</string>
<string name="other__shop__categories__restaurants">Restaurants</string>
<string name="other__shop__categories__retail">Retail</string>
<string name="other__shop__categories__streaming">Streaming</string>
<string name="other__shop__categories__travel">Travel</string>
<string name="other__shop__categories__voip" translatable="false">VoIP</string>
<string name="other__shop__discover__esims__description">Go borderless</string>
<string name="other__shop__discover__esims__title">ESims</string>
<string name="other__shop__discover__gift_cards__description">Shop with Bitcoin</string>
Expand Down
51 changes: 51 additions & 0 deletions app/src/test/java/to/bitkit/models/BitrefillCategoryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package to.bitkit.models

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@Config(sdk = [34], qualifiers = "en-rUS")
@RunWith(RobolectricTestRunner::class)
class BitrefillCategoryTest {

private val context = ApplicationProvider.getApplicationContext<Context>()

@Test
fun `every entry points at the string resource named after it`() {
BitrefillCategory.entries.forEach { category ->
val expected = "other__shop__categories__${category.name.lowercase()}"
val actual = context.resources.getResourceEntryName(category.titleRes)
assertEquals(expected, actual, "'${category.name}' resolves to the wrong string resource")
}
}

@Test
fun `every title resolves to a distinct non-blank label`() {
val titles = BitrefillCategory.entries.associateWith { context.getString(it.titleRes) }

titles.forEach { (category, title) ->
assertTrue(title.isNotBlank(), "'${category.name}' resolves to a blank label")
}
assertEquals(
BitrefillCategory.entries.size,
titles.values.toSet().size,
"categories share a label: ${titles.values.groupBy { it }.filterValues { it.size > 1 }.keys}",
)
}

@Test
fun `every route is a distinct buy path`() {
val routes = BitrefillCategory.entries.map { it.route }

routes.forEach { route ->
assertTrue(route.startsWith("buy/"), "route '$route' is not a buy path")
assertTrue(route.removePrefix("buy/").isNotBlank(), "route '$route' has no slug")
}
assertEquals(BitrefillCategory.entries.size, routes.toSet().size, "categories share a route")
}
}
1 change: 1 addition & 0 deletions changelog.d/next/715.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Shop gift card category names can now be translated.
2 changes: 2 additions & 0 deletions journeys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ fixtures, push notifications) live in each suite's README.
| [payment-requests](payment-requests) | 2 | Requires a linked fixture issuer; rejected shapes are unit fixtures |
| [pubky-marketplace](pubky-marketplace) | 1 | Two-wallet Paykit marketplace payment; integration fixture required |
| [security](security) | 1 | PIN result sheet layout at a long locale and font scale; no README |
| [shop](shop) | 1 | Shop Discover category titles and web view handoff; needs Bitrefill reachable; no README |
| [subscriptions](subscriptions) | 4 | Paykit subscription lifecycle across two wallets, plus the Payments tab |
| [tags](tags) | 1 | Tag input length cap on an activity; no backend, no README |
| [transfers](transfers) | 1 | Transfer to Spending settling after the LSP closes the channel; no README |
Expand Down Expand Up @@ -154,6 +155,7 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691):
| `transfers/closed-channel-transfer-settles.xml` | not ported — the closed-channel and order-closure settle rules are an iOS follow-up |
| `deeplinks/*` | not ported — iOS registers the `bitkit` scheme but has no screen or sheet router |
| `backup-restore/restore-keeps-tags-and-closed-channels.xml` | not ported yet — iOS already gates uploads across the whole restore (`AppScene.restoreFromMostRecentBackup` sets `BackupService.setRestoring(true)` before the timestamp probe), but still applies the three activity slices in one block (`BackupService.performFullRestoreFromLatestBackup`), which is the half this journey pins; port it with the iOS slice fix |
| `shop/gift-card-category-titles.xml` | not ported — iOS still hardcodes the category names, and its route in has no screen deeplink |
| `home/pull-to-refresh-rates.xml` | not ported — iOS does not refresh exchange rates on pull to refresh |
| `security/pin-result-long-label.xml` | not ported — the toggle exists on the iOS security success screen, but the overlap check is a follow-up |
| `tags/activity-tag-length-cap.xml` | not ported — iOS has no 20-character cap on tag input |
Expand Down
36 changes: 36 additions & 0 deletions journeys/shop/gift-card-category-titles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<journey name="gift card category titles">
<description>
Proves the Shop Discover gift card category rows render all 22 titles in order, survive an app
locale change, and pass the title shown on the row to the gift card web view. Precondition:
onboarded debug build, dev mode on (the screen deeplink is debug-only and gated on it), device
and app locale English, network access to Bitrefill.

This journey does not yet distinguish resource-backed titles from the hardcoded enum they
replace, and a pass is not proof of localization. The English values added in
`values/strings.xml:572-593` are the same text the enum carried, and no `values-*` file carries
an `other__shop__categories__*` key yet, so every row reads English in every shipped locale
either way. A pseudolocale cannot close the gap: `androidResources.localeFilters`
(`app/build.gradle.kts:276`) keeps only the 16 shipped locales, so `en-XA` resources are
stripped. Once the Transifex pull lands the German strings, replace the German step below with
assertions on concrete translated titles ("Food Delivery" and "Health &amp; Beauty" rows plus
the web view top bar) and drop the English fallback allowance; that is what makes this journey
discriminate. "VoIP" is not translatable and stays "VoIP" in every locale.

The screen deeplink skips the Shop intro without marking it seen; iOS has no screen router, so a
port reaches Shop Discover through the drawer's Shop entry instead.
</description>
<actions>
<action>Run `adb shell am start -W -a android.intent.action.VIEW -d "bitkit://screen/shop-discover" to.bitkit.dev`</action>
<action>Verify that the Shop Discover screen is visible with the "Tab-shop" and "Tab-map" tabs and a "GIFT CARD CATEGORIES" header</action>
<action>Scroll the category list to the end and verify these 22 rows appear in order: "Apparel", "Automobiles", "Cruises", "Ecommerce", "Electronics", "Entertainment", "Experiences", "Flights", "Food", "Food Delivery", "Games", "Gifts", "Groceries", "Health &amp; Beauty", "Home", "Multi-Brand", "Pets", "Restaurants", "Retail", "Streaming", "Travel", "VoIP"</action>
<action>Tap "Food Delivery"</action>
<action>Verify that the top bar title reads "Shop Food Delivery"</action>
<action>Wait for the web view to load, then take a screenshot and verify the Bitrefill Food Delivery listing is visible</action>
<action>Press the device back button</action>
<action>Run `adb shell cmd locale set-app-locales to.bitkit.dev --locales de`, then `adb shell am force-stop to.bitkit.dev` and relaunch the app</action>
<action>Run `adb shell am start -W -a android.intent.action.VIEW -d "bitkit://screen/shop-discover" to.bitkit.dev`</action>
<action>Verify that the app did not crash, the Map tab reads "Karte", and all 22 category rows are still listed in the same order; until the German strings land they all read their English titles, which is the expected state and not a failure</action>
<action>Run `adb shell cmd locale set-app-locales to.bitkit.dev --locales ''`, then `adb shell am force-stop to.bitkit.dev` and relaunch the app</action>
<action>Verify that the wallet overview is visible in English</action>
</actions>
</journey>
Loading