-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[Odometer] Create NewDot Odometer expense flow #77576
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Odometer] Create NewDot Odometer expense flow #77576
Conversation
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
|
TODO:
|
|
Update: |
f93eaa6 to
581d8f1
Compare
|
Update: currently working on skipConfirmation logic - for some reason it changes to manual expense after 1 use Screen.Recording.2025-12-22.at.20.45.14.mov |
93c67a0 to
612f908
Compare
@jakubkalinski0 to confirm, do you mean the Quick action button? Could you confirm the issue. I see that is shows 'Track Distance', but you land on the Odometer page. |
|
Now only failing typecheck comes from the missing translations. I am unsure how the procedure of adding the missing translations looks now since it was a while since I had to add any. @Julesssss I think that now there is some kind of script that someone else has to trigger? Is that correct? I am not totally sure how does that look like now |
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index ab60aa4b..340a31a9 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1012,15 +1012,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'Abonnement',
domains: 'Domänen',
},
- tabSelector: {
- chat: 'Chat',
- room: 'Raum',
- distance: 'Entfernung',
- manual: 'Manuell',
- scan: 'Scannen',
- map: 'Karte',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'Chat', room: 'Raum', distance: 'Entfernung', manual: 'Manuell', scan: 'Scannen', map: 'Karte', gps: 'GPS', odometer: 'Kilometerzähler'},
spreadsheet: {
upload: 'Eine Tabellenkalkulation hochladen',
import: 'Tabellenkalkulation importieren',
@@ -1364,6 +1356,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'Satz für diesen Workspace ungültig. Bitte wählen Sie einen verfügbaren Satz aus dem Workspace aus.',
endDateBeforeStartDate: 'Das Enddatum darf nicht vor dem Startdatum liegen',
endDateSameAsStartDate: 'Das Enddatum darf nicht mit dem Startdatum identisch sein',
+ odometerReadingRequired: 'Bitte geben Sie sowohl Start- als auch Endstand ein',
+ negativeDistanceNotAllowed: 'Endstand muss größer als Startstand sein',
},
dismissReceiptError: 'Fehler ausblenden',
dismissReceiptErrorConfirmation: 'Achtung! Wenn du diesen Fehler verwirfst, wird dein hochgeladener Beleg vollständig entfernt. Bist du sicher?',
@@ -7208,6 +7202,14 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard
error: {
selectSuggestedAddress: 'Bitte wählen Sie eine vorgeschlagene Adresse aus oder verwenden Sie den aktuellen Standort',
},
+ odometer: {
+ startReading: 'Lesen starten',
+ endReading: 'Lesen beenden',
+ saveForLater: 'Für später speichern',
+ totalDistance: 'Gesamtstrecke',
+ readingRequired: 'Start- und Endzählerstände sind erforderlich',
+ negativeDistanceNotAllowed: 'Endstand muss größer als Startstand sein',
+ },
},
reportCardLostOrDamaged: {
screenTitle: 'Zeugnis verloren oder beschädigt',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index a9b10cb4..153f80b0 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1014,15 +1014,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'Abonnement',
domains: 'Domaines',
},
- tabSelector: {
- chat: 'Discussion',
- room: 'Salle',
- distance: 'Distance',
- manual: 'Manuel',
- scan: 'Scanner',
- map: 'Carte',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'Discussion', room: 'Salle', distance: 'Distance', manual: 'Manuel', scan: 'Scanner', map: 'Carte', gps: 'GPS', odometer: 'Compteur kilométrique'},
spreadsheet: {
upload: 'Téléverser une feuille de calcul',
import: 'Importer une feuille de calcul',
@@ -1365,6 +1357,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'Taux non valide pour cet espace de travail. Veuillez sélectionner un taux disponible dans l’espace de travail.',
endDateBeforeStartDate: 'La date de fin ne peut pas être antérieure à la date de début',
endDateSameAsStartDate: 'La date de fin ne peut pas être identique à la date de début',
+ odometerReadingRequired: 'Veuillez saisir les relevés de début et de fin',
+ negativeDistanceNotAllowed: 'Le relevé de fin doit être supérieur au relevé de début',
},
dismissReceiptError: 'Ignorer l’erreur',
dismissReceiptErrorConfirmation: 'Attention ! Ignorer cette erreur supprimera entièrement votre reçu téléchargé. Êtes-vous sûr ?',
@@ -7217,6 +7211,14 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin
error: {
selectSuggestedAddress: 'Veuillez sélectionner une adresse suggérée ou utiliser la position actuelle',
},
+ odometer: {
+ startReading: 'Commencer la lecture',
+ endReading: 'Terminer la lecture',
+ saveForLater: 'Enregistrer pour plus tard',
+ totalDistance: 'Distance totale',
+ readingRequired: 'Les relevés de début et de fin sont requis',
+ negativeDistanceNotAllowed: 'Le relevé de fin doit être supérieur au relevé de début',
+ },
},
reportCardLostOrDamaged: {
screenTitle: 'Bulletin perdu ou endommagé',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 0e56b341..a5678720 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1010,15 +1010,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'Abbonamento',
domains: 'Domini',
},
- tabSelector: {
- chat: 'Chat',
- room: 'Stanza',
- distance: 'Distanza',
- manual: 'Manuale',
- scan: 'Scannerizza',
- map: 'Mappa',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'Chat', room: 'Stanza', distance: 'Distanza', manual: 'Manuale', scan: 'Scannerizza', map: 'Mappa', gps: 'GPS', odometer: 'Contachilometri'},
spreadsheet: {
upload: 'Carica un foglio di calcolo',
import: 'Importa foglio di calcolo',
@@ -1359,6 +1351,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'Tariffa non valida per questo workspace. Seleziona una tariffa disponibile dal workspace.',
endDateBeforeStartDate: 'La data di fine non può essere precedente alla data di inizio',
endDateSameAsStartDate: 'La data di fine non può essere uguale alla data di inizio',
+ odometerReadingRequired: 'Inserisci sia la lettura iniziale che quella finale',
+ negativeDistanceNotAllowed: 'La lettura finale deve essere maggiore della lettura iniziale',
},
dismissReceiptError: 'Ignora errore',
dismissReceiptErrorConfirmation: 'Attenzione! Se ignori questo errore, la ricevuta caricata verrà rimossa completamente. Sei sicuro?',
@@ -7191,6 +7185,14 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori
error: {
selectSuggestedAddress: 'Seleziona un indirizzo suggerito o usa la posizione attuale',
},
+ odometer: {
+ startReading: 'Inizia a leggere',
+ endReading: 'Fine lettura',
+ saveForLater: 'Salva per dopo',
+ totalDistance: 'Distanza totale',
+ readingRequired: 'Sono necessarie le letture iniziali e finali',
+ negativeDistanceNotAllowed: 'La lettura finale deve essere maggiore della lettura iniziale',
+ },
},
reportCardLostOrDamaged: {
screenTitle: 'Pagella smarrita o danneggiata',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 4143febe..d5136ae6 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1010,15 +1010,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'サブスクリプション',
domains: 'ドメイン',
},
- tabSelector: {
- chat: 'チャット',
- room: '部屋',
- distance: '距離',
- manual: '手動',
- scan: 'スキャン',
- map: '地図',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'チャット', room: '部屋', distance: '距離', manual: '手動', scan: 'スキャン', map: '地図', gps: 'GPS', odometer: 'オドメーター'},
spreadsheet: {
upload: 'スプレッドシートをアップロード',
import: 'スプレッドシートをインポート',
@@ -1359,6 +1351,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'このワークスペースでは無効なレートです。ワークスペースから利用可能なレートを選択してください。',
endDateBeforeStartDate: '終了日は開始日より前にはできません',
endDateSameAsStartDate: '終了日は開始日と同じにはできません',
+ odometerReadingRequired: '開始時刻と終了時刻の両方を入力してください',
+ negativeDistanceNotAllowed: '終了値は開始値より大きくなければなりません',
},
dismissReceiptError: 'エラーを閉じる',
dismissReceiptErrorConfirmation: '注意!このエラーを無視すると、アップロードした領収書が完全に削除されます。本当に実行しますか?',
@@ -7135,6 +7129,14 @@ ${reportName}
error: {
selectSuggestedAddress: '候補の住所を選択するか、現在地を使用してください',
},
+ odometer: {
+ startReading: '読み始める',
+ endReading: '読み終える',
+ saveForLater: '後で保存',
+ totalDistance: '合計距離',
+ readingRequired: '開始値と終了値の入力が必要です',
+ negativeDistanceNotAllowed: '終了値は開始値より大きくなければなりません',
+ },
},
reportCardLostOrDamaged: {
screenTitle: '成績証明書の紛失または損傷',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index d281e100..6f0d3ab4 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1010,15 +1010,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'Abonnement',
domains: 'Domeinen',
},
- tabSelector: {
- chat: 'Chat',
- room: 'Kamer',
- distance: 'Afstand',
- manual: 'Handmatig',
- scan: 'Scannen',
- map: 'Kaart',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'Chat', room: 'Kamer', distance: 'Afstand', manual: 'Handmatig', scan: 'Scannen', map: 'Kaart', gps: 'GPS', odometer: 'Kilometerteller'},
spreadsheet: {
upload: 'Een spreadsheet uploaden',
import: 'Spreadsheet importeren',
@@ -1358,6 +1350,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'Tarief is niet geldig voor deze workspace. Selecteer een beschikbaar tarief uit de workspace.',
endDateBeforeStartDate: 'De einddatum kan niet vóór de startdatum liggen',
endDateSameAsStartDate: 'De einddatum mag niet hetzelfde zijn als de startdatum',
+ odometerReadingRequired: 'Voer zowel de begin- als eindstand in',
+ negativeDistanceNotAllowed: 'Eindstand moet groter zijn dan beginstand',
},
dismissReceiptError: 'Foutmelding sluiten',
dismissReceiptErrorConfirmation: 'Let op! Als je deze foutmelding negeert, wordt je geüploade bon volledig verwijderd. Weet je het zeker?',
@@ -7178,6 +7172,14 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten
error: {
selectSuggestedAddress: 'Selecteer een voorgesteld adres of gebruik huidige locatie',
},
+ odometer: {
+ startReading: 'Begin met lezen',
+ endReading: 'Lezen beëindigen',
+ saveForLater: 'Later opslaan',
+ totalDistance: 'Totale afstand',
+ readingRequired: 'Begin- en eindstanden zijn vereist',
+ negativeDistanceNotAllowed: 'Eindstand moet groter zijn dan beginstand',
+ },
},
reportCardLostOrDamaged: {
screenTitle: 'Rapportkaart kwijt of beschadigd',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 031c5946..440aebef 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1010,15 +1010,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'Subskrypcja',
domains: 'Domeny',
},
- tabSelector: {
- chat: 'Czat',
- room: 'Pokój',
- distance: 'Dystans',
- manual: 'Ręczny',
- scan: 'Skanuj',
- map: 'Mapa',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'Czat', room: 'Pokój', distance: 'Dystans', manual: 'Ręczny', scan: 'Skanuj', map: 'Mapa', gps: 'GPS', odometer: 'Licznik przebiegu'},
spreadsheet: {
upload: 'Prześlij arkusz kalkulacyjny',
import: 'Importuj arkusz kalkulacyjny',
@@ -1356,6 +1348,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'Stawka nie jest prawidłowa dla tego przestrzeni roboczej. Wybierz dostępną stawkę z tej przestrzeni roboczej.',
endDateBeforeStartDate: 'Data zakończenia nie może być wcześniejsza niż data rozpoczęcia',
endDateSameAsStartDate: 'Data zakończenia nie może być taka sama jak data rozpoczęcia',
+ odometerReadingRequired: 'Wprowadź zarówno odczyt początkowy, jak i końcowy',
+ negativeDistanceNotAllowed: 'Odczyt końcowy musi być większy niż odczyt początkowy',
},
dismissReceiptError: 'Odrzuć błąd',
dismissReceiptErrorConfirmation: 'Uwaga! Odrzucenie tego błędu spowoduje całkowite usunięcie przesłanego paragonu. Czy na pewno chcesz kontynuować?',
@@ -7167,6 +7161,14 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
error: {
selectSuggestedAddress: 'Wybierz sugerowany adres lub użyj bieżącej lokalizacji',
},
+ odometer: {
+ startReading: 'Rozpocznij czytanie',
+ endReading: 'Zakończ czytanie',
+ saveForLater: 'Zapisz na później',
+ totalDistance: 'Całkowity dystans',
+ readingRequired: 'Wymagane są odczyty początkowe i końcowe',
+ negativeDistanceNotAllowed: 'Odczyt końcowy musi być większy niż odczyt początkowy',
+ },
},
reportCardLostOrDamaged: {
screenTitle: 'Świadectwo zgubione lub uszkodzone',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 747e178b..e02419bb 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1009,15 +1009,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: 'Assinatura',
domains: 'Domínios',
},
- tabSelector: {
- chat: 'Chat',
- room: 'Sala',
- distance: 'Distância',
- manual: 'Manual',
- scan: 'Escanear',
- map: 'Mapa',
- gps: 'GPS',
- },
+ tabSelector: {chat: 'Chat', room: 'Sala', distance: 'Distância', manual: 'Manual', scan: 'Escanear', map: 'Mapa', gps: 'GPS', odometer: 'Hodômetro'},
spreadsheet: {
upload: 'Enviar uma planilha',
import: 'Importar planilha',
@@ -1356,6 +1348,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: 'Taxa inválida para este workspace. Selecione uma taxa disponível do workspace.',
endDateBeforeStartDate: 'A data de término não pode ser anterior à data de início',
endDateSameAsStartDate: 'A data de término não pode ser igual à data de início',
+ odometerReadingRequired: 'Insira as leituras de início e de fim',
+ negativeDistanceNotAllowed: 'A leitura final deve ser maior que a leitura inicial',
},
dismissReceiptError: 'Dispensar erro',
dismissReceiptErrorConfirmation: 'Atenção! Ignorar este erro removerá completamente o seu recibo enviado. Tem certeza?',
@@ -7171,6 +7165,14 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe
error: {
selectSuggestedAddress: 'Selecione um endereço sugerido ou use a localização atual',
},
+ odometer: {
+ startReading: 'Começar a ler',
+ endReading: 'Finalizar leitura',
+ saveForLater: 'Salvar para depois',
+ totalDistance: 'Distância total',
+ readingRequired: 'Leituras inicial e final são obrigatórias',
+ negativeDistanceNotAllowed: 'A leitura final deve ser maior que a leitura inicial',
+ },
},
reportCardLostOrDamaged: {
screenTitle: 'Boletim perdido ou danificado',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index d0de96c6..727ed2fb 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -998,15 +998,7 @@ const translations: TranslationDeepObject<typeof en> = {
subscription: '订阅',
domains: '域名',
},
- tabSelector: {
- chat: '聊天',
- room: '房间',
- distance: '距离',
- manual: '手动',
- scan: '扫描',
- map: '地图',
- gps: 'GPS',
- },
+ tabSelector: {chat: '聊天', room: '房间', distance: '距离', manual: '手动', scan: '扫描', map: '地图', gps: 'GPS', odometer: '里程表'},
spreadsheet: {
upload: '上传电子表格',
import: '导入电子表格',
@@ -1336,6 +1328,8 @@ const translations: TranslationDeepObject<typeof en> = {
invalidRate: '此汇率对该工作区无效。请选择此工作区中的可用汇率。',
endDateBeforeStartDate: '结束日期不能早于开始日期',
endDateSameAsStartDate: '结束日期不能与开始日期相同',
+ odometerReadingRequired: '请输入起始读数和结束读数',
+ negativeDistanceNotAllowed: '结束读数必须大于开始读数',
},
dismissReceiptError: '忽略错误',
dismissReceiptErrorConfirmation: '提醒!关闭此错误会完全删除你上传的收据。确定要继续吗?',
@@ -7027,6 +7021,14 @@ ${reportName}
error: {
selectSuggestedAddress: '请选择一个建议地址或使用当前位置',
},
+ odometer: {
+ startReading: '开始阅读',
+ endReading: '结束阅读',
+ saveForLater: '稍后保存',
+ totalDistance: '总距离',
+ readingRequired: '必须提供起始读数和结束读数',
+ negativeDistanceNotAllowed: '结束读数必须大于开始读数',
+ },
},
reportCardLostOrDamaged: {
screenTitle: '成绩单遗失或损坏',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
Testing well for me locally so far 🎉 Ready for another review I think @DylanDylann Known issues:
|
|
Got it |
I removed your workaround and verified this fix. I didn't commit as that would prevent me from being able to final review. We can fix this in the new year quite easily 👍
|
|
@Julesssss @jakubkalinski0 Should we add a feature flag? |
| const reportName = (() => { | ||
| const name = computeReportName(selectedReport, allReports, allPolicies, undefined, reportNameValuePairs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to make this refactor here? Given the large scope of this PR, we should focus on the feature implementation. It would be better to move this into a separate PR so we can test it more thoroughly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reversed this on the new PR. @jakubkalinski0 when you're back please let us know if this was necessary for some reason!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be completely honest I don't remember the exact reason for this change 😅 I think there was some edge case that didn't work properly but if everything works correctly now then we are fine I guess
| if (!isEnabled) { | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you provide more detail on why we should make this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like maybe the hook should only be active when editing from confirmation page... I'll leave this change.
Then @jakubkalinski0 please take a look and share your thoughts when you are back. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats exactly the case. We agreed that we don't want DiscardChangesConfirmation to be shown when we are editing from confirmation page and thus change to this hook was needed
|
The edit flow doesn't work. When update the start and end point, we should save it locally and only call update API in the detail confirmation page Screen.Recording.2026-01-06.at.14.45.13.mov |
|
Could you also check the optimistic data when creating an odometer expense? When creating it offline, the merchant field is shown instead of the distance field, and there’s an error with the tax field (it’s inconsistent with the BE response) Screen.Recording.2026-01-06.at.14.51.47.mov |
@DylanDylann I don't think so. For the expense edits we have a unique API for each of the fields. For example, when updating expense description:
However, we should not show this modal. I applied a fix so we only show discard when CREATING the odometer expense. It's less dangerous when editing the distance post-creation |
I'm going to look into this as a separate issue here. |
b89dd96




Explanation of Change
This PR adds an Odometer option to the distance request flow allowing user to add odometer readings at the beginning and end of the trip. This explanation and implementation is mapped according to docs (only Phase 1; selected items from 1.A–1.J). Some code fragments are added in the explanation below for clarity. This PR introduces end-to-end handling of this feature allowing to create an actual
distance-odometerexpense. This PR only allows to add odometer readings without capturing and merging images or the 'Save for later' functionality.distance-odometertab and odometer step route.TabSelectoruses the meter icon, andDistanceRequestStartPagerenders the odometer screen:end > startbefore navigation:DiscardChangesConfirmationto block accidental loss of unsaved odometer inputs when leaving the screen:Fixed Issues
$ #77191
PROPOSAL: N/A
Tests
Odometer Distance Tests
Global Create -> Track distance -> Odometer tab
Missing fields validation and End < Start validation
Correct input 100 -> 160
Back/unsaved changes - Discard Changes modal
Tab switching state
Edit on confirmation (test errors, increase distance, test leave without saving)
Existing report (non-global create)
Skip-confirmation path
Default expense policy
Offline tests
Disconnect internet from your PC or force offline through troubleshoot
Everything else should work same as described in Tests
QA Steps
Basically same as Tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodhttps://swmansion.slack.com/archives/C01GTK53T8Q/p1766498048600409
STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
1to4.mov
5to6.mov
7to8.mov