From f353ae236df9dc3c8b9266c9060b4696eadcfec9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 10:30:31 -0300 Subject: [PATCH 01/12] fix: reuse same string keys for in-app and home-screen widgets --- Bitkit/Resources/Localization/ca.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/cs.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/de.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/el.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/en.lproj/Localizable.strings | 1 - .../Resources/Localization/es-419.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/es.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/fr.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/it.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/nl.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/pl.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings | 1 - Bitkit/Resources/Localization/ru.lproj/Localizable.strings | 1 - BitkitWidget/BlocksHomeScreenWidget.swift | 4 ++-- BitkitWidget/FactsHomeScreenWidget.swift | 4 ++-- BitkitWidget/NewsHomeScreenWidget.swift | 4 ++-- BitkitWidget/PriceHomeScreenWidget.swift | 4 ++-- 17 files changed, 8 insertions(+), 21 deletions(-) diff --git a/Bitkit/Resources/Localization/ca.lproj/Localizable.strings b/Bitkit/Resources/Localization/ca.lproj/Localizable.strings index 4df41828d..ecc896ca8 100644 --- a/Bitkit/Resources/Localization/ca.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ca.lproj/Localizable.strings @@ -978,7 +978,6 @@ "wallet__balance_hidden_message" = "Llisca el saldo de la teva cartera per revelar-lo de nou."; "wallet__balance_unit_switched_title" = "Canviat a {unit}"; "wallet__balance_unit_switched_message" = "Toca el saldo de la teva cartera per tornar-lo a {unit}."; -"widgets__widgets" = "Ginys"; "widgets__onboarding__title" = "Hola,\nGinys"; "widgets__onboarding__description" = "Gaudeix de fonts descentralitzades dels teus serveis web preferits afegint ginys divertits i útils a la teva cartera Bitkit."; "widgets__widget__nav_title" = "Giny"; diff --git a/Bitkit/Resources/Localization/cs.lproj/Localizable.strings b/Bitkit/Resources/Localization/cs.lproj/Localizable.strings index ab02e1b99..a7af52dd4 100644 --- a/Bitkit/Resources/Localization/cs.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/cs.lproj/Localizable.strings @@ -1136,7 +1136,6 @@ "wallet__receive_insufficient_text" = "Nedostatečná přijímací kapacita pro příjem této částky přes lightning."; "wallet__receive_foreground_title" = "Udržovat Bitkit na popředí"; "wallet__receive_foreground_msg" = "Při přepínání mezi aplikacemi může dojít k selhání plateb na váš disponibilní zůstatek."; -"widgets__widgets" = "Widgety"; "widgets__onboarding__title" = "Dobrý den,\nWidgety"; "widgets__onboarding__description" = "Užívejte si decentralizované kanály ze svých oblíbených webových služeb přidáním zábavných a užitečných widgetů do peněženky Bitkit."; "widgets__widget__nav_title" = "Widget"; diff --git a/Bitkit/Resources/Localization/de.lproj/Localizable.strings b/Bitkit/Resources/Localization/de.lproj/Localizable.strings index 869273695..2eee50823 100644 --- a/Bitkit/Resources/Localization/de.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/de.lproj/Localizable.strings @@ -1132,7 +1132,6 @@ "wallet__receive_insufficient_text" = "Unzureichende Empfangskapazität, um diesen Betrag über Lightning zu empfangen."; "wallet__receive_foreground_title" = "Bitkit im Vordergrund halten"; "wallet__receive_foreground_msg" = "Zahlungen auf Ihr Guthaben können fehlschlagen, wenn Sie zwischen Apps wechseln."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Hallo,\nWidgets"; "widgets__onboarding__description" = "Genieße dezentrale Feeds von deinen bevorzugten Webdiensten, indem du deinem Bitkit-Wallet lustige und nützliche Widgets hinzufügst."; "widgets__widget__nav_title" = "Widget"; diff --git a/Bitkit/Resources/Localization/el.lproj/Localizable.strings b/Bitkit/Resources/Localization/el.lproj/Localizable.strings index 8c7654fbe..ca950e262 100644 --- a/Bitkit/Resources/Localization/el.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/el.lproj/Localizable.strings @@ -839,7 +839,6 @@ "wallet__balance_hidden_message" = "Σύρετε το υπόλοιπο του πορτοφολιού σας για να το αποκαλύψετε ξανά."; "wallet__balance_unit_switched_title" = "Αλλαγή σε {unit}"; "wallet__balance_unit_switched_message" = "Πατήστε στο υπόλοιπο του πορτοφολιού σας για να το αλλάξετε πίσω σε {unit}."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Γεια σας,\nWidgets"; "widgets__onboarding__description" = "Απολαύστε αποκεντρωμένες ροές από τις αγαπημένες σας υπηρεσίες web, προσθέτοντας διασκεδαστικά και χρήσιμα widgets στο πορτοφόλι Bitkit σας."; "widgets__nav_title" = "Widgets"; diff --git a/Bitkit/Resources/Localization/en.lproj/Localizable.strings b/Bitkit/Resources/Localization/en.lproj/Localizable.strings index 4adc42912..7de19e7bf 100644 --- a/Bitkit/Resources/Localization/en.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/en.lproj/Localizable.strings @@ -1375,7 +1375,6 @@ "wallet__receive_insufficient_text" = "Insufficient receiving capacity to receive this amount over Lightning."; "wallet__receive_foreground_title" = "Keep Bitkit In Foreground"; "wallet__receive_foreground_msg" = "Payments to your spending balance might fail if you switch between apps."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__swipe" = "Swipe down\nto find your\nwidgets"; "widgets__onboarding__title" = "Hello,\nWidgets"; "widgets__onboarding__description" = "Enjoy decentralized feeds from your favorite web services, by adding fun and useful widgets to your Bitkit wallet."; diff --git a/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings b/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings index 6b6a61b7b..4fbc07158 100644 --- a/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings @@ -1147,7 +1147,6 @@ "wallet__receive_insufficient_text" = "Capacidad de recepción insuficiente para recibir esta cantidad a través de Lightning."; "wallet__receive_foreground_title" = "Mantener Bitkit en primer plano"; "wallet__receive_foreground_msg" = "Los pagos a su saldo de gastos pueden fallar si cambia de una aplicación a otra."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Hola,\nWidgets"; "widgets__onboarding__description" = "Disfruta feeds descentralizados de tus servicios web favoritos añadiendo widgets útiles y divertidos a tu wallet de Bitkit."; "widgets__nav_title" = "Widgets"; diff --git a/Bitkit/Resources/Localization/es.lproj/Localizable.strings b/Bitkit/Resources/Localization/es.lproj/Localizable.strings index 98a3f8cd1..acb04433e 100644 --- a/Bitkit/Resources/Localization/es.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/es.lproj/Localizable.strings @@ -1006,7 +1006,6 @@ "wallet__balance_unit_switched_title" = "Cambió a {unit}"; "wallet__balance_unit_switched_message" = "Toca tu saldo del monedero para cambiarlo de nuevo a {unit}."; "wallet__ldk_sync_error_title" = "Error de Sincronización de Lightning"; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Hola,\nWidgets"; "widgets__onboarding__description" = "Disfruta de fuentes descentralizadas de tus servicios web favoritos, añadiendo widgets divertidos y útiles a tu monedero Bitkit."; "widgets__nav_title" = "Widgets"; diff --git a/Bitkit/Resources/Localization/fr.lproj/Localizable.strings b/Bitkit/Resources/Localization/fr.lproj/Localizable.strings index 733882182..458df3548 100644 --- a/Bitkit/Resources/Localization/fr.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/fr.lproj/Localizable.strings @@ -1161,7 +1161,6 @@ "wallet__receive_insufficient_text" = "Capacité insuffisante pour recevoir ce montant en Lightning."; "wallet__receive_foreground_title" = "Garder Bitkit au premier plan"; "wallet__receive_foreground_msg" = "Les paiements sur votre solde du compte courant peuvent échouer si vous passez d\'une application à l\'autre."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Bonjour, \nWidgets"; "widgets__onboarding__description" = "Profitez des flux décentralisés de vos services web préférés en ajoutant des widgets amusants et utiles à votre portefeuille Bitkit."; "widgets__nav_title" = "Widgets"; diff --git a/Bitkit/Resources/Localization/it.lproj/Localizable.strings b/Bitkit/Resources/Localization/it.lproj/Localizable.strings index 0b1186efd..b4da8b7e3 100644 --- a/Bitkit/Resources/Localization/it.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/it.lproj/Localizable.strings @@ -1115,7 +1115,6 @@ "wallet__ldk_sync_error_title" = "Errore di sincronizzazione Lightning"; "wallet__receive_insufficient_title" = "Saldo di ricezione insufficiente."; "wallet__receive_insufficient_text" = "Capacità di ricezione insufficiente per ricevere questo importo tramite Lightning."; -"widgets__widgets" = "Widget"; "widgets__onboarding__title" = "Ciao,\nWidgets"; "widgets__onboarding__description" = "Goditi i feed decentralizzati dai tuoi servizi web preferiti, aggiungendo widget divertenti e utili al tuo portafoglio Bitkit."; "widgets__nav_title" = "Widget"; diff --git a/Bitkit/Resources/Localization/nl.lproj/Localizable.strings b/Bitkit/Resources/Localization/nl.lproj/Localizable.strings index f56438151..70628f1c9 100644 --- a/Bitkit/Resources/Localization/nl.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/nl.lproj/Localizable.strings @@ -1155,7 +1155,6 @@ "wallet__receive_insufficient_text" = "Onvoldoende ontvangstcapaciteit om dit bedrag via Lightning te ontvangen."; "wallet__receive_foreground_title" = "Bitkit im Vordergrund halten"; "wallet__receive_foreground_msg" = "Zahlungen auf Ihr Guthaben können fehlschlagen, wenn Sie zwischen Apps wechseln."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Hallo,\nWidgets"; "widgets__onboarding__description" = "Geniet van gedecentraliseerde feeds van uw favoriete webdiensten, door leuke en nuttige widgets aan uw Bitkit wallet toe te voegen."; "widgets__nav_title" = "Widgets"; diff --git a/Bitkit/Resources/Localization/pl.lproj/Localizable.strings b/Bitkit/Resources/Localization/pl.lproj/Localizable.strings index c76dd7f05..8004cc27b 100644 --- a/Bitkit/Resources/Localization/pl.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pl.lproj/Localizable.strings @@ -1162,7 +1162,6 @@ "wallet__receive_insufficient_text" = "Niewystarczająca zdolność odbiorcza, aby otrzymać tę kwotę przez Lightning."; "wallet__receive_foreground_title" = "Zachowaj Bitkit na pierwszym planie"; "wallet__receive_foreground_msg" = "Płatności na saldo wydatków mogą się nie powieść, jeśli przełączą się Państwo między aplikacjami."; -"widgets__widgets" = "Widgety"; "widgets__onboarding__title" = "Hej,\nWidgety"; "widgets__onboarding__description" = "Korzystaj ze zdecentralizowanych kanałów swoich ulubionych usług internetowych, dodając do portfela Bitkit przydatne i interaktywne widgety."; "widgets__nav_title" = "Widgety"; diff --git a/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings b/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings index b3197bebe..bcad5ec92 100644 --- a/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings @@ -1163,7 +1163,6 @@ "wallet__receive_insufficient_text" = "Limite de recebimento insuficiente para esse valor."; "wallet__receive_foreground_title" = "Mantenha a Bitkit em primeiro plano"; "wallet__receive_foreground_msg" = "Os pagamentos ao seu saldo de gastos podem falhar se o usuário alternar entre aplicativos."; -"widgets__widgets" = "Widgets"; "widgets__onboarding__title" = "Olá,\nWidgets"; "widgets__onboarding__description" = "Aproveite os feeds descentralizados de seus serviços da web favoritos, adicionando widgets divertidos e úteis à sua carteira Bitkit."; "widgets__nav_title" = "Widgets"; diff --git a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings index 3a4aa86e1..25823d92a 100644 --- a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings @@ -1149,7 +1149,6 @@ "wallet__ldk_sync_error_title" = "Ошибка Синхронизации Lightning"; "wallet__receive_insufficient_title" = "Недостаточный входящий баланс."; "wallet__receive_insufficient_text" = "Недостаточно входящей ликвидности для получения данной суммы через Lightning."; -"widgets__widgets" = "Виджеты"; "widgets__onboarding__title" = "Привет,\nВиджеты"; "widgets__onboarding__description" = "Наслаждайтесь децентрализованными лентами ваших любимых веб-сервисов, добавляя интересные и полезные виджеты в ваш кошелёк Bitkit."; "widgets__nav_title" = "Виджеты"; diff --git a/BitkitWidget/BlocksHomeScreenWidget.swift b/BitkitWidget/BlocksHomeScreenWidget.swift index 6f3c348a9..7543e6665 100644 --- a/BitkitWidget/BlocksHomeScreenWidget.swift +++ b/BitkitWidget/BlocksHomeScreenWidget.swift @@ -203,8 +203,8 @@ struct BitkitBlocksWidget: Widget { ) { entry in BlocksHomeScreenWidgetEntryView(entry: entry) } - .configurationDisplayName("Bitcoin Blocks") - .description("Latest mined Bitcoin block, mirroring the in-app blocks widget.") + .configurationDisplayName(t("widgets__blocks__name")) + .description(t("widgets__blocks__description")) .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/BitkitWidget/FactsHomeScreenWidget.swift b/BitkitWidget/FactsHomeScreenWidget.swift index c51b1381d..3c6979823 100644 --- a/BitkitWidget/FactsHomeScreenWidget.swift +++ b/BitkitWidget/FactsHomeScreenWidget.swift @@ -134,8 +134,8 @@ struct BitkitFactsWidget: Widget { ) { entry in FactsHomeScreenWidgetEntryView(entry: entry) } - .configurationDisplayName("widgets__facts__name") - .description("widgets__facts__description") + .configurationDisplayName(t("widgets__facts__name")) + .description(t("widgets__facts__description")) .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/BitkitWidget/NewsHomeScreenWidget.swift b/BitkitWidget/NewsHomeScreenWidget.swift index 4f8887ad5..1df2c48fd 100644 --- a/BitkitWidget/NewsHomeScreenWidget.swift +++ b/BitkitWidget/NewsHomeScreenWidget.swift @@ -270,8 +270,8 @@ struct BitkitNewsWidget: Widget { ) { entry in NewsHomeScreenWidgetEntryView(entry: entry) } - .configurationDisplayName("Bitcoin Headlines") - .description("Latest Bitcoin news headlines, mirroring the in-app headlines widget.") + .configurationDisplayName(t("widgets__news__name")) + .description(t("widgets__news__description")) .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/BitkitWidget/PriceHomeScreenWidget.swift b/BitkitWidget/PriceHomeScreenWidget.swift index f30057c11..711472267 100644 --- a/BitkitWidget/PriceHomeScreenWidget.swift +++ b/BitkitWidget/PriceHomeScreenWidget.swift @@ -281,8 +281,8 @@ struct BitkitPriceWidget: Widget { ) { entry in PriceHomeScreenWidgetEntryView(entry: entry) } - .configurationDisplayName("Bitcoin Price") - .description("Latest Bitcoin price and chart, mirroring the in-app price widget.") + .configurationDisplayName(t("widgets__price__name")) + .description(t("widgets__price__description")) .supportedFamilies([.systemSmall, .systemMedium]) } } From 14dfa69953490af8c7ea509298f5c6d597e9b4c4 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 10:46:30 -0300 Subject: [PATCH 02/12] fix: port translations from android --- .../Localization/ar.lproj/Localizable.strings | 31 +++++++++++++++++++ .../Localization/ca.lproj/Localizable.strings | 8 +++++ .../Localization/el.lproj/Localizable.strings | 12 +++++++ .../Localization/es.lproj/Localizable.strings | 8 +++++ .../Localization/it.lproj/Localizable.strings | 8 +++++ .../Localization/pt.lproj/Localizable.strings | 31 +++++++++++++++++++ .../Localization/ru.lproj/Localizable.strings | 8 +++++ 7 files changed, 106 insertions(+) diff --git a/Bitkit/Resources/Localization/ar.lproj/Localizable.strings b/Bitkit/Resources/Localization/ar.lproj/Localizable.strings index 872c04695..b99282662 100644 --- a/Bitkit/Resources/Localization/ar.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ar.lproj/Localizable.strings @@ -28,3 +28,34 @@ "wallet__drawer__support" = "الدعم"; "wallet__recipient_contact" = "جهة الاتصال"; "wallet__activity_contact" = "جهة الاتصال"; +"widgets__onboarding__title" = "مرحبًا،\nالأدوات"; +"widgets__onboarding__description" = "استمتع بموجزات لامركزية من خدمات الويب المفضلة لديك، بإضافة أدوات ممتعة ومفيدة إلى محفظة Bitkit."; +"widgets__widget__nav_title" = "الأداة"; +"widgets__widget__edit" = "موجز الأداة"; +"widgets__widget__edit_default" = "افتراضي"; +"widgets__widget__edit_custom" = "مخصص"; +"widgets__widget__edit_description" = "يرجى اختيار الحقول التي تريد عرضها في أداة {name}."; +"widgets__widget__source" = "المصدر"; +"widgets__add" = "إضافة أداة"; +"widgets__delete__title" = "حذف الأداة؟"; +"widgets__delete__description" = "هل أنت متأكد من رغبتك في حذف '{name}' من أدواتك؟"; +"widgets__price__name" = "سعر Bitcoin"; +"widgets__price__description" = "تحقق من أحدث أسعار صرف Bitcoin لمجموعة متنوعة من العملات."; +"widgets__news__name" = "عناوين Bitcoin"; +"widgets__news__description" = "اقرأ أحدث وأفضل عناوين Bitcoin من مصادر إخبارية متنوعة."; +"widgets__blocks__name" = "كتل Bitcoin"; +"widgets__blocks__description" = "افحص إحصائيات متنوعة عن كتل Bitcoin المُعدّنة حديثًا."; +"widgets__facts__name" = "حقائق Bitcoin"; +"widgets__facts__description" = "اكتشف حقائق ممتعة عن Bitcoin، في كل مرة تفتح محفظتك."; +"widgets__calculator__name" = "حاسبة Bitcoin"; +"widgets__calculator__description" = "حوّل مبالغ ₿ إلى {fiatSymbol} أو العكس."; +"widgets__weather__name" = "طقس Bitcoin"; +"widgets__weather__description" = "اكتشف متى يكون الوقت مناسبًا للتعامل على سلسلة Bitcoin."; +"widgets__weather__condition__good__title" = "ظروف مواتية"; +"widgets__weather__condition__good__description" = "كل شيء واضح. الآن وقت جيد للتعامل على السلسلة."; +"widgets__weather__condition__average__title" = "ظروف متوسطة"; +"widgets__weather__condition__average__description" = "معدل الكتلة التالية قريب من المتوسطات الشهرية."; +"widgets__weather__condition__poor__title" = "ظروف سيئة"; +"widgets__weather__condition__poor__description" = "إذا لم تكن في عجلة للتعامل، قد يكون من الأفضل الانتظار قليلاً."; +"widgets__weather__current_fee" = "متوسط الرسوم الحالي"; +"widgets__weather__next_block" = "تضمين الكتلة التالية"; diff --git a/Bitkit/Resources/Localization/ca.lproj/Localizable.strings b/Bitkit/Resources/Localization/ca.lproj/Localizable.strings index ecc896ca8..168f21d1d 100644 --- a/Bitkit/Resources/Localization/ca.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ca.lproj/Localizable.strings @@ -990,12 +990,20 @@ "widgets__delete__title" = "Eliminar giny?"; "widgets__delete__description" = "Estàs segur que vols eliminar \'{name}\' dels teus ginys?"; "widgets__price__name" = "Preu de Bitcoin"; +"widgets__price__description" = "Comprova els últims tipus de canvi de Bitcoin per a diverses monedes fiduciàries."; "widgets__price__error" = "No s\'han pogut obtenir les dades de preu"; "widgets__news__name" = "Capçaleres de Bitcoin"; +"widgets__news__description" = "Llegeix les últimes i millors notícies de Bitcoin de diversos llocs de notícies."; "widgets__news__error" = "No s\'han pogut obtenir les últimes notícies"; "widgets__blocks__name" = "Blocs de Bitcoin"; +"widgets__blocks__description" = "Examina diverses estadístiques dels blocs de Bitcoin recentment minats."; "widgets__blocks__error" = "No s\'han pogut obtenir les dades dels blocs"; "widgets__facts__name" = "Bitcoin Fets"; +"widgets__facts__description" = "Descobreix fets interessants sobre Bitcoin cada vegada que obres la cartera."; +"widgets__calculator__name" = "Calculadora de Bitcoin"; +"widgets__calculator__description" = "Converteix quantitats de ₿ a {fiatSymbol} o viceversa."; +"widgets__weather__name" = "Temps de Bitcoin"; +"widgets__weather__description" = "Descobreix quan és un bon moment per fer transaccions a la blockchain de Bitcoin."; "widgets__weather__condition__good__title" = "Condicions favorables"; "widgets__weather__condition__good__description" = "Ara seria un bon moment per fer transaccions a la blockchain."; "widgets__weather__condition__average__title" = "Condicions mitjanes"; diff --git a/Bitkit/Resources/Localization/el.lproj/Localizable.strings b/Bitkit/Resources/Localization/el.lproj/Localizable.strings index ca950e262..e78fdd7a7 100644 --- a/Bitkit/Resources/Localization/el.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/el.lproj/Localizable.strings @@ -851,9 +851,21 @@ "widgets__add" = "Προσθήκη Widget"; "widgets__delete__title" = "Διαγραφή Widget;"; "widgets__delete__description" = "Είστε σίγουροι ότι θέλετε να διαγράψετε το '{name}' από τα widgets σας;"; +"widgets__price__name" = "Τιμή Bitcoin"; +"widgets__price__description" = "Έλεγξε τις τελευταίες συναλλαγματικές ισοτιμίες Bitcoin για διάφορα fiat νομίσματα."; "widgets__price__error" = "Δεν ήταν δυνατή η λήψη δεδομένων τιμής"; +"widgets__news__name" = "Bitcoin Headlines"; +"widgets__news__description" = "Διάβασε τους τελευταίους τίτλους Bitcoin από διάφορες ειδησεογραφικές σελίδες."; "widgets__news__error" = "Δεν ήταν δυνατή η λήψη τελευταίων ειδήσεων"; +"widgets__blocks__name" = "Bitcoin Blocks"; +"widgets__blocks__description" = "Εξέτασε διάφορα στατιστικά για τα νεοεξορυχθέντα Bitcoin Blocks."; "widgets__blocks__error" = "Δεν ήταν δυνατή η λήψη δεδομένων blocks"; +"widgets__facts__name" = "Bitcoin Facts"; +"widgets__facts__description" = "Ανακάλυψε διασκεδαστικά γεγονότα για το Bitcoin, κάθε φορά που ανοίγεις το πορτοφόλι σου."; +"widgets__calculator__name" = "Αριθμομηχανή Bitcoin"; +"widgets__calculator__description" = "Μετατροπή ποσών ₿ σε {fiatSymbol} ή αντίστροφα."; +"widgets__weather__name" = "Bitcoin Weather"; +"widgets__weather__description" = "Μάθε πότε είναι καλή στιγμή για συναλλαγή στο Bitcoin blockchain."; "widgets__weather__condition__good__title" = "Ευνοϊκές Συνθήκες"; "widgets__weather__condition__good__description" = "Τώρα θα ήταν καλή στιγμή για συναλλαγές στο blockchain."; "widgets__weather__condition__average__title" = "Μέσες Συνθήκες"; diff --git a/Bitkit/Resources/Localization/es.lproj/Localizable.strings b/Bitkit/Resources/Localization/es.lproj/Localizable.strings index acb04433e..8421df428 100644 --- a/Bitkit/Resources/Localization/es.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/es.lproj/Localizable.strings @@ -1019,12 +1019,20 @@ "widgets__delete__title" = "¿Borrar Widget?"; "widgets__delete__description" = "¿Está seguro de querer borrar '{name}' de sus widgets?"; "widgets__price__name" = "Precio de Bitcoin"; +"widgets__price__description" = "Consulta los últimos tipos de cambio de Bitcoin para varias monedas fiduciarias."; "widgets__price__error" = "No se pudieron obtener los datos de precio"; "widgets__news__name" = "Titulares Bitcoin"; +"widgets__news__description" = "Lee los últimos y mejores titulares de Bitcoin de varios sitios de noticias."; "widgets__news__error" = "No se pudieron obtener las últimas noticias"; "widgets__blocks__name" = "Bloques Bitcoin"; +"widgets__blocks__description" = "Examina diversas estadísticas sobre los bloques de Bitcoin recién minados."; "widgets__blocks__error" = "No se pudieron obtener los datos de bloques"; "widgets__facts__name" = "Hechos Bitcoin"; +"widgets__facts__description" = "Descubre datos curiosos sobre Bitcoin, cada vez que abras tu monedero."; +"widgets__calculator__name" = "Calculadora Bitcoin"; +"widgets__calculator__description" = "Convierte cantidades de ₿ a {fiatSymbol} o viceversa."; +"widgets__weather__name" = "Tiempo Bitcoin"; +"widgets__weather__description" = "Descubre cuándo es un buen momento para transaccionar en la blockchain de Bitcoin."; "widgets__weather__condition__good__title" = "Condiciones Favorables"; "widgets__weather__condition__good__description" = "Ahora sería un buen momento para realizar transacciones en la blockchain."; "widgets__weather__condition__average__title" = "Condiciones Promedio"; diff --git a/Bitkit/Resources/Localization/it.lproj/Localizable.strings b/Bitkit/Resources/Localization/it.lproj/Localizable.strings index b4da8b7e3..49854cdbd 100644 --- a/Bitkit/Resources/Localization/it.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/it.lproj/Localizable.strings @@ -1128,12 +1128,20 @@ "widgets__delete__title" = "Eliminare Widget?"; "widgets__delete__description" = "Sei sicuro di volere eliminare '{name}' dai tuoi widget?"; "widgets__price__name" = "Prezzo Bitcoin"; +"widgets__price__description" = "Controlla i tassi di cambio Bitcoin piu' recenti per varie valute fiat."; "widgets__price__error" = "Impossibile ottenere i dati del prezzo"; "widgets__news__name" = "Titoli di Testa di Bitcoin"; +"widgets__news__description" = "Leggi le ultime notizie su Bitcoin da vari siti di informazione."; "widgets__news__error" = "Impossibile ottenere le ultime notizie"; "widgets__blocks__name" = "Blocchi Bitcoin"; +"widgets__blocks__description" = "Esamina varie statistiche sui blocchi Bitcoin appena estratti."; "widgets__blocks__error" = "Impossibile ottenere i dati dei blocchi"; "widgets__facts__name" = "Fatti su Bitcoin"; +"widgets__facts__description" = "Scopri fatti divertenti su Bitcoin ogni volta che apri il wallet."; +"widgets__calculator__name" = "Calcolatrice Bitcoin"; +"widgets__calculator__description" = "Converti importi ₿ in {fiatSymbol} o viceversa."; +"widgets__weather__name" = "Meteo Bitcoin"; +"widgets__weather__description" = "Scopri quando e' un buon momento per effettuare transazioni sulla blockchain Bitcoin."; "widgets__weather__condition__good__title" = "Condizioni Favorevoli"; "widgets__weather__condition__good__description" = "Ora sarebbe un buon momento per transare sulla blockchain."; "widgets__weather__condition__average__title" = "Condizioni Medie"; diff --git a/Bitkit/Resources/Localization/pt.lproj/Localizable.strings b/Bitkit/Resources/Localization/pt.lproj/Localizable.strings index 11bab719e..c99570f00 100644 --- a/Bitkit/Resources/Localization/pt.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pt.lproj/Localizable.strings @@ -6,3 +6,34 @@ "slashtags__profile_link_label_placeholder" = "Por exemplo, \"Website\"."; "wallet__error_broadcast_tx_connection" = "Por favor, verifique sua conexão e tente novamente.\n{message}"; "wallet__drawer__support" = "Suporte"; +"widgets__onboarding__title" = "Olá,\nWidgets"; +"widgets__onboarding__description" = "Aproveite os feeds descentralizados de seus serviços da web favoritos, adicionando widgets divertidos e úteis à sua carteira Bitkit."; +"widgets__widget__nav_title" = "Widget"; +"widgets__widget__edit" = "Widget Feed"; +"widgets__widget__edit_default" = "Padrão"; +"widgets__widget__edit_custom" = "Personalizada"; +"widgets__widget__edit_description" = "Por favor, selecione quais campos você deseja exibir no widget {name}."; +"widgets__widget__source" = "Fonte"; +"widgets__add" = "Adicionar Widget"; +"widgets__delete__title" = "Excluir o Widget?"; +"widgets__delete__description" = "Tem certeza de que deseja excluir '{name}' dos seus widgets?"; +"widgets__price__name" = "Preço do Bitcoin"; +"widgets__price__description" = "Verifique as últimas taxas de câmbio de Bitcoin para uma variedade de moedas fiduciárias."; +"widgets__news__name" = "Manchetes sobre Bitcoin"; +"widgets__news__description" = "Leia as melhores e mais recentes manchetes sobre Bitcoin de vários sites de notícias."; +"widgets__blocks__name" = "Blocos do Bitcoin"; +"widgets__blocks__description" = "Examinar várias estatísticas sobre blocos de Bitcoin recém-minerados."; +"widgets__facts__name" = "Fatos sobre o Bitcoin"; +"widgets__facts__description" = "Descubra fatos divertidos sobre o Bitcoin, sempre que abrir sua carteira."; +"widgets__calculator__name" = "Calculadora de Bitcoin"; +"widgets__calculator__description" = "Converta ₿ para {fiatSymbol} ou vice-versa."; +"widgets__weather__name" = "Tempo do Bitcoin"; +"widgets__weather__description" = "Descubra quando é um bom momento para fazer transações na blockchain do Bitcoin."; +"widgets__weather__condition__good__title" = "Condições favoráveis"; +"widgets__weather__condition__good__description" = "Tudo limpo. Agora seria um bom momento para fazer transações na blockchain."; +"widgets__weather__condition__average__title" = "Condições normais"; +"widgets__weather__condition__average__description" = "A taxa do próximo bloco está próxima das médias mensais."; +"widgets__weather__condition__poor__title" = "Condições ruins"; +"widgets__weather__condition__poor__description" = "Se você não estiver com pressa para fazer uma transação, talvez seja melhor esperar um pouco."; +"widgets__weather__current_fee" = "Tarifa média atual"; +"widgets__weather__next_block" = "Inclusão no próximo bloco"; diff --git a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings index 25823d92a..8be22b534 100644 --- a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings @@ -1162,12 +1162,20 @@ "widgets__delete__title" = "Удалить виджет?"; "widgets__delete__description" = "Вы уверены, что хотите удалить «{name}» из ваших виджетов?"; "widgets__price__name" = "Биткоин Цена"; +"widgets__price__description" = "Проверяйте последние курсы обмена Bitcoin для различных фиатных валют."; "widgets__price__error" = "Не удалось получить данные о цене"; "widgets__news__name" = "Биткоин Заголовки"; +"widgets__news__description" = "Читайте последние и лучшие заголовки о Bitcoin из различных новостных сайтов."; "widgets__news__error" = "Не удалось получить последние новости"; "widgets__blocks__name" = "Биткоин Блоки"; +"widgets__blocks__description" = "Изучайте различную статистику по недавно добытым блокам Bitcoin."; "widgets__blocks__error" = "Не удалось получить данные о блоках"; "widgets__facts__name" = "Биткоин Факты"; +"widgets__facts__description" = "Открывайте интересные факты о Bitcoin каждый раз, когда открываете кошелёк."; +"widgets__calculator__name" = "Калькулятор Bitcoin"; +"widgets__calculator__description" = "Конвертируйте суммы ₿ в {fiatSymbol} или наоборот."; +"widgets__weather__name" = "Погода Bitcoin"; +"widgets__weather__description" = "Узнайте, когда хорошее время для транзакций в блокчейне Bitcoin."; "widgets__weather__condition__good__title" = "Благоприятные Условия"; "widgets__weather__condition__good__description" = "Сейчас подходящее время для транзакций в блокчейне."; "widgets__weather__condition__average__title" = "Средние Условия"; From 37a5e1fe8a8ec5208f39e1a3887d2e0149d04412 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 11:56:47 -0300 Subject: [PATCH 03/12] fix: adapt space according available size --- BitkitWidget/BlocksHomeScreenWidget.swift | 30 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/BitkitWidget/BlocksHomeScreenWidget.swift b/BitkitWidget/BlocksHomeScreenWidget.swift index 7543e6665..32f2ca817 100644 --- a/BitkitWidget/BlocksHomeScreenWidget.swift +++ b/BitkitWidget/BlocksHomeScreenWidget.swift @@ -117,9 +117,22 @@ struct BlocksHomeScreenWidgetEntryView: View { // MARK: - Layouts + /// Candidate row spacings, from roomiest to tightest. `ViewThatFits` picks the first that + /// fits the available height, so larger screens keep 16pt while iPhone SE falls back to less. + private static let rowSpacings: [CGFloat] = [16, 10, 6, 2] + /// Compact (`.systemSmall`): icon + value rows for the selected fields. private func compactLayout(block: CachedBlock) -> some View { - VStack(alignment: .leading, spacing: 16) { + ViewThatFits(in: .vertical) { + ForEach(Self.rowSpacings, id: \.self) { spacing in + compactStack(block: block, spacing: spacing) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } + + private func compactStack(block: CachedBlock, spacing: CGFloat) -> some View { + VStack(alignment: .leading, spacing: spacing) { ForEach(entry.options.enabledFields, id: \.self) { field in HStack(alignment: .center, spacing: 8) { iconImage(field: field) @@ -132,12 +145,21 @@ struct BlocksHomeScreenWidgetEntryView: View { } } } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .topLeading) } /// Wide layout (`.systemMedium`): icon + label + value rows for the selected fields. private func wideLayout(block: CachedBlock, fields: [BlocksWidgetField]) -> some View { - VStack(alignment: .leading, spacing: 16) { + ViewThatFits(in: .vertical) { + ForEach(Self.rowSpacings, id: \.self) { spacing in + wideStack(block: block, fields: fields, spacing: spacing) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } + + private func wideStack(block: CachedBlock, fields: [BlocksWidgetField], spacing: CGFloat) -> some View { + VStack(alignment: .leading, spacing: spacing) { ForEach(fields, id: \.self) { field in HStack(alignment: .center, spacing: 8) { iconImage(field: field) @@ -155,7 +177,7 @@ struct BlocksHomeScreenWidgetEntryView: View { } } } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .topLeading) } private func iconImage(field: BlocksWidgetField) -> some View { From dbbb3403e3cf8d36b8f4844a962c6cc7498c9483 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 13:27:41 -0300 Subject: [PATCH 04/12] refactor: share components between in-app and home-screen widgets --- Bitkit.xcodeproj/project.pbxproj | 129 ++++++++------- Bitkit/Components/Widgets/BlocksWidget.swift | 68 -------- .../Widgets/BlocksWidgetContent.swift | 107 +++++++++++++ Bitkit/Components/Widgets/FactsWidget.swift | 42 ----- .../Widgets/FactsWidgetContent.swift | 86 ++++++++++ Bitkit/Components/Widgets/NewsWidget.swift | 71 +------- .../Widgets/NewsWidgetContent.swift | 84 ++++++++++ Bitkit/Components/Widgets/PriceWidget.swift | 130 --------------- .../Widgets/PriceWidgetContent.swift | 151 ++++++++++++++++++ .../Widgets/WeatherWidgetContent.swift | 25 ++- Bitkit/Components/Widgets/WidgetPalette.swift | 48 ++++++ .../Widgets/BlocksWidgetPreviewView.swift | 3 + .../Widgets/FactsWidgetPreviewView.swift | 3 + .../Views/Widgets/NewsWidgetPreviewView.swift | 18 ++- .../Widgets/PriceWidgetPreviewView.swift | 3 + BitkitWidget/BlocksHomeScreenWidget.swift | 107 ++----------- BitkitWidget/FactsHomeScreenWidget.swift | 78 ++------- BitkitWidget/NewsHomeScreenWidget.swift | 109 +++---------- BitkitWidget/PriceHomeScreenWidget.swift | 145 ++--------------- BitkitWidget/WeatherHomeScreenWidget.swift | 6 +- 20 files changed, 638 insertions(+), 775 deletions(-) create mode 100644 Bitkit/Components/Widgets/BlocksWidgetContent.swift create mode 100644 Bitkit/Components/Widgets/FactsWidgetContent.swift create mode 100644 Bitkit/Components/Widgets/NewsWidgetContent.swift create mode 100644 Bitkit/Components/Widgets/PriceWidgetContent.swift create mode 100644 Bitkit/Components/Widgets/WidgetPalette.swift diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index b01a6db17..8cbd5f0bb 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -109,6 +109,44 @@ ); target = 4A319B4E2E8F24F2002B9AC9 /* BitkitWidgetExtension */; }; + 4A319B672E8F24F5002B9AC9 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Components/Widgets/BlocksWidgetContent.swift, + Components/Widgets/FactsWidgetContent.swift, + Components/Widgets/NewsWidgetContent.swift, + Components/Widgets/PriceWidgetContent.swift, + Components/Widgets/WeatherWidgetContent.swift, + Components/Widgets/WidgetPalette.swift, + Constants/WidgetEnv.swift, + "Fonts/InterTight-Black.ttf", + "Fonts/InterTight-Bold.ttf", + "Fonts/InterTight-ExtraBold.ttf", + "Fonts/InterTight-Medium.ttf", + "Fonts/InterTight-Regular.ttf", + "Fonts/InterTight-SemiBold.ttf", + Models/BitcoinFacts.swift, + Models/BlocksWidgetData.swift, + Models/BlocksWidgetFields.swift, + Models/BlocksWidgetOptions.swift, + Models/NewsWidgetData.swift, + Models/NewsWidgetOptions.swift, + Models/PriceWidgetData.swift, + Models/PriceWidgetOptions.swift, + Models/WeatherWidgetData.swift, + Models/WeatherWidgetOptions.swift, + Services/Widgets/BlocksHomeScreenWidgetOptionsStore.swift, + Services/Widgets/MempoolWeatherAPI.swift, + Services/Widgets/NewsHomeScreenWidgetOptionsStore.swift, + Services/Widgets/PriceHomeScreenWidgetOptionsStore.swift, + Services/Widgets/WeatherHomeScreenWidgetOptionsStore.swift, + Styles/Colors.swift, + Styles/Fonts.swift, + Styles/TextStyle.swift, + Utilities/LocalizeHelpers.swift, + ); + target = 4A319B4E2E8F24F2002B9AC9 /* BitkitWidgetExtension */; + }; 96A44F3D2CEF5EA700FBACFF /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -123,8 +161,8 @@ Extensions/HexBytes.swift, "Extensions/LDKNode+AddressType.swift", Extensions/PaymentDetails.swift, - Models/BlocktankNotificationType.swift, Models/BlocksWidgetOptions.swift, + Models/BlocktankNotificationType.swift, Models/LnPeer.swift, Models/PubkyPublicKeyFormat.swift, Models/Toast.swift, @@ -180,39 +218,6 @@ ); target = 961058DB2C355B5500E1F1D8 /* BitkitNotification */; }; - 4A319B672E8F24F5002B9AC9 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Fonts/InterTight-Black.ttf, - Fonts/InterTight-Bold.ttf, - Fonts/InterTight-ExtraBold.ttf, - Fonts/InterTight-Medium.ttf, - Fonts/InterTight-Regular.ttf, - Constants/WidgetEnv.swift, - Fonts/InterTight-SemiBold.ttf, - Components/Widgets/WeatherWidgetContent.swift, - Models/BlocksWidgetData.swift, - Models/BlocksWidgetFields.swift, - Models/BlocksWidgetOptions.swift, - Models/BitcoinFacts.swift, - Models/NewsWidgetData.swift, - Models/NewsWidgetOptions.swift, - Models/PriceWidgetData.swift, - Models/PriceWidgetOptions.swift, - Models/WeatherWidgetData.swift, - Models/WeatherWidgetOptions.swift, - Services/Widgets/BlocksHomeScreenWidgetOptionsStore.swift, - Services/Widgets/MempoolWeatherAPI.swift, - Services/Widgets/NewsHomeScreenWidgetOptionsStore.swift, - Services/Widgets/PriceHomeScreenWidgetOptionsStore.swift, - Services/Widgets/WeatherHomeScreenWidgetOptionsStore.swift, - Styles/Colors.swift, - Styles/Fonts.swift, - Styles/TextStyle.swift, - Utilities/LocalizeHelpers.swift, - ); - target = 4A319B4E2E8F24F2002B9AC9 /* BitkitWidgetExtension */; - }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -554,31 +559,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - 4A319B712E8F2600002B9AC9 /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - 4A319B722E8F2600002B9AC9 /* ar */, - 4A319B732E8F2600002B9AC9 /* ca */, - 4A319B742E8F2600002B9AC9 /* cs */, - 4A319B752E8F2600002B9AC9 /* de */, - 4A319B762E8F2600002B9AC9 /* el */, - 4A319B772E8F2600002B9AC9 /* en */, - 4A319B782E8F2600002B9AC9 /* es-419 */, - 4A319B792E8F2600002B9AC9 /* es */, - 4A319B7A2E8F2600002B9AC9 /* fr */, - 4A319B7B2E8F2600002B9AC9 /* it */, - 4A319B7C2E8F2600002B9AC9 /* nl */, - 4A319B7D2E8F2600002B9AC9 /* pl */, - 4A319B7E2E8F2600002B9AC9 /* pt-BR */, - 4A319B7F2E8F2600002B9AC9 /* pt */, - 4A319B802E8F2600002B9AC9 /* ru */, - ); - name = Localizable.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin PBXShellScriptBuildPhase section */ 96EMBED0012026012000FRAME /* Remove Static Framework Stubs */ = { isa = PBXShellScriptBuildPhase; @@ -663,6 +643,31 @@ }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + 4A319B712E8F2600002B9AC9 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 4A319B722E8F2600002B9AC9 /* ar */, + 4A319B732E8F2600002B9AC9 /* ca */, + 4A319B742E8F2600002B9AC9 /* cs */, + 4A319B752E8F2600002B9AC9 /* de */, + 4A319B762E8F2600002B9AC9 /* el */, + 4A319B772E8F2600002B9AC9 /* en */, + 4A319B782E8F2600002B9AC9 /* es-419 */, + 4A319B792E8F2600002B9AC9 /* es */, + 4A319B7A2E8F2600002B9AC9 /* fr */, + 4A319B7B2E8F2600002B9AC9 /* it */, + 4A319B7C2E8F2600002B9AC9 /* nl */, + 4A319B7D2E8F2600002B9AC9 /* pl */, + 4A319B7E2E8F2600002B9AC9 /* pt-BR */, + 4A319B7F2E8F2600002B9AC9 /* pt */, + 4A319B802E8F2600002B9AC9 /* ru */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 4A319B632E8F24F4002B9AC9 /* Debug */ = { isa = XCBuildConfiguration; @@ -684,7 +689,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 2.2.1; - PRODUCT_BUNDLE_IDENTIFIER = "to.bitkit.widget"; + PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.widget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -717,7 +722,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 2.2.1; - PRODUCT_BUNDLE_IDENTIFIER = "to.bitkit.widget"; + PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.widget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -1143,7 +1148,7 @@ repositoryURL = "https://github.com/pubky/paykit-rs"; requirement = { kind = exactVersion; - version = 0.1.0-rc5; + version = "0.1.0-rc5"; }; }; 18D65DFE2EB9649F00252335 /* XCRemoteSwiftPackageReference "vss-rust-client-ffi" */ = { diff --git a/Bitkit/Components/Widgets/BlocksWidget.swift b/Bitkit/Components/Widgets/BlocksWidget.swift index 3344d0ae5..84b9fda63 100644 --- a/Bitkit/Components/Widgets/BlocksWidget.swift +++ b/Bitkit/Components/Widgets/BlocksWidget.swift @@ -46,74 +46,6 @@ struct BlocksWidget: View { } } -// MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium / .systemLarge OS widget) - -struct BlocksWidgetWideContent: View { - let data: CachedBlock - let options: BlocksWidgetOptions - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - ForEach(options.enabledFields, id: \.self) { field in - BlocksWidgetWideRow(field: field, value: field.value(from: data)) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } -} - -private struct BlocksWidgetWideRow: View { - let field: BlocksWidgetField - let value: String - - var body: some View { - HStack(alignment: .center, spacing: 8) { - Image(field.iconName) - .resizable() - .renderingMode(.template) - .foregroundColor(.brandAccent) - .frame(width: 20, height: 20) - - BodyMText(field.label, textColor: .white80) - .lineLimit(1) - .frame(maxWidth: .infinity, alignment: .leading) - - BodyMSBText(value) - .lineLimit(1) - .truncationMode(.middle) - } - } -} - -// MARK: - Compact layout (small carousel preview + 163×192 OS small widget) - -struct BlocksWidgetCompactContent: View { - let data: CachedBlock - let options: BlocksWidgetOptions - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - ForEach(options.enabledFields, id: \.self) { field in - HStack(alignment: .center, spacing: 8) { - Image(field.iconName) - .resizable() - .renderingMode(.template) - .foregroundColor(.brandAccent) - .frame(width: 20, height: 20) - - BodySSBText(field.value(from: data)) - .lineLimit(1) - .truncationMode(.middle) - } - } - } - .padding(16) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .background(Color.gray6) - .cornerRadius(16) - } -} - #Preview { BlocksWidget() .padding() diff --git a/Bitkit/Components/Widgets/BlocksWidgetContent.swift b/Bitkit/Components/Widgets/BlocksWidgetContent.swift new file mode 100644 index 000000000..e66a3e506 --- /dev/null +++ b/Bitkit/Components/Widgets/BlocksWidgetContent.swift @@ -0,0 +1,107 @@ +import SwiftUI +import WidgetKit + +// Shared Bitcoin Blocks widget content, reused by the in-app feed, the carousel preview, and the +// home-screen WidgetKit extension. Colors adapt to `widgetRenderingMode` via ``WidgetPalette``. + +private let blocksRowSpacings: [CGFloat] = [16, 10, 6, 2] + +// MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium / .systemLarge OS widget) + +struct BlocksWidgetWideContent: View { + let data: CachedBlock + let options: BlocksWidgetOptions + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + ViewThatFits(in: .vertical) { + ForEach(blocksRowSpacings, id: \.self) { spacing in + stack(spacing: spacing) + } + } + .frame(maxWidth: .infinity, alignment: .topLeading) + } + + private func stack(spacing: CGFloat) -> some View { + let palette = WidgetPalette(renderingMode: renderingMode) + return VStack(alignment: .leading, spacing: spacing) { + ForEach(options.enabledFields, id: \.self) { field in + BlocksWidgetWideRow(field: field, value: field.value(from: data), palette: palette) + } + } + .frame(maxWidth: .infinity, alignment: .topLeading) + } +} + +private struct BlocksWidgetWideRow: View { + let field: BlocksWidgetField + let value: String + let palette: WidgetPalette + + var body: some View { + HStack(alignment: .center, spacing: 8) { + BlocksWidgetIcon(field: field, palette: palette) + + BodyMText(field.label, textColor: palette.label) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .leading) + + BodyMSBText(value, textColor: palette.title) + .lineLimit(1) + .truncationMode(.middle) + .widgetAccentable() + } + } +} + +// MARK: - Compact layout (small carousel preview + .systemSmall OS widget) + +struct BlocksWidgetCompactContent: View { + let data: CachedBlock + let options: BlocksWidgetOptions + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + ViewThatFits(in: .vertical) { + ForEach(blocksRowSpacings, id: \.self) { spacing in + stack(spacing: spacing) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } + + private func stack(spacing: CGFloat) -> some View { + let palette = WidgetPalette(renderingMode: renderingMode) + return VStack(alignment: .leading, spacing: spacing) { + ForEach(options.enabledFields, id: \.self) { field in + HStack(alignment: .center, spacing: 8) { + BlocksWidgetIcon(field: field, palette: palette) + + BodySSBText(field.value(from: data), textColor: palette.title) + .lineLimit(1) + .truncationMode(.middle) + .widgetAccentable() + } + } + } + .frame(maxWidth: .infinity, alignment: .topLeading) + } +} + +// MARK: - Shared row icon + +private struct BlocksWidgetIcon: View { + let field: BlocksWidgetField + let palette: WidgetPalette + + var body: some View { + Image(field.iconName) + .resizable() + .renderingMode(.template) + .foregroundColor(palette.accent) + .frame(width: 20, height: 20) + .widgetAccentable() + } +} diff --git a/Bitkit/Components/Widgets/FactsWidget.swift b/Bitkit/Components/Widgets/FactsWidget.swift index b4f43db9f..f2c8f5652 100644 --- a/Bitkit/Components/Widgets/FactsWidget.swift +++ b/Bitkit/Components/Widgets/FactsWidget.swift @@ -25,48 +25,6 @@ struct FactsWidget: View { } } -struct FactsWidgetWideContent: View { - let fact: String - - var body: some View { - HStack(alignment: .top, spacing: 32) { - TitleText(fact) - .lineLimit(4) - .frame(maxWidth: .infinity, alignment: .leading) - - BitcoinLogo() - } - .frame(maxWidth: .infinity, alignment: .leading) - } -} - -struct FactsWidgetCompactContent: View { - let fact: String - - var body: some View { - BodyMSBText(fact) - .lineLimit(4) - .frame(maxWidth: .infinity, alignment: .leading) - .minimumScaleFactor(0.85) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .overlay(alignment: .bottomTrailing) { - BitcoinLogo() - } - .padding(16) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .background(Color.gray6) - .cornerRadius(16) - } -} - -private struct BitcoinLogo: View { - var body: some View { - Image("bitcoin") - .resizable() - .frame(width: 32, height: 32) - } -} - #Preview { VStack(spacing: 16) { FactsWidget() diff --git a/Bitkit/Components/Widgets/FactsWidgetContent.swift b/Bitkit/Components/Widgets/FactsWidgetContent.swift new file mode 100644 index 000000000..2b58dbd0d --- /dev/null +++ b/Bitkit/Components/Widgets/FactsWidgetContent.swift @@ -0,0 +1,86 @@ +import SwiftUI +import WidgetKit + +// Shared Bitcoin Facts widget content, reused by the in-app feed, the carousel preview, and the +// home-screen WidgetKit extension. Colors and the Bitcoin badge adapt to `widgetRenderingMode` +// via ``WidgetPalette``. Card chrome is supplied by the caller. + +// MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium OS widget) + +struct FactsWidgetWideContent: View { + let fact: String + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) + HStack(alignment: .top, spacing: 32) { + TitleText(fact, textColor: palette.title) + .lineLimit(4) + .minimumScaleFactor(0.85) + .frame(maxWidth: .infinity, alignment: .leading) + .widgetAccentable() + + BitcoinLogo(renderingMode: renderingMode) + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +// MARK: - Compact layout (small carousel preview + .systemSmall OS widget) + +struct FactsWidgetCompactContent: View { + let fact: String + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) + BodyMSBText(fact, textColor: palette.title) + .lineLimit(4) + .minimumScaleFactor(0.85) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .overlay(alignment: .bottomTrailing) { + BitcoinLogo(renderingMode: renderingMode) + } + .widgetAccentable() + } +} + +// MARK: - Bitcoin badge + +/// Orange ₿ badge in full color; a white circle with the glyph knocked out in tinted/monochrome +/// mode so it reads against the system wallpaper. `bitcoin` is a template glyph asset. +struct BitcoinLogo: View { + var renderingMode: WidgetRenderingMode = .fullColor + + var body: some View { + Group { + if renderingMode == .fullColor { + ZStack { + Circle() + .fill(Color.bitcoin) + + glyph + .foregroundColor(.white) + } + } else { + ZStack { + Circle() + .fill(Color.white) + + glyph + .blendMode(.destinationOut) + } + .compositingGroup() + } + } + .frame(width: 32, height: 32) + } + + private var glyph: some View { + Image("bitcoin") + .resizable() + .renderingMode(.template) + } +} diff --git a/Bitkit/Components/Widgets/NewsWidget.swift b/Bitkit/Components/Widgets/NewsWidget.swift index b111e1492..a0ccc23fe 100644 --- a/Bitkit/Components/Widgets/NewsWidget.swift +++ b/Bitkit/Components/Widgets/NewsWidget.swift @@ -44,75 +44,16 @@ struct NewsWidget: View { } else if viewModel.error != nil { WidgetContentBuilder.errorView(t("widgets__news__error")) } else if let data = viewModel.widgetData { - NewsWidgetWideContent(data: data, options: options) + NewsWidgetWideContent( + title: data.title, + publisher: data.publisher, + timeAgo: data.timeAgo, + options: options + ) } } } -// MARK: - Wide layout (in-app + 343-wide carousel page) - -struct NewsWidgetWideContent: View { - let data: WidgetData - let options: NewsWidgetOptions - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - if options.showTitle { - TitleText(data.title) - .lineLimit(4) - .frame(maxWidth: .infinity, alignment: .leading) - } - - if options.showSource || options.showDate { - HStack(alignment: .center, spacing: 8) { - if options.showSource { - BodySSBText(data.publisher, textColor: .brandAccent) - .lineLimit(1) - } - Spacer(minLength: 0) - if options.showDate { - BodySSBText(data.timeAgo, textColor: .textSecondary) - .lineLimit(1) - } - } - .frame(maxWidth: .infinity) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } -} - -// MARK: - Compact layout (small carousel preview + 163×192 OS widget) - -struct NewsWidgetCompactContent: View { - let data: WidgetData - let options: NewsWidgetOptions - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - if options.showTitle { - TitleText(data.title) - .lineLimit(4) - .frame(maxWidth: .infinity, alignment: .leading) - } - - Spacer(minLength: 8) - - if options.showDate { - HStack { - Spacer(minLength: 0) - BodySSBText(data.timeAgo, textColor: .textSecondary) - .lineLimit(1) - } - } - } - .padding(16) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .background(Color.gray6) - .cornerRadius(16) - } -} - #Preview { NewsWidget() .padding() diff --git a/Bitkit/Components/Widgets/NewsWidgetContent.swift b/Bitkit/Components/Widgets/NewsWidgetContent.swift new file mode 100644 index 000000000..e63eb258a --- /dev/null +++ b/Bitkit/Components/Widgets/NewsWidgetContent.swift @@ -0,0 +1,84 @@ +import SwiftUI +import WidgetKit + +// Shared Bitcoin Headlines widget content, reused by the in-app feed, the carousel preview, and the +// home-screen WidgetKit extension. Colors adapt to `widgetRenderingMode` via ``WidgetPalette``. +// +// Takes primitive fields rather than a model so both the in-app `WidgetData` and the widget +// extension's `CachedNewsArticle` can feed it. Card chrome is supplied by the caller. + +// MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium OS widget) + +struct NewsWidgetWideContent: View { + let title: String + let publisher: String + let timeAgo: String + let options: NewsWidgetOptions + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) + VStack(alignment: .leading, spacing: 0) { + if options.showTitle { + TitleText(title, textColor: palette.title) + .lineLimit(4) + .minimumScaleFactor(0.85) + .frame(maxWidth: .infinity, alignment: .leading) + .widgetAccentable() + } + + Spacer(minLength: 8) + + if options.showSource || options.showDate { + HStack(alignment: .center, spacing: 8) { + if options.showSource { + BodySSBText(publisher, textColor: palette.accent) + .lineLimit(1) + } + Spacer(minLength: 0) + if options.showDate { + BodySSBText(timeAgo, textColor: palette.secondary) + .lineLimit(1) + } + } + .frame(maxWidth: .infinity) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +// MARK: - Compact layout (small carousel preview + .systemSmall OS widget) + +struct NewsWidgetCompactContent: View { + let title: String + let timeAgo: String + let options: NewsWidgetOptions + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) + VStack(alignment: .leading, spacing: 0) { + if options.showTitle { + TitleText(title, textColor: palette.title) + .lineLimit(4) + .minimumScaleFactor(0.85) + .frame(maxWidth: .infinity, alignment: .leading) + .widgetAccentable() + } + + Spacer(minLength: 8) + + if options.showDate { + HStack { + Spacer(minLength: 0) + BodySSBText(timeAgo, textColor: palette.secondary) + .lineLimit(1) + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } +} diff --git a/Bitkit/Components/Widgets/PriceWidget.swift b/Bitkit/Components/Widgets/PriceWidget.swift index eb75c65fc..da82ac30e 100644 --- a/Bitkit/Components/Widgets/PriceWidget.swift +++ b/Bitkit/Components/Widgets/PriceWidget.swift @@ -1,4 +1,3 @@ -import Charts import SwiftUI /// Displays Bitcoin price for the user's selected trading pair and timeframe. @@ -55,135 +54,6 @@ struct PriceWidget: View { } } -// MARK: - Wide layout (in-app + carousel page) - -struct PriceWidgetWideContent: View { - let data: PriceData - let period: GraphPeriod - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - VStack(alignment: .leading, spacing: 4) { - HStack(alignment: .center, spacing: 16) { - CaptionMText("\(data.name) \(period.rawValue)", textColor: .textSecondary) - .textCase(.uppercase) - .frame(maxWidth: .infinity, alignment: .leading) - - TitleText( - data.change.formatted, - textColor: data.change.isPositive ? .greenAccent : .redAccent - ) - .lineLimit(1) - .accessibilityIdentifier("price_card_pair_change_\(data.name)") - } - .accessibilityIdentifier("PriceWidgetRow-\(data.name)") - - Text(data.price) - .font(Fonts.bold(size: 34)) - .foregroundColor(.textPrimary) - .lineLimit(1) - .minimumScaleFactor(0.7) - .frame(maxWidth: .infinity, alignment: .leading) - .accessibilityIdentifier("price_card_pair_price_\(data.name)") - } - - PriceChart(values: data.pastValues, isPositive: data.change.isPositive) - .frame(height: 48) - .accessibilityIdentifier("price_card_chart") - } - .frame(maxWidth: .infinity, alignment: .leading) - } -} - -// MARK: - Compact layout (small carousel preview only) - -struct PriceWidgetCompactContent: View { - let data: PriceData - let period: GraphPeriod - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - VStack(alignment: .leading, spacing: 8) { - HStack(spacing: 0) { - CaptionMText(data.name, textColor: .textSecondary) - .textCase(.uppercase) - Spacer(minLength: 0) - CaptionMText(period.rawValue, textColor: .textSecondary) - .textCase(.uppercase) - } - .accessibilityIdentifier("price_card_small_pair_row_\(data.name)") - - Text(data.price) - .font(Fonts.bold(size: 22)) - .foregroundColor(.textPrimary) - .lineLimit(1) - .minimumScaleFactor(0.7) - .accessibilityIdentifier("price_card_small_pair_price_\(data.name)") - - BodySSBText( - data.change.formatted, - textColor: data.change.isPositive ? .greenAccent : .redAccent - ) - .lineLimit(1) - .accessibilityIdentifier("price_card_small_pair_change_\(data.name)") - } - - PriceChart(values: data.pastValues, isPositive: data.change.isPositive) - .frame(height: 64) - .accessibilityIdentifier("price_card_small_chart") - } - .padding(16) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .background(Color.gray6) - .cornerRadius(16) - } -} - -// MARK: - Chart - -struct PriceChart: View { - let values: [Double] - let isPositive: Bool - - private let lineWidth: CGFloat = 1.3 - - private var normalizedValues: [Double] { - guard values.count > 1 else { return values } - - let minValue = values.min() ?? 0 - let maxValue = values.max() ?? 0 - let range = maxValue - minValue - - guard range > 0 else { return values.map { _ in 0.5 } } - - return values.map { value in - let normalized = (value - minValue) / range - return 0.15 + (normalized * 0.7) - } - } - - private var lineColor: Color { - isPositive ? .greenAccent : .redAccent - } - - var body: some View { - Chart { - ForEach(Array(normalizedValues.enumerated()), id: \.offset) { index, value in - LineMark( - x: .value("Index", index), - y: .value("Price", value) - ) - .foregroundStyle(lineColor) - .lineStyle(StrokeStyle(lineWidth: lineWidth)) - .interpolationMethod(.catmullRom) - } - } - .chartXAxis(.hidden) - .chartYAxis(.hidden) - .chartYScale(domain: 0.1 ... 0.9) - } -} - #Preview { PriceWidget() .padding() diff --git a/Bitkit/Components/Widgets/PriceWidgetContent.swift b/Bitkit/Components/Widgets/PriceWidgetContent.swift new file mode 100644 index 000000000..3bfea47ca --- /dev/null +++ b/Bitkit/Components/Widgets/PriceWidgetContent.swift @@ -0,0 +1,151 @@ +import Charts +import SwiftUI +import WidgetKit + +// Shared Bitcoin Price widget content, reused by the in-app feed, the carousel preview, and the +// home-screen WidgetKit extension. Colors adapt to `widgetRenderingMode` via ``WidgetPalette``. +// +// Card chrome (padding/background/corner) is supplied by the caller, not here. + +// MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium OS widget) + +struct PriceWidgetWideContent: View { + let data: PriceData + let period: GraphPeriod + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) + VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .center, spacing: 16) { + CaptionMText("\(data.name) \(period.rawValue)", textColor: palette.secondary) + .textCase(.uppercase) + .frame(maxWidth: .infinity, alignment: .leading) + + TitleText( + data.change.formatted, + textColor: palette.data(data.change.isPositive ? .greenAccent : .redAccent) + ) + .lineLimit(1) + .widgetAccentable() + .accessibilityIdentifier("price_card_pair_change_\(data.name)") + } + .accessibilityIdentifier("PriceWidgetRow-\(data.name)") + + Text(data.price) + .font(Fonts.bold(size: 34)) + .foregroundColor(palette.title) + .lineLimit(1) + .minimumScaleFactor(0.7) + .frame(maxWidth: .infinity, alignment: .leading) + .widgetAccentable() + .accessibilityIdentifier("price_card_pair_price_\(data.name)") + } + + PriceChart(values: data.pastValues, isPositive: data.change.isPositive, renderingMode: renderingMode) + .frame(height: 48) + .widgetAccentable() + .accessibilityIdentifier("price_card_chart") + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +// MARK: - Compact layout (small carousel preview + .systemSmall OS widget) + +struct PriceWidgetCompactContent: View { + let data: PriceData + let period: GraphPeriod + + @Environment(\.widgetRenderingMode) private var renderingMode + + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) + VStack(alignment: .leading, spacing: 0) { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 0) { + CaptionMText(data.name, textColor: palette.secondary) + .textCase(.uppercase) + Spacer(minLength: 0) + CaptionMText(period.rawValue, textColor: palette.secondary) + .textCase(.uppercase) + } + .accessibilityIdentifier("price_card_small_pair_row_\(data.name)") + + Text(data.price) + .font(Fonts.bold(size: 22)) + .foregroundColor(palette.title) + .lineLimit(1) + .minimumScaleFactor(0.7) + .widgetAccentable() + .accessibilityIdentifier("price_card_small_pair_price_\(data.name)") + + BodySSBText( + data.change.formatted, + textColor: palette.data(data.change.isPositive ? .greenAccent : .redAccent) + ) + .lineLimit(1) + .widgetAccentable() + .accessibilityIdentifier("price_card_small_pair_change_\(data.name)") + } + + Spacer(minLength: 8) + + PriceChart(values: data.pastValues, isPositive: data.change.isPositive, renderingMode: renderingMode) + .frame(height: 64) + .widgetAccentable() + .accessibilityIdentifier("price_card_small_chart") + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } +} + +// MARK: - Chart + +struct PriceChart: View { + let values: [Double] + let isPositive: Bool + /// Defaults to `.fullColor` so non-widget callers render the colored line. + var renderingMode: WidgetRenderingMode = .fullColor + + private let lineWidth: CGFloat = 1.3 + + private var normalizedValues: [Double] { + guard values.count > 1 else { return values } + + let minValue = values.min() ?? 0 + let maxValue = values.max() ?? 0 + let range = maxValue - minValue + + guard range > 0 else { return values.map { _ in 0.5 } } + + return values.map { value in + let normalized = (value - minValue) / range + return 0.15 + (normalized * 0.7) + } + } + + private var lineColor: Color { + guard renderingMode == .fullColor else { return .primary } + return isPositive ? .greenAccent : .redAccent + } + + var body: some View { + Chart { + ForEach(Array(normalizedValues.enumerated()), id: \.offset) { index, value in + LineMark( + x: .value("Index", index), + y: .value("Price", value) + ) + .foregroundStyle(lineColor) + .lineStyle(StrokeStyle(lineWidth: lineWidth)) + .interpolationMethod(.catmullRom) + } + } + .chartXAxis(.hidden) + .chartYAxis(.hidden) + .chartYScale(domain: 0.1 ... 0.9) + } +} diff --git a/Bitkit/Components/Widgets/WeatherWidgetContent.swift b/Bitkit/Components/Widgets/WeatherWidgetContent.swift index 793e8958e..82673aa5f 100644 --- a/Bitkit/Components/Widgets/WeatherWidgetContent.swift +++ b/Bitkit/Components/Widgets/WeatherWidgetContent.swift @@ -44,12 +44,15 @@ struct WeatherFeeMetric: View { /// in the space-constrained compact widget. var compactLabel: Bool = false + @Environment(\.widgetRenderingMode) private var renderingMode + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) VStack(alignment: .leading, spacing: 4) { - labelView + labelView(palette: palette) Text(value) .font(Fonts.bold(size: valueSize)) - .foregroundColor(valueColor) + .foregroundColor(palette.data(valueColor)) .kerning(-1) .lineLimit(1) .minimumScaleFactor(0.9) @@ -58,14 +61,14 @@ struct WeatherFeeMetric: View { } @ViewBuilder - private var labelView: some View { + private func labelView(palette: WidgetPalette) -> some View { if compactLabel { - FootnoteText(label, textColor: .white64) + FootnoteText(label, textColor: palette.secondary) .textCase(.uppercase) .lineLimit(1) .minimumScaleFactor(0.7) } else { - CaptionMText(label, textColor: .white64) + CaptionMText(label, textColor: palette.secondary) .textCase(.uppercase) .lineLimit(1) } @@ -79,12 +82,15 @@ struct WeatherWidgetWideContent: View { let conditionDescription: String let metricLabel: String + @Environment(\.widgetRenderingMode) private var renderingMode + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) HStack(alignment: .top, spacing: 16) { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 4) { - SubtitleText(conditionTitle, textColor: .white) - BodySText(conditionDescription, textColor: .white80) + SubtitleText(conditionTitle, textColor: palette.title) + BodySText(conditionDescription, textColor: palette.label) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } @@ -112,14 +118,17 @@ struct WeatherWidgetCompactContent: View { let conditionTitle: String let metricLabel: String + @Environment(\.widgetRenderingMode) private var renderingMode + var body: some View { + let palette = WidgetPalette(renderingMode: renderingMode) VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading) { Text(data.condition.icon) .font(.system(size: 58)) .minimumScaleFactor(0.85) .widgetAccentable() - SubtitleText(conditionTitle, textColor: .white) + SubtitleText(conditionTitle, textColor: palette.title) .lineLimit(1) .minimumScaleFactor(0.6) } diff --git a/Bitkit/Components/Widgets/WidgetPalette.swift b/Bitkit/Components/Widgets/WidgetPalette.swift new file mode 100644 index 000000000..9c98e029a --- /dev/null +++ b/Bitkit/Components/Widgets/WidgetPalette.swift @@ -0,0 +1,48 @@ +import SwiftUI +import WidgetKit + +/// Centralizes the color convention shared between the in-app widget feed and the home-screen +/// WidgetKit extension, so a single content view renders correctly in full color **and** in +/// tinted/monochrome (Smart Stack) rendering. +/// +/// Outside of a widget, `widgetRenderingMode` defaults to `.fullColor`, so in-app widgets get the +/// dark-theme palette automatically. In tinted/accented modes colors collapse to `.primary`/ +/// `.secondary` so WidgetKit can recolor them, and the container background becomes clear. +struct WidgetPalette { + let renderingMode: WidgetRenderingMode + + var isFullColor: Bool { + renderingMode == .fullColor + } + + /// Titles and primary values. + var title: Color { + isFullColor ? .white : .primary + } + + /// Strong secondary labels (e.g. Blocks field labels, weather description). + var label: Color { + isFullColor ? .white80 : .secondary + } + + /// Subtle metadata (price pair/period, news date, metric caption). + var secondary: Color { + isFullColor ? .white64 : .secondary + } + + /// Icons and accent text (block icons, news source). + var accent: Color { + isFullColor ? .brandAccent : .primary + } + + /// Container background — clear in tinted mode so the wallpaper shows through. + var background: Color { + isFullColor ? .gray6 : .clear + } + + /// Data-driven colors (green/red change, weather condition). Falls back to `.primary` in + /// tinted mode so the system can recolor consistently. + func data(_ color: Color) -> Color { + isFullColor ? color : .primary + } +} diff --git a/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift b/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift index 55796e081..a249161f8 100644 --- a/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift @@ -124,6 +124,9 @@ struct BlocksWidgetPreviewView: View { Group { if let data = viewModel.blockData { BlocksWidgetCompactContent(data: data, options: currentOptions) + .padding(16) + .background(Color.gray6) + .cornerRadius(16) } else { placeholderCompact } diff --git a/Bitkit/Views/Widgets/FactsWidgetPreviewView.swift b/Bitkit/Views/Widgets/FactsWidgetPreviewView.swift index 109d3c45f..6a0655f5a 100644 --- a/Bitkit/Views/Widgets/FactsWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/FactsWidgetPreviewView.swift @@ -71,6 +71,9 @@ struct FactsWidgetPreviewView: View { VStack { Spacer(minLength: 0) FactsWidgetCompactContent(fact: viewModel.fact) + .padding(16) + .background(Color.gray6) + .cornerRadius(16) .frame(width: 163, height: 192) Spacer(minLength: 0) } diff --git a/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift b/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift index ad05b9686..7bdbd8086 100644 --- a/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift @@ -126,7 +126,10 @@ struct NewsWidgetPreviewView: View { Spacer(minLength: 0) Group { if let data = viewModel.widgetData { - NewsWidgetCompactContent(data: data, options: currentOptions) + NewsWidgetCompactContent(title: data.title, timeAgo: data.timeAgo, options: currentOptions) + .padding(16) + .background(Color.gray6) + .cornerRadius(16) } else { placeholderCompact } @@ -142,10 +145,15 @@ struct NewsWidgetPreviewView: View { Spacer(minLength: 0) Group { if let data = viewModel.widgetData { - NewsWidgetWideContent(data: data, options: currentOptions) - .padding(16) - .background(Color.gray6) - .cornerRadius(16) + NewsWidgetWideContent( + title: data.title, + publisher: data.publisher, + timeAgo: data.timeAgo, + options: currentOptions + ) + .padding(16) + .background(Color.gray6) + .cornerRadius(16) } else { placeholderWide } diff --git a/Bitkit/Views/Widgets/PriceWidgetPreviewView.swift b/Bitkit/Views/Widgets/PriceWidgetPreviewView.swift index 1b95fdc66..464995ef0 100644 --- a/Bitkit/Views/Widgets/PriceWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/PriceWidgetPreviewView.swift @@ -137,6 +137,9 @@ struct PriceWidgetPreviewView: View { Group { if let data = primaryPrice { PriceWidgetCompactContent(data: data, period: currentOptions.selectedPeriod) + .padding(16) + .background(Color.gray6) + .cornerRadius(16) } else { placeholderCompact } diff --git a/BitkitWidget/BlocksHomeScreenWidget.swift b/BitkitWidget/BlocksHomeScreenWidget.swift index 32f2ca817..5d15be92c 100644 --- a/BitkitWidget/BlocksHomeScreenWidget.swift +++ b/BitkitWidget/BlocksHomeScreenWidget.swift @@ -93,9 +93,13 @@ struct BlocksHomeScreenWidgetEntryView: View { var entry: BlocksWidgetProvider.Entry + private var palette: WidgetPalette { + WidgetPalette(renderingMode: widgetRenderingMode) + } + var body: some View { content - .containerBackground(for: .widget) { backgroundView } + .containerBackground(for: .widget) { palette.background } } @ViewBuilder @@ -105,9 +109,10 @@ struct BlocksHomeScreenWidgetEntryView: View { } else if let block = entry.block { switch widgetFamily { case .systemSmall: - compactLayout(block: block) + BlocksWidgetCompactContent(data: block, options: entry.options) default: - wideLayout(block: block, fields: entry.options.enabledFields) + BlocksWidgetWideContent(data: block, options: entry.options) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } else { ProgressView() @@ -115,104 +120,10 @@ struct BlocksHomeScreenWidgetEntryView: View { } } - // MARK: - Layouts - - /// Candidate row spacings, from roomiest to tightest. `ViewThatFits` picks the first that - /// fits the available height, so larger screens keep 16pt while iPhone SE falls back to less. - private static let rowSpacings: [CGFloat] = [16, 10, 6, 2] - - /// Compact (`.systemSmall`): icon + value rows for the selected fields. - private func compactLayout(block: CachedBlock) -> some View { - ViewThatFits(in: .vertical) { - ForEach(Self.rowSpacings, id: \.self) { spacing in - compactStack(block: block, spacing: spacing) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - private func compactStack(block: CachedBlock, spacing: CGFloat) -> some View { - VStack(alignment: .leading, spacing: spacing) { - ForEach(entry.options.enabledFields, id: \.self) { field in - HStack(alignment: .center, spacing: 8) { - iconImage(field: field) - Text(field.value(from: block)) - .font(Fonts.semiBold(size: 15)) - .foregroundColor(titleTextColor) - .lineLimit(1) - .truncationMode(.middle) - .widgetAccentable() - } - } - } - .frame(maxWidth: .infinity, alignment: .topLeading) - } - - /// Wide layout (`.systemMedium`): icon + label + value rows for the selected fields. - private func wideLayout(block: CachedBlock, fields: [BlocksWidgetField]) -> some View { - ViewThatFits(in: .vertical) { - ForEach(Self.rowSpacings, id: \.self) { spacing in - wideStack(block: block, fields: fields, spacing: spacing) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - private func wideStack(block: CachedBlock, fields: [BlocksWidgetField], spacing: CGFloat) -> some View { - VStack(alignment: .leading, spacing: spacing) { - ForEach(fields, id: \.self) { field in - HStack(alignment: .center, spacing: 8) { - iconImage(field: field) - Text(field.label) - .font(Fonts.regular(size: 17)) - .foregroundColor(labelTextColor) - .lineLimit(1) - .frame(maxWidth: .infinity, alignment: .leading) - Text(field.value(from: block)) - .font(Fonts.semiBold(size: 17)) - .foregroundColor(titleTextColor) - .lineLimit(1) - .truncationMode(.middle) - .widgetAccentable() - } - } - } - .frame(maxWidth: .infinity, alignment: .topLeading) - } - - private func iconImage(field: BlocksWidgetField) -> some View { - Image(field.iconName) - .resizable() - .renderingMode(.template) - .foregroundColor(iconColor) - .frame(width: 20, height: 20) - .widgetAccentable() - } - private var errorView: some View { - Text("Couldn’t load blocks data.") - .font(Fonts.regular(size: 13)) - .foregroundColor(labelTextColor) + BodySText(t("widgets__blocks__error"), textColor: palette.secondary) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - - // MARK: - Colors - - private var backgroundView: some View { - widgetRenderingMode == .fullColor ? Color.gray6 : Color.clear - } - - private var titleTextColor: Color { - widgetRenderingMode == .fullColor ? .white : .primary - } - - private var labelTextColor: Color { - widgetRenderingMode == .fullColor ? .white.opacity(0.8) : .secondary - } - - private var iconColor: Color { - widgetRenderingMode == .fullColor ? .brandAccent : .primary - } } // MARK: - Widget Configuration diff --git a/BitkitWidget/FactsHomeScreenWidget.swift b/BitkitWidget/FactsHomeScreenWidget.swift index 3c6979823..e1300e571 100644 --- a/BitkitWidget/FactsHomeScreenWidget.swift +++ b/BitkitWidget/FactsHomeScreenWidget.swift @@ -42,85 +42,25 @@ struct FactsHomeScreenWidgetEntryView: View { var entry: FactsWidgetProvider.Entry + private var palette: WidgetPalette { + WidgetPalette(renderingMode: widgetRenderingMode) + } + var body: some View { content - .containerBackground(for: .widget) { backgroundView } + .containerBackground(for: .widget) { palette.background } } @ViewBuilder private var content: some View { switch widgetFamily { case .systemSmall: - compactLayout + FactsWidgetCompactContent(fact: entry.fact) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) default: - wideLayout - } - } - - private var compactLayout: some View { - Text(entry.fact) - .font(Fonts.semiBold(size: 17)) - .foregroundColor(textColor) - .lineLimit(4) - .minimumScaleFactor(0.85) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .overlay(alignment: .bottomTrailing) { - bitcoinLogo - } - .widgetAccentable() - } - - private var wideLayout: some View { - HStack(alignment: .top, spacing: 32) { - Text(entry.fact) - .font(Fonts.bold(size: 22)) - .foregroundColor(textColor) - .lineLimit(4) - .minimumScaleFactor(0.85) - .frame(maxWidth: .infinity, alignment: .topLeading) - .widgetAccentable() - - bitcoinLogo - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - private var bitcoinLogo: some View { - Group { - if widgetRenderingMode == .fullColor { - ZStack { - Circle() - .fill(Color.bitcoin) - - bitcoinGlyph - .foregroundColor(.white) - } - } else { - ZStack { - Circle() - .fill(Color.white) - - bitcoinGlyph - .blendMode(.destinationOut) - } - .compositingGroup() - } + FactsWidgetWideContent(fact: entry.fact) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - .frame(width: 32, height: 32) - } - - private var bitcoinGlyph: some View { - Image("bitcoin") - .resizable() - .renderingMode(.template) - } - - private var backgroundView: some View { - widgetRenderingMode == .fullColor ? Color.gray6 : Color.clear - } - - private var textColor: Color { - widgetRenderingMode == .fullColor ? .white : .primary } } diff --git a/BitkitWidget/NewsHomeScreenWidget.swift b/BitkitWidget/NewsHomeScreenWidget.swift index 1df2c48fd..bebbcc358 100644 --- a/BitkitWidget/NewsHomeScreenWidget.swift +++ b/BitkitWidget/NewsHomeScreenWidget.swift @@ -133,6 +133,10 @@ struct NewsHomeScreenWidgetEntryView: View { var entry: NewsWidgetProvider.Entry + private var palette: WidgetPalette { + WidgetPalette(renderingMode: widgetRenderingMode) + } + var body: some View { Group { if let url = articleURL { @@ -142,7 +146,7 @@ struct NewsHomeScreenWidgetEntryView: View { } } .widgetURL(articleURL) - .containerBackground(for: .widget) { backgroundView } + .containerBackground(for: .widget) { palette.background } } private var articleURL: URL? { @@ -157,9 +161,19 @@ struct NewsHomeScreenWidgetEntryView: View { } else if let article = entry.article { switch widgetFamily { case .systemSmall: - compactLayout(article: article) + NewsWidgetCompactContent( + title: article.title, + timeAgo: entry.timeAgo, + options: entry.options + ) default: - wideLayout(article: article) + NewsWidgetWideContent( + title: article.title, + publisher: article.publisher, + timeAgo: entry.timeAgo, + options: entry.options + ) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } else { ProgressView() @@ -167,97 +181,10 @@ struct NewsHomeScreenWidgetEntryView: View { } } - // MARK: - Compact (small widget — 163×192) - - private func compactLayout(article: CachedNewsArticle) -> some View { - VStack(alignment: .leading, spacing: 0) { - titleText(article.title) - .frame(maxWidth: .infinity, alignment: .leading) - - Spacer(minLength: 8) - - if entry.options.showDate { - HStack { - Spacer(minLength: 0) - Text(entry.timeAgo) - .font(Fonts.semiBold(size: 13)) - .tracking(0.4) - .foregroundColor(secondaryTextColor) - .lineLimit(1) - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - // MARK: - Wide (medium widget — 343×118) - - private func wideLayout(article: CachedNewsArticle) -> some View { - VStack(alignment: .leading, spacing: 0) { - titleText(article.title) - .frame(maxWidth: .infinity, alignment: .leading) - - Spacer(minLength: 8) - - if entry.options.showSource || entry.options.showDate { - HStack(alignment: .center, spacing: 8) { - if entry.options.showSource { - Text(article.publisher) - .font(Fonts.semiBold(size: 13)) - .tracking(0.4) - .foregroundColor(sourceTextColor) - .lineLimit(1) - } - Spacer(minLength: 0) - if entry.options.showDate { - Text(entry.timeAgo) - .font(Fonts.semiBold(size: 13)) - .tracking(0.4) - .foregroundColor(secondaryTextColor) - .lineLimit(1) - } - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - // MARK: - Sub-views - - private func titleText(_ value: String) -> some View { - Text(value) - .font(Fonts.bold(size: 22)) - .foregroundColor(titleTextColor) - .lineLimit(4) - .minimumScaleFactor(0.85) - .widgetAccentable() - } - private var errorView: some View { - Text("Couldn’t load headlines.") - .font(Fonts.regular(size: 13)) - .foregroundColor(secondaryTextColor) + BodySText(t("widgets__news__error"), textColor: palette.secondary) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - - // MARK: - Colors - - private var backgroundView: some View { - widgetRenderingMode == .fullColor ? Color.gray6 : Color.clear - } - - private var titleTextColor: Color { - widgetRenderingMode == .fullColor ? .white : .primary - } - - private var sourceTextColor: Color { - guard widgetRenderingMode == .fullColor else { return .primary } - return .brandAccent - } - - private var secondaryTextColor: Color { - widgetRenderingMode == .fullColor ? .white.opacity(0.64) : .secondary - } } // MARK: - Widget Configuration diff --git a/BitkitWidget/PriceHomeScreenWidget.swift b/BitkitWidget/PriceHomeScreenWidget.swift index 711472267..17152d781 100644 --- a/BitkitWidget/PriceHomeScreenWidget.swift +++ b/BitkitWidget/PriceHomeScreenWidget.swift @@ -1,4 +1,3 @@ -import Charts import SwiftUI import WidgetKit @@ -105,9 +104,13 @@ struct PriceHomeScreenWidgetEntryView: View { var entry: PriceWidgetProvider.Entry + private var palette: WidgetPalette { + WidgetPalette(renderingMode: widgetRenderingMode) + } + var body: some View { content - .containerBackground(for: .widget) { backgroundView } + .containerBackground(for: .widget) { palette.background } } @ViewBuilder @@ -117,9 +120,10 @@ struct PriceHomeScreenWidgetEntryView: View { } else if let primary = primaryPrice { switch widgetFamily { case .systemSmall: - compactLayout(data: primary) + PriceWidgetCompactContent(data: primary, period: entry.options.selectedPeriod) default: - wideLayout(data: primary) + PriceWidgetWideContent(data: primary, period: entry.options.selectedPeriod) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } else { ProgressView() @@ -134,141 +138,10 @@ struct PriceHomeScreenWidgetEntryView: View { return entry.prices.first } - // MARK: - Compact (small widget — 163×192) - - private func compactLayout(data: PriceData) -> some View { - VStack(alignment: .leading, spacing: 0) { - VStack(alignment: .leading, spacing: 8) { - HStack(spacing: 0) { - CaptionMText(data.name, textColor: secondaryTextColor) - Spacer(minLength: 0) - CaptionMText(entry.options.selectedPeriod.rawValue, textColor: secondaryTextColor) - } - - priceText(data.price, size: 22) - - Text(data.change.formatted) - .font(Fonts.semiBold(size: 15)) - .foregroundColor(changeColor(isPositive: data.change.isPositive)) - .lineLimit(1) - .widgetAccentable() - } - - Spacer(minLength: 8) - - chart(values: data.pastValues, isPositive: data.change.isPositive, idealHeight: 64) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - // MARK: - Wide (medium widget — 343×152) - - private func wideLayout(data: PriceData) -> some View { - VStack(alignment: .leading, spacing: 0) { - VStack(alignment: .leading, spacing: 4) { - HStack(alignment: .center, spacing: 16) { - CaptionMText("\(data.name) \(entry.options.selectedPeriod.rawValue)", textColor: secondaryTextColor) - .frame(maxWidth: .infinity, alignment: .leading) - - Text(data.change.formatted) - .font(Fonts.bold(size: 22)) - .foregroundColor(changeColor(isPositive: data.change.isPositive)) - .lineLimit(1) - .widgetAccentable() - } - - priceText(data.price, size: 34) - } - - Spacer(minLength: 4) - - chart(values: data.pastValues, isPositive: data.change.isPositive, idealHeight: 48, minHeight: 24) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - // MARK: - Sub-views - - private func priceText(_ value: String, size: CGFloat) -> some View { - Text(value) - .font(Fonts.bold(size: size)) - .foregroundColor(valueTextColor) - .lineLimit(1) - .widgetAccentable() - } - - private func chart(values: [Double], isPositive: Bool, idealHeight: CGFloat, minHeight: CGFloat = 32) -> some View { - PriceWidgetChart( - values: values, - isPositive: isPositive, - renderingMode: widgetRenderingMode - ) - .frame(minHeight: minHeight, maxHeight: idealHeight) - .widgetAccentable() - } - private var errorView: some View { - BodySText("Couldn’t load price.", textColor: secondaryTextColor) + BodySText(t("widgets__price__error"), textColor: palette.secondary) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - - // MARK: - Colors - - private var backgroundView: some View { - widgetRenderingMode == .fullColor ? Color.gray6 : Color.clear - } - - private var secondaryTextColor: Color { - widgetRenderingMode == .fullColor ? .white.opacity(0.64) : .secondary - } - - private var valueTextColor: Color { - widgetRenderingMode == .fullColor ? .white : .primary - } - - private func changeColor(isPositive: Bool) -> Color { - guard widgetRenderingMode == .fullColor else { return .primary } - return isPositive ? .greenAccent : .redAccent - } -} - -// MARK: - Chart - -private struct PriceWidgetChart: View { - let values: [Double] - let isPositive: Bool - let renderingMode: WidgetRenderingMode - - private var normalizedValues: [Double] { - guard values.count > 1 else { return values } - let minValue = values.min() ?? 0 - let maxValue = values.max() ?? 0 - let range = maxValue - minValue - guard range > 0 else { return values.map { _ in 0.5 } } - return values.map { 0.15 + (($0 - minValue) / range) * 0.7 } - } - - private var lineColor: Color { - guard renderingMode == .fullColor else { return .primary } - return isPositive ? .greenAccent : .redAccent - } - - var body: some View { - Chart { - ForEach(Array(normalizedValues.enumerated()), id: \.offset) { index, value in - LineMark( - x: .value("Index", index), - y: .value("Price", value) - ) - .foregroundStyle(lineColor) - .lineStyle(StrokeStyle(lineWidth: 1.3)) - .interpolationMethod(.catmullRom) - } - } - .chartXAxis(.hidden) - .chartYAxis(.hidden) - .chartYScale(domain: 0.1 ... 0.9) - } } // MARK: - Widget Configuration diff --git a/BitkitWidget/WeatherHomeScreenWidget.swift b/BitkitWidget/WeatherHomeScreenWidget.swift index 6aec753a4..de98aa49c 100644 --- a/BitkitWidget/WeatherHomeScreenWidget.swift +++ b/BitkitWidget/WeatherHomeScreenWidget.swift @@ -72,9 +72,13 @@ struct WeatherHomeScreenWidgetEntryView: View { var entry: WeatherWidgetProvider.Entry + private var palette: WidgetPalette { + WidgetPalette(renderingMode: widgetRenderingMode) + } + var body: some View { content - .containerBackground(for: .widget) { backgroundView } + .containerBackground(for: .widget) { palette.background } } @ViewBuilder From 8ad27bda61774890a0dfbdbf7c06b0b1a4c29d76 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 13:39:04 -0300 Subject: [PATCH 05/12] fix: wide logo accent --- Bitkit/Components/Widgets/FactsWidgetContent.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Bitkit/Components/Widgets/FactsWidgetContent.swift b/Bitkit/Components/Widgets/FactsWidgetContent.swift index 2b58dbd0d..fd1d42349 100644 --- a/Bitkit/Components/Widgets/FactsWidgetContent.swift +++ b/Bitkit/Components/Widgets/FactsWidgetContent.swift @@ -21,7 +21,7 @@ struct FactsWidgetWideContent: View { .frame(maxWidth: .infinity, alignment: .leading) .widgetAccentable() - BitcoinLogo(renderingMode: renderingMode) + BitcoinLogo() } .frame(maxWidth: .infinity, alignment: .leading) } @@ -40,10 +40,10 @@ struct FactsWidgetCompactContent: View { .lineLimit(4) .minimumScaleFactor(0.85) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .widgetAccentable() .overlay(alignment: .bottomTrailing) { - BitcoinLogo(renderingMode: renderingMode) + BitcoinLogo() } - .widgetAccentable() } } @@ -52,7 +52,7 @@ struct FactsWidgetCompactContent: View { /// Orange ₿ badge in full color; a white circle with the glyph knocked out in tinted/monochrome /// mode so it reads against the system wallpaper. `bitcoin` is a template glyph asset. struct BitcoinLogo: View { - var renderingMode: WidgetRenderingMode = .fullColor + @Environment(\.widgetRenderingMode) private var renderingMode var body: some View { Group { @@ -76,6 +76,7 @@ struct BitcoinLogo: View { } } .frame(width: 32, height: 32) + .widgetAccentable() } private var glyph: some View { From 339eeca14ffb55c64dfb689949d6fd7861cb2575 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 13:48:23 -0300 Subject: [PATCH 06/12] fix: remove spacer --- Bitkit/Components/Widgets/NewsWidgetContent.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Bitkit/Components/Widgets/NewsWidgetContent.swift b/Bitkit/Components/Widgets/NewsWidgetContent.swift index e63eb258a..61fae55e5 100644 --- a/Bitkit/Components/Widgets/NewsWidgetContent.swift +++ b/Bitkit/Components/Widgets/NewsWidgetContent.swift @@ -19,7 +19,7 @@ struct NewsWidgetWideContent: View { var body: some View { let palette = WidgetPalette(renderingMode: renderingMode) - VStack(alignment: .leading, spacing: 0) { + VStack(alignment: .leading, spacing: 16) { if options.showTitle { TitleText(title, textColor: palette.title) .lineLimit(4) @@ -28,8 +28,6 @@ struct NewsWidgetWideContent: View { .widgetAccentable() } - Spacer(minLength: 8) - if options.showSource || options.showDate { HStack(alignment: .center, spacing: 8) { if options.showSource { From 294a7aecdc55d84113c9e8eb944497ed029b9436 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 20 May 2026 14:04:43 -0300 Subject: [PATCH 07/12] fix: btc icon resource reuse --- .../icons/bitcoin-symbol.imageset/Contents.json | 15 +++++++++++++++ .../bitcoin-symbol.imageset/bitcoin-symbol.pdf | Bin 0 -> 6031 bytes .../Components/Widgets/FactsWidgetContent.swift | 4 +--- .../bitcoin-symbol.imageset/Contents.json | 15 +++++++++++++++ .../bitcoin-symbol.imageset/bitcoin-symbol.pdf | Bin 0 -> 6031 bytes 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/Contents.json create mode 100644 Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/bitcoin-symbol.pdf create mode 100644 BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/Contents.json create mode 100644 BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/bitcoin-symbol.pdf diff --git a/Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/Contents.json b/Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/Contents.json new file mode 100644 index 000000000..27cf96d8c --- /dev/null +++ b/Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bitcoin-symbol.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/bitcoin-symbol.pdf b/Bitkit/Assets.xcassets/icons/bitcoin-symbol.imageset/bitcoin-symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8085e5f487b0c98fefe28b59f67473616cc37838 GIT binary patch literal 6031 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 E2fv|X=>Px# literal 0 HcmV?d00001 diff --git a/Bitkit/Components/Widgets/FactsWidgetContent.swift b/Bitkit/Components/Widgets/FactsWidgetContent.swift index fd1d42349..35eb73c51 100644 --- a/Bitkit/Components/Widgets/FactsWidgetContent.swift +++ b/Bitkit/Components/Widgets/FactsWidgetContent.swift @@ -49,8 +49,6 @@ struct FactsWidgetCompactContent: View { // MARK: - Bitcoin badge -/// Orange ₿ badge in full color; a white circle with the glyph knocked out in tinted/monochrome -/// mode so it reads against the system wallpaper. `bitcoin` is a template glyph asset. struct BitcoinLogo: View { @Environment(\.widgetRenderingMode) private var renderingMode @@ -80,7 +78,7 @@ struct BitcoinLogo: View { } private var glyph: some View { - Image("bitcoin") + Image("bitcoin-symbol") .resizable() .renderingMode(.template) } diff --git a/BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/Contents.json b/BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/Contents.json new file mode 100644 index 000000000..27cf96d8c --- /dev/null +++ b/BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bitcoin-symbol.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/bitcoin-symbol.pdf b/BitkitWidget/Assets.xcassets/bitcoin-symbol.imageset/bitcoin-symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8085e5f487b0c98fefe28b59f67473616cc37838 GIT binary patch literal 6031 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 E2fv|X=>Px# literal 0 HcmV?d00001 From ba1bfb377d779cbb2fbb671eac415ddda9fe29b3 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 21 May 2026 07:19:20 -0300 Subject: [PATCH 08/12] fix: restore news widget spacer --- Bitkit/Components/Widgets/NewsWidget.swift | 1 + Bitkit/Components/Widgets/NewsWidgetContent.swift | 8 ++++++-- Bitkit/Views/Widgets/NewsWidgetPreviewView.swift | 3 ++- BitkitWidget/NewsHomeScreenWidget.swift | 1 - 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Bitkit/Components/Widgets/NewsWidget.swift b/Bitkit/Components/Widgets/NewsWidget.swift index a0ccc23fe..9040955b9 100644 --- a/Bitkit/Components/Widgets/NewsWidget.swift +++ b/Bitkit/Components/Widgets/NewsWidget.swift @@ -50,6 +50,7 @@ struct NewsWidget: View { timeAgo: data.timeAgo, options: options ) + .frame(height: NewsWidgetWideContent.inAppHeight) } } } diff --git a/Bitkit/Components/Widgets/NewsWidgetContent.swift b/Bitkit/Components/Widgets/NewsWidgetContent.swift index 61fae55e5..471c2c4ef 100644 --- a/Bitkit/Components/Widgets/NewsWidgetContent.swift +++ b/Bitkit/Components/Widgets/NewsWidgetContent.swift @@ -10,6 +10,8 @@ import WidgetKit // MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium OS widget) struct NewsWidgetWideContent: View { + static let inAppHeight: CGFloat = 118 + let title: String let publisher: String let timeAgo: String @@ -19,7 +21,7 @@ struct NewsWidgetWideContent: View { var body: some View { let palette = WidgetPalette(renderingMode: renderingMode) - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 0) { if options.showTitle { TitleText(title, textColor: palette.title) .lineLimit(4) @@ -28,6 +30,8 @@ struct NewsWidgetWideContent: View { .widgetAccentable() } + Spacer(minLength: 0) + if options.showSource || options.showDate { HStack(alignment: .center, spacing: 8) { if options.showSource { @@ -43,7 +47,7 @@ struct NewsWidgetWideContent: View { .frame(maxWidth: .infinity) } } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } diff --git a/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift b/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift index 7bdbd8086..e36e18d52 100644 --- a/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift @@ -151,6 +151,7 @@ struct NewsWidgetPreviewView: View { timeAgo: data.timeAgo, options: currentOptions ) + .frame(height: NewsWidgetWideContent.inAppHeight) .padding(16) .background(Color.gray6) .cornerRadius(16) @@ -172,7 +173,7 @@ struct NewsWidgetPreviewView: View { private var placeholderWide: some View { Color.gray6 .cornerRadius(16) - .frame(height: 118) + .frame(height: NewsWidgetWideContent.inAppHeight) .overlay(ProgressView()) } diff --git a/BitkitWidget/NewsHomeScreenWidget.swift b/BitkitWidget/NewsHomeScreenWidget.swift index bebbcc358..75dfda1c6 100644 --- a/BitkitWidget/NewsHomeScreenWidget.swift +++ b/BitkitWidget/NewsHomeScreenWidget.swift @@ -173,7 +173,6 @@ struct NewsHomeScreenWidgetEntryView: View { timeAgo: entry.timeAgo, options: entry.options ) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } else { ProgressView() From ca48637c68a544b2c641f124f4abf1d850d993ca Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 21 May 2026 07:53:33 -0300 Subject: [PATCH 09/12] fix: implement space between logic for blocks and new widgets --- Bitkit/Components/Widgets/BlocksWidget.swift | 1 + .../Widgets/BlocksWidgetContent.swift | 40 +++++++------------ Bitkit/Components/Widgets/NewsWidget.swift | 2 +- .../Widgets/NewsWidgetContent.swift | 2 +- .../Widgets/BlocksWidgetPreviewView.swift | 9 +++-- .../Views/Widgets/NewsWidgetPreviewView.swift | 10 +++-- BitkitWidget/BlocksHomeScreenWidget.swift | 1 - 7 files changed, 29 insertions(+), 36 deletions(-) diff --git a/Bitkit/Components/Widgets/BlocksWidget.swift b/Bitkit/Components/Widgets/BlocksWidget.swift index 84b9fda63..faae7a4d0 100644 --- a/Bitkit/Components/Widgets/BlocksWidget.swift +++ b/Bitkit/Components/Widgets/BlocksWidget.swift @@ -42,6 +42,7 @@ struct BlocksWidget: View { WidgetContentBuilder.errorView(t("widgets__blocks__error")) } else if let data = viewModel.blockData { BlocksWidgetWideContent(data: data, options: options) + .frame(height: BlocksWidgetWideContent.inAppContentHeight) } } } diff --git a/Bitkit/Components/Widgets/BlocksWidgetContent.swift b/Bitkit/Components/Widgets/BlocksWidgetContent.swift index e66a3e506..1a06f9612 100644 --- a/Bitkit/Components/Widgets/BlocksWidgetContent.swift +++ b/Bitkit/Components/Widgets/BlocksWidgetContent.swift @@ -4,33 +4,27 @@ import WidgetKit // Shared Bitcoin Blocks widget content, reused by the in-app feed, the carousel preview, and the // home-screen WidgetKit extension. Colors adapt to `widgetRenderingMode` via ``WidgetPalette``. -private let blocksRowSpacings: [CGFloat] = [16, 10, 6, 2] - // MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium / .systemLarge OS widget) struct BlocksWidgetWideContent: View { + static let inAppContentHeight: CGFloat = 124 + let data: CachedBlock let options: BlocksWidgetOptions @Environment(\.widgetRenderingMode) private var renderingMode var body: some View { - ViewThatFits(in: .vertical) { - ForEach(blocksRowSpacings, id: \.self) { spacing in - stack(spacing: spacing) - } - } - .frame(maxWidth: .infinity, alignment: .topLeading) - } - - private func stack(spacing: CGFloat) -> some View { let palette = WidgetPalette(renderingMode: renderingMode) - return VStack(alignment: .leading, spacing: spacing) { - ForEach(options.enabledFields, id: \.self) { field in + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(options.enabledFields.enumerated()), id: \.element) { index, field in + if index > 0 { + Spacer(minLength: 8) + } BlocksWidgetWideRow(field: field, value: field.value(from: data), palette: palette) } } - .frame(maxWidth: .infinity, alignment: .topLeading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } @@ -64,18 +58,12 @@ struct BlocksWidgetCompactContent: View { @Environment(\.widgetRenderingMode) private var renderingMode var body: some View { - ViewThatFits(in: .vertical) { - ForEach(blocksRowSpacings, id: \.self) { spacing in - stack(spacing: spacing) - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - private func stack(spacing: CGFloat) -> some View { let palette = WidgetPalette(renderingMode: renderingMode) - return VStack(alignment: .leading, spacing: spacing) { - ForEach(options.enabledFields, id: \.self) { field in + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(options.enabledFields.enumerated()), id: \.element) { index, field in + if index > 0 { + Spacer(minLength: 8) + } HStack(alignment: .center, spacing: 8) { BlocksWidgetIcon(field: field, palette: palette) @@ -86,7 +74,7 @@ struct BlocksWidgetCompactContent: View { } } } - .frame(maxWidth: .infinity, alignment: .topLeading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } diff --git a/Bitkit/Components/Widgets/NewsWidget.swift b/Bitkit/Components/Widgets/NewsWidget.swift index 9040955b9..1ba8a3f4f 100644 --- a/Bitkit/Components/Widgets/NewsWidget.swift +++ b/Bitkit/Components/Widgets/NewsWidget.swift @@ -50,7 +50,7 @@ struct NewsWidget: View { timeAgo: data.timeAgo, options: options ) - .frame(height: NewsWidgetWideContent.inAppHeight) + .frame(height: NewsWidgetWideContent.inAppContentHeight) } } } diff --git a/Bitkit/Components/Widgets/NewsWidgetContent.swift b/Bitkit/Components/Widgets/NewsWidgetContent.swift index 471c2c4ef..7d00d4e63 100644 --- a/Bitkit/Components/Widgets/NewsWidgetContent.swift +++ b/Bitkit/Components/Widgets/NewsWidgetContent.swift @@ -10,7 +10,7 @@ import WidgetKit // MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium OS widget) struct NewsWidgetWideContent: View { - static let inAppHeight: CGFloat = 118 + static let inAppContentHeight: CGFloat = 86 let title: String let publisher: String diff --git a/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift b/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift index a249161f8..6e3ad948a 100644 --- a/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/BlocksWidgetPreviewView.swift @@ -143,6 +143,7 @@ struct BlocksWidgetPreviewView: View { Group { if let data = viewModel.blockData { BlocksWidgetWideContent(data: data, options: currentOptions) + .frame(height: BlocksWidgetWideContent.inAppContentHeight) .padding(16) .background(Color.gray6) .cornerRadius(16) @@ -162,10 +163,12 @@ struct BlocksWidgetPreviewView: View { } private var placeholderWide: some View { - Color.gray6 + ProgressView() + .frame(maxWidth: .infinity) + .frame(height: BlocksWidgetWideContent.inAppContentHeight) + .padding(16) + .background(Color.gray6) .cornerRadius(16) - .frame(height: 180) - .overlay(ProgressView()) } // MARK: - Size label & page indicator diff --git a/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift b/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift index e36e18d52..7618a77db 100644 --- a/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift +++ b/Bitkit/Views/Widgets/NewsWidgetPreviewView.swift @@ -151,7 +151,7 @@ struct NewsWidgetPreviewView: View { timeAgo: data.timeAgo, options: currentOptions ) - .frame(height: NewsWidgetWideContent.inAppHeight) + .frame(height: NewsWidgetWideContent.inAppContentHeight) .padding(16) .background(Color.gray6) .cornerRadius(16) @@ -171,10 +171,12 @@ struct NewsWidgetPreviewView: View { } private var placeholderWide: some View { - Color.gray6 + ProgressView() + .frame(maxWidth: .infinity) + .frame(height: NewsWidgetWideContent.inAppContentHeight) + .padding(16) + .background(Color.gray6) .cornerRadius(16) - .frame(height: NewsWidgetWideContent.inAppHeight) - .overlay(ProgressView()) } // MARK: - Size label & page indicator diff --git a/BitkitWidget/BlocksHomeScreenWidget.swift b/BitkitWidget/BlocksHomeScreenWidget.swift index 5d15be92c..315cd0bcb 100644 --- a/BitkitWidget/BlocksHomeScreenWidget.swift +++ b/BitkitWidget/BlocksHomeScreenWidget.swift @@ -112,7 +112,6 @@ struct BlocksHomeScreenWidgetEntryView: View { BlocksWidgetCompactContent(data: block, options: entry.options) default: BlocksWidgetWideContent(data: block, options: entry.options) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } else { ProgressView() From 78b5f581af96ef7893d1f0af6f16a160e27f11b9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 21 May 2026 08:09:01 -0300 Subject: [PATCH 10/12] chore: remove unused resources --- .../icons/bitcoin.imageset/Contents.json | 12 ------------ .../icons/bitcoin.imageset/bitcoin.pdf | Bin 6147 -> 0 bytes .../bitcoin.imageset/Contents.json | 15 --------------- .../Assets.xcassets/bitcoin.imageset/bitcoin.pdf | Bin 6031 -> 0 bytes 4 files changed, 27 deletions(-) delete mode 100644 Bitkit/Assets.xcassets/icons/bitcoin.imageset/Contents.json delete mode 100644 Bitkit/Assets.xcassets/icons/bitcoin.imageset/bitcoin.pdf delete mode 100644 BitkitWidget/Assets.xcassets/bitcoin.imageset/Contents.json delete mode 100644 BitkitWidget/Assets.xcassets/bitcoin.imageset/bitcoin.pdf diff --git a/Bitkit/Assets.xcassets/icons/bitcoin.imageset/Contents.json b/Bitkit/Assets.xcassets/icons/bitcoin.imageset/Contents.json deleted file mode 100644 index 8a6583348..000000000 --- a/Bitkit/Assets.xcassets/icons/bitcoin.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "bitcoin.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Bitkit/Assets.xcassets/icons/bitcoin.imageset/bitcoin.pdf b/Bitkit/Assets.xcassets/icons/bitcoin.imageset/bitcoin.pdf deleted file mode 100644 index caaae04e51b6553e476e406da137a35ee3670892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6147 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 PrVND&?Axb*%;0|jDIlA0 diff --git a/BitkitWidget/Assets.xcassets/bitcoin.imageset/Contents.json b/BitkitWidget/Assets.xcassets/bitcoin.imageset/Contents.json deleted file mode 100644 index a64f61927..000000000 --- a/BitkitWidget/Assets.xcassets/bitcoin.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "bitcoin.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/BitkitWidget/Assets.xcassets/bitcoin.imageset/bitcoin.pdf b/BitkitWidget/Assets.xcassets/bitcoin.imageset/bitcoin.pdf deleted file mode 100644 index 8085e5f487b0c98fefe28b59f67473616cc37838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6031 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 E2fv|X=>Px# From d95cea5fd6a5e09bccfca39c1fb793bb1a46a3e1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 21 May 2026 08:28:40 -0300 Subject: [PATCH 11/12] fix: title line limiter --- Bitkit/Components/Widgets/NewsWidgetContent.swift | 3 ++- BitkitWidget/NewsHomeScreenWidget.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Bitkit/Components/Widgets/NewsWidgetContent.swift b/Bitkit/Components/Widgets/NewsWidgetContent.swift index 7d00d4e63..78582b366 100644 --- a/Bitkit/Components/Widgets/NewsWidgetContent.swift +++ b/Bitkit/Components/Widgets/NewsWidgetContent.swift @@ -16,6 +16,7 @@ struct NewsWidgetWideContent: View { let publisher: String let timeAgo: String let options: NewsWidgetOptions + var titleLineLimit: Int = 2 @Environment(\.widgetRenderingMode) private var renderingMode @@ -24,7 +25,7 @@ struct NewsWidgetWideContent: View { VStack(alignment: .leading, spacing: 0) { if options.showTitle { TitleText(title, textColor: palette.title) - .lineLimit(4) + .lineLimit(titleLineLimit) .minimumScaleFactor(0.85) .frame(maxWidth: .infinity, alignment: .leading) .widgetAccentable() diff --git a/BitkitWidget/NewsHomeScreenWidget.swift b/BitkitWidget/NewsHomeScreenWidget.swift index 75dfda1c6..6b436be98 100644 --- a/BitkitWidget/NewsHomeScreenWidget.swift +++ b/BitkitWidget/NewsHomeScreenWidget.swift @@ -171,7 +171,8 @@ struct NewsHomeScreenWidgetEntryView: View { title: article.title, publisher: article.publisher, timeAgo: entry.timeAgo, - options: entry.options + options: entry.options, + titleLineLimit: 3 ) } } else { From 53924f1f0a49785c3f9dd71b62e7e2a1da7c092e Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 21 May 2026 08:32:34 -0300 Subject: [PATCH 12/12] refactor: remove unnecessary comment --- Bitkit/Components/Widgets/NewsWidgetContent.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Bitkit/Components/Widgets/NewsWidgetContent.swift b/Bitkit/Components/Widgets/NewsWidgetContent.swift index 78582b366..df5ed088f 100644 --- a/Bitkit/Components/Widgets/NewsWidgetContent.swift +++ b/Bitkit/Components/Widgets/NewsWidgetContent.swift @@ -1,12 +1,6 @@ import SwiftUI import WidgetKit -// Shared Bitcoin Headlines widget content, reused by the in-app feed, the carousel preview, and the -// home-screen WidgetKit extension. Colors adapt to `widgetRenderingMode` via ``WidgetPalette``. -// -// Takes primitive fields rather than a model so both the in-app `WidgetData` and the widget -// extension's `CachedNewsArticle` can feed it. Card chrome is supplied by the caller. - // MARK: - Wide layout (in-app + 343-wide carousel page + .systemMedium OS widget) struct NewsWidgetWideContent: View {