feat(native): home-screen widget and travel-day Live Activity on iOS

A WidgetKit extension (TREKWidgets) with two parts: the next trip on the home
screen, with a countdown before it starts and today's day and next stops
while it runs, and a Live Activity for the travel day on the lock screen and
in the Dynamic Island showing the next timed stop and the day's progress.

The web app builds both from the trips stored offline on the device
(native/widgets.ts) and hands them over through TrekShell
(setWidgetSnapshot, syncTripDay, endTripDay). The texts are already
translated, so the widget needs no localisation of its own. The snapshot
lives in the keychain group the app shares with its extensions, so no App
Group is needed. Taps open com.liketrek.trek://navigate/<path>.

NativeWidgetsSync refreshes on start, on returning to the foreground and every
quarter hour. Both parts are gated behind the widgets / liveActivity
capabilities, so older binaries and the PWA are unaffected.
This commit is contained in:
Maurice
2026-11-06 13:52:52 +01:00
parent c6f91716a4
commit f35b189ca2
48 changed files with 1148 additions and 19 deletions
+7
View File
@@ -82,6 +82,13 @@ const native: TranslationStrings = {
'native.lock.settingHint': 'يطلب ذلك عند فتح TREK وبعد نصف دقيقة في الخلفية، ويخفي التطبيق في مبدّل التطبيقات.',
'native.share.title': 'الاستيراد إلى أي رحلة؟',
'native.share.noTrips': 'لا توجد رحلة بعد. أنشئ رحلة أولًا ثم شارك المستند مجددًا.',
'native.widget.dayOf': 'اليوم {n} من {m}',
'native.widget.today': 'اليوم',
'native.widget.noTrip': 'لا توجد رحلة مخططة بعد',
'native.widget.inDays': 'بعد {n} أيام',
'native.widget.tomorrow': 'غدًا',
'native.widget.nothingPlanned': 'لا شيء مخطط لليوم',
'native.widget.dayDone': 'انتهى كل شيء لليوم',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'TREK açılanda və arxa fonda yarım dəqiqədən sonra soruşur, tətbiqi tətbiq dəyişdiricisində gizlədir.',
'native.share.title': 'Hansı səyahətə idxal edilsin?',
'native.share.noTrips': 'Hələ səyahət yoxdur. Əvvəlcə birini yaradın, sonra sənədi yenidən paylaşın.',
'native.widget.dayOf': '{m} gündən {n}-ci gün',
'native.widget.today': 'Bu gün',
'native.widget.noTrip': 'Hələ planlanmış səyahət yoxdur',
'native.widget.inDays': '{n} gün sonra',
'native.widget.tomorrow': 'Sabah',
'native.widget.nothingPlanned': 'Bu gün üçün plan yoxdur',
'native.widget.dayDone': 'Bu gün üçün hər şey bitdi',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Pede ao abrir o TREK e após meio minuto em segundo plano, e oculta o app no seletor de apps.',
'native.share.title': 'Importar para qual viagem?',
'native.share.noTrips': 'Ainda não há viagens. Crie uma primeiro e compartilhe o documento de novo.',
'native.widget.dayOf': 'Dia {n} de {m}',
'native.widget.today': 'Hoje',
'native.widget.noTrip': 'Nenhuma viagem planejada ainda',
'native.widget.inDays': 'em {n} dias',
'native.widget.tomorrow': 'Amanhã',
'native.widget.nothingPlanned': 'Nada planejado para hoje',
'native.widget.dayDone': 'Tudo feito por hoje',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Ho demana en obrir TREK i després de mig minut en segon pla, i amaga l’app al selector d’apps.',
'native.share.title': 'A quin viatge vols importar-ho?',
'native.share.noTrips': 'Encara no hi ha cap viatge. Crea’n un i torna a compartir el document.',
'native.widget.dayOf': 'Dia {n} de {m}',
'native.widget.today': 'Avui',
'native.widget.noTrip': 'Encara no hi ha cap viatge planificat',
'native.widget.inDays': 'd’aquí a {n} dies',
'native.widget.tomorrow': 'Demà',
'native.widget.nothingPlanned': 'Res planificat per avui',
'native.widget.dayDone': 'Tot fet per avui',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Zeptá se při otevření TREK a po půl minutě na pozadí a skryje aplikaci v přepínači aplikací.',
'native.share.title': 'Importovat do které cesty?',
'native.share.noTrips': 'Zatím žádná cesta. Nejdřív nějakou vytvořte a pak dokument sdílejte znovu.',
'native.widget.dayOf': 'Den {n} z {m}',
'native.widget.today': 'Dnes',
'native.widget.noTrip': 'Zatím žádná naplánovaná cesta',
'native.widget.inDays': 'za {n} dní',
'native.widget.tomorrow': 'Zítra',
'native.widget.nothingPlanned': 'Na dnešek nic naplánováno',
'native.widget.dayDone': 'Na dnešek hotovo',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Fragt beim Öffnen und nach einer halben Minute im Hintergrund nach und verbirgt TREK im App-Umschalter.',
'native.share.title': 'In welchen Trip importieren?',
'native.share.noTrips': 'Noch kein Trip. Lege zuerst einen an und teile das Dokument dann erneut.',
'native.widget.dayOf': 'Tag {n} von {m}',
'native.widget.today': 'Heute',
'native.widget.noTrip': 'Noch keine Reise geplant',
'native.widget.inDays': 'in {n} Tagen',
'native.widget.tomorrow': 'Morgen',
'native.widget.nothingPlanned': 'Für heute nichts geplant',
'native.widget.dayDone': 'Für heute alles geschafft',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Asks when TREK opens and after half a minute in the background, and hides the app in the app switcher.',
'native.share.title': 'Import into which trip?',
'native.share.noTrips': 'No trip yet. Create one first, then share the document again.',
'native.widget.dayOf': 'Day {n} of {m}',
'native.widget.today': 'Today',
'native.widget.noTrip': 'No trip planned yet',
'native.widget.inDays': 'in {n} days',
'native.widget.tomorrow': 'Tomorrow',
'native.widget.nothingPlanned': 'Nothing planned for today',
'native.widget.dayDone': 'All done for today',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Lo pide al abrir TREK y tras medio minuto en segundo plano, y oculta la app en el selector de apps.',
'native.share.title': '¿En qué viaje importar?',
'native.share.noTrips': 'Aún no hay viajes. Crea uno primero y vuelve a compartir el documento.',
'native.widget.dayOf': 'Día {n} de {m}',
'native.widget.today': 'Hoy',
'native.widget.noTrip': 'Aún no hay ningún viaje planeado',
'native.widget.inDays': 'en {n} días',
'native.widget.tomorrow': 'Mañana',
'native.widget.nothingPlanned': 'Nada planeado para hoy',
'native.widget.dayDone': 'Todo hecho por hoy',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Küsib TREK-i avamisel ja pärast poolt minutit taustal ning peidab rakenduse rakenduste vahetajas.',
'native.share.title': 'Millisesse reisi importida?',
'native.share.noTrips': 'Reise veel pole. Loo esmalt üks ja jaga dokumenti uuesti.',
'native.widget.dayOf': 'Päev {n}/{m}',
'native.widget.today': 'Täna',
'native.widget.noTrip': 'Reisi pole veel planeeritud',
'native.widget.inDays': '{n} päeva pärast',
'native.widget.tomorrow': 'Homme',
'native.widget.nothingPlanned': 'Tänaseks pole midagi planeeritud',
'native.widget.dayDone': 'Tänaseks kõik tehtud',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Le demande à l’ouverture de TREK et après une demi-minute en arrière-plan, et masque l’app dans le sélecteur d’apps.',
'native.share.title': 'Importer dans quel voyage ?',
'native.share.noTrips': 'Pas encore de voyage. Créez-en un, puis partagez à nouveau le document.',
'native.widget.dayOf': 'Jour {n} sur {m}',
'native.widget.today': 'Aujourd’hui',
'native.widget.noTrip': 'Aucun voyage prévu pour l’instant',
'native.widget.inDays': 'dans {n} jours',
'native.widget.tomorrow': 'Demain',
'native.widget.nothingPlanned': 'Rien de prévu aujourd’hui',
'native.widget.dayDone': 'Tout est fait pour aujourd’hui',
};
export default native;
+7
View File
@@ -87,6 +87,13 @@ const native: TranslationStrings = {
'Ζητείται κατά το άνοιγμα του TREK και μετά από μισό λεπτό στο παρασκήνιο, και η εφαρμογή κρύβεται στην εναλλαγή εφαρμογών.',
'native.share.title': 'Εισαγωγή σε ποιο ταξίδι;',
'native.share.noTrips': 'Δεν υπάρχει ακόμα ταξίδι. Δημιουργήστε ένα πρώτα και κοινοποιήστε ξανά το έγγραφο.',
'native.widget.dayOf': 'Ημέρα {n} από {m}',
'native.widget.today': 'Σήμερα',
'native.widget.noTrip': 'Δεν υπάρχει ακόμα προγραμματισμένο ταξίδι',
'native.widget.inDays': 'σε {n} ημέρες',
'native.widget.tomorrow': 'Αύριο',
'native.widget.nothingPlanned': 'Τίποτα προγραμματισμένο για σήμερα',
'native.widget.dayDone': 'Όλα έγιναν για σήμερα',
};
export default native;
+7
View File
@@ -82,6 +82,13 @@ const native: TranslationStrings = {
'native.lock.settingHint': 'מבקש בעת פתיחת TREK ואחרי חצי דקה ברקע, ומסתיר את האפליקציה במחליף האפליקציות.',
'native.share.title': 'לייבא לאיזה טיול?',
'native.share.noTrips': 'אין עדיין טיול. צרו טיול ואז שתפו את המסמך שוב.',
'native.widget.dayOf': 'יום {n} מתוך {m}',
'native.widget.today': 'היום',
'native.widget.noTrip': 'עדיין לא תוכנן טיול',
'native.widget.inDays': 'בעוד {n} ימים',
'native.widget.tomorrow': 'מחר',
'native.widget.nothingPlanned': 'אין תוכניות להיום',
'native.widget.dayDone': 'הכול הושלם להיום',
};
export default native;
+7
View File
@@ -86,6 +86,13 @@ const native: TranslationStrings = {
'Rákérdez a TREK megnyitásakor és fél perc háttérben töltött idő után, és elrejti az alkalmazást az alkalmazásváltóban.',
'native.share.title': 'Melyik utazásba importáljuk?',
'native.share.noTrips': 'Még nincs utazás. Előbb hozz létre egyet, aztán oszd meg újra a dokumentumot.',
'native.widget.dayOf': '{m}/{n}. nap',
'native.widget.today': 'Ma',
'native.widget.noTrip': 'Még nincs tervezett utazás',
'native.widget.inDays': '{n} nap múlva',
'native.widget.tomorrow': 'Holnap',
'native.widget.nothingPlanned': 'Mára nincs semmi tervezve',
'native.widget.dayDone': 'Mára minden kész',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Meminta saat TREK dibuka dan setelah setengah menit di latar belakang, serta menyembunyikan aplikasi di pengalih aplikasi.',
'native.share.title': 'Impor ke perjalanan mana?',
'native.share.noTrips': 'Belum ada perjalanan. Buat satu dulu, lalu bagikan dokumennya lagi.',
'native.widget.dayOf': 'Hari {n} dari {m}',
'native.widget.today': 'Hari ini',
'native.widget.noTrip': 'Belum ada perjalanan yang direncanakan',
'native.widget.inDays': '{n} hari lagi',
'native.widget.tomorrow': 'Besok',
'native.widget.nothingPlanned': 'Tidak ada rencana hari ini',
'native.widget.dayDone': 'Semua selesai hari ini',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Lo chiede all’apertura di TREK e dopo mezzo minuto in background, e nasconde l’app nel selettore delle app.',
'native.share.title': 'Importare in quale viaggio?',
'native.share.noTrips': 'Ancora nessun viaggio. Creane uno, poi condividi di nuovo il documento.',
'native.widget.dayOf': 'Giorno {n} di {m}',
'native.widget.today': 'Oggi',
'native.widget.noTrip': 'Nessun viaggio ancora in programma',
'native.widget.inDays': 'tra {n} giorni',
'native.widget.tomorrow': 'Domani',
'native.widget.nothingPlanned': 'Niente in programma oggi',
'native.widget.dayDone': 'Tutto fatto per oggi',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'TREKを開いたときと、バックグラウンドで30秒経過したあとに確認し、アプリスイッチャーでは内容を隠します。',
'native.share.title': 'どの旅行に取り込みますか?',
'native.share.noTrips': 'まだ旅行がありません。先に作成してから、もう一度ドキュメントを共有してください。',
'native.widget.dayOf': '{m}日中{n}日目',
'native.widget.today': '今日',
'native.widget.noTrip': 'まだ予定している旅行はありません',
'native.widget.inDays': 'あと{n}日',
'native.widget.tomorrow': '明日',
'native.widget.nothingPlanned': '今日の予定はありません',
'native.widget.dayDone': '今日の予定はすべて完了',
};
export default native;
+7
View File
@@ -83,6 +83,13 @@ const native: TranslationStrings = {
'TREK을 열 때와 백그라운드에서 30초가 지난 후에 확인하며, 앱 전환기에서는 내용을 숨깁니다.',
'native.share.title': '어느 여행으로 가져올까요?',
'native.share.noTrips': '아직 여행이 없습니다. 먼저 여행을 만든 뒤 문서를 다시 공유하세요.',
'native.widget.dayOf': '{m}일 중 {n}일째',
'native.widget.today': '오늘',
'native.widget.noTrip': '아직 계획된 여행이 없습니다',
'native.widget.inDays': '{n}일 후',
'native.widget.tomorrow': '내일',
'native.widget.nothingPlanned': '오늘은 계획이 없습니다',
'native.widget.dayDone': '오늘 일정 완료',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Vraagt erom bij het openen van TREK en na een halve minuut op de achtergrond, en verbergt de app in de appkiezer.',
'native.share.title': 'In welke reis importeren?',
'native.share.noTrips': 'Nog geen reis. Maak er eerst een aan en deel het document dan opnieuw.',
'native.widget.dayOf': 'Dag {n} van {m}',
'native.widget.today': 'Vandaag',
'native.widget.noTrip': 'Nog geen reis gepland',
'native.widget.inDays': 'over {n} dagen',
'native.widget.tomorrow': 'Morgen',
'native.widget.nothingPlanned': 'Niets gepland voor vandaag',
'native.widget.dayDone': 'Alles gedaan voor vandaag',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Pyta przy otwieraniu TREK i po pół minuty w tle oraz ukrywa aplikację w przełączniku aplikacji.',
'native.share.title': 'Do której podróży zaimportować?',
'native.share.noTrips': 'Nie ma jeszcze podróży. Najpierw utwórz podróż, a potem udostępnij dokument ponownie.',
'native.widget.dayOf': 'Dzień {n} z {m}',
'native.widget.today': 'Dziś',
'native.widget.noTrip': 'Nie zaplanowano jeszcze żadnej podróży',
'native.widget.inDays': 'za {n} dni',
'native.widget.tomorrow': 'Jutro',
'native.widget.nothingPlanned': 'Na dziś nic nie zaplanowano',
'native.widget.dayDone': 'Na dziś wszystko zrobione',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Запрашивается при открытии TREK и через полминуты в фоне, а в переключателе приложений содержимое скрыто.',
'native.share.title': 'В какую поездку импортировать?',
'native.share.noTrips': 'Поездок пока нет. Сначала создайте поездку, затем снова поделитесь документом.',
'native.widget.dayOf': 'День {n} из {m}',
'native.widget.today': 'Сегодня',
'native.widget.noTrip': 'Поездок пока не запланировано',
'native.widget.inDays': 'через {n} дн.',
'native.widget.tomorrow': 'Завтра',
'native.widget.nothingPlanned': 'На сегодня ничего не запланировано',
'native.widget.dayDone': 'На сегодня всё',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Opýta sa pri otvorení TREK a po pol minúte na pozadí a skryje aplikáciu v prepínači aplikácií.',
'native.share.title': 'Importovať do ktorej cesty?',
'native.share.noTrips': 'Zatiaľ žiadna cesta. Najprv nejakú vytvorte a potom dokument zdieľajte znova.',
'native.widget.dayOf': 'Deň {n} z {m}',
'native.widget.today': 'Dnes',
'native.widget.noTrip': 'Zatiaľ žiadna naplánovaná cesta',
'native.widget.inDays': 'o {n} dní',
'native.widget.tomorrow': 'Zajtra',
'native.widget.nothingPlanned': 'Na dnes nič naplánované',
'native.widget.dayDone': 'Na dnes hotovo',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Frågar när TREK öppnas och efter en halv minut i bakgrunden, och döljer appen i appväxlaren.',
'native.share.title': 'Importera till vilken resa?',
'native.share.noTrips': 'Ingen resa ännu. Skapa en först och dela sedan dokumentet igen.',
'native.widget.dayOf': 'Dag {n} av {m}',
'native.widget.today': 'I dag',
'native.widget.noTrip': 'Ingen resa planerad ännu',
'native.widget.inDays': 'om {n} dagar',
'native.widget.tomorrow': 'I morgon',
'native.widget.nothingPlanned': 'Inget planerat i dag',
'native.widget.dayDone': 'Allt klart för i dag',
};
export default native;
+7
View File
@@ -82,6 +82,13 @@ const native: TranslationStrings = {
'native.lock.settingHint': 'ถามเมื่อเปิด TREK และหลังจากอยู่เบื้องหลังครึ่งนาที และซ่อนแอปในตัวสลับแอป',
'native.share.title': 'นำเข้าไปยังทริปไหน',
'native.share.noTrips': 'ยังไม่มีทริป สร้างทริปก่อนแล้วแชร์เอกสารอีกครั้ง',
'native.widget.dayOf': 'วันที่ {n} จาก {m}',
'native.widget.today': 'วันนี้',
'native.widget.noTrip': 'ยังไม่มีทริปที่วางแผนไว้',
'native.widget.inDays': 'อีก {n} วัน',
'native.widget.tomorrow': 'พรุ่งนี้',
'native.widget.nothingPlanned': 'วันนี้ไม่มีแผน',
'native.widget.dayDone': 'วันนี้เสร็จหมดแล้ว',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'TREK açılırken ve arka planda yarım dakika sonra sorar, uygulamayı uygulama değiştiricide gizler.',
'native.share.title': 'Hangi geziye aktarılsın?',
'native.share.noTrips': 'Henüz gezi yok. Önce bir gezi oluşturun, sonra belgeyi yeniden paylaşın.',
'native.widget.dayOf': '{m} günün {n}. günü',
'native.widget.today': 'Bugün',
'native.widget.noTrip': 'Henüz planlanmış gezi yok',
'native.widget.inDays': '{n} gün sonra',
'native.widget.tomorrow': 'Yarın',
'native.widget.nothingPlanned': 'Bugün için plan yok',
'native.widget.dayDone': 'Bugünlük her şey tamam',
};
export default native;
+7
View File
@@ -84,6 +84,13 @@ const native: TranslationStrings = {
'Запитує під час відкриття TREK і після пів хвилини у фоні, а в перемикачі застосунків вміст приховано.',
'native.share.title': 'До якої подорожі імпортувати?',
'native.share.noTrips': 'Подорожей ще немає. Спершу створіть подорож, а потім знову поділіться документом.',
'native.widget.dayOf': 'День {n} з {m}',
'native.widget.today': 'Сьогодні',
'native.widget.noTrip': 'Подорожей ще не заплановано',
'native.widget.inDays': 'через {n} дн.',
'native.widget.tomorrow': 'Завтра',
'native.widget.nothingPlanned': 'На сьогодні нічого не заплановано',
'native.widget.dayDone': 'На сьогодні все',
};
export default native;
+7
View File
@@ -85,6 +85,13 @@ const native: TranslationStrings = {
'Hỏi khi mở TREK và sau nửa phút chạy nền, đồng thời ẩn ứng dụng trong trình chuyển ứng dụng.',
'native.share.title': 'Nhập vào chuyến đi nào?',
'native.share.noTrips': 'Chưa có chuyến đi nào. Hãy tạo một chuyến trước rồi chia sẻ lại tài liệu.',
'native.widget.dayOf': 'Ngày {n}/{m}',
'native.widget.today': 'Hôm nay',
'native.widget.noTrip': 'Chưa có chuyến đi nào được lên kế hoạch',
'native.widget.inDays': 'còn {n} ngày',
'native.widget.tomorrow': 'Ngày mai',
'native.widget.nothingPlanned': 'Hôm nay không có kế hoạch',
'native.widget.dayDone': 'Hôm nay đã xong',
};
export default native;
+7
View File
@@ -82,6 +82,13 @@ const native: TranslationStrings = {
'native.lock.settingHint': '開啟 TREK 時以及在背景停留半分鐘後會要求驗證,並在 App 切換器中隱藏內容。',
'native.share.title': '要匯入到哪個行程?',
'native.share.noTrips': '還沒有行程。請先建立一個,然後再次分享該文件。',
'native.widget.dayOf': '第 {n} 天,共 {m} 天',
'native.widget.today': '今天',
'native.widget.noTrip': '還沒有計畫中的行程',
'native.widget.inDays': '{n} 天後',
'native.widget.tomorrow': '明天',
'native.widget.nothingPlanned': '今天沒有安排',
'native.widget.dayDone': '今天的行程已全部完成',
};
export default native;
+7
View File
@@ -82,6 +82,13 @@ const native: TranslationStrings = {
'native.lock.settingHint': '打开 TREK 时以及在后台停留半分钟后会要求验证,并在应用切换器中隐藏内容。',
'native.share.title': '导入到哪个行程?',
'native.share.noTrips': '还没有行程。请先创建一个,然后再次分享该文档。',
'native.widget.dayOf': '第 {n} 天,共 {m} 天',
'native.widget.today': '今天',
'native.widget.noTrip': '还没有计划中的行程',
'native.widget.inDays': '{n} 天后',
'native.widget.tomorrow': '明天',
'native.widget.nothingPlanned': '今天没有安排',
'native.widget.dayDone': '今天的行程已全部完成',
};
export default native;