Show the list of features included in each product (#1157)

List product features inline in the paywall. Inside the list, highlight
the required features that originated the paywall.
This commit is contained in:
Davide 2025-02-10 20:18:56 +01:00 committed by GitHub
parent 954475e117
commit 914520009a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 119 additions and 57 deletions

View File

@ -55,6 +55,7 @@ struct InstalledProfileView: View, Routable {
toggleButton toggleButton
} }
.modifier(HeaderModifier(layout: layout)) .modifier(HeaderModifier(layout: layout))
.unanimated()
} }
} }

View File

@ -786,20 +786,20 @@ public enum Strings {
} }
} }
} }
public enum Product {
/// Included features
public static let includedFeatures = Strings.tr("Localizable", "views.paywall.product.included_features", fallback: "Included features")
}
public enum Rows { public enum Rows {
/// Restore purchases /// Restore purchases
public static let restorePurchases = Strings.tr("Localizable", "views.paywall.rows.restore_purchases", fallback: "Restore purchases") public static let restorePurchases = Strings.tr("Localizable", "views.paywall.rows.restore_purchases", fallback: "Restore purchases")
} }
public enum Sections { public enum Sections {
public enum IncludedFeatures {
/// Also includes
public static let header = Strings.tr("Localizable", "views.paywall.sections.included_features.header", fallback: "Also includes")
}
public enum Products { public enum Products {
/// All purchases support Family Sharing. /// All purchases support Family Sharing.
public static let footer = Strings.tr("Localizable", "views.paywall.sections.products.footer", fallback: "All purchases support Family Sharing.") public static let footer = Strings.tr("Localizable", "views.paywall.sections.products.footer", fallback: "All purchases support Family Sharing.")
/// Available products /// Suggested products
public static let header = Strings.tr("Localizable", "views.paywall.sections.products.header", fallback: "Available products") public static let header = Strings.tr("Localizable", "views.paywall.sections.products.header", fallback: "Suggested products")
} }
public enum RequiredFeatures { public enum RequiredFeatures {
/// Required features /// Required features

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Ihre Käufe werden überprüft."; "views.paywall.alerts.verification.connect.1" = "Ihre Käufe werden überprüft.";
"views.paywall.alerts.verification.connect.2" = "Falls die Überprüfung nicht abgeschlossen werden kann, wird die Verbindung in %d Minuten beendet."; "views.paywall.alerts.verification.connect.2" = "Falls die Überprüfung nicht abgeschlossen werden kann, wird die Verbindung in %d Minuten beendet.";
"views.paywall.alerts.verification.edit" = "Bitte warten Sie, während Ihre Käufe überprüft werden."; "views.paywall.alerts.verification.edit" = "Bitte warten Sie, während Ihre Käufe überprüft werden.";
"views.paywall.product.included_features" = "Enthaltene Funktionen";
"views.paywall.rows.restore_purchases" = "Käufe wiederherstellen"; "views.paywall.rows.restore_purchases" = "Käufe wiederherstellen";
"views.paywall.sections.all_features.header" = "Die Vollversion enthält"; "views.paywall.sections.all_features.header" = "Die Vollversion enthält";
"views.paywall.sections.full_products.header" = "Vollversion"; "views.paywall.sections.full_products.header" = "Vollversion";
"views.paywall.sections.included_features.header" = "Enthält außerdem"; "views.paywall.sections.included_features.header" = "Enthält außerdem";
"views.paywall.sections.products.footer" = "Alle Käufe unterstützen die Familienfreigabe."; "views.paywall.sections.products.footer" = "Alle Käufe unterstützen die Familienfreigabe.";
"views.paywall.sections.products.header" = "Verfügbare Produkte"; "views.paywall.sections.products.header" = "Empfohlene Produkte";
"views.paywall.sections.required_features.header" = "Erforderliche Funktionen"; "views.paywall.sections.required_features.header" = "Erforderliche Funktionen";
"views.paywall.sections.restore.footer" = "Wenn du diese App oder Funktion in der Vergangenheit gekauft hast, kannst du deine Käufe wiederherstellen."; "views.paywall.sections.restore.footer" = "Wenn du diese App oder Funktion in der Vergangenheit gekauft hast, kannst du deine Käufe wiederherstellen.";
"views.paywall.sections.restore.header" = "Wiederherstellen"; "views.paywall.sections.restore.header" = "Wiederherstellen";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Οι αγορές σας επαληθεύονται."; "views.paywall.alerts.verification.connect.1" = "Οι αγορές σας επαληθεύονται.";
"views.paywall.alerts.verification.connect.2" = "Αν η επαλήθευση δεν ολοκληρωθεί, η σύνδεση θα τερματιστεί σε %d λεπτά."; "views.paywall.alerts.verification.connect.2" = "Αν η επαλήθευση δεν ολοκληρωθεί, η σύνδεση θα τερματιστεί σε %d λεπτά.";
"views.paywall.alerts.verification.edit" = "Παρακαλώ περιμένετε όσο επαληθεύονται οι αγορές σας."; "views.paywall.alerts.verification.edit" = "Παρακαλώ περιμένετε όσο επαληθεύονται οι αγορές σας.";
"views.paywall.product.included_features" = "Περιλαμβανόμενες λειτουργίες";
"views.paywall.rows.restore_purchases" = "Επαναφορά αγορών"; "views.paywall.rows.restore_purchases" = "Επαναφορά αγορών";
"views.paywall.sections.all_features.header" = "Η πλήρης έκδοση περιλαμβάνει"; "views.paywall.sections.all_features.header" = "Η πλήρης έκδοση περιλαμβάνει";
"views.paywall.sections.full_products.header" = "Πλήρης έκδοση"; "views.paywall.sections.full_products.header" = "Πλήρης έκδοση";
"views.paywall.sections.included_features.header" = "Περιλαμβάνει επίσης"; "views.paywall.sections.included_features.header" = "Περιλαμβάνει επίσης";
"views.paywall.sections.products.footer" = "Όλες οι αγορές υποστηρίζουν την Οικογενειακή Κοινή Χρήση."; "views.paywall.sections.products.footer" = "Όλες οι αγορές υποστηρίζουν την Οικογενειακή Κοινή Χρήση.";
"views.paywall.sections.products.header" = "Διαθέσιμα προϊόντα"; "views.paywall.sections.products.header" = "Προτεινόμενα προϊόντα";
"views.paywall.sections.required_features.header" = "Απαιτούμενες λειτουργίες"; "views.paywall.sections.required_features.header" = "Απαιτούμενες λειτουργίες";
"views.paywall.sections.restore.footer" = "Εάν αγοράσατε αυτήν την εφαρμογή ή λειτουργία στο παρελθόν, μπορείτε να επαναφέρετε τις αγορές σας."; "views.paywall.sections.restore.footer" = "Εάν αγοράσατε αυτήν την εφαρμογή ή λειτουργία στο παρελθόν, μπορείτε να επαναφέρετε τις αγορές σας.";
"views.paywall.sections.restore.header" = "Επαναφορά"; "views.paywall.sections.restore.header" = "Επαναφορά";

View File

@ -78,12 +78,12 @@
"views.migration.alerts.delete.message" = "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@"; "views.migration.alerts.delete.message" = "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@";
"views.paywall.sections.required_features.header" = "Required features"; "views.paywall.sections.required_features.header" = "Required features";
"views.paywall.sections.included_features.header" = "Also includes"; "views.paywall.sections.products.header" = "Suggested products";
"views.paywall.sections.products.header" = "Available products";
"views.paywall.sections.products.footer" = "All purchases support Family Sharing."; "views.paywall.sections.products.footer" = "All purchases support Family Sharing.";
"views.paywall.sections.restore.header" = "Restore"; "views.paywall.sections.restore.header" = "Restore";
"views.paywall.sections.restore.footer" = "If you bought this app or feature in the past, you can restore your purchases."; "views.paywall.sections.restore.footer" = "If you bought this app or feature in the past, you can restore your purchases.";
"views.paywall.rows.restore_purchases" = "Restore purchases"; "views.paywall.rows.restore_purchases" = "Restore purchases";
"views.paywall.product.included_features" = "Included features";
"views.paywall.alerts.confirmation.title" = "Purchase required"; "views.paywall.alerts.confirmation.title" = "Purchase required";
"views.paywall.alerts.confirmation.message" = "This profile requires paid features to work."; "views.paywall.alerts.confirmation.message" = "This profile requires paid features to work.";
"views.paywall.alerts.confirmation.message.connect" = "You may test the connection for %d minutes."; "views.paywall.alerts.confirmation.message.connect" = "You may test the connection for %d minutes.";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Tus compras están siendo verificadas."; "views.paywall.alerts.verification.connect.1" = "Tus compras están siendo verificadas.";
"views.paywall.alerts.verification.connect.2" = "Si la verificación no se completa, la conexión finalizará en %d minutos."; "views.paywall.alerts.verification.connect.2" = "Si la verificación no se completa, la conexión finalizará en %d minutos.";
"views.paywall.alerts.verification.edit" = "Por favor, espera mientras verificamos tus compras."; "views.paywall.alerts.verification.edit" = "Por favor, espera mientras verificamos tus compras.";
"views.paywall.product.included_features" = "Funciones incluidas";
"views.paywall.rows.restore_purchases" = "Restaurar compras"; "views.paywall.rows.restore_purchases" = "Restaurar compras";
"views.paywall.sections.all_features.header" = "La versión completa incluye"; "views.paywall.sections.all_features.header" = "La versión completa incluye";
"views.paywall.sections.full_products.header" = "Versión completa"; "views.paywall.sections.full_products.header" = "Versión completa";
"views.paywall.sections.included_features.header" = "También incluye"; "views.paywall.sections.included_features.header" = "También incluye";
"views.paywall.sections.products.footer" = "Todas las compras admiten En Familia."; "views.paywall.sections.products.footer" = "Todas las compras admiten En Familia.";
"views.paywall.sections.products.header" = "Productos disponibles"; "views.paywall.sections.products.header" = "Productos sugeridos";
"views.paywall.sections.required_features.header" = "Características requeridas"; "views.paywall.sections.required_features.header" = "Características requeridas";
"views.paywall.sections.restore.footer" = "Si compraste esta app o característica en el pasado, puedes restaurar tus compras."; "views.paywall.sections.restore.footer" = "Si compraste esta app o característica en el pasado, puedes restaurar tus compras.";
"views.paywall.sections.restore.header" = "Restaurar"; "views.paywall.sections.restore.header" = "Restaurar";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Vos achats sont en cours de vérification."; "views.paywall.alerts.verification.connect.1" = "Vos achats sont en cours de vérification.";
"views.paywall.alerts.verification.connect.2" = "Si la vérification ne peut être complétée, la connexion sarrêtera dans %d minutes."; "views.paywall.alerts.verification.connect.2" = "Si la vérification ne peut être complétée, la connexion sarrêtera dans %d minutes.";
"views.paywall.alerts.verification.edit" = "Veuillez patienter pendant la vérification de vos achats."; "views.paywall.alerts.verification.edit" = "Veuillez patienter pendant la vérification de vos achats.";
"views.paywall.product.included_features" = "Fonctionnalités incluses";
"views.paywall.rows.restore_purchases" = "Restaurer les achats"; "views.paywall.rows.restore_purchases" = "Restaurer les achats";
"views.paywall.sections.all_features.header" = "La version complète inclut"; "views.paywall.sections.all_features.header" = "La version complète inclut";
"views.paywall.sections.full_products.header" = "Version complète"; "views.paywall.sections.full_products.header" = "Version complète";
"views.paywall.sections.included_features.header" = "Comprend également"; "views.paywall.sections.included_features.header" = "Comprend également";
"views.paywall.sections.products.footer" = "Tous les achats prennent en charge le Partage familial."; "views.paywall.sections.products.footer" = "Tous les achats prennent en charge le Partage familial.";
"views.paywall.sections.products.header" = "Produits disponibles"; "views.paywall.sections.products.header" = "Produits suggérés";
"views.paywall.sections.required_features.header" = "Fonctionnalités requises"; "views.paywall.sections.required_features.header" = "Fonctionnalités requises";
"views.paywall.sections.restore.footer" = "Si vous avez acheté cette application ou cette fonctionnalité dans le passé, vous pouvez restaurer vos achats."; "views.paywall.sections.restore.footer" = "Si vous avez acheté cette application ou cette fonctionnalité dans le passé, vous pouvez restaurer vos achats.";
"views.paywall.sections.restore.header" = "Restaurer"; "views.paywall.sections.restore.header" = "Restaurer";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "I tuoi acquisti sono in fase di verifica."; "views.paywall.alerts.verification.connect.1" = "I tuoi acquisti sono in fase di verifica.";
"views.paywall.alerts.verification.connect.2" = "Se la verifica non può essere completata, la connessione terminerà tra %d minuti."; "views.paywall.alerts.verification.connect.2" = "Se la verifica non può essere completata, la connessione terminerà tra %d minuti.";
"views.paywall.alerts.verification.edit" = "Attendere mentre i tuoi acquisti vengono verificati."; "views.paywall.alerts.verification.edit" = "Attendere mentre i tuoi acquisti vengono verificati.";
"views.paywall.product.included_features" = "Funzionalità incluse";
"views.paywall.rows.restore_purchases" = "Ripristina acquisti"; "views.paywall.rows.restore_purchases" = "Ripristina acquisti";
"views.paywall.sections.all_features.header" = "La versione completa include"; "views.paywall.sections.all_features.header" = "La versione completa include";
"views.paywall.sections.full_products.header" = "Versione completa"; "views.paywall.sections.full_products.header" = "Versione completa";
"views.paywall.sections.included_features.header" = "Include anche"; "views.paywall.sections.included_features.header" = "Include anche";
"views.paywall.sections.products.footer" = "Tutti gli acquisti supportano “In famiglia”."; "views.paywall.sections.products.footer" = "Tutti gli acquisti supportano “In famiglia”.";
"views.paywall.sections.products.header" = "Prodotti disponibili"; "views.paywall.sections.products.header" = "Prodotti suggeriti";
"views.paywall.sections.required_features.header" = "Funzionalità richieste"; "views.paywall.sections.required_features.header" = "Funzionalità richieste";
"views.paywall.sections.restore.footer" = "Se hai acquistato questa app o funzionalità in passato, puoi ripristinare i tuoi acquisti."; "views.paywall.sections.restore.footer" = "Se hai acquistato questa app o funzionalità in passato, puoi ripristinare i tuoi acquisti.";
"views.paywall.sections.restore.header" = "Ripristina"; "views.paywall.sections.restore.header" = "Ripristina";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Je aankopen worden geverifieerd."; "views.paywall.alerts.verification.connect.1" = "Je aankopen worden geverifieerd.";
"views.paywall.alerts.verification.connect.2" = "Als de verificatie niet kan worden voltooid, wordt de verbinding over %d minuten beëindigd."; "views.paywall.alerts.verification.connect.2" = "Als de verificatie niet kan worden voltooid, wordt de verbinding over %d minuten beëindigd.";
"views.paywall.alerts.verification.edit" = "Even geduld terwijl we je aankopen verifiëren."; "views.paywall.alerts.verification.edit" = "Even geduld terwijl we je aankopen verifiëren.";
"views.paywall.product.included_features" = "Inbegrepen functies";
"views.paywall.rows.restore_purchases" = "Aankopen herstellen"; "views.paywall.rows.restore_purchases" = "Aankopen herstellen";
"views.paywall.sections.all_features.header" = "De volledige versie bevat"; "views.paywall.sections.all_features.header" = "De volledige versie bevat";
"views.paywall.sections.full_products.header" = "Volledige versie"; "views.paywall.sections.full_products.header" = "Volledige versie";
"views.paywall.sections.included_features.header" = "Bevat ook"; "views.paywall.sections.included_features.header" = "Bevat ook";
"views.paywall.sections.products.footer" = "Alle aankopen ondersteunen Delen met gezin."; "views.paywall.sections.products.footer" = "Alle aankopen ondersteunen Delen met gezin.";
"views.paywall.sections.products.header" = "Beschikbare producten"; "views.paywall.sections.products.header" = "Voorgestelde producten";
"views.paywall.sections.required_features.header" = "Vereiste functies"; "views.paywall.sections.required_features.header" = "Vereiste functies";
"views.paywall.sections.restore.footer" = "Als je deze app of functie eerder hebt gekocht, kun je je aankopen herstellen."; "views.paywall.sections.restore.footer" = "Als je deze app of functie eerder hebt gekocht, kun je je aankopen herstellen.";
"views.paywall.sections.restore.header" = "Herstellen"; "views.paywall.sections.restore.header" = "Herstellen";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Twoje zakupy są weryfikowane."; "views.paywall.alerts.verification.connect.1" = "Twoje zakupy są weryfikowane.";
"views.paywall.alerts.verification.connect.2" = "Jeśli weryfikacja nie zostanie zakończona, połączenie zostanie zakończone za %d minut."; "views.paywall.alerts.verification.connect.2" = "Jeśli weryfikacja nie zostanie zakończona, połączenie zostanie zakończone za %d minut.";
"views.paywall.alerts.verification.edit" = "Proszę czekać, trwa weryfikacja zakupów."; "views.paywall.alerts.verification.edit" = "Proszę czekać, trwa weryfikacja zakupów.";
"views.paywall.product.included_features" = "Uwzględnione funkcje";
"views.paywall.rows.restore_purchases" = "Przywróć zakupy"; "views.paywall.rows.restore_purchases" = "Przywróć zakupy";
"views.paywall.sections.all_features.header" = "Pełna wersja zawiera"; "views.paywall.sections.all_features.header" = "Pełna wersja zawiera";
"views.paywall.sections.full_products.header" = "Pełna wersja"; "views.paywall.sections.full_products.header" = "Pełna wersja";
"views.paywall.sections.included_features.header" = "Zawiera także"; "views.paywall.sections.included_features.header" = "Zawiera także";
"views.paywall.sections.products.footer" = "Wszystkie zakupy obsługują Chmurę rodzinną."; "views.paywall.sections.products.footer" = "Wszystkie zakupy obsługują Chmurę rodzinną.";
"views.paywall.sections.products.header" = "Dostępne produkty"; "views.paywall.sections.products.header" = "Sugerowane produkty";
"views.paywall.sections.required_features.header" = "Wymagane funkcje"; "views.paywall.sections.required_features.header" = "Wymagane funkcje";
"views.paywall.sections.restore.footer" = "Jeśli wcześniej kupiłeś tę aplikację lub funkcję, możesz przywrócić swoje zakupy."; "views.paywall.sections.restore.footer" = "Jeśli wcześniej kupiłeś tę aplikację lub funkcję, możesz przywrócić swoje zakupy.";
"views.paywall.sections.restore.header" = "Przywróć"; "views.paywall.sections.restore.header" = "Przywróć";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Suas compras estão sendo verificadas."; "views.paywall.alerts.verification.connect.1" = "Suas compras estão sendo verificadas.";
"views.paywall.alerts.verification.connect.2" = "Se a verificação não for concluída, a conexão será encerrada em %d minutos."; "views.paywall.alerts.verification.connect.2" = "Se a verificação não for concluída, a conexão será encerrada em %d minutos.";
"views.paywall.alerts.verification.edit" = "Aguarde enquanto suas compras estão sendo verificadas."; "views.paywall.alerts.verification.edit" = "Aguarde enquanto suas compras estão sendo verificadas.";
"views.paywall.product.included_features" = "Recursos incluídos";
"views.paywall.rows.restore_purchases" = "Restaurar compras"; "views.paywall.rows.restore_purchases" = "Restaurar compras";
"views.paywall.sections.all_features.header" = "A versão completa inclui"; "views.paywall.sections.all_features.header" = "A versão completa inclui";
"views.paywall.sections.full_products.header" = "Versão completa"; "views.paywall.sections.full_products.header" = "Versão completa";
"views.paywall.sections.included_features.header" = "Também inclui"; "views.paywall.sections.included_features.header" = "Também inclui";
"views.paywall.sections.products.footer" = "Todas as compras são compatíveis com Compartilhamento Familiar."; "views.paywall.sections.products.footer" = "Todas as compras são compatíveis com Compartilhamento Familiar.";
"views.paywall.sections.products.header" = "Produtos disponíveis"; "views.paywall.sections.products.header" = "Produtos sugeridos";
"views.paywall.sections.required_features.header" = "Recursos necessários"; "views.paywall.sections.required_features.header" = "Recursos necessários";
"views.paywall.sections.restore.footer" = "Se você comprou este app ou recurso no passado, pode restaurar suas compras."; "views.paywall.sections.restore.footer" = "Se você comprou este app ou recurso no passado, pode restaurar suas compras.";
"views.paywall.sections.restore.header" = "Restaurar"; "views.paywall.sections.restore.header" = "Restaurar";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Ваши покупки проверяются."; "views.paywall.alerts.verification.connect.1" = "Ваши покупки проверяются.";
"views.paywall.alerts.verification.connect.2" = "Если проверка не будет завершена, соединение завершится через %d минут."; "views.paywall.alerts.verification.connect.2" = "Если проверка не будет завершена, соединение завершится через %d минут.";
"views.paywall.alerts.verification.edit" = "Пожалуйста, подождите, пока ваши покупки проверяются."; "views.paywall.alerts.verification.edit" = "Пожалуйста, подождите, пока ваши покупки проверяются.";
"views.paywall.product.included_features" = "Включенные функции";
"views.paywall.rows.restore_purchases" = "Восстановить покупки"; "views.paywall.rows.restore_purchases" = "Восстановить покупки";
"views.paywall.sections.all_features.header" = "Полная версия включает"; "views.paywall.sections.all_features.header" = "Полная версия включает";
"views.paywall.sections.full_products.header" = "Полная версия"; "views.paywall.sections.full_products.header" = "Полная версия";
"views.paywall.sections.included_features.header" = "Также включает"; "views.paywall.sections.included_features.header" = "Также включает";
"views.paywall.sections.products.footer" = "Все покупки поддерживают Семейный доступ."; "views.paywall.sections.products.footer" = "Все покупки поддерживают Семейный доступ.";
"views.paywall.sections.products.header" = "Доступные продукты"; "views.paywall.sections.products.header" = "Рекомендуемые продукты";
"views.paywall.sections.required_features.header" = "Необходимые функции"; "views.paywall.sections.required_features.header" = "Необходимые функции";
"views.paywall.sections.restore.footer" = "Если вы уже купили это приложение или функцию в прошлом, вы можете восстановить свои покупки."; "views.paywall.sections.restore.footer" = "Если вы уже купили это приложение или функцию в прошлом, вы можете восстановить свои покупки.";
"views.paywall.sections.restore.header" = "Восстановить"; "views.paywall.sections.restore.header" = "Восстановить";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Dina köp verifieras."; "views.paywall.alerts.verification.connect.1" = "Dina köp verifieras.";
"views.paywall.alerts.verification.connect.2" = "Om verifieringen inte kan slutföras kommer anslutningen att avslutas om %d minuter."; "views.paywall.alerts.verification.connect.2" = "Om verifieringen inte kan slutföras kommer anslutningen att avslutas om %d minuter.";
"views.paywall.alerts.verification.edit" = "Vänligen vänta medan dina köp verifieras."; "views.paywall.alerts.verification.edit" = "Vänligen vänta medan dina köp verifieras.";
"views.paywall.product.included_features" = "Inkluderade funktioner";
"views.paywall.rows.restore_purchases" = "Återställ köp"; "views.paywall.rows.restore_purchases" = "Återställ köp";
"views.paywall.sections.all_features.header" = "Den fullständiga versionen innehåller"; "views.paywall.sections.all_features.header" = "Den fullständiga versionen innehåller";
"views.paywall.sections.full_products.header" = "Fullständig version"; "views.paywall.sections.full_products.header" = "Fullständig version";
"views.paywall.sections.included_features.header" = "Inkluderar även"; "views.paywall.sections.included_features.header" = "Inkluderar även";
"views.paywall.sections.products.footer" = "Alla köp stöder Familjedelning."; "views.paywall.sections.products.footer" = "Alla köp stöder Familjedelning.";
"views.paywall.sections.products.header" = "Tillgängliga produkter"; "views.paywall.sections.products.header" = "Föreslagna produkter";
"views.paywall.sections.required_features.header" = "Krävda funktioner"; "views.paywall.sections.required_features.header" = "Krävda funktioner";
"views.paywall.sections.restore.footer" = "Om du har köpt denna app eller funktion tidigare kan du återställa dina köp."; "views.paywall.sections.restore.footer" = "Om du har köpt denna app eller funktion tidigare kan du återställa dina köp.";
"views.paywall.sections.restore.header" = "Återställ"; "views.paywall.sections.restore.header" = "Återställ";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "Ваші покупки перевіряються."; "views.paywall.alerts.verification.connect.1" = "Ваші покупки перевіряються.";
"views.paywall.alerts.verification.connect.2" = "Якщо перевірку не вдасться завершити, підключення завершиться через %d хвилин."; "views.paywall.alerts.verification.connect.2" = "Якщо перевірку не вдасться завершити, підключення завершиться через %d хвилин.";
"views.paywall.alerts.verification.edit" = "Будь ласка, зачекайте, поки ваші покупки перевіряються."; "views.paywall.alerts.verification.edit" = "Будь ласка, зачекайте, поки ваші покупки перевіряються.";
"views.paywall.product.included_features" = "Включені функції";
"views.paywall.rows.restore_purchases" = "Відновити покупки"; "views.paywall.rows.restore_purchases" = "Відновити покупки";
"views.paywall.sections.all_features.header" = "Повна версія включає"; "views.paywall.sections.all_features.header" = "Повна версія включає";
"views.paywall.sections.full_products.header" = "Повна версія"; "views.paywall.sections.full_products.header" = "Повна версія";
"views.paywall.sections.included_features.header" = "Також включає"; "views.paywall.sections.included_features.header" = "Також включає";
"views.paywall.sections.products.footer" = "Усі покупки підтримують “Сімейний доступ”."; "views.paywall.sections.products.footer" = "Усі покупки підтримують “Сімейний доступ”.";
"views.paywall.sections.products.header" = "Доступні продукти"; "views.paywall.sections.products.header" = "Рекомендовані продукти";
"views.paywall.sections.required_features.header" = "Необхідні функції"; "views.paywall.sections.required_features.header" = "Необхідні функції";
"views.paywall.sections.restore.footer" = "Якщо ви раніше купували цей додаток або функцію, ви можете відновити свої покупки."; "views.paywall.sections.restore.footer" = "Якщо ви раніше купували цей додаток або функцію, ви можете відновити свої покупки.";
"views.paywall.sections.restore.header" = "Відновлення"; "views.paywall.sections.restore.header" = "Відновлення";

View File

@ -260,12 +260,13 @@
"views.paywall.alerts.verification.connect.1" = "您的购买正在验证中。"; "views.paywall.alerts.verification.connect.1" = "您的购买正在验证中。";
"views.paywall.alerts.verification.connect.2" = "如果无法完成验证,连接将在 %d 分钟后断开。"; "views.paywall.alerts.verification.connect.2" = "如果无法完成验证,连接将在 %d 分钟后断开。";
"views.paywall.alerts.verification.edit" = "请稍候,您的购买正在验证中。"; "views.paywall.alerts.verification.edit" = "请稍候,您的购买正在验证中。";
"views.paywall.product.included_features" = "包含的功能";
"views.paywall.rows.restore_purchases" = "恢复购买"; "views.paywall.rows.restore_purchases" = "恢复购买";
"views.paywall.sections.all_features.header" = "完整版本包括"; "views.paywall.sections.all_features.header" = "完整版本包括";
"views.paywall.sections.full_products.header" = "完整版本"; "views.paywall.sections.full_products.header" = "完整版本";
"views.paywall.sections.included_features.header" = "还包括"; "views.paywall.sections.included_features.header" = "还包括";
"views.paywall.sections.products.footer" = "所有购买均支持“家人共享”。"; "views.paywall.sections.products.footer" = "所有购买均支持“家人共享”。";
"views.paywall.sections.products.header" = "可用产品"; "views.paywall.sections.products.header" = "推荐产品";
"views.paywall.sections.required_features.header" = "必需功能"; "views.paywall.sections.required_features.header" = "必需功能";
"views.paywall.sections.restore.footer" = "如果您过去购买过此应用或功能,可以恢复您的购买记录。"; "views.paywall.sections.restore.footer" = "如果您过去购买过此应用或功能,可以恢复您的购买记录。";
"views.paywall.sections.restore.header" = "恢复"; "views.paywall.sections.restore.header" = "恢复";

View File

@ -74,6 +74,7 @@ extension Theme {
case tunnelUninstall case tunnelUninstall
case tvOff case tvOff
case tvOn case tvOn
case undisclose
case upgrade case upgrade
case warning case warning
} }
@ -135,6 +136,7 @@ extension Theme.ImageName {
return "tv" return "tv"
} }
case .tvOn: return "tv" case .tvOn: return "tv"
case .undisclose: return "chevron.up"
case .upgrade: return "arrow.up.circle" case .upgrade: return "arrow.up.circle"
case .warning: return "exclamationmark.triangle" case .warning: return "exclamationmark.triangle"
} }

View File

@ -37,7 +37,7 @@ enum FeatureListViewStyle {
struct FeatureListView<Content>: View where Content: View { struct FeatureListView<Content>: View where Content: View {
let style: FeatureListViewStyle let style: FeatureListViewStyle
let header: String var header: String?
let features: [AppFeature] let features: [AppFeature]
@ -50,12 +50,7 @@ struct FeatureListView<Content>: View where Content: View {
#if !os(tvOS) #if !os(tvOS)
case .table: case .table:
// XXX: work around Table artifact when 1 row and no headers tableView
if features.count > 1 {
tableView
} else {
listView
}
#endif #endif
} }
} }
@ -70,10 +65,8 @@ private extension FeatureListView {
#if !os(tvOS) #if !os(tvOS)
var tableView: some View { var tableView: some View {
Table(features.sorted()) { Table(features.sorted()) {
TableColumn("", content: content) TableColumn(header ?? "", content: content)
} }
.withoutColumnHeaders()
.themeSection(header: header)
} }
#endif #endif
} }

View File

@ -36,6 +36,8 @@ public struct PaywallProductView: View {
private let product: InAppProduct private let product: InAppProduct
private let highlightedFeatures: Set<AppFeature>
@Binding @Binding
private var purchasingIdentifier: String? private var purchasingIdentifier: String?
@ -43,10 +45,14 @@ public struct PaywallProductView: View {
private let onError: (Error) -> Void private let onError: (Error) -> Void
@State
private var isPresentingFeatures = false
public init( public init(
iapManager: IAPManager, iapManager: IAPManager,
style: PaywallProductViewStyle, style: PaywallProductViewStyle,
product: InAppProduct, product: InAppProduct,
highlightedFeatures: Set<AppFeature> = [],
purchasingIdentifier: Binding<String?>, purchasingIdentifier: Binding<String?>,
onComplete: @escaping (String, InAppPurchaseResult) -> Void, onComplete: @escaping (String, InAppPurchaseResult) -> Void,
onError: @escaping (Error) -> Void onError: @escaping (Error) -> Void
@ -54,12 +60,30 @@ public struct PaywallProductView: View {
self.iapManager = iapManager self.iapManager = iapManager
self.style = style self.style = style
self.product = product self.product = product
self.highlightedFeatures = highlightedFeatures
_purchasingIdentifier = purchasingIdentifier _purchasingIdentifier = purchasingIdentifier
self.onComplete = onComplete self.onComplete = onComplete
self.onError = onError self.onError = onError
} }
public var body: some View { public var body: some View {
VStack(alignment: .leading) {
productView
Group {
includedFeaturesButton
.padding(.top, 8)
includedFeaturesList
.if(isPresentingFeatures)
}
.font(.subheadline)
}
}
}
private extension PaywallProductView {
@ViewBuilder
var productView: some View {
if #available(iOS 17, macOS 14, tvOS 17, *) { if #available(iOS 17, macOS 14, tvOS 17, *) {
StoreKitProductView( StoreKitProductView(
style: style, style: style,
@ -79,4 +103,60 @@ public struct PaywallProductView: View {
) )
} }
} }
var includedFeaturesButton: some View {
Button {
isPresentingFeatures.toggle()
} label: {
HStack {
Text(Strings.Views.Paywall.Product.includedFeatures)
ThemeImage(isPresentingFeatures ? .undisclose : .disclose)
}
.contentShape(.rect)
}
.buttonStyle(.plain)
.cursor(.hand)
}
var includedFeaturesList: some View {
AppProduct(rawValue: product.productIdentifier)
.map { product in
FeatureListView(
style: .list,
features: product.features,
content: featureView
)
}
}
func featureView(for feature: AppFeature) -> some View {
HStack {
ThemeImage(.marked)
.opaque(highlightedFeatures.contains(feature))
Text(feature.localizedDescription)
.fontWeight(highlightedFeatures.contains(feature) ? .bold : .regular)
.scrollableOnTV()
}
}
}
#Preview {
List {
PaywallProductView(
iapManager: .forPreviews,
style: .paywall,
product: InAppProduct(
productIdentifier: AppProduct.Features.appleTV.rawValue,
localizedTitle: "Foo",
localizedPrice: "$10",
native: nil
),
highlightedFeatures: [.appleTV],
purchasingIdentifier: .constant(nil),
onComplete: { _, _ in },
onError: { _ in }
)
}
.withMockEnvironment()
} }

View File

@ -88,9 +88,6 @@ private extension PaywallView {
Form { Form {
requiredFeaturesView requiredFeaturesView
productsView productsView
if suggestedProducts == nil {
alsoIncludedEssentialsView
}
restoreView restoreView
linksView linksView
} }
@ -123,6 +120,7 @@ private extension PaywallView {
iapManager: iapManager, iapManager: iapManager,
style: .paywall, style: .paywall,
product: $0, product: $0,
highlightedFeatures: requiredFeatures,
purchasingIdentifier: $purchasingIdentifier, purchasingIdentifier: $purchasingIdentifier,
onComplete: onComplete, onComplete: onComplete,
onError: onError onError: onError
@ -136,22 +134,6 @@ private extension PaywallView {
} }
} }
var alsoIncludedEssentialsView: some View {
essentialFeatures
.filter {
!requiredFeatures.contains($0)
}
.nilIfEmpty
.map {
FeatureListView(
style: featuresStyle,
header: Strings.Views.Paywall.Sections.IncludedFeatures.header,
features: $0,
content: featureView(for:)
)
}
}
var linksView: some View { var linksView: some View {
Section { Section {
Link(Strings.Unlocalized.eula, destination: Constants.shared.websites.eula) Link(Strings.Unlocalized.eula, destination: Constants.shared.websites.eula)
@ -159,14 +141,6 @@ private extension PaywallView {
} }
} }
var featuresStyle: FeatureListViewStyle {
#if os(iOS)
.list
#else
.table
#endif
}
func featureView(for feature: AppFeature) -> some View { func featureView(for feature: AppFeature) -> some View {
Text(feature.localizedDescription) Text(feature.localizedDescription)
.scrollableOnTV() .scrollableOnTV()