From dd3afe04624fff291146ca8df6f329b70a3e3c04 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 14:16:56 +0000 Subject: [PATCH] Add the App Store link to the friend invite message Invites now carry both store links, labelled "iPhone:" and "Android:", since the recipient's phone is unknown. The sender's own store is listed first, as friends tend to share a platform. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DHTD3vveFPNVDz9uxWiyD9 --- CLAUDE.md | 2 +- lib/friends_screen.dart | 24 ++++++++++++++++++------ test/widget_test.dart | 17 +++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index aca0f5a..e37544d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,7 +88,7 @@ Fully implemented end-to-end. ## Tests -- `test/widget_test.dart` uses `firebase_auth_mocks` + `fake_cloud_firestore` and `setupFirebaseCoreMocks()` — tests run without real Firebase. **707 Dart tests passing.** +- `test/widget_test.dart` uses `firebase_auth_mocks` + `fake_cloud_firestore` and `setupFirebaseCoreMocks()` — tests run without real Firebase. **709 Dart tests passing.** - `functions/src/test/test.index.ts` uses `firebase-functions-test`. **606 TypeScript tests passing** (pure unit tests + auth/validation integration tests that gracefully skip when no emulator is running). Includes tests for `updateFcmTokens`, `diffFriends`, `shouldNotifyFirstClaim`, `shouldNotifyOvertake`, `buildOsmChange`, `parsePhotos`, `nextQuotaState`, `pointsForMonarch`, `maxDailyFromClaims`, `repointClaimsForPostbox` (mock Firestore), `submitReport`/`reviewReport` auth & validation, and the `plan_route` CLI helpers. - `test/cross_language_sync_test.dart` is the drift guard for facts duplicated across languages/files. Beyond the constants listed under "Added features", it now also parses source to assert: every `startScoring` call site sends an `attemptId` (Dart sheet, Wear, **and the Kotlin car**); every claim surface consults `MaintenanceGuard` and every entry point initialises Remote Config; and every collection the Cloud Functions touch has a `match` block in `firestore.rules`. Each is verified to FAIL when the thing it guards is removed. `countySlug` is checked against all 218 features of the heatmap geojson (TS side, `test.index.ts`). diff --git a/lib/friends_screen.dart b/lib/friends_screen.dart index b4b5183..584d300 100644 --- a/lib/friends_screen.dart +++ b/lib/friends_screen.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/foundation.dart' show setEquals; +import 'package:flutter/foundation.dart' + show TargetPlatform, defaultTargetPlatform, setEquals; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:postbox_game/analytics_service.dart'; @@ -21,13 +22,24 @@ import 'package:share_plus/share_plus.dart'; const String _playStoreUrl = 'https://play.google.com/store/apps/details?id=com.code418.postbox_game'; +/// App Store listing, from the app's Apple ID in App Store Connect. +const String _appStoreUrl = 'https://apps.apple.com/app/id6819213888'; + /// Builds the invite message shared via the OS share sheet. Includes the /// user's UID so the recipient can add them back as a friend. -String buildInviteMessage(String uid) => - "Join me on Postbox Game! Hunt down Britain's historic postboxes, " - 'claim them for points and climb the leaderboards.\n\n' - 'Add me as a friend with my UID: $uid\n\n' - '$_playStoreUrl'; +/// +/// The recipient's phone is unknown, so both store links are included, +/// labelled. The sender's own store comes first ([platform], defaulting to +/// this device), since friends tend to share a platform. +String buildInviteMessage(String uid, {TargetPlatform? platform}) { + final ios = (platform ?? defaultTargetPlatform) == TargetPlatform.iOS; + const iphone = 'iPhone: $_appStoreUrl'; + const android = 'Android: $_playStoreUrl'; + return "Join me on Postbox Game! Hunt down Britain's historic postboxes, " + 'claim them for points and climb the leaderboards.\n\n' + 'Add me as a friend with my UID: $uid\n\n' + '${ios ? '$iphone\n$android' : '$android\n$iphone'}'; +} /// Friends list and add-friend by UID. /// Display names learned from one batched `users` lookup of [batch], given diff --git a/test/widget_test.dart b/test/widget_test.dart index 156992c..db44f8b 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1012,6 +1012,23 @@ void main() { test('uses no em-dash, matching the app writing voice', () { expect(buildInviteMessage('abc123'), isNot(contains('—'))); }); + + test('includes the App Store link too, whatever the sender uses', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + expect(buildInviteMessage('abc123', platform: platform), + contains('https://apps.apple.com/app/id6819213888'), + reason: '$platform'); + } + }); + + test("lists the sender's own store first", () { + int at(String msg, String s) => msg.indexOf(s); + final fromIos = buildInviteMessage('abc123', platform: TargetPlatform.iOS); + expect(at(fromIos, 'iPhone:'), lessThan(at(fromIos, 'Android:'))); + final fromAndroid = + buildInviteMessage('abc123', platform: TargetPlatform.android); + expect(at(fromAndroid, 'Android:'), lessThan(at(fromAndroid, 'iPhone:'))); + }); }); // ---------------------------------------------------------------------------