Compare commits

..

225 Commits

Author SHA1 Message Date
David Perez
b5b022caaa Update to latest Bitwarden SDK (#5405) 2025-06-23 12:10:30 -05:00
David Perez
8a371f8c21 🍒 PM-22846: Fix Events service domain (#5397) 2025-06-20 15:03:20 +00:00
aj-rosado
469df4495a [PM-22568] Change totp seed field to a password field (#5350) 2025-06-13 16:32:12 +00:00
David Perez
053dfc1647 PM-22643: Do not clear error dialogs when updating TOTP data (#5361) 2025-06-13 16:25:39 +00:00
Patrick Honkonen
7de770ca03 [PM-22441] Refactor DigitalAssetLinkService to use source website (#5351) 2025-06-13 16:03:27 +00:00
David Perez
861a4281fa PM-22642, PM-22644: Add MP reprompt for TOTP code and secure note (#5359) 2025-06-13 16:01:58 +00:00
Patrick Honkonen
265014fd64 [PM-22665] Add BitwardenPackageManager abstraction (#5360) 2025-06-13 16:01:56 +00:00
bw-ghapp[bot]
5d32fe9caf Crowdin Pull - Password Manager (#5356)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-06-13 13:53:17 +00:00
bw-ghapp[bot]
44ba0f548a Crowdin Pull - Authenticator (#5355)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-06-13 13:52:33 +00:00
David Perez
694443f2e1 Password field tooltip support (#5354) 2025-06-12 21:56:05 +00:00
David Perez
0ade60025c PM-22634: Fix parsing of system language (#5353) 2025-06-12 20:47:12 +00:00
David Perez
5adccca823 PM-22477: Update the timestamp format for ciphers (#5352) 2025-06-12 18:49:20 +00:00
David Perez
3c86bb425b Add tests for the EditItemScreen and EditItemViewModel (#5348) 2025-06-12 14:30:00 +00:00
Patrick Honkonen
3474e0b608 [PM-22461] Add About privileged apps screen (#5335) 2025-06-12 14:19:19 +00:00
Patrick Honkonen
0f2476bebf Update BitwardenContentBlock divider padding logic (#5346) 2025-06-11 19:44:41 +00:00
Patrick Honkonen
edffb8dd6f Add tooltip to BitwardenTextRow (#5344) 2025-06-11 17:36:15 +00:00
David Perez
2dc6c170f5 PM-22551: Update toasts to snackbars for Sends (#5339) 2025-06-11 16:33:59 +00:00
David Perez
2a8a16ab3f BWA-160: Modernize QrCodeScanScreen (#5342) 2025-06-10 19:04:39 +00:00
Patrick Honkonen
76995a28ad [deps] Update googleProtoBufJava to 4.31.1 (#5343) 2025-06-10 19:02:55 +00:00
David Perez
7e146800a8 PM-22522: Update time picker language (#5338) 2025-06-10 19:02:31 +00:00
Patrick Honkonen
7a2f1c294f [deps] Update sonarqube plugin (#5307) 2025-06-10 18:22:15 +00:00
David Perez
44d4926300 Remove unused dialogs (#5337) 2025-06-10 15:49:15 +00:00
Álison Fernandes
e4c160d1e0 [PM-22437] Add product release notes to GitHub Releases (#5318) 2025-06-09 20:46:25 +00:00
Álison Fernandes
0f9f9d9dce [PM-22389] GitHub Release workflow supports releasing BWPM and BWA (#5312) 2025-06-09 19:40:34 +00:00
David Perez
a0c2600517 PM-10286: VerificationCodeScreen should not show MP reprompt if there is no master password (#5336) 2025-06-09 19:32:02 +00:00
Patrick Honkonen
c60df56648 [PM-21458] Add UserManagedPrivilegedApps feature flag (#5325) 2025-06-09 19:04:38 +00:00
David Perez
9cdfe0c5d6 PM-22502: Format dates and times correctly for locale (#5333) 2025-06-09 18:30:30 +00:00
renovate[bot]
d822be62e1 [deps]: Lock file maintenance (#5331)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 16:17:25 +00:00
Matt Andreko
7adbfdcc84 Fix permissions for check-run action (#5316) 2025-06-09 13:34:28 +00:00
David Perez
beb4c533c8 Update the SnackbarRelayManager (#5317) 2025-06-06 18:28:08 +00:00
Patrick Honkonen
e1cd813445 [PM-19107] Introduce user-trusted privileged apps for Credential Manager (#4848) 2025-06-06 17:51:06 +00:00
David Perez
f769900976 PM-22456: Move Temporal Accessor Extensions to 'Core' module (#5324) 2025-06-06 17:29:02 +00:00
David Perez
a0ff94195f Update Junit to v5.13.0 (#5323) 2025-06-06 16:31:05 +00:00
bw-ghapp[bot]
9853f137d2 Crowdin Pull - Authenticator (#5319)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-06-06 14:07:19 +00:00
bw-ghapp[bot]
b591534bd9 Crowdin Pull - Password Manager (#5320)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-06-06 14:06:57 +00:00
David Perez
d2c329264c PM-22397: Remove custom deletion date (#5311) 2025-06-05 19:20:12 +00:00
David Perez
a9791c3f9f PM-22402: Update File Send error message (#5313) 2025-06-05 18:39:28 +00:00
David Perez
a59eaf5d40 PM-22362: AddSendScreen should include 'Required.' when describing the max file size (#5310) 2025-06-04 13:43:10 +00:00
David Perez
d2129cf507 PM-22357: Delete Send button should use a capital S (#5309) 2025-06-03 21:03:17 +00:00
Patrick Honkonen
903c260ad1 [PM-21891] Migrate filled and outlined button components to ui module (#5302) 2025-06-03 20:09:13 +00:00
Patrick Honkonen
09a8c01824 [deps] Update Google guava library (#5305) 2025-06-03 20:03:37 +00:00
David Perez
a1a4c217de PM-22346: Remove the period from the generic error title (#5308) 2025-06-03 18:40:45 +00:00
David Perez
7fbe3510b5 PM-22345: Flight recorder banner should not dismiss when navigating to settings (#5306) 2025-06-03 16:58:48 +00:00
Patrick Honkonen
0892e0ff1f [PM-21782] Pass encryptedFor to cipher functions (#5297) 2025-06-03 16:34:46 +00:00
Patrick Honkonen
0934d47159 [deps] Update protobuf (#5304) 2025-06-03 16:33:43 +00:00
David Perez
caf1c2eed5 PM-22265: Add Copy Notes button to ViewSendScreen (#5303) 2025-06-03 16:32:34 +00:00
David Perez
803d519c24 Update AGP to 8.10.1 (#5301) 2025-06-03 15:00:36 +00:00
David Perez
a3d2e51c8e PM-22310: Replace Ok with Okay (#5298) 2025-06-02 22:54:47 +00:00
David Perez
891def5e32 PM-22302: Remove unused string resources (#5296) 2025-06-02 19:56:16 +00:00
Álison Fernandes
00ded69a84 [QA-1126] Add placeholder workflow for device farm testing (#5292) 2025-06-02 12:39:16 +00:00
ifernandezdiaz
c5f597aedb [QA-1164] Adding missing testTags for View Send page (#5290) 2025-05-30 21:12:09 +00:00
David Perez
f43367ebfa PM-10286: View Master Password Prompt (#5280) 2025-05-30 21:04:59 +00:00
Patrick Honkonen
997769bb1c [PM-21475] Remove deprecated SSO details endpoint feature flag (#5286) 2025-05-30 16:08:04 +00:00
David Perez
65d1a4f12a BWA-159: Update the ManualCodeEntryScreen to allow scrolling (#5287) 2025-05-30 13:58:23 +00:00
bw-ghapp[bot]
f7c1278805 Crowdin Pull - Authenticator (#5288)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-05-30 13:32:48 +00:00
bw-ghapp[bot]
aa3602a5ce Crowdin Pull - Password Manager (#5289)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-05-30 13:30:29 +00:00
André Bispo
af18848159 [PM-20146] Remove native-create-account-flow feature flag (#5283) 2025-05-29 21:42:16 +00:00
David Perez
f3b7d0f732 PM-21631: Check for Search Screen when navigating after deleting a Send (#5284) 2025-05-29 21:29:56 +00:00
David Perez
e250a8dc1e BWA-158: Authenticator Edit Item should use a single LazyColum to allow for scrolling (#5285) 2025-05-29 21:12:15 +00:00
David Perez
ab2ac60957 PM-15229: Update logic for handling edge-to-edge (#5282) 2025-05-29 18:25:23 +00:00
Patrick Honkonen
b877487ce1 [PM-22169] Migrate app bar components to ui module (#5279) 2025-05-29 14:54:49 +00:00
David Perez
ef68879778 Clean up lint errors and suppressions (#5281) 2025-05-29 14:52:15 +00:00
André Bispo
a4e4d1488b [PM-21577] Fix delete button not showing bug (#5276) 2025-05-29 14:16:05 +00:00
David Perez
cf8578f3ef PM-21135: Fix view send field order (#5277) 2025-05-28 18:14:26 +00:00
David Perez
bdd0660e2b PM-21134: Fix send link title (#5275) 2025-05-28 15:27:28 +00:00
David Perez
294ef674bc PM-13040: Add known username field for the Disney Plus App (#5271) 2025-05-27 21:49:57 +00:00
David Perez
b9a897a9fc Update Firebase BOM to v33.14.0 (#5272) 2025-05-27 21:49:17 +00:00
Patrick Honkonen
6b12b9757f [PM-17686] Correct body text for "Replace existing certificate" dialog (#5270) 2025-05-27 21:28:12 +00:00
David Perez
61411ca73c Update Compose BOM to 2025.05.01 (#5269) 2025-05-27 16:16:50 +00:00
David Perez
3908827a14 PM-17660: Improve the way we remember the annotated string (#5257) 2025-05-27 15:21:19 +00:00
renovate[bot]
e553d7a015 [deps]: Lock file maintenance (#5267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 14:54:55 +00:00
renovate[bot]
3015b768c6 [deps]: Update org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.4.0 (#5265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 14:40:24 +00:00
David Perez
21b8ef92ba PM-21952: Move navigation package to UI module (#5260) 2025-05-27 13:48:07 +00:00
David Perez
0e3e6069fa Update Retrofit BOM to 3.0.0 (#5258) 2025-05-27 13:46:55 +00:00
David Perez
8d8dee5171 PM-21916: Move the FAB to the UI module (#5251) 2025-05-27 13:46:38 +00:00
Michał Chęciński
97b6bccd72 Add stub for publishing releases workflow (#5268) 2025-05-27 13:26:01 +00:00
Álison Fernandes
2a6813e4a2 [PM-21336] CI restructuring #1 - Consolidate Crowdin pull and push workflows (#5253) 2025-05-26 14:21:16 +00:00
aj-rosado
29e7899525 [PM-21537] Fix remove individual vault collection selection (#5262) 2025-05-23 19:41:00 +00:00
Amy Galles
4cd603006f [BRE-768] Creating stub workflow for testing (#5259) 2025-05-23 18:37:25 +00:00
bw-ghapp[bot]
1a091e198c Autosync Crowdin Translations (#5252)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-05-23 16:05:23 +00:00
David Perez
3551e75596 PM-16705: Improve the node validation logic (#5250) 2025-05-22 22:10:46 +00:00
André Bispo
6d976bea4c [PM-21577] Handle organization limitItemDeletion from sync response. (#5244) 2025-05-22 22:09:17 +00:00
Patrick Honkonen
d5c04123d9 [PM-21888] Migrate icon buttons to ui module (#5241) 2025-05-22 20:31:12 +00:00
David Perez
000a7d141e PM-17660: Add additional context for the sync feature (#5243) 2025-05-22 17:55:50 +00:00
Patrick Honkonen
c9d4d35f07 [PM-21851] Use rememberVectorPainter from platform UI in Authenticator (#5240) 2025-05-21 20:58:08 +00:00
Patrick Honkonen
4216f3f5a0 Rename all java source dirs to kotlin (#5239) 2025-05-21 19:14:53 +00:00
David Perez
c14545107d PM-21879: Move SpanStyleUtil and StringResExtensions to UI module (#5238) 2025-05-21 17:23:04 +00:00
Patrick Honkonen
d1a8cbf59f Bump authenticatorbridge to 1.0.1 (#5230) 2025-05-21 16:37:41 +00:00
Patrick Honkonen
1acc1a87a6 [PM-21851] Migrate RememberVectorPainter to ui module (#5233) 2025-05-21 16:17:44 +00:00
André Bispo
fd73360539 [PM-21405] Delete account error message (#5237) 2025-05-21 15:34:42 +00:00
Patrick Honkonen
178625222a Update target and compile SDK to 36 (#5229) 2025-05-21 15:01:59 +00:00
Patrick Honkonen
3ea17eb71c [PM-21849] Rename ui module source dir to kotlin (#5232) 2025-05-21 14:18:11 +00:00
Álison Fernandes
6ccb035ffd [PM-21825] Set missing workflow permissions (#5235) 2025-05-21 13:17:01 +00:00
Patrick Honkonen
5c3008d080 [PM-21385] Use flatMapLatest for accountSyncStateFlow (#5231) 2025-05-20 20:37:36 +00:00
Patrick Honkonen
54efc74907 [PM-21385] Defer feature flag check for Bitwarden account sync (#5222) 2025-05-20 18:09:15 +00:00
David Perez
34aed2ac65 Update authenticator compose tests to allow for easier use of local compositions (#5228) 2025-05-20 17:01:23 +00:00
Patrick Honkonen
3d152f5c36 Bump Kotlin to 2.1.21 (#5227) 2025-05-20 15:42:10 +00:00
Patrick Honkonen
4c8e5602dd [PM-21354] Migrate ColorExtensions and its tests to ui module (#5216) 2025-05-20 14:59:13 +00:00
Vince Grassia
6e44ee2eb0 [BRE-552] Fix Actionlint findings (#5223) 2025-05-20 14:02:52 +00:00
David Perez
4895f2a18a Update test fixtures to allow for easier customization (#5224) 2025-05-19 21:27:21 +00:00
David Perez
fc4f02c4d5 Rename AddSend UI to AddEditSend (#5221) 2025-05-19 21:26:31 +00:00
Patrick Honkonen
6719534494 [PM-21357] Migrate ModifierExtensions to ui module (#5215) 2025-05-19 20:02:52 +00:00
Patrick Honkonen
183584f678 [PM-21386] Fix typo in sync with Bitwarden message (#5220) 2025-05-19 17:26:09 +00:00
David Perez
046bb0fa39 PM-21080: Remove the isRemotelyConfigured flag (#5193) 2025-05-19 15:56:19 +00:00
David Perez
9508b4ba90 PM-21701: Remove segmented control from Add Send Screen and update the screen title (#5217) 2025-05-19 14:48:26 +00:00
Patrick Honkonen
07e4e6a806 [PM-21726] Move OmitFromCoverage to annotation module (#5214) 2025-05-19 13:19:16 +00:00
David Perez
4d142a6a5c PM-21133: Add View Send navigation (#5187) 2025-05-16 18:48:22 +00:00
Patrick Honkonen
f02a3a249b [PM-21703] Consolidate Robolectric and Compose test base classes (#5210) 2025-05-16 17:48:23 +00:00
David Perez
28149532a0 PM-21707: Allow nullable captcha token (#5213) 2025-05-16 16:58:12 +00:00
David Perez
7f5426dea0 PM-19770: Fix the verify email domains (#5212) 2025-05-16 15:49:13 +00:00
Patrick Honkonen
d7d703c977 [PM-21692] Move WindowSize and related util to ui module (#5208) 2025-05-16 15:38:45 +00:00
bw-ghapp[bot]
7fda5d799f Autosync Crowdin Translations (#5211)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-05-16 13:42:32 +00:00
Patrick Honkonen
3c1a0a352a [PM-21356] Migrate ui ListExtensions to ui module (#5200) 2025-05-15 21:45:50 +00:00
David Perez
cb0b135429 PM-21696: Make sure environment is up-to-date (#5209) 2025-05-15 21:44:44 +00:00
Patrick Honkonen
7422efd07a [PM-21366] Migrate BitwardenTheme to ui module (#5207) 2025-05-15 19:52:34 +00:00
Patrick Honkonen
27a9fc52b7 [PM-21657] Migrate Typography to the ui module (#5198) 2025-05-15 15:55:51 +00:00
Patrick Honkonen
c105c102a3 [PM-21359] Migrate StringExtensions to ui module (#5202) 2025-05-15 15:51:47 +00:00
Patrick Honkonen
c83bd8f4a8 [PM-21361] Delete unused ToastUtils (#5203) 2025-05-15 14:20:17 +00:00
Patrick Honkonen
d820b3345a [PM-21676] Relocate Authenticator local manager providers (#5206) 2025-05-15 14:09:39 +00:00
Patrick Honkonen
b71b01d48d [PM-21655] Migrate BitwardenShapes to ui module (#5197) 2025-05-15 13:47:05 +00:00
Patrick Honkonen
8a0f67c0e9 [PM-21361] Migrate TopAppBarScrollBehaviorExtensions to ui module (#5204) 2025-05-15 13:45:01 +00:00
Patrick Honkonen
b4d85e07ba [PM-21358] Migrate PaddingValuesExtensions.kt to ui module (#5201) 2025-05-15 13:40:09 +00:00
David Perez
dfd58822b7 PM-21445: Update the Send delete buttons (#5195) 2025-05-14 21:45:59 +00:00
Patrick Honkonen
f14a1404e3 [PM-21654] Migrate ColorScheme to ui module (#5196) 2025-05-14 21:11:28 +00:00
David Perez
f1950600a1 PM-21641: Allow delete and restore logic to be remotely configured (#5194) 2025-05-14 19:23:21 +00:00
Patrick Honkonen
7d6b6a5959 [PM-21575] Migrate AppTheme enum class to ui module (#5182) 2025-05-14 17:53:02 +00:00
David Perez
ea70191429 PM-21631: Update Edit Send Screen to navigate to Vault Unlocked root (#5190) 2025-05-14 17:51:56 +00:00
David Perez
db956b9b91 PM-21634: Update loading Dialog to be a real dialog (#5191) 2025-05-14 17:51:06 +00:00
David Perez
119812507a Remove logging from tests (#5192) 2025-05-14 17:50:55 +00:00
Patrick Honkonen
a97c962428 [DynamicColors] Update toggle button switch dynamic color scheme (#4886) 2025-05-14 14:17:33 +00:00
David Perez
456adf3158 PM-21610: Update SearchScreen and VaultItemListingScreen for better Sends support (#5188) 2025-05-14 13:57:29 +00:00
Patrick Honkonen
62cb962298 [DynamicColors] Add support for dynamic colors (#4850) 2025-05-13 21:59:44 +00:00
Patrick Honkonen
7f4e65d7e4 [PM-21567] Implement CredentialEntryBuilder interface (#5177) 2025-05-13 21:18:16 +00:00
David Perez
860a2e265f PM-21134, PM-21135, PM-21136, PM-21137: Create View Send Screen (#5178) 2025-05-13 18:47:43 +00:00
David Perez
6d68c3ae24 PM-21591: Add navigation routing for the ViewSendScreen (#5185) 2025-05-13 17:28:10 +00:00
David Perez
97b8c51ab3 PM-21598: Update multi-tonal illustrations and icons to support dynamic colors (#5186) 2025-05-13 17:01:47 +00:00
Patrick Honkonen
a2449a2f19 [PM-21574] Migrate CardStyle to the UI module (#5181) 2025-05-13 17:00:43 +00:00
Patrick Honkonen
1d73bbd440 [PM-21585] Display item folder location when only in a single folder (#5184) 2025-05-13 15:13:32 +00:00
Patrick Honkonen
da62244000 [PM-21573] Migrate EventsEffect to ui module (#5180) 2025-05-13 14:13:57 +00:00
Patrick Honkonen
11b767c98f [PM-21572] Migrate NoPersonalizedLearningInterceptor to ui module (#5179) 2025-05-12 21:48:50 +00:00
David Perez
cd4db467e3 Clean up lint warnings (#5176) 2025-05-12 17:52:56 +00:00
David Perez
7fdf165273 PM-21555: Fix crash on older server versions (#5174) 2025-05-12 16:21:55 +00:00
renovate[bot]
e49bab637c [deps]: Lock file maintenance (#5171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 13:37:51 +00:00
renovate[bot]
14ac194cb7 [deps]: Update com.google.devtools.ksp to v2.1.20-2.0.1 (#5170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 13:37:40 +00:00
aj-bw
578f96a944 BRE-609/android-pr-target-change (#5092) 2025-05-12 13:13:24 +00:00
David Perez
2c71ab7d27 PM-21445: Update Add Edit Sends UI (#5166) 2025-05-09 21:30:28 +00:00
David Perez
c5ee389231 PM-21351: Clear scemantics on new send button (#5165) 2025-05-09 20:51:14 +00:00
Patrick Honkonen
5037af07c7 [PM-21367] Support passkey requests with multiple options (#5161) 2025-05-09 18:23:59 +00:00
bw-ghapp[bot]
d6d1e8e97f Autosync Crowdin Translations (#5164)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-05-09 13:24:17 +00:00
David Perez
652168f946 PM-21397: Create initial View Send scaffold (#5163) 2025-05-08 22:55:07 +00:00
Patrick Honkonen
d4d5d2c2a8 [PM-21355] Migrate LifecycleEventEffect to ui module (#5162) 2025-05-08 22:06:59 +00:00
David Perez
ed148c2089 PM-21252: Create mock NavHostController for navigation testing (#5159) 2025-05-08 19:58:03 +00:00
Patrick Honkonen
564304616d [PM-21365] Migrate BitwardenColorScheme to ui module (#5158) 2025-05-08 19:48:51 +00:00
Patrick Honkonen
0d0b8d6780 [PM-21353] Migrate DensityExtensions to ui module (#5157) 2025-05-08 19:32:04 +00:00
David Perez
472e41f6bc PM-21351: Hide new send button from accessibility when on the empty sends screen (#5160) 2025-05-08 18:52:24 +00:00
Patrick Honkonen
fb9c68755a [PM-21328] Migrate BaseViewModelTest and MainDispatcherExtension to test fixtures (#5146) 2025-05-08 16:54:55 +00:00
David Perez
d7671f47ea PM-21348: Type-safe navigation for authenticator (#5156) 2025-05-08 16:31:10 +00:00
Patrick Honkonen
9c7270df69 [PM-21344] Migrate BackgroundEvent to ui module (#5155) 2025-05-08 16:08:07 +00:00
David Perez
733290569c Update Lifecycle library to v2.9.0 (#5150) 2025-05-08 15:21:34 +00:00
David Perez
cbaa8a329e Fix duplicated launched effect key (#5154) 2025-05-08 14:27:22 +00:00
David Perez
f968d7698a PM-21332: Move NavGraphBuilder extensions to common UI module (#5147) 2025-05-08 14:15:42 +00:00
Patrick Honkonen
68cd08b069 [PM-21325] Migrate BaseViewModel to ui module (#5145) 2025-05-08 14:06:16 +00:00
David Perez
84683894a6 Update AGP to 8.10.0 (#5152) 2025-05-08 13:44:09 +00:00
David Perez
ed2d9ecb80 Update Dagger Hilt to v2.56.2 (#5151) 2025-05-08 13:43:05 +00:00
David Perez
8f4d46954e Update the navigation component to v2.9.0 (#5149) 2025-05-08 13:40:13 +00:00
David Perez
a9fc6ff589 Update compose BOM to 2025.05.00 (#5148) 2025-05-08 13:39:42 +00:00
André Bispo
5c8f5670e4 [PM-21203] Old user migration login error. (#5136) 2025-05-07 21:25:32 +00:00
David Perez
eec88d4924 PM-21324: Move common UI transitions to UI module (#5144) 2025-05-07 21:14:47 +00:00
Patrick Honkonen
82da193e55 [PM-21199] Rename FIDO2 objects to reference CredentialManager (#5128) 2025-05-07 20:25:38 +00:00
David Perez
76fb85ac1f Update Compose BOM to 2025.04.01 (#5134) 2025-05-07 19:16:32 +00:00
Patrick Honkonen
625ac0ea5f Update mockk to version 1.14.2 (#5139) 2025-05-07 18:14:01 +00:00
David Perez
4e88833737 Clean up how we handle test coverage on navigation files (#5142) 2025-05-07 17:53:07 +00:00
David Perez
ecea2ef7c1 PM-21285: Ensure route data is serializable (#5141) 2025-05-07 16:53:14 +00:00
Patrick Honkonen
0eccc7197e Update Protocol Buffers library version (#5140) 2025-05-07 15:36:06 +00:00
David Perez
5dd34afe81 PM-19771: Allow forward slashes in emails (#5137) 2025-05-06 22:12:48 +00:00
Patrick Honkonen
5abcc5b1f7 [PM-17222] Enhance autofill accessibility processor (#5116) 2025-05-06 21:44:06 +00:00
David Perez
6fec95cb84 PM-21255: Implement type-safe navigation (#5131) 2025-05-06 20:46:53 +00:00
David Perez
1d68c1fdf6 Update Firebase BOM to v33.13.0 (#5135) 2025-05-06 20:22:23 +00:00
David Perez
0c2de427dc Update WorkManager to 2.10.1 (#5132) 2025-05-06 20:13:28 +00:00
David Perez
f932682949 PM-21110: Add a generate crash button to the debug menu (#5125) 2025-05-06 18:51:03 +00:00
David Perez
e1f432ea5d Update the Navigation component library (#5130) 2025-05-06 14:47:34 +00:00
Patrick Honkonen
31de7fc331 Remove unused FeatureFlagsConfiguration (#5129) 2025-05-06 00:56:18 +00:00
David Perez
07469672ba PM-21156: Fix ConfigService retrofit instance (#5126) 2025-05-05 14:51:47 +00:00
André Bispo
1a2beea770 [PM-18092] Update cipher delete restore permissions (#5075)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-05-05 13:56:58 +00:00
aj-rosado
639ca02739 [PM-14222] Managed user account deletion prevention (#5114)
Co-authored-by: Matt Portune <mportune@macbook-work.lan>
2025-05-02 20:22:58 +00:00
Patrick Honkonen
186bea2d1d [PM-20127] Prevent double UV prompt during FIDO 2 operations (#5124) 2025-05-02 14:52:14 +00:00
bw-ghapp[bot]
3dc187da87 Autosync Crowdin Translations (#5122)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-05-02 13:14:01 +00:00
aj-rosado
69708c1285 [PM-20037] Remove native-carousel-flow feature flag (#5121) 2025-05-01 21:48:25 +00:00
Patrick Honkonen
ad1566f4b0 [PM-14846] Improve IP Address and Port Handling in StringExtensions (#5118) 2025-05-01 20:39:01 +00:00
David Perez
32d0ca7bcd PM-21088: Remove the unused IgnoreEnvironmentCheck feature flag (#5119) 2025-05-01 16:49:58 +00:00
André Bispo
0353f0c153 [PM-20466] Invalid master password returns generic error. (#5100) 2025-05-01 14:37:59 +00:00
Patrick Honkonen
7436122953 [PM-19846] Mark network module implementation details internal (#5115) 2025-05-01 14:26:19 +00:00
Patrick Honkonen
23ef5b38fe [PM-20508] Centralize passkey credential entry creation (#5033) 2025-04-30 15:31:27 +00:00
Patrick Honkonen
fe1fe770c7 Use Google's Digital Asset Links API to verify digital asset links (#5101) 2025-04-30 13:39:04 +00:00
David Perez
240bca3c2f PM-20552: Ensure userState does not emit while the active user is unlocking (#5112) 2025-04-29 20:56:31 +00:00
David Perez
8c7cc27c5d PM-20966: Log app state changes (#5110) 2025-04-29 20:56:15 +00:00
Patrick Honkonen
a4aa9837a6 [PM-17686] Allow overwriting TLS certificates (#5111) 2025-04-28 21:44:41 +00:00
Patrick Honkonen
b901de9ddf Correct indention in app/strings.xml (#5109) 2025-04-28 18:24:33 +00:00
Patrick Honkonen
96df23f0af Drop all tables when performing destructive migration in Authenticator (#5108) 2025-04-28 15:28:59 +00:00
renovate[bot]
0eb149941d [deps]: Update gh minor (#5102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 14:23:36 +00:00
renovate[bot]
6f44e64375 [deps]: Update kotlin (#5103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 14:22:46 +00:00
renovate[bot]
cda86b842e [deps]: Lock file maintenance (#5106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 13:32:05 +00:00
renovate[bot]
e1608b426d [deps]: Update sonarsource/sonarqube-scan-action action to v5 (#5105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 13:28:19 +00:00
renovate[bot]
b6017baf54 [deps]: Update actions/create-github-app-token action to v2 (#5104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 13:21:09 +00:00
Patrick Honkonen
0f6d15d6a6 [PM-20549] Introduce BitwardenServiceClient (#5091) 2025-04-25 20:26:01 +00:00
André Bispo
cd11164544 [PM-18942] Force sync for revoke/restore notification. (#5098) 2025-04-25 16:14:17 +00:00
David Perez
1b9d2bfab4 Omit navigation files from test coverage (#5095) 2025-04-25 15:46:54 +00:00
bw-ghapp[bot]
373b789fbb Autosync Crowdin Translations (#5096)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-04-25 14:08:02 +00:00
André Bispo
985e576a82 [PM-20148] Remove app-review-prompt feature flag. (#5093) 2025-04-24 20:18:50 +00:00
David Perez
b11e4481f9 PM-20365: Add pre-auth settings support (#5094) 2025-04-24 17:48:03 +00:00
David Perez
5ac0f2b111 Update to AGP 8.9.2 (#5089) 2025-04-23 19:54:09 +00:00
David Perez
37a0d19efc PM-20400: Display snackbar confirming log deletion (#5088) 2025-04-23 19:53:44 +00:00
David Perez
54983bc92e Add helper for concurrent map (#5086) 2025-04-23 19:53:05 +00:00
David Perez
36989875a6 Update to Junit 5.12.2 (#5087) 2025-04-23 19:52:48 +00:00
David Perez
88b0fe59bb PM-20516: Update NetworkConnectionManager (#5085) 2025-04-23 19:52:29 +00:00
David Perez
e4d0c48eed PM-20510: Log whenever the screen changes (#5083) 2025-04-23 19:47:06 +00:00
Patrick Honkonen
bd364a1108 Update Room dependency to version 2.7.1 (#5090) 2025-04-23 19:10:19 +00:00
Patrick Honkonen
39b88d6064 [PM-20389] Define and implement network module CertificateProvider (#5073) 2025-04-22 19:14:06 +00:00
David Perez
da709e039b PM-19809: Update flight recorder tooltip url (#5082) 2025-04-22 19:12:09 +00:00
David Perez
31311964d0 Cleanup minor lint warnings (#5081) 2025-04-21 21:35:05 +00:00
David Perez
8cbd7369c5 PM-19594: Add flight recorder banner (#5079) 2025-04-21 14:46:53 +00:00
David Perez
2a1669cf87 PM-20426: Update Block Autofill UI (#5078) 2025-04-21 14:04:05 +00:00
David Perez
4c4007a734 PM-20385: Delete confirmation dialog should dismiss on confirmation (#5077) 2025-04-21 14:03:45 +00:00
David Perez
70dc82d1b6 PM-20422: Update tab navigation (#5076) 2025-04-21 14:03:20 +00:00
bw-ghapp[bot]
021ece138b Autosync Crowdin Translations (#5074)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-04-18 14:30:44 +00:00
2028 changed files with 94044 additions and 100871 deletions

View File

@@ -0,0 +1,133 @@
# Get Release Notes from Jira script
Fetches release notes from Jira issues.
## Prerequisites
- Python dev environment - use [uv](https://github.com/astral-sh/uv)
- Jira API token. Generate one at: https://id.atlassian.com/manage-profile/security/api-tokens
- Install dependencies:
```bash
uv pip install -r pyproject.toml
```
## Usage
```bash
./jira_release_notes.py RELEASE-1762 example@example.com T0k3n123
```
# Output Format
The script retrieves the content from a custom field and handles two types of Jira release notes formats:
1. Bullet Points:
```
• Point 1
• Point 2
• Point 3
```
2. Single Line:
```
Single line of release notes text
```
## Jira JSON format example
### Single line
```json
...
"customfield_10335": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Single line release notes"
}
]
}
]
},
...
```
### Bullet points
```json
...
"customfield_10335": {
"type": "doc",
"version": 1,
"content": [
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Release notes list item 1"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Release notes list item 2"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Release notes list item 3"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Release notes list item 4"
}
]
}
]
}
]
}
]
},
...
```

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import sys
import base64
import json
import requests
def extract_text_from_content(content):
if isinstance(content, list):
texts = [extract_text_from_content(item) for item in content]
return '\n'.join(text for text in texts if text.strip())
if isinstance(content, dict):
if content.get('type') == 'text':
return content.get('text', '')
elif content.get('type') == 'paragraph':
return extract_text_from_content(content.get('content', []))
elif content.get('type') == 'bulletList':
return extract_text_from_content(content.get('content', []))
elif content.get('type') == 'listItem':
item_text = extract_text_from_content(content.get('content', []))
return f"* {item_text.strip()}"
return ''
def parse_release_notes(response_json):
try:
fields = response_json.get('fields', {})
release_notes_field = fields.get('customfield_10335', {})
if not release_notes_field or not release_notes_field.get('content'):
return ''
release_notes = extract_text_from_content(release_notes_field.get('content', []))
return release_notes
except Exception as e:
print(f"Error parsing release notes: {str(e)}", file=sys.stderr)
return ''
def main():
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <issue_id> <jira_email> <jira_api_token>")
sys.exit(1)
jira_issue_id = sys.argv[1]
jira_email = sys.argv[2]
jira_api_token = sys.argv[3]
jira_base_url = "https://bitwarden.atlassian.net"
auth = base64.b64encode(f"{jira_email}:{jira_api_token}".encode()).decode()
headers = {
"Authorization": f"Basic {auth}",
"Content-Type": "application/json"
}
response = requests.get(
f"{jira_base_url}/rest/api/3/issue/{jira_issue_id}",
headers=headers
)
if response.status_code != 200:
print(f"Error fetching Jira issue: {response.status_code}", file=sys.stderr)
sys.exit(1)
release_notes = parse_release_notes(response.json())
print(release_notes)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
[project]
name = "jira-get-release-notes"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32.3",
]

91
.github/scripts/jira-get-release-notes/uv.lock generated vendored Normal file
View File

@@ -0,0 +1,91 @@
version = 1
revision = 2
requires-python = ">=3.12"
[[package]]
name = "certifi"
version = "2025.4.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
{ url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
{ url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
{ url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
{ url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
{ url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
{ url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
{ url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
{ url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
{ url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
{ url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
{ url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
{ url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
]
[[package]]
name = "jira-get-release-notes"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "requests" },
]
[package.metadata]
requires-dist = [{ name = "requests", specifier = ">=2.32.3" }]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "requests"
version = "2.32.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
]
[[package]]
name = "urllib3"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
]

View File

@@ -29,6 +29,10 @@ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_VERSION: 17
permissions:
contents: read
packages: read
jobs:
build:
name: Build Authenticator
@@ -39,10 +43,10 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
@@ -52,7 +56,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ github.workspace }}/build-cache
@@ -61,13 +65,13 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true
@@ -98,7 +102,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true
@@ -162,10 +166,10 @@ jobs:
json_key:${{ github.workspace }}/secrets/authenticator_play_store-creds.json }}
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
@@ -175,7 +179,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ github.workspace }}/build-cache
@@ -184,7 +188,7 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
@@ -224,7 +228,7 @@ jobs:
- name: Upload release Play Store .aab artifact
if: ${{ matrix.variant == 'aab' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.bitwarden.authenticator.aab
path: authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab
@@ -232,7 +236,7 @@ jobs:
- name: Upload release .apk artifact
if: ${{ matrix.variant == 'apk' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.bitwarden.authenticator.apk
path: authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk
@@ -252,7 +256,7 @@ jobs:
- name: Upload .apk SHA file for release
if: ${{ matrix.variant == 'apk' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: authenticator-android-apk-sha256.txt
path: ./authenticator-android-apk-sha256.txt
@@ -260,7 +264,7 @@ jobs:
- name: Upload .aab SHA file for release
if: ${{ matrix.variant == 'aab' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: authenticator-android-aab-sha256.txt
path: ./authenticator-android-aab-sha256.txt

View File

@@ -30,6 +30,10 @@ env:
JAVA_VERSION: 17
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
permissions:
contents: read
packages: read
jobs:
build:
name: Build
@@ -40,10 +44,10 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
@@ -53,7 +57,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ github.workspace }}/build-cache
@@ -62,13 +66,13 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true
@@ -85,7 +89,7 @@ jobs:
run: bundle exec fastlane assembleDebugApks
- name: Upload test reports on failure
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: failure()
with:
name: test-reports
@@ -106,7 +110,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true
@@ -157,10 +161,10 @@ jobs:
--name app_play_prod_firebase-creds.json --file ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json --output none
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
@@ -170,7 +174,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ github.workspace }}/build-cache
@@ -179,7 +183,7 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
@@ -195,7 +199,7 @@ jobs:
- name: Increment version
run: |
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
DEFAULT_VERSION_CODE=$((11000+GITHUB_RUN_NUMBER))
bundle exec fastlane setBuildVersionInfo \
versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \
versionName:${{ inputs.version-name }}
@@ -253,7 +257,7 @@ jobs:
- name: Upload release Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.aab
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab
@@ -261,7 +265,7 @@ jobs:
- name: Upload beta Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.beta.aab
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab
@@ -269,7 +273,7 @@ jobs:
- name: Upload release .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.apk
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
@@ -277,7 +281,7 @@ jobs:
- name: Upload beta .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.beta.apk
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.beta.apk
@@ -286,7 +290,7 @@ jobs:
# When building variants other than 'prod'
- name: Upload debug .apk artifact
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.apk
@@ -324,7 +328,7 @@ jobs:
- name: Upload .apk SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.apk-sha256.txt
path: ./com.x8bit.bitwarden.apk-sha256.txt
@@ -332,7 +336,7 @@ jobs:
- name: Upload .apk SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.beta.apk-sha256.txt
path: ./com.x8bit.bitwarden.beta.apk-sha256.txt
@@ -340,7 +344,7 @@ jobs:
- name: Upload .aab SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.aab-sha256.txt
path: ./com.x8bit.bitwarden.aab-sha256.txt
@@ -348,7 +352,7 @@ jobs:
- name: Upload .aab SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.beta.aab-sha256.txt
path: ./com.x8bit.bitwarden.beta.aab-sha256.txt
@@ -356,7 +360,7 @@ jobs:
- name: Upload .apk SHA file for debug
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
@@ -405,7 +409,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true
@@ -442,10 +446,10 @@ jobs:
--name app_fdroid_firebase-creds.json --file ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json --output none
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
@@ -455,7 +459,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ github.workspace }}/build-cache
@@ -464,7 +468,7 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
@@ -481,7 +485,7 @@ jobs:
# Start from 11000 to prevent collisions with mobile build version codes
- name: Increment version
run: |
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
DEFAULT_VERSION_CODE=$((11000+GITHUB_RUN_NUMBER))
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
bundle exec fastlane setBuildVersionInfo \
versionCode:$VERSION_CODE \
@@ -515,7 +519,7 @@ jobs:
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden-fdroid.apk
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk
@@ -527,14 +531,14 @@ jobs:
> ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
- name: Upload F-Droid SHA file
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden-fdroid.apk-sha256.txt
path: ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
if-no-files-found: error
- name: Upload F-Droid Beta .apk artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.beta-fdroid.apk
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.apk
@@ -546,7 +550,7 @@ jobs:
> ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
- name: Upload F-Droid Beta SHA file
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
path: ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt

View File

@@ -1,56 +0,0 @@
name: Crowdin Sync - Authenticator
on:
workflow_dispatch:
inputs: {}
schedule:
- cron: '0 0 * * 5'
jobs:
crowdin-sync:
name: Autosync
runs-on: ubuntu-24.04
env:
_CROWDIN_PROJECT_ID: "673718"
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Log in to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Download translations
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
with:
config: crowdin-bwa.yml
upload_sources: false
upload_translations: false
download_translations: true
github_user_name: "bitwarden-devops-bot"
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync
create_pull_request: true
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}

View File

@@ -1,17 +1,29 @@
name: Crowdin Sync
name: Cron / Crowdin Pull
run-name: Crowdin Pull - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }}
on:
workflow_dispatch:
inputs: {}
schedule:
- cron: '0 0 * * 5'
jobs:
crowdin-sync:
name: Autosync
name: Crowdin Pull - ${{ matrix.name }} - ${{ github.event_name }}
runs-on: ubuntu-24.04
env:
_CROWDIN_PROJECT_ID: "269690"
permissions:
contents: write
pull-requests: write
strategy:
matrix:
include:
- name: Password Manager
project_id: 269690
config: crowdin-bwpm.yml
branch: crowdin-pull-bwpm
- name: Authenticator
project_id: 673718
config: crowdin-bwa.yml
branch: crowdin-pull-bwa
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -29,28 +41,29 @@ jobs:
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Download translations
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
- name: Download translations for ${{ matrix.name }}
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
_CROWDIN_PROJECT_ID: ${{ matrix.project_id }}
with:
config: crowdin.yml
config: ${{ matrix.config }}
upload_sources: false
upload_translations: false
download_translations: true
github_user_name: "bitwarden-devops-bot"
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync
commit_message: "Crowdin Pull - ${{ matrix.name }}"
localization_branch_name: ${{ matrix.branch }}
create_pull_request: true
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"
pull_request_title: "Crowdin Pull - ${{ matrix.name }}"
pull_request_body: ":inbox_tray: New translations for ${{ matrix.name }} received!"
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}

View File

@@ -1,30 +0,0 @@
name: Crowdin Push - Authenticator
on:
workflow_dispatch:
push:
branches:
- "main"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_VERSION: 17
jobs:
crowdin-push:
name: Crowdin Push
runs-on: ubuntu-24.04
env:
_CROWDIN_PROJECT_ID: "673718"
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Upload sources
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
with:
config: crowdin-bwa.yml
upload_sources: true
upload_translations: false

View File

@@ -1,17 +1,18 @@
name: Crowdin Push
name: CI / Crowdin Push
run-name: Crowdin Push - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'CI' }}
on:
workflow_dispatch:
push:
branches:
- "main"
- main
jobs:
crowdin-push:
name: Crowdin Push
name: Crowdin Push - ${{ github.event_name }}
runs-on: ubuntu-24.04
env:
_CROWDIN_PROJECT_ID: "269690"
permissions:
contents: read
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -28,12 +29,24 @@ jobs:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token"
- name: Upload sources
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
- name: Upload sources for Password Manager
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
_CROWDIN_PROJECT_ID: "269690"
with:
config: crowdin.yml
config: crowdin-bwpm.yml
upload_sources: true
upload_translations: false
- name: Upload sources for Authenticator
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
_CROWDIN_PROJECT_ID: "673718"
with:
config: crowdin-bwa.yml
upload_sources: true
upload_translations: false

View File

@@ -3,45 +3,24 @@ name: Create GitHub Release
on:
workflow_dispatch:
inputs:
version-name:
description: 'Version Name - E.g. "2024.11.1"'
required: true
type: string
version-number:
description: 'Version Number - E.g. "123456"'
required: true
type: string
artifact-run-id:
description: 'GitHub Action Run ID containing artifacts'
required: true
type: string
draft:
description: 'Create as draft release'
type: boolean
default: true
prerelease:
description: 'Mark as pre-release'
type: boolean
default: true
make-latest:
description: 'Set as the latest release'
type: boolean
branch-protection-type:
description: 'Branch protection type'
type: choice
options:
- Branch Name
- GitHub API
default: Branch Name
release-ticket-id:
description: 'Release Ticket ID - e.g. RELEASE-1762'
required: true
type: string
env:
ARTIFACTS_PATH: artifacts
jobs:
create-release:
name: Create GitHub Release
runs-on: ubuntu-24.04
permissions:
contents: write
actions: read
steps:
- name: Check out repository
@@ -54,31 +33,74 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
BRANCH_PROTECTION_TYPE: ${{ inputs.branch-protection-type }}
run: |
release_branch=$(gh run view $ARTIFACT_RUN_ID --json headBranch -q .headBranch)
workflow_data=$(gh run view $ARTIFACT_RUN_ID --json headBranch,workflowName)
release_branch=$(echo "$workflow_data" | jq -r .headBranch)
workflow_name=$(echo "$workflow_data" | jq -r .workflowName)
case "$BRANCH_PROTECTION_TYPE" in
"Branch Name")
if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then
echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches."
exit 1
fi
# branch protection check
if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then
echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches."
exit 1
fi
echo "release_branch=$release_branch" >> $GITHUB_OUTPUT
echo "workflow_name=$workflow_name" >> $GITHUB_OUTPUT
case "$workflow_name" in
*"Password Manager"* | "Build")
echo "app_name=Password Manager" >> $GITHUB_OUTPUT
echo "app_name_suffix=bwpm" >> $GITHUB_OUTPUT
;;
"GitHub API")
#NOTE requires token with "administration:read" scope
if ! gh api "repos/${{ github.repository }}/branches/$release_branch/protection" | grep -q "required_status_checks"; then
echo "::error::Branch '$release_branch' is not protected. Releases must be created from protected branches. If that's not correct, confirm if the github token user has the 'administration:read' scope."
exit 1
fi
*"Authenticator"*)
echo "app_name=Authenticator" >> $GITHUB_OUTPUT
echo "app_name_suffix=bwa" >> $GITHUB_OUTPUT
;;
*)
echo "::error::Unsupported branch protection type: $BRANCH_PROTECTION_TYPE"
echo "::error::Unknown workflow name: $workflow_name"
exit 1
;;
esac
echo "release_branch=$release_branch" >> $GITHUB_OUTPUT
- name: Get version info from run logs and set release tag name
id: get_release_info
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
_APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }}
run: |
workflow_log=$(gh run view $ARTIFACT_RUN_ID --log)
version_number_with_trailing_dot=$(grep -m 1 "Setting version code to" <<< "$workflow_log" | sed 's/.*Setting version code to //')
version_number=${version_number_with_trailing_dot%.} # remove trailing dot
version_name_with_trailing_dot=$(grep -m 1 "Setting version name to" <<< "$workflow_log" | sed 's/.*Setting version name to //')
version_name=${version_name_with_trailing_dot%.} # remove trailing dot
if [[ -z "$version_name" ]]; then
echo "::warning::Version name not found. Using default value - 0.0.0"
version_name="0.0.0"
else
echo "✅ Found version name: $version_name"
fi
if [[ -z "$version_number" ]]; then
echo "::warning::Version number not found. Using default value - 0"
version_number="0"
else
echo "✅ Found version number: $version_number"
fi
echo "version_number=$version_number" >> $GITHUB_OUTPUT
echo "version_name=$version_name" >> $GITHUB_OUTPUT
tag_name="v$version_name-$_APP_NAME_SUFFIX" # e.g. v2025.6.0-bwpm
echo "🔖 New tag name: $tag_name"
echo "tag_name=$tag_name" >> $GITHUB_OUTPUT
last_release_tag=$(git tag -l --sort=-authordate | grep "$APP_NAME_SUFFIX" | head -n 1)
echo "🔖 Last release tag: $last_release_tag"
echo "last_release_tag=$last_release_tag" >> $GITHUB_OUTPUT
- name: Download artifacts
env:
@@ -93,37 +115,106 @@ jobs:
find $ARTIFACTS_PATH -type f
fi
- name: Create Release
id: create_release
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
tag_name: "v${{ inputs.version-name }}"
name: "${{ inputs.version-name }} (${{ inputs.version-number }})"
prerelease: ${{ inputs.prerelease }}
draft: ${{ inputs.draft }}
make_latest: ${{ inputs.make-latest }}
target_commitish: ${{ steps.get_release_branch.outputs.release_branch }}
generate_release_notes: true
files: |
artifacts/**/*
- name: Update Release Description
- name: Get product release notes
id: get_release_notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_ID: ${{ steps.create_release.outputs.id }}
RELEASE_URL: ${{ steps.create_release.outputs.url }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
_RELEASE_TICKET_ID: ${{ inputs.release-ticket-id }}
_JIRA_API_EMAIL: ${{ secrets.JIRA_API_EMAIL }}
_JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
run: |
# Get current release body
current_body=$(gh api /repos/${{ github.repository }}/releases/$RELEASE_ID --jq .body)
echo "Getting product release notes"
product_release_notes=$(python3 .github/scripts/jira-get-release-notes/jira_release_notes.py $_RELEASE_TICKET_ID $_JIRA_API_EMAIL $_JIRA_API_TOKEN)
# Append build source to the end
updated_body="${current_body}
if [[ -z "$product_release_notes" || $product_release_notes == "Error checking"* ]]; then
echo "::warning::Failed to fetch release notes from Jira. Output: $product_release_notes"
product_release_notes="<insert product release notes here>"
else
echo "✅ Product release notes:"
echo "$product_release_notes"
fi
echo "$product_release_notes" > product_release_notes.txt
- name: Create Release
id: create_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
_APP_NAME: ${{ steps.get_release_branch.outputs.app_name }}
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
_VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }}
_TARGET_COMMIT: ${{ steps.get_release_branch.outputs.release_branch }}
_TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }}
_LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }}
run: |
echo "⌛️ Creating release for $_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER) on $_TARGET_COMMIT"
release_url=$(gh release create "$_TAG_NAME" \
--title "$_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER)" \
--target "$_TARGET_COMMIT" \
--generate-notes \
--notes-start-tag "$_LAST_RELEASE_TAG" \
--draft \
$ARTIFACTS_PATH/*/*)
echo "✅ Release created: $release_url"
# Get release info for outputs
release_data=$(gh release view "$_TAG_NAME" --json id)
release_id=$(echo "$release_data" | jq -r .id)
echo "id=$release_id" >> $GITHUB_OUTPUT
echo "url=$release_url" >> $GITHUB_OUTPUT
- name: Update Release Description
id: update_release_description
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
_TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }}
run: |
echo "Getting current release body. Tag: $_TAG_NAME"
current_body=$(gh release view "$_TAG_NAME" --json body --jq .body)
product_release_notes=$(cat product_release_notes.txt)
# Update release description with product release notes and builds source
updated_body="# Overview
${product_release_notes}
${current_body}
**Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID"
# Update release
gh api --method PATCH /repos/${{ github.repository }}/releases/$RELEASE_ID \
-f body="$updated_body"
new_release_url=$(gh release edit "$_TAG_NAME" --notes "$updated_body")
echo "# :rocket: Release ready at:" >> $GITHUB_STEP_SUMMARY
echo "$RELEASE_URL" >> $GITHUB_STEP_SUMMARY
# draft release links change after editing
echo "release_url=$new_release_url" >> $GITHUB_OUTPUT
- name: Add Release Summary
env:
_RELEASE_TAG: ${{ steps.get_release_info.outputs.tag_name }}
_LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }}
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
_VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }}
_RELEASE_BRANCH: ${{ steps.get_release_branch.outputs.release_branch }}
_RELEASE_URL: ${{ steps.update_release_description.outputs.release_url }}
run: |
echo "# :fish_cake: Release ready at:" >> $GITHUB_STEP_SUMMARY
echo "$_RELEASE_URL" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "$_VERSION_NAME" == "0.0.0" || "$_VERSION_NUMBER" == "0" ]]; then
echo "> [!CAUTION]" >> $GITHUB_STEP_SUMMARY
echo "> Version name or number wasn't previously found and a default value was used. You'll need to manually update the release Title, Tag and Description, specifically, the "Full Changelog" link." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo ":clipboard: Confirm that the defined GitHub Release options are correct:" >> $GITHUB_STEP_SUMMARY
echo " * :bookmark: New tag name: \`$_RELEASE_TAG\`" >> $GITHUB_STEP_SUMMARY
echo " * :palm_tree: Target branch: \`$_RELEASE_BRANCH\`" >> $GITHUB_STEP_SUMMARY
echo " * :ocean: Previous tag set in the description \"Full Changelog\" link: \`$_LAST_RELEASE_TAG\`" >> $GITHUB_STEP_SUMMARY
echo " * :white_check_mark: Description has automated release notes and they match the commits in the release branch" >> $GITHUB_STEP_SUMMARY
echo "> [!NOTE]" >> $GITHUB_STEP_SUMMARY
echo "> Commits directly pushed to branches without a Pull Request won't appear in the automated release notes." >> $GITHUB_STEP_SUMMARY

View File

@@ -0,0 +1,12 @@
name: Publish GitHub Release as newest
on:
workflow_dispatch:
jobs:
stub:
runs-on: ubuntu-24.04
name: Stub
steps:
- name: Stub
run: echo "This is a stub job to trigger the workflow."

14
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Publish
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-24.04
name: Promote build to Production in Play Store
steps:
- name: TEST STEP
run: exit 0

View File

@@ -21,7 +21,7 @@ jobs:
fetch-depth: 0
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
uses: checkmarx/ast-github-action@ef93013c95adc60160bc22060875e90800d3ecfc # 2.3.19
with:
project_name: ${{ github.repository }}
cx_tenant: ${{ secrets.CHECKMARX_TENANT }}
@@ -34,7 +34,7 @@ jobs:
--output-path .
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
with:
sarif_file: cx_result.sarif
@@ -51,7 +51,7 @@ jobs:
fetch-depth: 0
- name: Scan with SonarCloud
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
uses: sonarsource/sonarqube-scan-action@aa494459d7c39c106cc77b166de8b4250a32bb97 # v5.1.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:

View File

@@ -2,13 +2,23 @@ name: Scan Pull Requests
on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened]
branches-ignore:
- main
pull_request_target:
types: [opened, synchronize]
types: [opened, synchronize, reopened]
branches:
- main
permissions: {}
jobs:
check-run:
name: Check PR run
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
permissions:
contents: read
sast:
name: SAST scan
@@ -26,7 +36,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
uses: checkmarx/ast-github-action@ef93013c95adc60160bc22060875e90800d3ecfc # 2.3.19
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
@@ -41,7 +51,7 @@ jobs:
--output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
with:
sarif_file: cx_result.sarif
sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
@@ -63,7 +73,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with SonarCloud
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
uses: sonarsource/sonarqube-scan-action@aa494459d7c39c106cc77b166de8b4250a32bb97 # v5.1.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:

16
.github/workflows/test-device.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Test Device
on:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
name: Test Device
runs-on: ubuntu-24.04
steps:
- name: Placeholder step
run: echo "Placeholder workflow step"

View File

@@ -9,7 +9,7 @@ on:
pull_request:
types: [opened, synchronize]
merge_group:
type: [checks_requested]
types: [checks_requested]
workflow_dispatch:
env:
@@ -30,10 +30,10 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
@@ -43,7 +43,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
${{ github.workspace }}/build-cache
@@ -52,12 +52,12 @@ jobs:
${{ runner.os }}-build-
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env._JAVA_VERSION }}
@@ -75,7 +75,7 @@ jobs:
bundle exec fastlane check
- name: Upload test reports
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always()
with:
name: test-reports
@@ -91,7 +91,7 @@ jobs:
- name: Upload to codecov.io
id: upload-to-codecov
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
if: github.event_name == 'push' || github.event_name == 'pull_request'
continue-on-error: true
with:
@@ -110,7 +110,7 @@ jobs:
echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY
echo "> Uploading code coverage report failed. Please check the \"Upload to codecov.io\" step of \"Process Test Reports\" job for more details." >> $GITHUB_STEP_SUMMARY
if [ ! -z "$PR_NUMBER" ]; then
if [ -n "$PR_NUMBER" ]; then
message=$'> [!WARNING]\n> @'$RUN_ACTOR' Uploading code coverage report failed. Please check the "Upload to codecov.io" step of [Process Test Reports job]('$_GITHUB_ACTION_RUN_URL') for more details.'
gh pr comment --repo $GITHUB_REPOSITORY $PR_NUMBER --body "$message"
fi

View File

@@ -9,26 +9,26 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.2)
aws-partitions (1.1084.0)
aws-sdk-core (3.222.1)
aws-eventstream (1.4.0)
aws-partitions (1.1113.0)
aws-sdk-core (3.225.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (1.104.0)
aws-sdk-core (~> 3, >= 3.225.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.183.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-s3 (1.189.0)
aws-sdk-core (~> 3, >= 3.225.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-sigv4 (1.12.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
base64 (0.3.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
@@ -71,7 +71,7 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.227.1)
fastlane (2.227.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -113,7 +113,7 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-firebase_app_distribution (0.10.0)
fastlane-plugin-firebase_app_distribution (0.10.1)
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
fastlane-sirp (1.0.0)
@@ -165,7 +165,7 @@ GEM
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.10.2)
json (2.12.2)
jwt (2.10.1)
base64
logger (1.7.0)
@@ -175,13 +175,13 @@ GEM
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.2.1)
naturally (2.2.2)
nkf (0.2.0)
optparse (0.6.0)
os (1.1.4)
plist (3.7.2)
public_suffix (6.0.1)
rake (13.2.1)
public_suffix (6.0.2)
rake (13.3.0)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -192,7 +192,7 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
signet (0.19.0)
signet (0.20.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)

View File

@@ -4,6 +4,7 @@
- [Compatibility](#compatibility)
- [Setup](#setup)
- [Theme](#theme)
- [Dependencies](#dependencies)
## Compatibility
@@ -15,7 +16,6 @@
## Setup
1. Clone the repository:
```sh
@@ -52,6 +52,25 @@
Please avoid mixing formatting and logical changes in the same commit/PR. When possible, fix any large formatting issues in a separate PR before opening one to make logical changes to the same code. This helps others focus on the meaningful code changes when reviewing the code.
## Theme
### Icons & Illustrations
The app supports light mode, dark mode and dynamic colors. Most icons in the app will display correctly using tinting but multi-tonal icons and illustrations require extra processing in order to be displayed properly with dynamic colors.
All illustrations and multi-tonal icons require the svg paths to be tagged with the `name` attribute in order for each individual path to be tinted the appropriate color. Any untagged path will not be tinted and the resulting image will be incorrect.
The supported tags are as follows:
* outline
* primary
* secondary
* tertiary
* accent
* logo
* navigation
* navigationActiveAccent
## Dependencies
### Application Dependencies

1
annotation/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,42 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.bitwarden.annotation"
compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
minSdk = libs.versions.minSdkBwa.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
compileOptions {
sourceCompatibility(libs.versions.jvmTarget.get())
targetCompatibility(libs.versions.jvmTarget.get())
}
@Suppress("UnstableApiUsage")
testFixtures {
enable = true
}
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
}
}

View File

@@ -1,4 +1,4 @@
package com.bitwarden.core.annotation
package com.bitwarden.annotation
/**
* Used to omit the annotated class from test coverage reporting. This should be used sparingly and

View File

@@ -211,8 +211,9 @@ dependencies {
add("standardImplementation", dependencyNotation)
}
implementation(files("libs/authenticatorbridge-1.0.0-release.aar"))
implementation(files("libs/authenticatorbridge-1.0.1-release.aar"))
implementation(project(":annotation"))
implementation(project(":core"))
implementation(project(":data"))
implementation(project(":network"))
@@ -275,6 +276,7 @@ dependencies {
// Pull in test fixtures from other modules
testImplementation(testFixtures(project(":data")))
testImplementation(testFixtures(project(":network")))
testImplementation(testFixtures(project(":ui")))
testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.google.hilt.android.testing)

Binary file not shown.

View File

@@ -0,0 +1,38 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "ce40856ec88770d11b7afb587c7deabc",
"entities": [
{
"tableName": "privileged_apps",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` TEXT NOT NULL, PRIMARY KEY(`package_name`, `signature`))",
"fields": [
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "signature",
"columnName": "signature",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"package_name",
"signature"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ce40856ec88770d11b7afb587c7deabc')"
]
}
}

View File

@@ -15,7 +15,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
<!-- Protect access to AuthenticatorBridgeService using this custom permission.
Note that each build type uses a different value for knownCerts.
@@ -37,15 +37,18 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:intentMatchingFlags="enforceIntentFilter"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/LaunchTheme"
tools:ignore="CredentialDependency"
tools:replace="appComponentFactory"
tools:targetApi="33">
tools:targetApi="36">
<activity
android:name=".MainActivity"
android:configChanges="uiMode"
android:exported="true"
android:launchMode="@integer/launchModeAPIlevel"
android:theme="@style/LaunchTheme"
@@ -76,16 +79,15 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="vault.bitwarden.com" />
<data android:host="vault.bitwarden.eu" />
<data android:host="*.bitwarden.com" />
<data android:host="*.bitwarden.eu" />
<data android:host="*.bitwarden.pw" />
<data android:pathPattern="/redirect-connector.*" />
</intent-filter>
<intent-filter>
<action android:name="com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY" />
<action android:name="com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY" />
<action android:name="com.x8bit.bitwarden.fido2.ACTION_UNLOCK_ACCOUNT" />
<action android:name="com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY" />
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY" />
<action android:name="com.x8bit.bitwarden.credentials.ACTION_UNLOCK_ACCOUNT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
@@ -310,6 +312,14 @@
android:exported="true"
android:permission="${applicationId}.permission.AUTHENTICATOR_BRIDGE_SERVICE" />
<!-- Firebase SDK initOrder is 100. We use a higher order to initialize first -->
<provider
android:name=".data.platform.contentprovider.UncaughtErrorLoggingContentProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="false"
android:initOrder="101" />
</application>
<queries>

View File

@@ -1,102 +0,0 @@
package com.x8bit.bitwarden.data.auth.datasource.network.di
import com.bitwarden.network.service.AccountsService
import com.bitwarden.network.service.AccountsServiceImpl
import com.bitwarden.network.service.AuthRequestsService
import com.bitwarden.network.service.AuthRequestsServiceImpl
import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.DevicesServiceImpl
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.HaveIBeenPwnedServiceImpl
import com.bitwarden.network.service.IdentityService
import com.bitwarden.network.service.IdentityServiceImpl
import com.bitwarden.network.service.NewAuthRequestService
import com.bitwarden.network.service.NewAuthRequestServiceImpl
import com.bitwarden.network.service.OrganizationService
import com.bitwarden.network.service.OrganizationServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import retrofit2.create
import javax.inject.Singleton
/**
* Provides network dependencies in the auth package.
*/
@Module
@InstallIn(SingletonComponent::class)
object AuthNetworkModule {
@Provides
@Singleton
fun providesAccountService(
retrofits: Retrofits,
json: Json,
): AccountsService = AccountsServiceImpl(
unauthenticatedAccountsApi = retrofits.unauthenticatedApiRetrofit.create(),
authenticatedAccountsApi = retrofits.authenticatedApiRetrofit.create(),
unauthenticatedKeyConnectorApi = retrofits.createStaticRetrofit().create(),
authenticatedKeyConnectorApi = retrofits
.createStaticRetrofit(isAuthenticated = true)
.create(),
json = json,
)
@Provides
@Singleton
fun providesAuthRequestsService(
retrofits: Retrofits,
): AuthRequestsService = AuthRequestsServiceImpl(
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
)
@Provides
@Singleton
fun providesDevicesService(
retrofits: Retrofits,
): DevicesService = DevicesServiceImpl(
authenticatedDevicesApi = retrofits.authenticatedApiRetrofit.create(),
unauthenticatedDevicesApi = retrofits.unauthenticatedApiRetrofit.create(),
)
@Provides
@Singleton
fun providesIdentityService(
retrofits: Retrofits,
json: Json,
): IdentityService = IdentityServiceImpl(
unauthenticatedIdentityApi = retrofits.unauthenticatedIdentityRetrofit.create(),
json = json,
)
@Provides
@Singleton
fun providesHaveIBeenPwnedService(
retrofits: Retrofits,
): HaveIBeenPwnedService = HaveIBeenPwnedServiceImpl(
api = retrofits
.createStaticRetrofit(baseUrl = "https://api.pwnedpasswords.com")
.create(),
)
@Provides
@Singleton
fun providesNewAuthRequestService(
retrofits: Retrofits,
): NewAuthRequestService = NewAuthRequestServiceImpl(
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
unauthenticatedAuthRequestsApi = retrofits.unauthenticatedApiRetrofit.create(),
)
@Provides
@Singleton
fun providesOrganizationService(
retrofits: Retrofits,
): OrganizationService = OrganizationServiceImpl(
authenticatedOrganizationApi = retrofits.authenticatedApiRetrofit.create(),
unauthenticatedOrganizationApi = retrofits.unauthenticatedApiRetrofit.create(),
)
}

View File

@@ -1,28 +0,0 @@
package com.x8bit.bitwarden.data.auth.repository.model
import java.time.ZonedDateTime
/**
* Response types when checking for an email's claimed domain organization.
*/
sealed class OrganizationDomainSsoDetailsResult {
/**
* The request was successful.
*
* @property isSsoAvailable Indicates if SSO is available for the email address.
* @property organizationIdentifier The claimed organization identifier for the email address.
* @property verifiedDate The date and time when the domain was verified.
*/
data class Success(
val isSsoAvailable: Boolean,
val organizationIdentifier: String,
val verifiedDate: ZonedDateTime?,
) : OrganizationDomainSsoDetailsResult()
/**
* The request failed.
*/
data class Failure(
val error: Throwable,
) : OrganizationDomainSsoDetailsResult()
}

View File

@@ -1,30 +0,0 @@
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.di
import com.bitwarden.network.service.DigitalAssetLinkService
import com.bitwarden.network.service.DigitalAssetLinkServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.create
import javax.inject.Singleton
/**
* Provides network dependencies in the fido2 package.
*/
@Module
@InstallIn(SingletonComponent::class)
object Fido2NetworkModule {
@Provides
@Singleton
fun provideDigitalAssetLinkService(
retrofits: Retrofits,
): DigitalAssetLinkService =
DigitalAssetLinkServiceImpl(
digitalAssetLinkApi = retrofits
.createStaticRetrofit()
.create(),
)
}

View File

@@ -1,89 +0,0 @@
package com.x8bit.bitwarden.data.autofill.fido2.di
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import com.bitwarden.data.manager.DispatcherManager
import com.bitwarden.network.service.DigitalAssetLinkService
import com.bitwarden.sdk.Fido2CredentialStore
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManagerImpl
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManagerImpl
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessor
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessorImpl
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import java.time.Clock
import javax.inject.Singleton
/**
* Provides dependencies within the fido2 package.
*/
@Module
@InstallIn(SingletonComponent::class)
object Fido2ProviderModule {
@RequiresApi(Build.VERSION_CODES.S)
@Provides
@Singleton
fun provideCredentialProviderProcessor(
@ApplicationContext context: Context,
authRepository: AuthRepository,
vaultRepository: VaultRepository,
fido2CredentialStore: Fido2CredentialStore,
fido2CredentialManager: Fido2CredentialManager,
dispatcherManager: DispatcherManager,
intentManager: IntentManager,
biometricsEncryptionManager: BiometricsEncryptionManager,
featureFlagManager: FeatureFlagManager,
clock: Clock,
): Fido2ProviderProcessor =
Fido2ProviderProcessorImpl(
context,
authRepository,
vaultRepository,
fido2CredentialStore,
fido2CredentialManager,
intentManager,
clock,
biometricsEncryptionManager,
featureFlagManager,
dispatcherManager,
)
@Provides
@Singleton
fun provideFido2CredentialManager(
vaultSdkSource: VaultSdkSource,
fido2CredentialStore: Fido2CredentialStore,
json: Json,
): Fido2CredentialManager =
Fido2CredentialManagerImpl(
vaultSdkSource = vaultSdkSource,
fido2CredentialStore = fido2CredentialStore,
json = json,
)
@Provides
@Singleton
fun provideFido2OriginManager(
assetManager: AssetManager,
digitalAssetLinkService: DigitalAssetLinkService,
): Fido2OriginManager =
Fido2OriginManagerImpl(
assetManager = assetManager,
digitalAssetLinkService = digitalAssetLinkService,
)
}

View File

@@ -1,155 +0,0 @@
package com.x8bit.bitwarden.data.autofill.fido2.manager
import androidx.credentials.provider.CallingAppInfo
import com.bitwarden.network.model.DigitalAssetLinkResponseJson
import com.bitwarden.network.service.DigitalAssetLinkService
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.util.getSignatureFingerprintAsHexString
import com.x8bit.bitwarden.data.platform.util.validatePrivilegedApp
import timber.log.Timber
private const val GOOGLE_ALLOW_LIST_FILE_NAME = "fido2_privileged_google.json"
private const val COMMUNITY_ALLOW_LIST_FILE_NAME = "fido2_privileged_community.json"
/**
* Primary implementation of [Fido2OriginManager].
*/
@Suppress("TooManyFunctions")
class Fido2OriginManagerImpl(
private val assetManager: AssetManager,
private val digitalAssetLinkService: DigitalAssetLinkService,
) : Fido2OriginManager {
override suspend fun validateOrigin(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult {
return if (callingAppInfo.isOriginPopulated()) {
validatePrivilegedAppOrigin(callingAppInfo)
} else {
validateCallingApplicationAssetLinks(callingAppInfo, relyingPartyId)
}
}
private suspend fun validateCallingApplicationAssetLinks(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult = digitalAssetLinkService
.getDigitalAssetLinkForRp(relyingParty = relyingPartyId)
.onFailure {
return Fido2ValidateOriginResult.Error.AssetLinkNotFound
}
.mapCatching { statements ->
statements
.filterMatchingAppStatementsOrNull(
rpPackageName = callingAppInfo.packageName,
)
?: return Fido2ValidateOriginResult.Error.ApplicationNotFound
}
.mapCatching { matchingStatements ->
callingAppInfo
.getSignatureFingerprintAsHexString()
?.let { certificateFingerprint ->
matchingStatements
.filterMatchingAppSignaturesOrNull(
signature = certificateFingerprint,
)
}
?: return Fido2ValidateOriginResult.Error.ApplicationFingerprintNotVerified
}
.fold(
onSuccess = {
Fido2ValidateOriginResult.Success(null)
},
onFailure = {
Fido2ValidateOriginResult.Error.Unknown
},
)
private suspend fun validatePrivilegedAppOrigin(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult {
val googleAllowListResult =
validatePrivilegedAppSignatureWithGoogleList(callingAppInfo)
return when (googleAllowListResult) {
is Fido2ValidateOriginResult.Success -> {
// Application was found and successfully validated against the Google allow list so
// we can return the result as the final validation result.
googleAllowListResult
}
is Fido2ValidateOriginResult.Error -> {
// Check the community allow list if the Google allow list failed, and return the
// result as the final validation result.
validatePrivilegedAppSignatureWithCommunityList(callingAppInfo)
}
}
}
private suspend fun validatePrivilegedAppSignatureWithGoogleList(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult =
validatePrivilegedAppSignatureWithAllowList(
callingAppInfo = callingAppInfo,
fileName = GOOGLE_ALLOW_LIST_FILE_NAME,
)
private suspend fun validatePrivilegedAppSignatureWithCommunityList(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult =
validatePrivilegedAppSignatureWithAllowList(
callingAppInfo = callingAppInfo,
fileName = COMMUNITY_ALLOW_LIST_FILE_NAME,
)
private suspend fun validatePrivilegedAppSignatureWithAllowList(
callingAppInfo: CallingAppInfo,
fileName: String,
): Fido2ValidateOriginResult =
assetManager
.readAsset(fileName)
.mapCatching { allowList ->
callingAppInfo.validatePrivilegedApp(
allowList = allowList,
)
}
.fold(
onSuccess = { it },
onFailure = {
Timber.e(it, "Failed to validate privileged app: ${callingAppInfo.packageName}")
Fido2ValidateOriginResult.Error.Unknown
},
)
/**
* Returns statements targeting the calling Android application, or null.
*/
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppStatementsOrNull(
rpPackageName: String,
): List<DigitalAssetLinkResponseJson>? =
filter { statement ->
val target = statement.target
target.namespace == "android_app" &&
target.packageName == rpPackageName &&
statement.relation.containsAll(
listOf(
"delegate_permission/common.handle_all_urls",
),
)
}
.takeUnless { it.isEmpty() }
/**
* Returns statements that match the given [signature], or null.
*/
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppSignaturesOrNull(
signature: String,
): List<DigitalAssetLinkResponseJson>? =
filter { statement ->
statement.target.sha256CertFingerprints
?.contains(signature)
?: false
}
.takeUnless { it.isEmpty() }
}

View File

@@ -1,73 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.authenticator
import com.bitwarden.network.util.HEADER_KEY_AUTHORIZATION
import com.bitwarden.network.util.HEADER_VALUE_BEARER_PREFIX
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
import com.x8bit.bitwarden.data.auth.repository.util.parseJwtTokenDataOrNull
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import javax.inject.Singleton
/**
* An authenticator used to refresh the access token when a 401 is returned from an API. Upon
* successfully getting a new access token, the original request is retried.
*/
@Singleton
class RefreshAuthenticator : Authenticator {
/**
* A provider required to update tokens.
*/
var authenticatorProvider: AuthenticatorProvider? = null
override fun authenticate(
route: Route?,
response: Response,
): Request? {
val accessToken = requireNotNull(
response
.request
.header(name = HEADER_KEY_AUTHORIZATION)
?.substringAfter(delimiter = HEADER_VALUE_BEARER_PREFIX),
)
return when (val userId = parseJwtTokenDataOrNull(accessToken)?.userId) {
null -> {
// We unable to get the user ID, let's just let the 401 pass through.
null
}
authenticatorProvider?.activeUserId -> {
// In order to prevent potential deadlocks or thread starvation we want the call
// to refresh the access token to be strictly synchronous with no internal thread
// hopping.
authenticatorProvider
?.refreshAccessTokenSynchronously(userId)
?.fold(
onFailure = {
authenticatorProvider?.logout(
userId = userId,
reason = LogoutReason.TokenRefreshFail,
)
null
},
onSuccess = {
response.request
.newBuilder()
.header(
name = HEADER_KEY_AUTHORIZATION,
value = "$HEADER_VALUE_BEARER_PREFIX${it.accessToken}",
)
.build()
},
)
}
else -> {
// We are no longer the active user, let's just cancel.
null
}
}
}
}

View File

@@ -1,121 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.di
import com.bitwarden.network.interceptor.AuthTokenInterceptor
import com.bitwarden.network.interceptor.BaseUrlInterceptors
import com.bitwarden.network.interceptor.BaseUrlsProvider
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.bitwarden.network.service.ConfigService
import com.bitwarden.network.service.ConfigServiceImpl
import com.bitwarden.network.service.EventService
import com.bitwarden.network.service.EventServiceImpl
import com.bitwarden.network.service.PushService
import com.bitwarden.network.service.PushServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.manager.AuthTokenManager
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.RetrofitsImpl
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManager
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManagerImpl
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_NAME
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
import com.x8bit.bitwarden.data.platform.manager.KeyManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import retrofit2.create
import javax.inject.Singleton
/**
* This class provides network-related functionality for the application.
* It initializes and configures the networking components.
*/
@Module
@InstallIn(SingletonComponent::class)
object PlatformNetworkModule {
@Provides
@Singleton
fun providesConfigService(
retrofits: Retrofits,
): ConfigService = ConfigServiceImpl(retrofits.unauthenticatedApiRetrofit.create())
@Provides
@Singleton
fun providesEventService(
retrofits: Retrofits,
): EventService = EventServiceImpl(
eventApi = retrofits.authenticatedEventsRetrofit.create(),
)
@Provides
@Singleton
fun providePushService(
retrofits: Retrofits,
authDiskSource: AuthDiskSource,
): PushService = PushServiceImpl(
pushApi = retrofits.authenticatedApiRetrofit.create(),
appId = authDiskSource.uniqueAppId,
)
@Provides
@Singleton
fun providesAuthTokenInterceptor(
authTokenManager: AuthTokenManager,
): AuthTokenInterceptor = AuthTokenInterceptor(
authTokenProvider = authTokenManager,
)
@Provides
@Singleton
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor(
userAgent = HEADER_VALUE_USER_AGENT,
clientName = HEADER_VALUE_CLIENT_NAME,
clientVersion = HEADER_VALUE_CLIENT_VERSION,
)
@Provides
@Singleton
fun providesRefreshAuthenticator(): RefreshAuthenticator = RefreshAuthenticator()
@Provides
@Singleton
fun provideSslManager(
keyManager: KeyManager,
environmentRepository: EnvironmentRepository,
): SslManager =
SslManagerImpl(
keyManager = keyManager,
environmentRepository = environmentRepository,
)
@Provides
@Singleton
fun providesBaseUrlInterceptors(
baseUrlsProvider: BaseUrlsProvider,
): BaseUrlInterceptors =
BaseUrlInterceptors(baseUrlsProvider = baseUrlsProvider)
@Provides
@Singleton
fun provideRetrofits(
authTokenInterceptor: AuthTokenInterceptor,
baseUrlInterceptors: BaseUrlInterceptors,
headersInterceptor: HeadersInterceptor,
refreshAuthenticator: RefreshAuthenticator,
sslManager: SslManager,
json: Json,
): Retrofits =
RetrofitsImpl(
authTokenInterceptor = authTokenInterceptor,
baseUrlInterceptors = baseUrlInterceptors,
headersInterceptor = headersInterceptor,
refreshAuthenticator = refreshAuthenticator,
sslManager = sslManager,
json = json,
)
}

View File

@@ -1,20 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.ssl
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
/**
* Interface for managing SSL connections.
*/
interface SslManager {
/**
* The SSL context to use for SSL connections.
*/
val sslContext: SSLContext
/**
* The trust managers to use for SSL connections.
*/
val trustManagers: Array<TrustManager>
}

View File

@@ -1,116 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.ssl
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.core.net.toUri
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsCertificate
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost
import com.x8bit.bitwarden.data.platform.manager.KeyManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import java.net.Socket
import java.security.KeyStore
import java.security.Principal
import java.security.PrivateKey
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedKeyManager
/**
* Primary implementation of [SslManager].
*/
class SslManagerImpl(
private val keyManager: KeyManager,
private val environmentRepository: EnvironmentRepository,
) : SslManager {
/*
This property must only be accessed from a background thread. Accessing this property from
the main thread will result in an exception being thrown when retrieving the mutual TLS
certificate from [KeyManager].
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@get:WorkerThread
internal val mutualTlsCertificate: MutualTlsCertificate?
get() {
val keyUri = getKeyUri()
?: return null
val host = MutualTlsKeyHost
.entries
.find { it.name == keyUri.authority }
?: return null
val alias = keyUri.path
?.trim('/')
?.takeUnless { it.isEmpty() }
?: return null
return keyManager.getMutualTlsCertificateChain(
alias = alias,
host = host,
)
}
override val trustManagers: Array<TrustManager>
get() = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
.apply { init(null as KeyStore?) }
.trustManagers
override val sslContext: SSLContext
get() = SSLContext
.getInstance("TLS")
.apply {
init(
arrayOf(X509ExtendedKeyManagerImpl()),
trustManagers,
null,
)
}
private fun getKeyUri(): Uri? = environmentRepository
.environment
.environmentUrlData
.keyUri
?.toUri()
private inner class X509ExtendedKeyManagerImpl : X509ExtendedKeyManager() {
override fun chooseClientAlias(
keyType: Array<out String>?,
issuers: Array<out Principal>?,
socket: Socket?,
): String = mutualTlsCertificate?.alias ?: ""
override fun getCertificateChain(
alias: String?,
): Array<X509Certificate>? =
mutualTlsCertificate
?.certificateChain
?.toTypedArray()
override fun getPrivateKey(alias: String?): PrivateKey? =
mutualTlsCertificate
?.privateKey
//region Unused server side methods
override fun getServerAliases(
alias: String?,
issuers: Array<out Principal>?,
): Array<String> = arrayOf()
override fun getClientAliases(
keyType: String?,
issuers: Array<out Principal>?,
): Array<String> = emptyArray()
override fun chooseServerAlias(
alias: String?,
issuers: Array<out Principal>?,
socket: Socket?,
): String = ""
//endregion Unused server side methods
}
}

View File

@@ -1,12 +0,0 @@
package com.x8bit.bitwarden.data.platform.manager.network
/**
* Manager to detect and handle changes to network connectivity.
*/
interface NetworkConnectionManager {
/**
* Returns `true` if the application has a network connection and access to the Internet is
* available.
*/
val isNetworkConnected: Boolean
}

View File

@@ -1,22 +0,0 @@
package com.x8bit.bitwarden.data.platform.manager.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
/**
* Primary implementation of [NetworkConnectionManager].
*/
class NetworkConnectionManagerImpl(
context: Context,
) : NetworkConnectionManager {
private val connectivityManager: ConnectivityManager = context
.applicationContext
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
override val isNetworkConnected: Boolean
get() = connectivityManager
.getNetworkCapabilities(connectivityManager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
}

View File

@@ -1,87 +0,0 @@
package com.x8bit.bitwarden.data.vault.datasource.network.di
import com.bitwarden.network.service.CiphersService
import com.bitwarden.network.service.CiphersServiceImpl
import com.bitwarden.network.service.DownloadService
import com.bitwarden.network.service.DownloadServiceImpl
import com.bitwarden.network.service.FolderService
import com.bitwarden.network.service.FolderServiceImpl
import com.bitwarden.network.service.SendsService
import com.bitwarden.network.service.SendsServiceImpl
import com.bitwarden.network.service.SyncService
import com.bitwarden.network.service.SyncServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import retrofit2.create
import java.time.Clock
import javax.inject.Singleton
/**
* Provides network dependencies in the vault package.
*/
@Module
@InstallIn(SingletonComponent::class)
object VaultNetworkModule {
@Provides
@Singleton
fun provideCiphersService(
retrofits: Retrofits,
json: Json,
clock: Clock,
): CiphersService = CiphersServiceImpl(
azureApi = retrofits
.createStaticRetrofit()
.create(),
ciphersApi = retrofits.authenticatedApiRetrofit.create(),
json = json,
clock = clock,
)
@Provides
@Singleton
fun providesFolderService(
retrofits: Retrofits,
json: Json,
): FolderService = FolderServiceImpl(
foldersApi = retrofits.authenticatedApiRetrofit.create(),
json = json,
)
@Provides
@Singleton
fun provideSendsService(
retrofits: Retrofits,
json: Json,
clock: Clock,
): SendsService = SendsServiceImpl(
azureApi = retrofits
.createStaticRetrofit()
.create(),
sendsApi = retrofits.authenticatedApiRetrofit.create(),
json = json,
clock = clock,
)
@Provides
@Singleton
fun provideSyncService(
retrofits: Retrofits,
): SyncService = SyncServiceImpl(
syncApi = retrofits.authenticatedApiRetrofit.create(),
)
@Provides
@Singleton
fun provideDownloadService(
retrofits: Retrofits,
): DownloadService = DownloadServiceImpl(
downloadApi = retrofits
.createStaticRetrofit()
.create(),
)
}

View File

@@ -1,79 +0,0 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
/**
* Route constant for navigating to the [SetupAutoFillScreen].
*/
private const val SETUP_AUTO_FILL_PREFIX = "setup_auto_fill"
private const val SETUP_AUTO_FILL_AS_ROOT_PREFIX = "${SETUP_AUTO_FILL_PREFIX}_as_root"
private const val SETUP_AUTO_FILL_NAV_ARG = "isInitialSetup"
private const val SETUP_AUTO_FILL_ROUTE = "$SETUP_AUTO_FILL_PREFIX/{$SETUP_AUTO_FILL_NAV_ARG}"
const val SETUP_AUTO_FILL_AS_ROOT_ROUTE =
"$SETUP_AUTO_FILL_AS_ROOT_PREFIX/{$SETUP_AUTO_FILL_NAV_ARG}"
/**
* Arguments for the [SetupAutoFillScreen] using [SavedStateHandle].
*/
@OmitFromCoverage
data class SetupAutoFillScreenArgs(val isInitialSetup: Boolean) {
constructor(savedStateHandle: SavedStateHandle) : this(
isInitialSetup = requireNotNull(savedStateHandle[SETUP_AUTO_FILL_NAV_ARG]),
)
}
/**
* Navigate to the setup auto-fill screen.
*/
fun NavController.navigateToSetupAutoFillScreen(navOptions: NavOptions? = null) {
this.navigate("$SETUP_AUTO_FILL_PREFIX/false", navOptions)
}
/**
* Navigate to the setup auto-fill screen as the root.
*/
fun NavController.navigateToSetupAutoFillAsRootScreen(navOptions: NavOptions? = null) {
this.navigate("$SETUP_AUTO_FILL_AS_ROOT_PREFIX/true", navOptions)
}
/**
* Add the setup auto-fil screen to the nav graph.
*/
fun NavGraphBuilder.setupAutoFillDestination(onNavigateBack: () -> Unit) {
composableWithSlideTransitions(
route = SETUP_AUTO_FILL_ROUTE,
arguments = setupAutofillNavArgs,
) {
SetupAutoFillScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Add the setup autofil screen to the root nav graph.
*/
fun NavGraphBuilder.setupAutoFillDestinationAsRoot() {
composableWithPushTransitions(
route = SETUP_AUTO_FILL_AS_ROOT_ROUTE,
arguments = setupAutofillNavArgs,
) {
SetupAutoFillScreen(
onNavigateBack = {
// No-Op
},
)
}
}
private val setupAutofillNavArgs = listOf(
navArgument(SETUP_AUTO_FILL_NAV_ARG) {
type = NavType.BoolType
},
)

View File

@@ -1,88 +0,0 @@
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
/**
* Route constants for [SetupUnlockScreen]
*/
private const val SETUP_UNLOCK_PREFIX = "setup_unlock"
private const val SETUP_UNLOCK_AS_ROOT_PREFIX = "${SETUP_UNLOCK_PREFIX}_as_root"
private const val SETUP_UNLOCK_INITIAL_SETUP_ARG = "isInitialSetup"
const val SETUP_UNLOCK_AS_ROOT_ROUTE = "$SETUP_UNLOCK_AS_ROOT_PREFIX/" +
"{$SETUP_UNLOCK_INITIAL_SETUP_ARG}"
private const val SETUP_UNLOCK_ROUTE = "$SETUP_UNLOCK_PREFIX/{$SETUP_UNLOCK_INITIAL_SETUP_ARG}"
/**
* Class to retrieve setup unlock arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class SetupUnlockArgs(
val isInitialSetup: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
isInitialSetup = requireNotNull(savedStateHandle[SETUP_UNLOCK_INITIAL_SETUP_ARG]),
)
}
/**
* Navigate to the setup unlock screen.
*/
fun NavController.navigateToSetupUnlockScreen(navOptions: NavOptions? = null) {
this.navigate("$SETUP_UNLOCK_PREFIX/false", navOptions)
}
/**
* Navigate to the setup unlock screen as root.
*/
fun NavController.navigateToSetupUnlockScreenAsRoot(navOptions: NavOptions? = null) {
this.navigate("$SETUP_UNLOCK_AS_ROOT_PREFIX/true", navOptions)
}
/**
* Add the setup unlock screen to a nav graph.
*/
fun NavGraphBuilder.setupUnlockDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = SETUP_UNLOCK_ROUTE,
arguments = setupUnlockArguments,
) {
SetupUnlockScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Add the setup unlock screen to the root nav graph.
*/
fun NavGraphBuilder.setupUnlockDestinationAsRoot() {
composableWithPushTransitions(
route = SETUP_UNLOCK_AS_ROOT_ROUTE,
arguments = setupUnlockArguments,
) {
SetupUnlockScreen(
onNavigateBack = {
// No-Op
},
)
}
}
private val setupUnlockArguments = listOf(
navArgument(
name = SETUP_UNLOCK_INITIAL_SETUP_ARG,
builder = {
type = NavType.BoolType
},
),
)

View File

@@ -1,28 +0,0 @@
package com.x8bit.bitwarden.ui.auth.feature.environment
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val ENVIRONMENT_ROUTE = "environment"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.environmentDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = ENVIRONMENT_ROUTE,
) {
EnvironmentScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the about screen.
*/
fun NavController.navigateToEnvironment(navOptions: NavOptions? = null) {
navigate(ENVIRONMENT_ROUTE, navOptions)
}

View File

@@ -1,49 +0,0 @@
package com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val EMAIL_ADDRESS: String = "email_address"
private const val MASTER_PASSWORD_HINT_ROUTE: String = "master_password_hint/{$EMAIL_ADDRESS}"
/**
* Class to retrieve login arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class MasterPasswordHintArgs(val emailAddress: String) {
constructor(savedStateHandle: SavedStateHandle) : this(
checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
)
}
/**
* Navigate to the master password hint screen.
*/
fun NavController.navigateToMasterPasswordHint(
emailAddress: String,
navOptions: NavOptions? = null,
) {
this.navigate("master_password_hint/$emailAddress", navOptions)
}
/**
* Add the master password hint screen to the nav graph.
*/
fun NavGraphBuilder.masterPasswordHintDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = MASTER_PASSWORD_HINT_ROUTE,
arguments = listOf(
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
),
) {
MasterPasswordHintScreen(onNavigateBack = onNavigateBack)
}
}

View File

@@ -1,89 +0,0 @@
package com.x8bit.bitwarden.ui.auth.feature.twofactorlogin
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.network.util.base64UrlDecodeOrNull
import com.bitwarden.network.util.base64UrlEncode
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val EMAIL_ADDRESS = "email_address"
private const val PASSWORD = "password"
private const val ORG_IDENTIFIER = "org_identifier"
private const val TWO_FACTOR_LOGIN_PREFIX = "two_factor_login"
private const val NEW_DEVICE_VERIFICATION = "new_device_verification"
private const val TWO_FACTOR_LOGIN_ROUTE =
"$TWO_FACTOR_LOGIN_PREFIX/{$EMAIL_ADDRESS}?" +
"$PASSWORD={$PASSWORD}&" +
"$ORG_IDENTIFIER={$ORG_IDENTIFIER}&" +
"$NEW_DEVICE_VERIFICATION={$NEW_DEVICE_VERIFICATION}"
/**
* Class to retrieve Two-Factor Login arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class TwoFactorLoginArgs(
val emailAddress: String,
val password: String?,
val orgIdentifier: String?,
val isNewDeviceVerification: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
emailAddress = checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
password = savedStateHandle.get<String>(PASSWORD)?.base64UrlDecodeOrNull(),
orgIdentifier = savedStateHandle.get<String>(ORG_IDENTIFIER)?.base64UrlDecodeOrNull(),
isNewDeviceVerification = savedStateHandle.get<Boolean>(NEW_DEVICE_VERIFICATION) ?: false,
)
}
/**
* Navigate to the Two-Factor Login screen.
*/
fun NavController.navigateToTwoFactorLogin(
emailAddress: String,
password: String?,
orgIdentifier: String?,
navOptions: NavOptions? = null,
isNewDeviceVerification: Boolean = false,
) {
this.navigate(
route = "$TWO_FACTOR_LOGIN_PREFIX/$emailAddress?" +
"$PASSWORD=${password?.base64UrlEncode()}&" +
"$ORG_IDENTIFIER=${orgIdentifier?.base64UrlEncode()}&" +
"$NEW_DEVICE_VERIFICATION=$isNewDeviceVerification",
navOptions = navOptions,
)
}
/**
* Add the Two-Factor Login screen to the nav graph.
*/
fun NavGraphBuilder.twoFactorLoginDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = TWO_FACTOR_LOGIN_ROUTE,
arguments = listOf(
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
navArgument(PASSWORD) {
type = NavType.StringType
nullable = true
},
navArgument(ORG_IDENTIFIER) {
type = NavType.StringType
nullable = true
},
navArgument(NEW_DEVICE_VERIFICATION) {
type = NavType.BoolType
},
),
) {
TwoFactorLoginScreen(
onNavigateBack = onNavigateBack,
)
}
}

View File

@@ -1,82 +0,0 @@
package com.x8bit.bitwarden.ui.auth.feature.vaultunlock
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.model.UnlockType
private const val VAULT_UNLOCK_TYPE: String = "unlock_type"
private const val TDE_VAULT_UNLOCK_ROUTE_PREFIX: String = "tde_vault_unlock"
private const val TDE_VAULT_UNLOCK_ROUTE: String =
"$TDE_VAULT_UNLOCK_ROUTE_PREFIX/{$VAULT_UNLOCK_TYPE}"
private const val VAULT_UNLOCK_ROUTE_PREFIX: String = "vault_unlock"
const val VAULT_UNLOCK_ROUTE: String = "$VAULT_UNLOCK_ROUTE_PREFIX/{$VAULT_UNLOCK_TYPE}"
/**
* Class to retrieve vault unlock arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class VaultUnlockArgs(
val unlockType: UnlockType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
unlockType = checkNotNull(savedStateHandle.get<UnlockType>(VAULT_UNLOCK_TYPE)),
)
}
/**
* Navigate to the Vault Unlock screen.
*/
fun NavController.navigateToVaultUnlock(
navOptions: NavOptions? = null,
) {
navigate(
route = "$VAULT_UNLOCK_ROUTE_PREFIX/${UnlockType.STANDARD}",
navOptions = navOptions,
)
}
/**
* Add the Vault Unlock screen to the nav graph.
*/
fun NavGraphBuilder.vaultUnlockDestination() {
composable(
route = VAULT_UNLOCK_ROUTE,
arguments = listOf(
navArgument(VAULT_UNLOCK_TYPE) { type = NavType.EnumType(UnlockType::class.java) },
),
) {
VaultUnlockScreen()
}
}
/**
* Navigate to the Vault Unlock screen for TDE.
*/
fun NavController.navigateToTdeVaultUnlock(
navOptions: NavOptions? = null,
) {
navigate(
route = "$TDE_VAULT_UNLOCK_ROUTE_PREFIX/${UnlockType.TDE}",
navOptions = navOptions,
)
}
/**
* Add the Vault Unlock screen to the TDE nav graph.
*/
fun NavGraphBuilder.tdeVaultUnlockDestination() {
composable(
route = TDE_VAULT_UNLOCK_ROUTE,
arguments = listOf(
navArgument(VAULT_UNLOCK_TYPE) { type = NavType.EnumType(UnlockType::class.java) },
),
) {
VaultUnlockScreen()
}
}

View File

@@ -1,26 +0,0 @@
package com.x8bit.bitwarden.ui.autofill.fido2.manager
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.AssertFido2CredentialResult
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.GetFido2CredentialsResult
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.RegisterFido2CredentialResult
/**
* A manager for completing the FIDO 2 creation process.
*/
interface Fido2CompletionManager {
/**
* Completes the FIDO 2 registration process with the provided [result].
*/
fun completeFido2Registration(result: RegisterFido2CredentialResult)
/**
* Complete the FIDO 2 credential assertion process with the provided [result].
*/
fun completeFido2Assertion(result: AssertFido2CredentialResult)
/**
* Complete the FIDO 2 "Get credentials" process with the provided [result].
*/
fun completeFido2GetCredentialsRequest(result: GetFido2CredentialsResult)
}

View File

@@ -1,17 +0,0 @@
package com.x8bit.bitwarden.ui.autofill.fido2.manager
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.AssertFido2CredentialResult
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.GetFido2CredentialsResult
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.RegisterFido2CredentialResult
/**
* A no-op implementation of [Fido2CompletionManagerImpl] provided when the build version is below
* UPSIDE_DOWN_CAKE (34). These versions do not support [androidx.credentials.CredentialProvider].
*/
object Fido2CompletionManagerUnsupportedApiImpl : Fido2CompletionManager {
override fun completeFido2Registration(result: RegisterFido2CredentialResult) = Unit
override fun completeFido2Assertion(result: AssertFido2CredentialResult) = Unit
override fun completeFido2GetCredentialsRequest(result: GetFido2CredentialsResult) = Unit
}

View File

@@ -1,28 +0,0 @@
package com.x8bit.bitwarden.ui.autofill.fido2.manager.model
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import com.bitwarden.fido.Fido2CredentialAutofillView
import com.bitwarden.ui.util.Text
/**
* Represents the result of fetching FIDO2 credentials.
*/
sealed class GetFido2CredentialsResult {
/**
* Represents a successful result with a list of matching credentials, the original request
* option, and associated user ID.
*/
data class Success(
val credentials: List<Fido2CredentialAutofillView>,
val option: BeginGetPublicKeyCredentialOption,
val userId: String,
) : GetFido2CredentialsResult()
/**
* Represents an error result with a message.
*
* @property message The error message.
*/
data class Error(val message: Text) : GetFido2CredentialsResult()
}

View File

@@ -1,19 +0,0 @@
package com.x8bit.bitwarden.ui.platform.base.util
import android.content.Context
import android.widget.Toast
import com.bitwarden.core.annotation.OmitFromCoverage
/**
* Shows a [Toast] with a message indicating something is not yet implemented.
*/
@OmitFromCoverage
fun showNotYetImplementedToast(context: Context) {
Toast
.makeText(
context,
"Not yet implemented",
Toast.LENGTH_SHORT,
)
.show()
}

View File

@@ -1,195 +0,0 @@
package com.x8bit.bitwarden.ui.platform.components.dialog
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerColors
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldButtonColors
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenRowOfActions
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.util.orNow
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
import java.time.Instant
import java.time.ZoneOffset
import java.time.ZonedDateTime
/**
* A custom composable representing a button that can display the date picker dialog.
*
* This composable displays an [OutlinedTextField] with a dropdown icon as a trailing icon.
* When the field is clicked, a date picker dialog appears.
*
* @param label The displayed label.
* @param currentZonedDateTime The currently displayed time.
* @param formatPattern The pattern to format the displayed time.
* @param onDateSelect The callback to be invoked when a new date is selected.
* @param isEnabled Whether the button is enabled.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
*/
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun BitwardenDateSelectButton(
label: String,
currentZonedDateTime: ZonedDateTime?,
formatPattern: String,
onDateSelect: (ZonedDateTime) -> Unit,
isEnabled: Boolean,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
) {
var shouldShowDialog: Boolean by rememberSaveable { mutableStateOf(false) }
val formattedDate by remember(currentZonedDateTime) {
mutableStateOf(
currentZonedDateTime
?.toFormattedPattern(formatPattern)
?: "mm/dd/yyyy",
)
}
TextField(
modifier = modifier
.clearAndSetSemantics {
role = Role.DropdownList
contentDescription = "$label, $formattedDate"
}
.defaultMinSize(minHeight = 60.dp)
.cardStyle(
cardStyle = cardStyle,
clickEnabled = isEnabled,
onClick = { shouldShowDialog = !shouldShowDialog },
)
.padding(top = 4.dp),
textStyle = BitwardenTheme.typography.bodyLarge,
readOnly = true,
label = { Text(text = label) },
value = formattedDate,
onValueChange = { },
enabled = shouldShowDialog,
trailingIcon = {
BitwardenRowOfActions(
modifier = Modifier.padding(end = 4.dp),
) {
Icon(
painter = rememberVectorPainter(id = R.drawable.ic_chevron_down),
contentDescription = null,
modifier = Modifier.minimumInteractiveComponentSize(),
)
}
},
colors = bitwardenTextFieldButtonColors(),
)
if (shouldShowDialog) {
val datePickerState = rememberDatePickerState(
initialSelectedDateMillis = currentZonedDateTime.orNow().toInstant().toEpochMilli(),
)
DatePickerDialog(
shape = BitwardenTheme.shapes.dialog,
colors = bitwardenDatePickerColors(),
onDismissRequest = { shouldShowDialog = false },
confirmButton = {
BitwardenTextButton(
label = stringResource(id = R.string.ok),
onClick = {
onDateSelect(
ZonedDateTime
.ofInstant(
Instant.ofEpochMilli(
requireNotNull(datePickerState.selectedDateMillis),
),
ZoneOffset.UTC,
)
.withZoneSameLocal(currentZonedDateTime.orNow().zone),
)
shouldShowDialog = false
},
modifier = Modifier.testTag(tag = "AcceptAlertButton"),
)
},
dismissButton = {
BitwardenTextButton(
label = stringResource(id = R.string.cancel),
onClick = { shouldShowDialog = false },
modifier = Modifier.testTag(tag = "DismissAlertButton"),
)
},
modifier = Modifier.semantics {
testTagsAsResourceId = true
testTag = "AlertPopup"
},
) {
DatePicker(
state = datePickerState,
colors = bitwardenDatePickerColors(),
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun bitwardenDatePickerColors(): DatePickerColors = DatePickerColors(
containerColor = BitwardenTheme.colorScheme.background.primary,
titleContentColor = BitwardenTheme.colorScheme.text.secondary,
headlineContentColor = BitwardenTheme.colorScheme.text.primary,
weekdayContentColor = BitwardenTheme.colorScheme.text.primary,
subheadContentColor = BitwardenTheme.colorScheme.text.secondary,
navigationContentColor = BitwardenTheme.colorScheme.icon.primary,
yearContentColor = BitwardenTheme.colorScheme.text.primary,
disabledYearContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
currentYearContentColor = BitwardenTheme.colorScheme.filledButton.foreground,
selectedYearContentColor = BitwardenTheme.colorScheme.filledButton.foreground,
disabledSelectedYearContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
selectedYearContainerColor = BitwardenTheme.colorScheme.filledButton.background,
disabledSelectedYearContainerColor = BitwardenTheme.colorScheme.filledButton.backgroundDisabled,
dayContentColor = BitwardenTheme.colorScheme.text.primary,
disabledDayContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
selectedDayContentColor = BitwardenTheme.colorScheme.text.reversed,
disabledSelectedDayContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
selectedDayContainerColor = BitwardenTheme.colorScheme.filledButton.background,
disabledSelectedDayContainerColor = BitwardenTheme.colorScheme.filledButton.backgroundDisabled,
todayContentColor = BitwardenTheme.colorScheme.outlineButton.foreground,
todayDateBorderColor = BitwardenTheme.colorScheme.outlineButton.border,
dayInSelectionRangeContainerColor = BitwardenTheme.colorScheme.filledButton.background,
dividerColor = BitwardenTheme.colorScheme.stroke.divider,
dayInSelectionRangeContentColor = BitwardenTheme.colorScheme.text.primary,
dateTextFieldColors = bitwardenTextFieldColors(
disabledBorderColor = BitwardenTheme.colorScheme.outlineButton.borderDisabled,
focusedBorderColor = BitwardenTheme.colorScheme.stroke.border,
unfocusedBorderColor = BitwardenTheme.colorScheme.stroke.divider,
),
)

View File

@@ -1,113 +0,0 @@
package com.x8bit.bitwarden.ui.platform.components.dialog
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldButtonColors
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenRowOfActions
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.util.orNow
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
import java.time.ZonedDateTime
/**
* A custom composable representing a button that can display the time picker dialog.
*
* This composable displays an [OutlinedTextField] with a dropdown icon as a trailing icon.
* When the field is clicked, a time picker dialog appears.
*
* @param label The displayed label.
* @param currentZonedDateTime The currently displayed time.
* @param formatPattern The pattern to format the displayed time.
* @param onTimeSelect The callback to be invoked when a new time is selected.
* @param isEnabled Whether the button is enabled.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param is24Hour Indicates if the time selector should use a 24 hour format or a 12 hour format
* with AM/PM.
*/
@Composable
fun BitwardenTimeSelectButton(
label: String,
currentZonedDateTime: ZonedDateTime?,
formatPattern: String,
onTimeSelect: (hour: Int, minute: Int) -> Unit,
isEnabled: Boolean,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
is24Hour: Boolean = false,
) {
var shouldShowDialog: Boolean by rememberSaveable { mutableStateOf(false) }
val formattedTime by remember(currentZonedDateTime) {
mutableStateOf(
currentZonedDateTime
?.toFormattedPattern(formatPattern)
?: "--:-- --",
)
}
TextField(
modifier = modifier
.clearAndSetSemantics {
role = Role.DropdownList
contentDescription = "$label, $formattedTime"
}
.defaultMinSize(minHeight = 60.dp)
.cardStyle(
cardStyle = cardStyle,
clickEnabled = isEnabled,
onClick = { shouldShowDialog = !shouldShowDialog },
)
.padding(top = 4.dp),
textStyle = BitwardenTheme.typography.bodyLarge,
readOnly = true,
label = { Text(text = label) },
value = formattedTime,
onValueChange = { },
enabled = shouldShowDialog,
trailingIcon = {
BitwardenRowOfActions(
modifier = Modifier.padding(end = 4.dp),
) {
Icon(
painter = rememberVectorPainter(id = R.drawable.ic_chevron_down),
contentDescription = null,
modifier = Modifier.minimumInteractiveComponentSize(),
)
}
},
colors = bitwardenTextFieldButtonColors(),
)
if (shouldShowDialog) {
BitwardenTimePickerDialog(
initialHour = currentZonedDateTime.orNow().hour,
initialMinute = currentZonedDateTime.orNow().minute,
onTimeSelect = { hour, minute ->
shouldShowDialog = false
onTimeSelect(hour, minute)
},
onDismissRequest = { shouldShowDialog = false },
is24Hour = is24Hour,
)
}
}

View File

@@ -1,19 +0,0 @@
package com.x8bit.bitwarden.ui.platform.components.util
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
/**
* Returns a [VectorPainter] built from the given [id] to circumvent issues with painter resources
* recomposing unnecessarily.
*/
@Composable
fun rememberVectorPainter(
@DrawableRes id: Int,
): VectorPainter = rememberVectorPainter(
image = ImageVector.vectorResource(id),
)

View File

@@ -1,148 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
private const val SEARCH_TYPE: String = "search_type"
private const val SEARCH_TYPE_SEND_ALL: String = "search_type_sends_all"
private const val SEARCH_TYPE_SEND_TEXT: String = "search_type_sends_text"
private const val SEARCH_TYPE_SEND_FILE: String = "search_type_sends_file"
private const val SEARCH_TYPE_VAULT_ALL: String = "search_type_vault_all"
private const val SEARCH_TYPE_VAULT_LOGINS: String = "search_type_vault_logins"
private const val SEARCH_TYPE_VAULT_CARDS: String = "search_type_vault_cards"
private const val SEARCH_TYPE_VAULT_IDENTITIES: String = "search_type_vault_identities"
private const val SEARCH_TYPE_VAULT_SECURE_NOTES: String = "search_type_vault_secure_notes"
private const val SEARCH_TYPE_VAULT_COLLECTION: String = "search_type_vault_collection"
private const val SEARCH_TYPE_VAULT_NO_FOLDER: String = "search_type_vault_no_folder"
private const val SEARCH_TYPE_VAULT_FOLDER: String = "search_type_vault_folder"
private const val SEARCH_TYPE_VAULT_TRASH: String = "search_type_vault_trash"
private const val SEARCH_TYPE_VAULT_VERIFICATION_CODES: String =
"search_type_vault_verification_codes"
private const val SEARCH_TYPE_ID: String = "search_type_id"
private const val SEARCH_TYPE_VAULT_SSH_KEYS: String = "search_type_vault_ssh_keys"
private const val SEARCH_ROUTE_PREFIX: String = "search"
private const val SEARCH_ROUTE: String = "$SEARCH_ROUTE_PREFIX/{$SEARCH_TYPE}/{$SEARCH_TYPE_ID}"
/**
* Class to retrieve search arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class SearchArgs(
val type: SearchType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
type = determineSearchType(
searchTypeString = requireNotNull(savedStateHandle.get<String>(SEARCH_TYPE)),
id = savedStateHandle.get<String>(SEARCH_TYPE_ID),
),
)
}
/**
* Add search destinations to the nav graph.
*/
fun NavGraphBuilder.searchDestination(
onNavigateBack: () -> Unit,
onNavigateToEditSend: (sendId: String) -> Unit,
onNavigateToEditCipher: (args: VaultAddEditArgs) -> Unit,
onNavigateToViewCipher: (args: VaultItemArgs) -> Unit,
) {
composableWithSlideTransitions(
route = SEARCH_ROUTE,
arguments = listOf(
navArgument(SEARCH_TYPE) { type = NavType.StringType },
navArgument(SEARCH_TYPE_ID) {
type = NavType.StringType
nullable = true
},
),
) {
SearchScreen(
onNavigateBack = onNavigateBack,
onNavigateToEditSend = onNavigateToEditSend,
onNavigateToEditCipher = onNavigateToEditCipher,
onNavigateToViewCipher = onNavigateToViewCipher,
)
}
}
/**
* Navigate to the search screen.
*/
fun NavController.navigateToSearch(
searchType: SearchType,
navOptions: NavOptions? = null,
) {
navigate(
route = "$SEARCH_ROUTE_PREFIX/${searchType.toTypeString()}/${searchType.toIdOrNull()}",
navOptions = navOptions,
)
}
private fun determineSearchType(
searchTypeString: String,
id: String?,
): SearchType =
when (searchTypeString) {
SEARCH_TYPE_SEND_ALL -> SearchType.Sends.All
SEARCH_TYPE_SEND_TEXT -> SearchType.Sends.Texts
SEARCH_TYPE_SEND_FILE -> SearchType.Sends.Files
SEARCH_TYPE_VAULT_ALL -> SearchType.Vault.All
SEARCH_TYPE_VAULT_LOGINS -> SearchType.Vault.Logins
SEARCH_TYPE_VAULT_CARDS -> SearchType.Vault.Cards
SEARCH_TYPE_VAULT_IDENTITIES -> SearchType.Vault.Identities
SEARCH_TYPE_VAULT_SECURE_NOTES -> SearchType.Vault.SecureNotes
SEARCH_TYPE_VAULT_COLLECTION -> SearchType.Vault.Collection(requireNotNull(id))
SEARCH_TYPE_VAULT_NO_FOLDER -> SearchType.Vault.NoFolder
SEARCH_TYPE_VAULT_FOLDER -> SearchType.Vault.Folder(requireNotNull(id))
SEARCH_TYPE_VAULT_TRASH -> SearchType.Vault.Trash
SEARCH_TYPE_VAULT_VERIFICATION_CODES -> SearchType.Vault.VerificationCodes
SEARCH_TYPE_VAULT_SSH_KEYS -> SearchType.Vault.SshKeys
else -> throw IllegalArgumentException("Invalid Search Type")
}
private fun SearchType.toTypeString(): String =
when (this) {
SearchType.Sends.All -> SEARCH_TYPE_SEND_ALL
SearchType.Sends.Files -> SEARCH_TYPE_SEND_FILE
SearchType.Sends.Texts -> SEARCH_TYPE_SEND_TEXT
SearchType.Vault.All -> SEARCH_TYPE_VAULT_ALL
SearchType.Vault.Cards -> SEARCH_TYPE_VAULT_CARDS
is SearchType.Vault.Collection -> SEARCH_TYPE_VAULT_COLLECTION
is SearchType.Vault.Folder -> SEARCH_TYPE_VAULT_FOLDER
SearchType.Vault.Identities -> SEARCH_TYPE_VAULT_IDENTITIES
SearchType.Vault.Logins -> SEARCH_TYPE_VAULT_LOGINS
SearchType.Vault.NoFolder -> SEARCH_TYPE_VAULT_NO_FOLDER
SearchType.Vault.SecureNotes -> SEARCH_TYPE_VAULT_SECURE_NOTES
SearchType.Vault.Trash -> SEARCH_TYPE_VAULT_TRASH
SearchType.Vault.VerificationCodes -> SEARCH_TYPE_VAULT_VERIFICATION_CODES
SearchType.Vault.SshKeys -> SEARCH_TYPE_VAULT_SSH_KEYS
}
private fun SearchType.toIdOrNull(): String? =
when (this) {
SearchType.Sends.All -> null
SearchType.Sends.Files -> null
SearchType.Sends.Texts -> null
SearchType.Vault.All -> null
SearchType.Vault.Cards -> null
is SearchType.Vault.Collection -> collectionId
is SearchType.Vault.Folder -> folderId
SearchType.Vault.Identities -> null
SearchType.Vault.Logins -> null
SearchType.Vault.NoFolder -> null
SearchType.Vault.SecureNotes -> null
SearchType.Vault.Trash -> null
SearchType.Vault.VerificationCodes -> null
SearchType.Vault.SshKeys -> null
}

View File

@@ -1,92 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.navigation
import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
import com.x8bit.bitwarden.ui.platform.feature.settings.about.aboutDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.about.navigateToAbout
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.accountSecurityDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.navigateToAccountSecurity
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.appearanceDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.navigateToAppearance
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.autoFillDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill.blockAutoFillDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill.navigateToBlockAutoFillScreen
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill
import com.x8bit.bitwarden.ui.platform.feature.settings.other.navigateToOther
import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.navigateToVaultSettings
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultSettingsDestination
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
const val SETTINGS_GRAPH_ROUTE: String = "settings_graph"
private const val SETTINGS_ROUTE: String = "settings"
/**
* Add settings destinations to the nav graph.
*/
@Suppress("LongParameterList")
fun NavGraphBuilder.settingsGraph(
navController: NavController,
onNavigateToDeleteAccount: () -> Unit,
onNavigateToExportVault: () -> Unit,
onNavigateToFolders: () -> Unit,
onNavigateToPendingRequests: () -> Unit,
onNavigateToSetupUnlockScreen: () -> Unit,
onNavigateToSetupAutoFillScreen: () -> Unit,
onNavigateToFlightRecorder: () -> Unit,
onNavigateToRecordedLogs: () -> Unit,
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
) {
navigation(
startDestination = SETTINGS_ROUTE,
route = SETTINGS_GRAPH_ROUTE,
) {
composableWithRootPushTransitions(
route = SETTINGS_ROUTE,
) {
SettingsScreen(
onNavigateToAbout = { navController.navigateToAbout() },
onNavigateToAccountSecurity = { navController.navigateToAccountSecurity() },
onNavigateToAppearance = { navController.navigateToAppearance() },
onNavigateToAutoFill = { navController.navigateToAutoFill() },
onNavigateToOther = { navController.navigateToOther() },
onNavigateToVault = { navController.navigateToVaultSettings() },
)
}
aboutDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
)
accountSecurityDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
onNavigateToPendingRequests = onNavigateToPendingRequests,
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
)
appearanceDestination(onNavigateBack = { navController.popBackStack() })
autoFillDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToBlockAutoFillScreen = { navController.navigateToBlockAutoFillScreen() },
onNavigateToSetupAutofill = onNavigateToSetupAutoFillScreen,
)
otherDestination(onNavigateBack = { navController.popBackStack() })
vaultSettingsDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToExportVault = onNavigateToExportVault,
onNavigateToFolders = onNavigateToFolders,
onNavigateToImportLogins = onNavigateToImportLogins,
)
blockAutoFillDestination(onNavigateBack = { navController.popBackStack() })
}
}
/**
* Navigate to the settings screen.
*/
fun NavController.navigateToSettingsGraph(navOptions: NavOptions? = null) {
navigate(SETTINGS_GRAPH_ROUTE, navOptions)
}

View File

@@ -1,34 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val ABOUT_ROUTE = "settings_about"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.aboutDestination(
onNavigateBack: () -> Unit,
onNavigateToFlightRecorder: () -> Unit,
onNavigateToRecordedLogs: () -> Unit,
) {
composableWithPushTransitions(
route = ABOUT_ROUTE,
) {
AboutScreen(
onNavigateBack = onNavigateBack,
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
)
}
}
/**
* Navigate to the about screen.
*/
fun NavController.navigateToAbout(navOptions: NavOptions? = null) {
navigate(ABOUT_ROUTE, navOptions)
}

View File

@@ -1,56 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val FINGERPRINT: String = "fingerprint"
private const val LOGIN_APPROVAL_PREFIX = "login_approval"
private const val LOGIN_APPROVAL_ROUTE = "$LOGIN_APPROVAL_PREFIX?$FINGERPRINT={$FINGERPRINT}"
/**
* Class to retrieve login approval arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class LoginApprovalArgs(val fingerprint: String?) {
constructor(savedStateHandle: SavedStateHandle) : this(
fingerprint = savedStateHandle.get<String>(FINGERPRINT),
)
}
/**
* Add login approval destinations to the nav graph.
*/
fun NavGraphBuilder.loginApprovalDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = LOGIN_APPROVAL_ROUTE,
arguments = listOf(
navArgument(FINGERPRINT) {
type = NavType.StringType
nullable = true
defaultValue = null
},
),
) {
LoginApprovalScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the Login Approval screen.
*/
fun NavController.navigateToLoginApproval(
fingerprint: String?,
navOptions: NavOptions? = null,
) {
navigate("$LOGIN_APPROVAL_PREFIX?$FINGERPRINT=$fingerprint", navOptions)
}

View File

@@ -1,28 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val APPEARANCE_ROUTE = "settings_appearance"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.appearanceDestination(
onNavigateBack: () -> Unit,
) {
composableWithPushTransitions(
route = APPEARANCE_ROUTE,
) {
AppearanceScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the appearance screen.
*/
fun NavController.navigateToAppearance(navOptions: NavOptions? = null) {
navigate(APPEARANCE_ROUTE, navOptions)
}

View File

@@ -1,30 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val FLIGHT_RECORDER_ROUTE = "flight_recorder_config"
/**
* Add flight recorder destination to the nav graph.
*/
fun NavGraphBuilder.flightRecorderDestination(
onNavigateBack: () -> Unit,
) {
composableWithPushTransitions(
route = FLIGHT_RECORDER_ROUTE,
) {
FlightRecorderScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the flight recorder screen.
*/
fun NavController.navigateToFlightRecorder(navOptions: NavOptions? = null) {
navigate(FLIGHT_RECORDER_ROUTE, navOptions)
}

View File

@@ -1,30 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.recordedLogs
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val FLIGHT_RECORDER_RECORDED_LOGS_ROUTE = "flight_recorder_recorded_logs"
/**
* Add recorded logs destination to the nav graph.
*/
fun NavGraphBuilder.recordedLogsDestination(
onNavigateBack: () -> Unit,
) {
composableWithPushTransitions(
route = FLIGHT_RECORDER_RECORDED_LOGS_ROUTE,
) {
RecordedLogsScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the flight recorder recorded logs screen.
*/
fun NavController.navigateToRecordedLogs(navOptions: NavOptions? = null) {
navigate(FLIGHT_RECORDER_RECORDED_LOGS_ROUTE, navOptions)
}

View File

@@ -1,93 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.model.FolderAddEditType
private const val ADD_TYPE: String = "add"
private const val EDIT_TYPE: String = "edit"
private const val EDIT_ITEM_ID: String = "folder_edit_id"
private const val PARENT_FOLDER_NAME: String = "parent_folder_name"
private const val ADD_EDIT_ITEM_PREFIX: String = "folder_add_edit_item"
private const val ADD_EDIT_ITEM_TYPE: String = "folder_add_edit_type"
private const val ADD_EDIT_ITEM_ROUTE: String =
"$ADD_EDIT_ITEM_PREFIX/{$ADD_EDIT_ITEM_TYPE}" +
"?$EDIT_ITEM_ID={$EDIT_ITEM_ID}&$PARENT_FOLDER_NAME={$PARENT_FOLDER_NAME}"
/**
* Class to retrieve folder add & edit arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class FolderAddEditArgs(
val folderAddEditType: FolderAddEditType,
val parentFolderName: String?,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
folderAddEditType = when (requireNotNull(savedStateHandle[ADD_EDIT_ITEM_TYPE])) {
ADD_TYPE -> FolderAddEditType.AddItem
EDIT_TYPE -> FolderAddEditType.EditItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID]))
else -> throw IllegalStateException("Unknown FolderAddEditType.")
},
parentFolderName = savedStateHandle[PARENT_FOLDER_NAME],
)
}
/**
* Add the folder add & edit screen to the nav graph.
*/
fun NavGraphBuilder.folderAddEditDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = ADD_EDIT_ITEM_ROUTE,
arguments = listOf(
navArgument(ADD_EDIT_ITEM_TYPE) { type = NavType.StringType },
navArgument(EDIT_ITEM_ID) {
nullable = true
type = NavType.StringType
},
navArgument(PARENT_FOLDER_NAME) {
nullable = true
type = NavType.StringType
},
),
) {
FolderAddEditScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the folder add & edit screen.
*/
fun NavController.navigateToFolderAddEdit(
folderAddEditType: FolderAddEditType,
parentFolderName: String? = null,
navOptions: NavOptions? = null,
) {
navigate(
route = "$ADD_EDIT_ITEM_PREFIX/${folderAddEditType.toTypeString()}" +
"?$EDIT_ITEM_ID=${folderAddEditType.toIdOrNull()}" +
"&$PARENT_FOLDER_NAME=$parentFolderName",
navOptions = navOptions,
)
}
private fun FolderAddEditType.toTypeString(): String =
when (this) {
is FolderAddEditType.AddItem -> ADD_TYPE
is FolderAddEditType.EditItem -> EDIT_TYPE
}
private fun FolderAddEditType.toIdOrNull(): String? =
when (this) {
is FolderAddEditType.AddItem -> null
is FolderAddEditType.EditItem -> folderId
}

View File

@@ -1,28 +0,0 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val OTHER_ROUTE = "settings_other"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.otherDestination(
onNavigateBack: () -> Unit,
) {
composableWithPushTransitions(
route = OTHER_ROUTE,
) {
OtherScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the about screen.
*/
fun NavController.navigateToOther(navOptions: NavOptions? = null) {
navigate(OTHER_ROUTE, navOptions)
}

View File

@@ -1,41 +0,0 @@
package com.x8bit.bitwarden.ui.platform.manager.snackbar
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onCompletion
/**
* The default implementation of the [SnackbarRelayManager] interface.
*/
class SnackbarRelayManagerImpl : SnackbarRelayManager {
private val mutableSnackbarRelayMap =
mutableMapOf<SnackbarRelay, MutableSharedFlow<BitwardenSnackbarData?>>()
override fun sendSnackbarData(data: BitwardenSnackbarData, relay: SnackbarRelay) {
getSnackbarDataFlowInternal(relay).tryEmit(data)
}
override fun getSnackbarDataFlow(relay: SnackbarRelay): Flow<BitwardenSnackbarData> =
getSnackbarDataFlowInternal(relay)
.onCompletion {
// when the subscription is ended, remove the relay from the map.
mutableSnackbarRelayMap.remove(relay)
}
.filterNotNull()
@OptIn(ExperimentalCoroutinesApi::class)
override fun clearRelayBuffer(relay: SnackbarRelay) {
getSnackbarDataFlowInternal(relay).resetReplayCache()
}
private fun getSnackbarDataFlowInternal(
relay: SnackbarRelay,
): MutableSharedFlow<BitwardenSnackbarData?> =
mutableSnackbarRelayMap.getOrPut(relay) {
bufferedMutableSharedFlow(replay = 1)
}
}

View File

@@ -1,31 +0,0 @@
package com.x8bit.bitwarden.ui.platform.util
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
/**
* Creates a new [SpanStyle] from the specified [color] and [textStyle].
*/
fun spanStyleOf(
color: Color,
textStyle: TextStyle,
): SpanStyle =
SpanStyle(
color = color,
fontSize = textStyle.fontSize,
fontWeight = textStyle.fontWeight,
fontStyle = textStyle.fontStyle,
fontSynthesis = textStyle.fontSynthesis,
fontFamily = textStyle.fontFamily,
fontFeatureSettings = textStyle.fontFeatureSettings,
letterSpacing = textStyle.letterSpacing,
baselineShift = textStyle.baselineShift,
textGeometricTransform = textStyle.textGeometricTransform,
localeList = textStyle.localeList,
background = textStyle.background,
textDecoration = textStyle.textDecoration,
shadow = textStyle.shadow,
platformStyle = textStyle.platformStyle?.spanStyle,
drawStyle = textStyle.drawStyle,
)

View File

@@ -1,22 +0,0 @@
package com.x8bit.bitwarden.ui.platform.util
import java.time.Clock
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAccessor
/**
* Converts the [TemporalAccessor] to a formatted string based on the provided pattern and timezone.
*/
fun TemporalAccessor.toFormattedPattern(
pattern: String,
zone: ZoneId,
): String = DateTimeFormatter.ofPattern(pattern).withZone(zone).format(this)
/**
* Converts the [TemporalAccessor] to a formatted string based on the provided pattern and timezone.
*/
fun TemporalAccessor.toFormattedPattern(
pattern: String,
clock: Clock = Clock.systemDefaultZone(),
): String = toFormattedPattern(pattern = pattern, zone = clock.zone)

View File

@@ -1,102 +0,0 @@
package com.x8bit.bitwarden.ui.tools.feature.generator
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
/**
* The functions below pertain to entry into the [GeneratorScreen].
*/
private const val GENERATOR_MODAL_ROUTE_PREFIX: String = "generator_modal"
private const val GENERATOR_MODE_TYPE: String = "generator_mode_type"
private const val GENERATOR_WEBSITE: String = "generator_website"
private const val USERNAME_GENERATOR: String = "username_generator"
private const val PASSWORD_GENERATOR: String = "password_generator"
const val GENERATOR_ROUTE: String = "generator"
private const val GENERATOR_MODAL_ROUTE: String =
"$GENERATOR_MODAL_ROUTE_PREFIX/{$GENERATOR_MODE_TYPE}?$GENERATOR_WEBSITE={$GENERATOR_WEBSITE}"
/**
* Class to retrieve vault item listing arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class GeneratorArgs(
val type: GeneratorMode,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
type = when (savedStateHandle.get<String>(GENERATOR_MODE_TYPE)) {
USERNAME_GENERATOR -> GeneratorMode.Modal.Username(
website = savedStateHandle[GENERATOR_WEBSITE],
)
PASSWORD_GENERATOR -> GeneratorMode.Modal.Password
else -> GeneratorMode.Default
},
)
}
/**
* Add generator destination to the root nav graph.
*/
fun NavGraphBuilder.generatorDestination(
onNavigateToPasswordHistory: () -> Unit,
onDimNavBarRequest: (Boolean) -> Unit,
) {
composable(GENERATOR_ROUTE) {
GeneratorScreen(
onNavigateToPasswordHistory = onNavigateToPasswordHistory,
onNavigateBack = {},
onDimNavBarRequest = onDimNavBarRequest,
)
}
}
/**
* Add the generator modal destination to the nav graph.
*/
fun NavGraphBuilder.generatorModalDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = GENERATOR_MODAL_ROUTE,
arguments = listOf(
navArgument(GENERATOR_MODE_TYPE) { type = NavType.StringType },
navArgument(GENERATOR_WEBSITE) {
type = NavType.StringType
nullable = true
},
),
) {
GeneratorScreen(
onNavigateToPasswordHistory = {},
onNavigateBack = onNavigateBack,
onDimNavBarRequest = {},
)
}
}
/**
* Navigate to the generator screen in the given mode with the corresponding website, if one exists.
*/
fun NavController.navigateToGeneratorModal(
mode: GeneratorMode.Modal,
navOptions: NavOptions? = null,
) {
val generatorModeType = when (mode) {
GeneratorMode.Modal.Password -> PASSWORD_GENERATOR
is GeneratorMode.Modal.Username -> USERNAME_GENERATOR
}
val website = (mode as? GeneratorMode.Modal.Username)?.website
navigate(
route = "$GENERATOR_MODAL_ROUTE_PREFIX/$generatorModeType?$GENERATOR_WEBSITE=$website",
navOptions = navOptions,
)
}

View File

@@ -1,86 +0,0 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.passwordhistory
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorPasswordHistoryMode
private const val DEFAULT_MODE: String = "default"
private const val ITEM_MODE: String = "item"
private const val PASSWORD_HISTORY_PREFIX: String = "password_history"
private const val PASSWORD_HISTORY_MODE: String = "password_history_mode"
private const val PASSWORD_HISTORY_ITEM_ID: String = "password_history_id"
private const val PASSWORD_HISTORY_ROUTE: String =
PASSWORD_HISTORY_PREFIX +
"/{$PASSWORD_HISTORY_MODE}" +
"?$PASSWORD_HISTORY_ITEM_ID={$PASSWORD_HISTORY_ITEM_ID}"
/**
* Class to retrieve password history arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class PasswordHistoryArgs(
val passwordHistoryMode: GeneratorPasswordHistoryMode,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
passwordHistoryMode = when (requireNotNull(savedStateHandle[PASSWORD_HISTORY_MODE])) {
DEFAULT_MODE -> GeneratorPasswordHistoryMode.Default
ITEM_MODE -> GeneratorPasswordHistoryMode.Item(
requireNotNull(savedStateHandle[PASSWORD_HISTORY_ITEM_ID]),
)
else -> throw IllegalStateException("Unknown VaultAddEditType.")
},
)
}
/**
* Add password history destination to the graph.
*/
fun NavGraphBuilder.passwordHistoryDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = PASSWORD_HISTORY_ROUTE,
arguments = listOf(
navArgument(PASSWORD_HISTORY_MODE) { type = NavType.StringType },
),
) {
PasswordHistoryScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the Password History Screen.
*/
fun NavController.navigateToPasswordHistory(
passwordHistoryMode: GeneratorPasswordHistoryMode,
navOptions: NavOptions? = null,
) {
navigate(
route = "$PASSWORD_HISTORY_PREFIX/${passwordHistoryMode.toModeString()}" +
"?$PASSWORD_HISTORY_ITEM_ID=${passwordHistoryMode.toIdOrNull()}",
navOptions = navOptions,
)
}
private fun GeneratorPasswordHistoryMode.toModeString(): String =
when (this) {
is GeneratorPasswordHistoryMode.Default -> DEFAULT_MODE
is GeneratorPasswordHistoryMode.Item -> ITEM_MODE
}
private fun GeneratorPasswordHistoryMode.toIdOrNull(): String? =
when (this) {
is GeneratorPasswordHistoryMode.Default -> null
is GeneratorPasswordHistoryMode.Item -> itemId
}

View File

@@ -1,88 +0,0 @@
package com.x8bit.bitwarden.ui.tools.feature.send.addsend
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenDateSelectButton
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTimeSelectButton
import com.x8bit.bitwarden.ui.platform.util.orNow
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
/**
* Displays a UI for selecting a customizable date and time.
*
* @param dateLabel The display label for the date selection field.
* @param timeLabel The display label for the time selection field.
* @param currentZonedDateTime The currently selected time, `null` when no time is selected yet.
* @param dateFormatPattern The pattern to use when displaying the date.
* @param timeFormatPattern The pattern for displaying the time.
* @param onDateSelect The callback for being notified of updates to the selected date and time.
* This will only be `null` when there is no selected time.
* @param isEnabled Whether the button is enabled.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
*/
@Composable
fun AddSendCustomDateChooser(
dateLabel: String,
timeLabel: String,
currentZonedDateTime: ZonedDateTime?,
dateFormatPattern: String,
timeFormatPattern: String,
onDateSelect: (ZonedDateTime?) -> Unit,
isEnabled: Boolean,
modifier: Modifier = Modifier,
) {
// This tracks the date component (year, month, and day) and ignores lower level
// components.
var date: ZonedDateTime? by remember { mutableStateOf(currentZonedDateTime) }
// This tracks just the time component (hours and minutes) and ignores the higher level
// components. 0 representing midnight and counting up from there.
var timeMillis: Long by remember {
mutableLongStateOf(
currentZonedDateTime.orNow().let {
it.hour.hours.inWholeMilliseconds + it.minute.minutes.inWholeMilliseconds
},
)
}
val derivedDateTimeMillis: ZonedDateTime? by remember {
derivedStateOf { date?.plus(timeMillis, ChronoUnit.MILLIS) }
}
Row(
modifier = modifier,
) {
BitwardenDateSelectButton(
modifier = Modifier.weight(1f),
label = dateLabel,
formatPattern = dateFormatPattern,
currentZonedDateTime = currentZonedDateTime,
isEnabled = isEnabled,
onDateSelect = {
date = it
onDateSelect(derivedDateTimeMillis)
},
cardStyle = null,
)
BitwardenTimeSelectButton(
modifier = Modifier.weight(1f),
label = timeLabel,
formatPattern = timeFormatPattern,
currentZonedDateTime = currentZonedDateTime,
isEnabled = isEnabled,
onTimeSelect = { hour, minute ->
timeMillis = hour.hours.inWholeMilliseconds + minute.minutes.inWholeMilliseconds
onDateSelect(derivedDateTimeMillis)
},
cardStyle = null,
)
}
}

View File

@@ -1,132 +0,0 @@
package com.x8bit.bitwarden.ui.tools.feature.send.addsend
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import kotlinx.collections.immutable.toImmutableList
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
/**
* Displays UX for choosing deletion date of a send.
*/
@Suppress("LongMethod")
@Composable
fun SendDeletionDateChooser(
currentZonedDateTime: ZonedDateTime,
dateFormatPattern: String,
timeFormatPattern: String,
onDateSelect: (ZonedDateTime) -> Unit,
isEnabled: Boolean,
modifier: Modifier = Modifier,
) {
val defaultOption = DeletionOptions.SEVEN_DAYS
val options = DeletionOptions.entries.associateWith { it.text() }
var selectedOption: DeletionOptions by rememberSaveable { mutableStateOf(defaultOption) }
Column(
modifier = modifier
.defaultMinSize(minHeight = 60.dp)
.cardStyle(cardStyle = CardStyle.Full, paddingVertical = 0.dp),
) {
BitwardenMultiSelectButton(
label = stringResource(id = R.string.deletion_date),
isEnabled = isEnabled,
options = options.values.toImmutableList(),
selectedOption = selectedOption.text(),
onOptionSelected = { selected ->
selectedOption = options.entries.first { it.value == selected }.key
if (selectedOption != DeletionOptions.CUSTOM) {
onDateSelect(
// Add the appropriate milliseconds offset based on the selected option
ZonedDateTime.now().plus(selectedOption.offsetMillis, ChronoUnit.MILLIS),
)
}
},
insets = PaddingValues(top = 6.dp, bottom = 4.dp),
cardStyle = null,
)
AnimatedVisibility(visible = selectedOption == DeletionOptions.CUSTOM) {
Column {
BitwardenHorizontalDivider(modifier = Modifier.padding(start = 16.dp))
AddSendCustomDateChooser(
dateLabel = stringResource(id = R.string.deletion_date),
timeLabel = stringResource(id = R.string.deletion_time),
currentZonedDateTime = currentZonedDateTime,
dateFormatPattern = dateFormatPattern,
timeFormatPattern = timeFormatPattern,
onDateSelect = { onDateSelect(requireNotNull(it)) },
isEnabled = isEnabled,
)
}
}
BitwardenHorizontalDivider(modifier = Modifier.padding(start = 16.dp))
Spacer(modifier = Modifier.height(height = 12.dp))
Text(
text = stringResource(id = R.string.deletion_date_info),
style = BitwardenTheme.typography.bodySmall,
color = BitwardenTheme.colorScheme.text.secondary,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(height = 12.dp))
}
}
private enum class DeletionOptions(
val text: Text,
val offsetMillis: Long,
) {
ONE_HOUR(
text = R.string.one_hour.asText(),
offsetMillis = 1.hours.inWholeMilliseconds,
),
ONE_DAY(
text = R.string.one_day.asText(),
offsetMillis = 1.days.inWholeMilliseconds,
),
TWO_DAYS(
text = R.string.two_days.asText(),
offsetMillis = 2.days.inWholeMilliseconds,
),
THREE_DAYS(
text = R.string.three_days.asText(),
offsetMillis = 3.days.inWholeMilliseconds,
),
SEVEN_DAYS(
text = R.string.seven_days.asText(),
offsetMillis = 7.days.inWholeMilliseconds,
),
THIRTY_DAYS(
text = R.string.thirty_days.asText(),
offsetMillis = 30.days.inWholeMilliseconds,
),
CUSTOM(
text = R.string.custom.asText(),
offsetMillis = -1L,
),
}

View File

@@ -1,78 +0,0 @@
package com.x8bit.bitwarden.ui.tools.feature.send.addsend
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
private const val ADD_TYPE: String = "add"
private const val EDIT_TYPE: String = "edit"
private const val EDIT_ITEM_ID: String = "edit_send_id"
private const val ADD_SEND_ITEM_PREFIX: String = "add_send_item"
private const val ADD_SEND_ITEM_TYPE: String = "add_send_item_type"
const val ADD_SEND_ROUTE: String =
"$ADD_SEND_ITEM_PREFIX/{$ADD_SEND_ITEM_TYPE}?$EDIT_ITEM_ID={$EDIT_ITEM_ID}"
/**
* Class to retrieve send add & edit arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class AddSendArgs(
val sendAddType: AddSendType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
sendAddType = when (requireNotNull(savedStateHandle.get<String>(ADD_SEND_ITEM_TYPE))) {
ADD_TYPE -> AddSendType.AddItem
EDIT_TYPE -> AddSendType.EditItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID]))
else -> throw IllegalStateException("Unknown VaultAddEditType.")
},
)
}
/**
* Add the new send screen to the nav graph.
*/
fun NavGraphBuilder.addSendDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = ADD_SEND_ROUTE,
arguments = listOf(
navArgument(ADD_SEND_ITEM_TYPE) {
type = NavType.StringType
},
),
) {
AddSendScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the new send screen.
*/
fun NavController.navigateToAddSend(
sendAddType: AddSendType,
navOptions: NavOptions? = null,
) {
navigate(
route = "$ADD_SEND_ITEM_PREFIX/${sendAddType.toTypeString()}" +
"?${EDIT_ITEM_ID}=${sendAddType.toIdOrNull()}",
navOptions = navOptions,
)
}
private fun AddSendType.toTypeString(): String =
when (this) {
is AddSendType.AddItem -> ADD_TYPE
is AddSendType.EditItem -> EDIT_TYPE
}
private fun AddSendType.toIdOrNull(): String? =
(this as? AddSendType.EditItem)?.sendItemId

View File

@@ -1,62 +0,0 @@
package com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendAction
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendViewModel
import java.time.ZonedDateTime
/**
* A collection of handler functions for managing actions within the context of adding
* send items.
*/
data class AddSendHandlers(
val onNameChange: (String) -> Unit,
val onFileTypeSelect: () -> Unit,
val onTextTypeSelect: () -> Unit,
val onChooseFileClick: (hasPermission: Boolean) -> Unit,
val onFileChoose: (IntentManager.FileData) -> Unit,
val onTextChange: (String) -> Unit,
val onIsHideByDefaultToggle: (Boolean) -> Unit,
val onMaxAccessCountChange: (Int) -> Unit,
val onPasswordChange: (String) -> Unit,
val onNoteChange: (String) -> Unit,
val onHideEmailToggle: (Boolean) -> Unit,
val onDeactivateSendToggle: (Boolean) -> Unit,
val onDeletionDateChange: (ZonedDateTime) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [AddSendHandlers] by binding actions to the provided
* [AddSendViewModel].
*/
fun create(
viewModel: AddSendViewModel,
): AddSendHandlers =
AddSendHandlers(
onNameChange = { viewModel.trySendAction(AddSendAction.NameChange(it)) },
onFileTypeSelect = { viewModel.trySendAction(AddSendAction.FileTypeClick) },
onTextTypeSelect = { viewModel.trySendAction(AddSendAction.TextTypeClick) },
onChooseFileClick = { viewModel.trySendAction(AddSendAction.ChooseFileClick(it)) },
onFileChoose = { viewModel.trySendAction(AddSendAction.FileChoose(it)) },
onTextChange = { viewModel.trySendAction(AddSendAction.TextChange(it)) },
onIsHideByDefaultToggle = {
viewModel.trySendAction(AddSendAction.HideByDefaultToggle(it))
},
onMaxAccessCountChange = {
viewModel.trySendAction(AddSendAction.MaxAccessCountChange(it))
},
onPasswordChange = { viewModel.trySendAction(AddSendAction.PasswordChange(it)) },
onNoteChange = { viewModel.trySendAction(AddSendAction.NoteChange(it)) },
onHideEmailToggle = {
viewModel.trySendAction(AddSendAction.HideMyEmailToggle(it))
},
onDeactivateSendToggle = {
viewModel.trySendAction(AddSendAction.DeactivateThisSendToggle(it))
},
onDeletionDateChange = {
viewModel.trySendAction(AddSendAction.DeletionDateChange(it))
},
)
}
}

View File

@@ -1,162 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
private const val ADD_TYPE: String = "add"
private const val EDIT_TYPE: String = "edit"
private const val CLONE_TYPE: String = "clone"
private const val EDIT_ITEM_ID: String = "vault_edit_id"
private const val LOGIN: String = "login"
private const val CARD: String = "card"
private const val IDENTITY: String = "identity"
private const val SECURE_NOTE: String = "secure_note"
private const val SSH_KEY: String = "ssh_key"
private const val CIPHER_TYPE: String = "vault_item_type"
private const val ADD_EDIT_ITEM_PREFIX: String = "vault_add_edit_item"
private const val ADD_EDIT_ITEM_TYPE: String = "vault_add_edit_type"
private const val ADD_SELECTED_FOLDER_ID: String = "vault_add_selected_folder_id"
private const val ADD_SELECTED_COLLECTION_ID: String = "vault_add_selected_collection_id"
private const val ADD_EDIT_ITEM_ROUTE: String =
ADD_EDIT_ITEM_PREFIX +
"/{$ADD_EDIT_ITEM_TYPE}" +
"?$EDIT_ITEM_ID={$EDIT_ITEM_ID}" +
"?$CIPHER_TYPE={$CIPHER_TYPE}" +
"?$ADD_SELECTED_FOLDER_ID={$ADD_SELECTED_FOLDER_ID}" +
"?$ADD_SELECTED_COLLECTION_ID={$ADD_SELECTED_COLLECTION_ID}"
/**
* Class to retrieve vault add & edit arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class VaultAddEditArgs(
val vaultAddEditType: VaultAddEditType,
val vaultItemCipherType: VaultItemCipherType,
val selectedFolderId: String? = null,
val selectedCollectionId: String? = null,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
vaultAddEditType = when (requireNotNull(savedStateHandle[ADD_EDIT_ITEM_TYPE])) {
ADD_TYPE -> VaultAddEditType.AddItem
EDIT_TYPE -> VaultAddEditType.EditItem(
vaultItemId = requireNotNull(savedStateHandle[EDIT_ITEM_ID]),
)
CLONE_TYPE -> VaultAddEditType.CloneItem(
vaultItemId = requireNotNull(savedStateHandle[EDIT_ITEM_ID]),
)
else -> throw IllegalStateException("Unknown VaultAddEditType.")
},
vaultItemCipherType = requireNotNull(savedStateHandle.get<String>(CIPHER_TYPE))
.toVaultItemCipherType(),
selectedFolderId = savedStateHandle[ADD_SELECTED_FOLDER_ID],
selectedCollectionId = savedStateHandle[ADD_SELECTED_COLLECTION_ID],
)
}
/**
* Add the vault add & edit screen to the nav graph.
*/
@Suppress("LongParameterList")
fun NavGraphBuilder.vaultAddEditDestination(
onNavigateBack: () -> Unit,
onNavigateToManualCodeEntryScreen: () -> Unit,
onNavigateToQrCodeScanScreen: () -> Unit,
onNavigateToGeneratorModal: (GeneratorMode.Modal) -> Unit,
onNavigateToAttachments: (cipherId: String) -> Unit,
onNavigateToMoveToOrganization: (cipherId: String, showOnlyCollections: Boolean) -> Unit,
) {
composableWithSlideTransitions(
route = ADD_EDIT_ITEM_ROUTE,
arguments = listOf(
navArgument(ADD_EDIT_ITEM_TYPE) { type = NavType.StringType },
navArgument(CIPHER_TYPE) { type = NavType.StringType },
navArgument(ADD_SELECTED_FOLDER_ID) {
type = NavType.StringType
nullable = true
},
navArgument(ADD_SELECTED_COLLECTION_ID) {
type = NavType.StringType
nullable = true
},
navArgument(ADD_SELECTED_COLLECTION_ID) {
type = NavType.StringType
nullable = true
},
),
) {
VaultAddEditScreen(
onNavigateBack = onNavigateBack,
onNavigateToManualCodeEntryScreen = onNavigateToManualCodeEntryScreen,
onNavigateToQrCodeScanScreen = onNavigateToQrCodeScanScreen,
onNavigateToGeneratorModal = onNavigateToGeneratorModal,
onNavigateToAttachments = onNavigateToAttachments,
onNavigateToMoveToOrganization = onNavigateToMoveToOrganization,
)
}
}
/**
* Navigate to the vault add & edit screen.
*/
fun NavController.navigateToVaultAddEdit(
args: VaultAddEditArgs,
navOptions: NavOptions? = null,
) {
navigate(
route = "$ADD_EDIT_ITEM_PREFIX/${args.vaultAddEditType.toTypeString()}" +
"?$EDIT_ITEM_ID=${args.vaultAddEditType.toIdOrNull()}" +
"?$CIPHER_TYPE=${args.vaultItemCipherType.toTypeString()}" +
"?$ADD_SELECTED_FOLDER_ID=${args.selectedFolderId}" +
"?$ADD_SELECTED_COLLECTION_ID=${args.selectedCollectionId}",
navOptions = navOptions,
)
}
private fun VaultAddEditType.toTypeString(): String =
when (this) {
is VaultAddEditType.AddItem -> ADD_TYPE
is VaultAddEditType.EditItem -> EDIT_TYPE
is VaultAddEditType.CloneItem -> CLONE_TYPE
}
private fun VaultAddEditType.toIdOrNull(): String? =
when (this) {
is VaultAddEditType.AddItem -> null
is VaultAddEditType.CloneItem -> vaultItemId
is VaultAddEditType.EditItem -> vaultItemId
}
private fun VaultItemCipherType.toTypeString(): String =
when (this) {
VaultItemCipherType.LOGIN -> LOGIN
VaultItemCipherType.CARD -> CARD
VaultItemCipherType.IDENTITY -> IDENTITY
VaultItemCipherType.SECURE_NOTE -> SECURE_NOTE
VaultItemCipherType.SSH_KEY -> SSH_KEY
}
private fun String.toVaultItemCipherType(): VaultItemCipherType =
when (this) {
LOGIN -> VaultItemCipherType.LOGIN
CARD -> VaultItemCipherType.CARD
IDENTITY -> VaultItemCipherType.IDENTITY
SECURE_NOTE -> VaultItemCipherType.SECURE_NOTE
SSH_KEY -> VaultItemCipherType.SSH_KEY
else -> throw IllegalStateException(
"Edit Item string arguments for VaultAddEditNavigation must match!",
)
}

View File

@@ -1,55 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.attachments
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val ATTACHMENTS_CIPHER_ID = "cipher_id"
private const val ATTACHMENTS_ROUTE_PREFIX = "attachments"
private const val ATTACHMENTS_ROUTE = "$ATTACHMENTS_ROUTE_PREFIX/{$ATTACHMENTS_CIPHER_ID}"
/**
* Class to retrieve arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class AttachmentsArgs(val cipherId: String) {
constructor(savedStateHandle: SavedStateHandle) : this(
cipherId = checkNotNull(savedStateHandle.get<String>(ATTACHMENTS_CIPHER_ID)),
)
}
/**
* Add the attachments screen to the nav graph.
*/
fun NavGraphBuilder.attachmentDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = ATTACHMENTS_ROUTE,
arguments = listOf(
navArgument(ATTACHMENTS_CIPHER_ID) { type = NavType.StringType },
),
) {
AttachmentsScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the attachments screen.
*/
fun NavController.navigateToAttachment(
cipherId: String,
navOptions: NavOptions? = null,
) {
navigate(
route = "$ATTACHMENTS_ROUTE_PREFIX/$cipherId",
navOptions = navOptions,
)
}

View File

@@ -1,58 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.importlogins
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
private const val IMPORT_LOGINS_PREFIX = "import-logins"
private const val IMPORT_LOGINS_NAV_ARG = "snackbarRelay"
private const val IMPORT_LOGINS_ROUTE = "$IMPORT_LOGINS_PREFIX/{$IMPORT_LOGINS_NAV_ARG}"
/**
* Arguments for the [ImportLoginsScreen] using [SavedStateHandle].
*/
@OmitFromCoverage
data class ImportLoginsArgs(val snackBarRelay: SnackbarRelay) {
constructor(savedStateHandle: SavedStateHandle) : this(
snackBarRelay = SnackbarRelay.valueOf(
requireNotNull(savedStateHandle[IMPORT_LOGINS_NAV_ARG]),
),
)
}
/**
* Helper function to navigate to the import logins screen.
*/
fun NavController.navigateToImportLoginsScreen(
snackbarRelay: SnackbarRelay,
navOptions: NavOptions? = null,
) {
navigate(route = "$IMPORT_LOGINS_PREFIX/$snackbarRelay", navOptions = navOptions)
}
/**
* Adds the import logins screen to the navigation graph.
*/
fun NavGraphBuilder.importLoginsScreenDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = IMPORT_LOGINS_ROUTE,
arguments = listOf(
navArgument(IMPORT_LOGINS_NAV_ARG) {
type = NavType.StringType
nullable = false
},
),
) {
ImportLoginsScreen(
onNavigateBack = onNavigateBack,
)
}
}

View File

@@ -1,101 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.item
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
private const val LOGIN: String = "login"
private const val CARD: String = "card"
private const val IDENTITY: String = "identity"
private const val SECURE_NOTE: String = "secure_note"
private const val SSH_KEY: String = "ssh_key"
private const val VAULT_ITEM_CIPHER_TYPE: String = "vault_item_cipher_type"
private const val VAULT_ITEM_PREFIX = "vault_item"
private const val VAULT_ITEM_ID = "vault_item_id"
private const val VAULT_ITEM_ROUTE = "$VAULT_ITEM_PREFIX/{$VAULT_ITEM_ID}" +
"?$VAULT_ITEM_CIPHER_TYPE={$VAULT_ITEM_CIPHER_TYPE}"
/**
* Class to retrieve vault item arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class VaultItemArgs(
val vaultItemId: String,
val cipherType: VaultItemCipherType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
vaultItemId = checkNotNull(savedStateHandle.get<String>(VAULT_ITEM_ID)),
cipherType = requireNotNull(savedStateHandle.get<String>(VAULT_ITEM_CIPHER_TYPE))
.toVaultItemCipherType(),
)
}
/**
* Add the vault item screen to the nav graph.
*/
fun NavGraphBuilder.vaultItemDestination(
onNavigateBack: () -> Unit,
onNavigateToVaultEditItem: (args: VaultAddEditArgs) -> Unit,
onNavigateToMoveToOrganization: (vaultItemId: String, showOnlyCollections: Boolean) -> Unit,
onNavigateToAttachments: (vaultItemId: String) -> Unit,
onNavigateToPasswordHistory: (vaultItemId: String) -> Unit,
) {
composableWithSlideTransitions(
route = VAULT_ITEM_ROUTE,
arguments = listOf(
navArgument(VAULT_ITEM_ID) { type = NavType.StringType },
navArgument(VAULT_ITEM_CIPHER_TYPE) { type = NavType.StringType },
),
) {
VaultItemScreen(
onNavigateBack = onNavigateBack,
onNavigateToVaultAddEditItem = onNavigateToVaultEditItem,
onNavigateToMoveToOrganization = onNavigateToMoveToOrganization,
onNavigateToAttachments = onNavigateToAttachments,
onNavigateToPasswordHistory = onNavigateToPasswordHistory,
)
}
}
/**
* Navigate to the vault item screen.
*/
fun NavController.navigateToVaultItem(
args: VaultItemArgs,
navOptions: NavOptions? = null,
) {
navigate(
route = "$VAULT_ITEM_PREFIX/${args.vaultItemId}" +
"?$VAULT_ITEM_CIPHER_TYPE=${args.cipherType.toTypeString()}",
navOptions = navOptions,
)
}
private fun VaultItemCipherType.toTypeString(): String =
when (this) {
VaultItemCipherType.LOGIN -> LOGIN
VaultItemCipherType.CARD -> CARD
VaultItemCipherType.IDENTITY -> IDENTITY
VaultItemCipherType.SECURE_NOTE -> SECURE_NOTE
VaultItemCipherType.SSH_KEY -> SSH_KEY
}
private fun String.toVaultItemCipherType(): VaultItemCipherType =
when (this) {
LOGIN -> VaultItemCipherType.LOGIN
CARD -> VaultItemCipherType.CARD
IDENTITY -> VaultItemCipherType.IDENTITY
SECURE_NOTE -> VaultItemCipherType.SECURE_NOTE
SSH_KEY -> VaultItemCipherType.SSH_KEY
else -> throw IllegalStateException(
"Edit Item string arguments for VaultAddEditNavigation must match!",
)
}

View File

@@ -1,34 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.item.component
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.semantics
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
/**
* Update Text UI common for all item types.
*/
@Composable
fun VaultItemUpdateText(
header: String,
text: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.semantics(mergeDescendants = true) { },
) {
Text(
text = header,
style = BitwardenTheme.typography.bodySmall,
color = BitwardenTheme.colorScheme.text.secondary,
)
Text(
text = text,
style = BitwardenTheme.typography.bodySmall,
color = BitwardenTheme.colorScheme.text.secondary,
)
}
}

View File

@@ -1,283 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
private const val CARD: String = "card"
private const val COLLECTION: String = "collection"
private const val FOLDER: String = "folder"
private const val IDENTITY: String = "identity"
private const val LOGIN: String = "login"
private const val SSH_KEY: String = "ssh_key"
private const val SECURE_NOTE: String = "secure_note"
private const val SEND_FILE: String = "send_file"
private const val SEND_TEXT: String = "send_text"
private const val TRASH: String = "trash"
private const val VAULT_ITEM_LISTING_PREFIX: String = "vault_item_listing"
private const val VAULT_ITEM_LISTING_AS_ROOT_PREFIX: String = "vault_item_listing_as_root"
private const val VAULT_ITEM_LISTING_TYPE: String = "vault_item_listing_type"
private const val ID: String = "id"
private const val VAULT_ITEM_LISTING_ROUTE: String =
"$VAULT_ITEM_LISTING_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
"?$ID={$ID}"
private const val VAULT_ITEM_LISTING_AS_ROOT_ROUTE: String =
"$VAULT_ITEM_LISTING_AS_ROOT_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
"?$ID={$ID}"
private const val SEND_ITEM_LISTING_PREFIX: String = "send_item_listing"
private const val SEND_ITEM_LISTING_ROUTE: String =
"$SEND_ITEM_LISTING_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
"?$ID={$ID}"
/**
* Class to retrieve vault item listing arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class VaultItemListingArgs(
val vaultItemListingType: VaultItemListingType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
vaultItemListingType = determineVaultItemListingType(
vaultItemListingTypeString = checkNotNull(
savedStateHandle[VAULT_ITEM_LISTING_TYPE],
) as String,
id = savedStateHandle[ID],
),
)
}
/**
* Add the [VaultItemListingScreen] to the nav graph.
*/
@Suppress("LongParameterList")
fun NavGraphBuilder.vaultItemListingDestination(
onNavigateBack: () -> Unit,
onNavigateToVaultItemScreen: (args: VaultItemArgs) -> Unit,
onNavigateToVaultEditItemScreen: (args: VaultAddEditArgs) -> Unit,
onNavigateToVaultItemListing: (vaultItemListingType: VaultItemListingType) -> Unit,
onNavigateToVaultAddItemScreen: (args: VaultAddEditArgs) -> Unit,
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
) {
internalVaultItemListingDestination(
route = VAULT_ITEM_LISTING_ROUTE,
onNavigateBack = onNavigateBack,
onNavigateToAddSendItem = { },
onNavigateToEditSendItem = { },
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
onNavigateToVaultItemListing = onNavigateToVaultItemListing,
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
)
}
/**
* Add the [VaultItemListingScreen] to the nav graph.
*/
@Suppress("LongParameterList")
fun NavGraphBuilder.vaultItemListingDestinationAsRoot(
onNavigateBack: () -> Unit,
onNavigateToVaultItemScreen: (args: VaultItemArgs) -> Unit,
onNavigateToVaultEditItemScreen: (args: VaultAddEditArgs) -> Unit,
onNavigateToVaultAddItemScreen: (args: VaultAddEditArgs) -> Unit,
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
) {
composableWithStayTransitions(
route = VAULT_ITEM_LISTING_AS_ROOT_ROUTE,
arguments = listOf(
navArgument(
name = VAULT_ITEM_LISTING_TYPE,
builder = {
type = NavType.StringType
},
),
),
) {
VaultItemListingScreen(
onNavigateBack = onNavigateBack,
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
onNavigateToAddFolder = onNavigateToAddFolderScreen,
onNavigateToVaultItemListing = {},
onNavigateToAddSendItem = {},
onNavigateToEditSendItem = {},
)
}
}
/**
* Add the [VaultItemListingScreen] to the nav graph.
*/
fun NavGraphBuilder.sendItemListingDestination(
onNavigateBack: () -> Unit,
onNavigateToAddSendItem: () -> Unit,
onNavigateToEditSendItem: (sendId: String) -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
) {
internalVaultItemListingDestination(
route = SEND_ITEM_LISTING_ROUTE,
onNavigateBack = onNavigateBack,
onNavigateToAddSendItem = onNavigateToAddSendItem,
onNavigateToEditSendItem = onNavigateToEditSendItem,
onNavigateToVaultAddItemScreen = { },
onNavigateToAddFolderScreen = { _ -> },
onNavigateToVaultItemScreen = { },
onNavigateToVaultEditItemScreen = { },
onNavigateToVaultItemListing = { },
onNavigateToSearch = { onNavigateToSearchSend(it as SearchType.Sends) },
)
}
/**
* Add the [VaultItemListingScreen] to the nav graph.
*/
@Suppress("LongParameterList")
private fun NavGraphBuilder.internalVaultItemListingDestination(
route: String,
onNavigateBack: () -> Unit,
onNavigateToVaultItemScreen: (args: VaultItemArgs) -> Unit,
onNavigateToVaultEditItemScreen: (args: VaultAddEditArgs) -> Unit,
onNavigateToVaultItemListing: (vaultItemListingType: VaultItemListingType) -> Unit,
onNavigateToVaultAddItemScreen: (args: VaultAddEditArgs) -> Unit,
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
onNavigateToAddSendItem: () -> Unit,
onNavigateToEditSendItem: (sendId: String) -> Unit,
onNavigateToSearch: (searchType: SearchType) -> Unit,
) {
composableWithPushTransitions(
route = route,
arguments = listOf(
navArgument(
name = VAULT_ITEM_LISTING_TYPE,
builder = {
type = NavType.StringType
},
),
navArgument(
name = ID,
builder = {
type = NavType.StringType
nullable = true
},
),
),
) {
VaultItemListingScreen(
onNavigateBack = onNavigateBack,
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
onNavigateToAddSendItem = onNavigateToAddSendItem,
onNavigateToEditSendItem = onNavigateToEditSendItem,
onNavigateToVaultItemListing = onNavigateToVaultItemListing,
onNavigateToSearch = onNavigateToSearch,
onNavigateToAddFolder = onNavigateToAddFolderScreen,
)
}
}
/**
* Navigate to the [VaultItemListingScreen] for vault.
*/
fun NavController.navigateToVaultItemListing(
vaultItemListingType: VaultItemListingType,
navOptions: NavOptions? = null,
) {
navigate(
route = "$VAULT_ITEM_LISTING_PREFIX/${vaultItemListingType.toTypeString()}" +
"?$ID=${vaultItemListingType.toIdOrNull()}",
navOptions = navOptions,
)
}
/**
* Navigate to the [VaultItemListingScreen] for vault.
*/
fun NavController.navigateToVaultItemListingAsRoot(
vaultItemListingType: VaultItemListingType,
navOptions: NavOptions? = null,
) {
navigate(
route = "$VAULT_ITEM_LISTING_AS_ROOT_PREFIX/${vaultItemListingType.toTypeString()}" +
"?$ID=${vaultItemListingType.toIdOrNull()}",
navOptions = navOptions,
)
}
/**
* Navigate to the [VaultItemListingScreen] for sends.
*/
fun NavController.navigateToSendItemListing(
vaultItemListingType: VaultItemListingType,
navOptions: NavOptions? = null,
) {
navigate(
route = "$SEND_ITEM_LISTING_PREFIX/${vaultItemListingType.toTypeString()}" +
"?$ID=${vaultItemListingType.toIdOrNull()}",
navOptions = navOptions,
)
}
private fun VaultItemListingType.toTypeString(): String {
return when (this) {
is VaultItemListingType.Card -> CARD
is VaultItemListingType.Collection -> COLLECTION
is VaultItemListingType.Folder -> FOLDER
is VaultItemListingType.Identity -> IDENTITY
is VaultItemListingType.Login -> LOGIN
is VaultItemListingType.SecureNote -> SECURE_NOTE
is VaultItemListingType.Trash -> TRASH
is VaultItemListingType.SendFile -> SEND_FILE
is VaultItemListingType.SendText -> SEND_TEXT
is VaultItemListingType.SshKey -> SSH_KEY
}
}
private fun VaultItemListingType.toIdOrNull(): String? =
when (this) {
is VaultItemListingType.Collection -> collectionId
is VaultItemListingType.Folder -> folderId
is VaultItemListingType.Card -> null
is VaultItemListingType.Identity -> null
is VaultItemListingType.Login -> null
is VaultItemListingType.SecureNote -> null
is VaultItemListingType.Trash -> null
is VaultItemListingType.SendFile -> null
is VaultItemListingType.SendText -> null
is VaultItemListingType.SshKey -> null
}
private fun determineVaultItemListingType(
vaultItemListingTypeString: String,
id: String?,
): VaultItemListingType {
return when (vaultItemListingTypeString) {
LOGIN -> VaultItemListingType.Login
CARD -> VaultItemListingType.Card
IDENTITY -> VaultItemListingType.Identity
SECURE_NOTE -> VaultItemListingType.SecureNote
SSH_KEY -> VaultItemListingType.SshKey
TRASH -> VaultItemListingType.Trash
FOLDER -> VaultItemListingType.Folder(folderId = id)
COLLECTION -> VaultItemListingType.Collection(collectionId = requireNotNull(id))
SEND_FILE -> VaultItemListingType.SendFile
SEND_TEXT -> VaultItemListingType.SendText
// This should never occur, vaultItemListingTypeString must match
else -> throw IllegalStateException()
}
}

View File

@@ -1,41 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
import androidx.annotation.StringRes
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
/**
* Returns the string resource ID corresponding to the error message for the given
* [Fido2ValidateOriginResult.Error].
*/
val Fido2ValidateOriginResult.Error.messageResourceId: Int
@StringRes
get() = when (this) {
Fido2ValidateOriginResult.Error.ApplicationFingerprintNotVerified -> {
R.string.passkey_operation_failed_because_app_could_not_be_verified
}
Fido2ValidateOriginResult.Error.ApplicationNotFound -> {
R.string.passkey_operation_failed_because_app_not_found_in_asset_links
}
Fido2ValidateOriginResult.Error.AssetLinkNotFound -> {
R.string.passkey_operation_failed_because_of_missing_asset_links
}
Fido2ValidateOriginResult.Error.PasskeyNotSupportedForApp -> {
R.string.passkeys_not_supported_for_this_app
}
Fido2ValidateOriginResult.Error.PrivilegedAppNotAllowed -> {
R.string.passkey_operation_failed_because_browser_is_not_privileged
}
Fido2ValidateOriginResult.Error.PrivilegedAppSignatureNotFound -> {
R.string.passkey_operation_failed_because_browser_signature_does_not_match
}
Fido2ValidateOriginResult.Error.Unknown -> {
R.string.generic_error_message
}
}

View File

@@ -1,70 +0,0 @@
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val VAULT_MOVE_TO_ORGANIZATION_PREFIX = "vault_move_to_organization"
private const val VAULT_MOVE_TO_ORGANIZATION_ID = "vault_move_to_organization_id"
private const val VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS =
"vault_move_to_organization_only_collections"
private const val VAULT_MOVE_TO_ORGANIZATION_ROUTE =
VAULT_MOVE_TO_ORGANIZATION_PREFIX +
"/{$VAULT_MOVE_TO_ORGANIZATION_ID}" +
"/{$VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS}"
/**
* Class to retrieve vault move to organization arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class VaultMoveToOrganizationArgs(
val vaultItemId: String,
val showOnlyCollections: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
vaultItemId = checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ID]) as String,
showOnlyCollections =
(checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS]) as String)
.toBoolean(),
)
}
/**
* Add the vault move to organization screen to the nav graph.
*/
fun NavGraphBuilder.vaultMoveToOrganizationDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = VAULT_MOVE_TO_ORGANIZATION_ROUTE,
arguments = listOf(
navArgument(VAULT_MOVE_TO_ORGANIZATION_ID) { type = NavType.StringType },
navArgument(VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS) {
type = NavType.StringType
},
),
) {
VaultMoveToOrganizationScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the vault move to organization screen.
*/
fun NavController.navigateToVaultMoveToOrganization(
vaultItemId: String,
showOnlyCollections: Boolean,
navOptions: NavOptions? = null,
) {
navigate(
route = "$VAULT_MOVE_TO_ORGANIZATION_PREFIX/$vaultItemId/$showOnlyCollections",
navOptions = navOptions,
)
}

View File

@@ -2,7 +2,7 @@ package com.x8bit.bitwarden
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.annotation.OmitFromCoverage
/**
* An activity to be launched and then immediately closed so that the OS Shade can be collapsed

View File

@@ -4,7 +4,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.annotation.OmitFromCoverage
import dagger.hilt.android.AndroidEntryPoint
/**

View File

@@ -1,13 +1,13 @@
package com.x8bit.bitwarden
import android.content.Intent
import com.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getDuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getSsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.getWebAuthResultOrNull
import com.x8bit.bitwarden.data.auth.util.getYubiKeyResultOrNull
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

View File

@@ -4,7 +4,7 @@ import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden
import android.content.Intent
import androidx.lifecycle.viewModelScope
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.util.getTotpCopyIntentOrNull
@@ -9,7 +10,6 @@ import com.x8bit.bitwarden.data.platform.util.launchWithTimeout
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
import com.x8bit.bitwarden.data.vault.repository.util.statusFor
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull

View File

@@ -5,10 +5,10 @@ import android.content.Intent
import android.os.Build
import androidx.annotation.Keep
import androidx.core.app.AppComponentFactory
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.autofill.BitwardenAutofillService
import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService
import com.x8bit.bitwarden.data.autofill.fido2.BitwardenFido2ProviderService
import com.x8bit.bitwarden.data.credentials.BitwardenCredentialProviderService
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService
import com.x8bit.bitwarden.data.tiles.BitwardenGeneratorTileService
import com.x8bit.bitwarden.data.tiles.BitwardenVaultTileService
@@ -30,7 +30,7 @@ class BitwardenAppComponentFactory : AppComponentFactory() {
* * [BitwardenAccessibilityService]
* * [BitwardenAutofillService]
* * [BitwardenAutofillTileService]
* * [BitwardenFido2ProviderService]
* * [BitwardenCredentialProviderService]
* * [BitwardenVaultTileService]
* * [BitwardenGeneratorTileService]
*/
@@ -63,7 +63,7 @@ class BitwardenAppComponentFactory : AppComponentFactory() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
super.instantiateServiceCompat(
cl,
BitwardenFido2ProviderService::class.java.name,
BitwardenCredentialProviderService::class.java.name,
intent,
)
} else {

View File

@@ -1,13 +1,15 @@
package com.x8bit.bitwarden
import android.app.Application
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager
import com.x8bit.bitwarden.data.platform.manager.LogsManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConfigManager
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManager
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject
/**
@@ -21,6 +23,9 @@ class BitwardenApplication : Application() {
@Inject
lateinit var logsManager: LogsManager
@Inject
lateinit var networkConnectionManager: NetworkConnectionManager
@Inject
lateinit var networkConfigManager: NetworkConfigManager
@@ -32,4 +37,9 @@ class BitwardenApplication : Application() {
@Inject
lateinit var restrictionManager: RestrictionManager
override fun onLowMemory() {
super.onLowMemory()
Timber.w("onLowMemory")
}
}

View File

@@ -11,20 +11,24 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.core.os.LocaleListCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.bitwarden.core.annotation.OmitFromCoverage
import com.bitwarden.annotation.OmitFromCoverage
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.util.setupEdgeToEdge
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
import com.x8bit.bitwarden.data.platform.manager.util.ObserveScreenDataEffect
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.components.util.rememberBitwardenNavController
import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
@@ -32,9 +36,9 @@ import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScre
import com.x8bit.bitwarden.ui.platform.feature.rootnav.ROOT_ROUTE
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.util.appLanguage
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.map
import javax.inject.Inject
/**
@@ -62,69 +66,35 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var debugLaunchManager: DebugMenuLaunchManager
@Suppress("LongMethod")
override fun onCreate(savedInstanceState: Bundle?) {
var shouldShowSplashScreen = true
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
mainViewModel.trySendAction(
MainAction.ReceiveFirstIntent(
intent = intent,
),
)
mainViewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = intent))
}
// Within the app the theme will change dynamically and will be managed by the
// OS, but we need to ensure we properly set the values when upgrading from older versions
// that handle this differently or when the activity restarts.
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
setupEdgeToEdge(appThemeFlow = mainViewModel.stateFlow.map { it.theme })
setContent {
val navController = rememberBitwardenNavController(name = "MainActivity")
SetupEventsEffect(navController = navController)
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
val navController = rememberNavController()
EventsEffect(viewModel = mainViewModel) { event ->
when (event) {
is MainEvent.CompleteAccessibilityAutofill -> {
handleCompleteAccessibilityAutofill(event)
}
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
MainEvent.Recreate -> handleRecreate()
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
is MainEvent.ShowToast -> {
Toast
.makeText(
baseContext,
event.message.invoke(resources),
Toast.LENGTH_SHORT,
)
.show()
}
is MainEvent.UpdateAppLocale -> {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(event.localeName),
)
}
is MainEvent.UpdateAppTheme -> {
AppCompatDelegate.setDefaultNightMode(event.osTheme)
}
}
}
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
LocalManagerProvider(featureFlagsState = state.featureFlagsState) {
ObserveScreenDataEffect(
onDataUpdate = remember(mainViewModel) {
{
mainViewModel.trySendAction(
MainAction.ResumeScreenDataReceived(it),
)
}
{ mainViewModel.trySendAction(MainAction.ResumeScreenDataReceived(it)) }
},
)
BitwardenTheme(theme = state.theme) {
BitwardenTheme(
theme = state.theme,
dynamicColor = state.isDynamicColorsEnabled,
) {
NavHost(
navController = navController,
startDestination = ROOT_ROUTE,
@@ -145,11 +115,7 @@ class MainActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
mainViewModel.trySendAction(
action = MainAction.ReceiveNewIntent(
intent = intent,
),
)
mainViewModel.trySendAction(action = MainAction.ReceiveNewIntent(intent = intent))
}
override fun onResume() {
@@ -196,6 +162,34 @@ class MainActivity : AppCompatActivity() {
.takeIf { it }
?: super.dispatchKeyEvent(event)
@Composable
private fun SetupEventsEffect(navController: NavController) {
EventsEffect(viewModel = mainViewModel) { event ->
when (event) {
is MainEvent.CompleteAccessibilityAutofill -> {
handleCompleteAccessibilityAutofill(event)
}
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
MainEvent.Recreate -> handleRecreate()
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
is MainEvent.ShowToast -> {
Toast
.makeText(baseContext, event.message.invoke(resources), Toast.LENGTH_SHORT)
.show()
}
is MainEvent.UpdateAppLocale -> {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(event.localeName),
)
}
is MainEvent.UpdateAppTheme -> AppCompatDelegate.setDefaultNightMode(event.osTheme)
}
}
}
private fun sendOpenDebugMenuEvent() {
mainViewModel.trySendAction(MainAction.OpenDebugMenu)
}

View File

@@ -4,6 +4,8 @@ import android.content.Intent
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.bitwarden.vault.CipherView
@@ -13,13 +15,13 @@ import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CreateCredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
import com.x8bit.bitwarden.data.credentials.manager.BitwardenCredentialManager
import com.x8bit.bitwarden.data.credentials.util.getCreateCredentialRequestOrNull
import com.x8bit.bitwarden.data.credentials.util.getFido2AssertionRequestOrNull
import com.x8bit.bitwarden.data.credentials.util.getGetCredentialsRequestOrNull
import com.x8bit.bitwarden.data.platform.manager.AppResumeManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
@@ -33,9 +35,7 @@ import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.model.FeatureFlagsState
import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut
@@ -72,7 +72,7 @@ class MainViewModel @Inject constructor(
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
private val specialCircumstanceManager: SpecialCircumstanceManager,
private val garbageCollectionManager: GarbageCollectionManager,
private val fido2CredentialManager: Fido2CredentialManager,
private val bitwardenCredentialManager: BitwardenCredentialManager,
private val intentManager: IntentManager,
private val settingsRepository: SettingsRepository,
private val vaultRepository: VaultRepository,
@@ -88,6 +88,7 @@ class MainViewModel @Inject constructor(
isErrorReportingDialogEnabled = featureFlagManager.getFeatureFlag(
key = FlagKey.MobileErrorReporting,
),
isDynamicColorsEnabled = settingsRepository.isDynamicColorsEnabled,
),
) {
private var specialCircumstance: SpecialCircumstance?
@@ -138,6 +139,12 @@ class MainViewModel @Inject constructor(
.onEach(::trySendAction)
.launchIn(viewModelScope)
settingsRepository
.isDynamicColorsEnabledFlow
.map { MainAction.Internal.DynamicColorsUpdate(it) }
.onEach(::trySendAction)
.launchIn(viewModelScope)
authRepository
.userStateFlow
.drop(count = 1)
@@ -209,6 +216,7 @@ class MainViewModel @Inject constructor(
is MainAction.Internal.ScreenCaptureUpdate -> handleScreenCaptureUpdate(action)
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
is MainAction.Internal.VaultUnlockStateChange -> handleVaultUnlockStateChange()
is MainAction.Internal.DynamicColorsUpdate -> handleDynamicColorsUpdate(action)
is MainAction.Internal.OnMobileErrorReportingReceive -> {
handleOnMobileErrorReportingReceive(action)
}
@@ -269,6 +277,10 @@ class MainViewModel @Inject constructor(
recreateUiAndGarbageCollect()
}
private fun handleDynamicColorsUpdate(action: MainAction.Internal.DynamicColorsUpdate) {
mutableStateFlow.update { it.copy(isDynamicColorsEnabled = action.isDynamicColorsEnabled) }
}
private fun handleFirstIntentReceived(action: MainAction.ReceiveFirstIntent) {
handleIntent(
intent = action.intent,
@@ -310,8 +322,8 @@ class MainViewModel @Inject constructor(
val hasVaultShortcut = intent.isMyVaultShortcut
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
val completeRegistrationData = intent.getCompleteRegistrationDataIntentOrNull()
val fido2CreateCredentialRequest = intent.getFido2CreateCredentialRequestOrNull()
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
val createCredentialRequest = intent.getCreateCredentialRequestOrNull()
val getCredentialsRequest = intent.getGetCredentialsRequestOrNull()
val fido2AssertCredentialRequest = intent.getFido2AssertionRequestOrNull()
when {
passwordlessRequestData != null -> {
@@ -370,23 +382,23 @@ class MainViewModel @Inject constructor(
)
}
fido2CreateCredentialRequest != null -> {
createCredentialRequest != null -> {
// Set the user's verification status when a new FIDO 2 request is received to force
// explicit verification if the user's vault is unlocked when the request is
// received.
fido2CredentialManager.isUserVerified =
fido2CreateCredentialRequest.isUserPreVerified
bitwardenCredentialManager.isUserVerified =
createCredentialRequest.isUserPreVerified
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CreateCredentialRequest,
SpecialCircumstance.ProviderCreateCredential(
createCredentialRequest = createCredentialRequest,
)
// Switch accounts if the selected user is not the active user.
if (authRepository.activeUserId != null &&
authRepository.activeUserId != fido2CreateCredentialRequest.userId
authRepository.activeUserId != createCredentialRequest.userId
) {
authRepository.switchAccount(fido2CreateCredentialRequest.userId)
authRepository.switchAccount(createCredentialRequest.userId)
}
}
@@ -394,7 +406,7 @@ class MainViewModel @Inject constructor(
// Set the user's verification status when a new FIDO 2 request is received to force
// explicit verification if the user's vault is unlocked when the request is
// received.
fido2CredentialManager.isUserVerified =
bitwardenCredentialManager.isUserVerified =
fido2AssertCredentialRequest.isUserPreVerified
specialCircumstanceManager.specialCircumstance =
@@ -403,10 +415,10 @@ class MainViewModel @Inject constructor(
)
}
fido2GetCredentialsRequest != null -> {
getCredentialsRequest != null -> {
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2GetCredentials(
fido2GetCredentialsRequest = fido2GetCredentialsRequest,
SpecialCircumstance.ProviderGetCredentials(
getCredentialsRequest = getCredentialsRequest,
)
}
@@ -482,6 +494,7 @@ class MainViewModel @Inject constructor(
data class MainState(
val theme: AppTheme,
val isScreenCaptureAllowed: Boolean,
val isDynamicColorsEnabled: Boolean,
private val isErrorReportingDialogEnabled: Boolean,
) : Parcelable {
/**
@@ -572,6 +585,13 @@ sealed class MainAction {
* Indicates a relevant change in the current vault lock state.
*/
data object VaultUnlockStateChange : Internal()
/**
* Indicates that the dynamic colors state has changed.
*/
data class DynamicColorsUpdate(
val isDynamicColorsEnabled: Boolean,
) : Internal()
}
}

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
import com.bitwarden.network.model.SyncResponseJson
import com.bitwarden.network.provider.AppIdProvider
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
@@ -12,7 +13,7 @@ import java.time.Instant
* Primary access point for disk information.
*/
@Suppress("TooManyFunctions")
interface AuthDiskSource {
interface AuthDiskSource : AppIdProvider {
/**
* The currently persisted authenticator sync symmetric key. This key is used for
@@ -20,13 +21,6 @@ interface AuthDiskSource {
*/
var authenticatorSyncSymmetricKey: ByteArray?
/**
* Retrieves a unique ID for the application that is stored locally. This will generate a new
* one if it does not yet exist and it will only be reset for new installs or when clearing
* application data.
*/
val uniqueAppId: String
/**
* The currently persisted saved email address (or `null` if not set).
*/

View File

@@ -0,0 +1,65 @@
package com.x8bit.bitwarden.data.auth.datasource.network.di
import com.bitwarden.network.BitwardenServiceClient
import com.bitwarden.network.service.AccountsService
import com.bitwarden.network.service.AuthRequestsService
import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.IdentityService
import com.bitwarden.network.service.NewAuthRequestService
import com.bitwarden.network.service.OrganizationService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides network dependencies in the auth package.
*/
@Module
@InstallIn(SingletonComponent::class)
object AuthNetworkModule {
@Provides
@Singleton
fun providesAccountService(
bitwardenServiceClient: BitwardenServiceClient,
): AccountsService = bitwardenServiceClient.accountsService
@Provides
@Singleton
fun providesAuthRequestsService(
bitwardenServiceClient: BitwardenServiceClient,
): AuthRequestsService = bitwardenServiceClient.authRequestsService
@Provides
@Singleton
fun providesDevicesService(
bitwardenServiceClient: BitwardenServiceClient,
): DevicesService = bitwardenServiceClient.devicesService
@Provides
@Singleton
fun providesIdentityService(
bitwardenServiceClient: BitwardenServiceClient,
): IdentityService = bitwardenServiceClient.identityService
@Provides
@Singleton
fun providesHaveIBeenPwnedService(
bitwardenServiceClient: BitwardenServiceClient,
): HaveIBeenPwnedService = bitwardenServiceClient.haveIBeenPwnedService
@Provides
@Singleton
fun providesNewAuthRequestService(
bitwardenServiceClient: BitwardenServiceClient,
): NewAuthRequestService = bitwardenServiceClient.newAuthRequestService
@Provides
@Singleton
fun providesOrganizationService(
bitwardenServiceClient: BitwardenServiceClient,
): OrganizationService = bitwardenServiceClient.organizationService
}

Some files were not shown because too many files have changed in this diff Show More