1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

..

129 Commits

Author SHA1 Message Date
Federico Maccaroni
ca95ada8e8 MAUI Sample iOS Extension Tap gesture workaround 2023-12-26 18:47:59 -03:00
mpbw2
fa022a1a4f Revert "initial commit of android credential provider service (wip)"
This reverts commit 6011b63958.
2023-12-20 18:23:08 -05:00
mpbw2
6011b63958 initial commit of android credential provider service (wip) 2023-12-20 18:04:21 -05:00
Dinis Vieira
7d79b98bf2 PM-3349 Minor ui fix (space between buttons in delete account page) 2023-12-20 19:04:26 +00:00
Dinis Vieira
bf35d1f2dc PM-3349 minor fix to add space in HomePage between the region picker labels. 2023-12-17 22:26:15 +00:00
Dinis Vieira
c01a8f8d93 PM-3349 Ensure "_isResumed=true" is set on App.xaml.cs:Bootstrap 2023-12-15 23:48:11 +00:00
Federico Maccaroni
8484b4af30 PM-3349 Uncommented the Deploy step for Android job 2023-12-15 16:38:40 -03:00
Federico Maccaroni
6b9faed45f PM-3349 Commented the Deploy step for Android job given that we're using the hotfix-rc branch for testing iOS on TestFlight 2023-12-15 12:08:01 -03:00
Federico Maccaroni
770a1c5dfe Merge branch 'main' into feature/maui-migration 2023-12-15 11:53:57 -03:00
Federico Maccaroni
3a40a4cda8 PM-3349 Upgraded Android targetSdkVersion to 34 2023-12-14 17:52:11 -03:00
Federico Maccaroni
9bcd2e51f7 Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-12-14 17:32:48 -03:00
Federico Maccaroni
741214a1cc PM-3349 PM-3350 Enabled argon2id and fixed one issue with the Uris when getting the icon image 2023-12-14 17:32:43 -03:00
Federico Maccaroni
aad87dfdce Update .github/workflows/build.yml setup-xcode commit hash
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2023-12-14 17:28:10 -03:00
Federico Maccaroni
8fc1e9a3b9 PM-3350 Updated Plugin.Fingerprint so biometrics work 2023-12-14 16:32:04 -03:00
Federico Maccaroni
2a8e15146e PM-3350 Apply Cryptography TrimmerRootAssembly only to iOS 2023-12-14 14:28:20 -03:00
Federico Maccaroni
8559d5908e Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-12-14 13:16:41 -03:00
Federico Maccaroni
f60c4d94fe PM-3350 Fix crash on Release by adding Interpreter on iOS and also adding System.Security.Cryptography to be ignored by the linker 2023-12-14 13:16:32 -03:00
Dinis Vieira
4bf695d18c PM-3349 Fix for app crashing on Android when Dark mode is enabled
Removed unused button style for android
2023-12-13 19:20:43 +00:00
Federico Maccaroni
9ccd0834ff Merge branch 'main' into feature/maui-migration 2023-12-13 10:24:29 -03:00
Federico Maccaroni
a806f17d3b PM-3350 Changed linker to use default mode given that "Full" is presenting some problems as the linker is stripping things it shouldn't and we're trying to solve it. So for now we will use the mode "Link SDK assemblies only" so QA can test. 2023-12-12 21:10:51 -03:00
Federico Maccaroni
436a162df2 Merge branch 'main' into feature/maui-migration 2023-12-12 20:19:42 -03:00
Federico Maccaroni
b5dbb9ae5e PM-3350 Bumped iOS version 2023-12-11 13:21:31 -03:00
Federico Maccaroni
7a5f7c0274 Merge branch 'master' into feature/maui-migration 2023-12-11 11:42:46 -03:00
Federico Maccaroni
5803635f44 PM-3349 PM-3350 Refactored cipher bindings to have a simpler approach reusing a new CipherItemViewModel to avoid unwanted issues in the app 2023-12-07 23:59:06 -03:00
Federico Maccaroni
19c393842f PM-3350 Cleared left ClipLogger from the iOS extensions debug logging. 2023-12-07 18:14:02 -03:00
Federico Maccaroni
15a306490d Merge branch 'feature/maui-migration' into feature/maui-migration-ios-ext-tap-workaround 2023-12-07 17:41:53 -03:00
Federico Maccaroni
a4a3d31c19 PM-3350 Fix iOS extensions navigation and Window/RootViewController handling for TapGestureRecognizer to work 2023-12-07 17:40:20 -03:00
Dinis Vieira
922dc683af PM-3349 Replaced CrossCurrentActivity plugin with MAUI internal CurrentActivity 2023-12-06 17:27:17 +00:00
Dinis Vieira
bae1b3e891 PM-3349 PM-3350 Minor change: removed unneeded HorizontalTextAlignment from Label. 2023-12-04 16:54:55 +00:00
Federico Maccaroni
a5888827c9 Merge branch 'feature/maui-migration' into feature/maui-migration-ios-ext-tap-workaround 2023-12-04 12:18:01 -03:00
Federico Maccaroni
0348940a12 Merge branch 'master' into feature/maui-migration 2023-12-04 12:17:48 -03:00
Federico Maccaroni
4c2998337d Merge branch 'feature/maui-migration' into feature/maui-migration-ios-ext-tap-workaround 2023-12-04 11:44:03 -03:00
Federico Maccaroni
7ea86380f4 PM-3349 PM-3350 build.yml uncommented jobs so we have a more complete workflow 2023-12-04 11:41:55 -03:00
Federico Maccaroni
406f4425c8 PM-3349 PM-3350 Added MAUI label on self-host settings and on about settings to differentiate from XF app 2023-12-04 11:35:35 -03:00
Federico Maccaroni
95ca911444 PM-3350 Improved MTouch linking and extra args on iOS related csprojs 2023-12-04 11:30:54 -03:00
Federico Maccaroni
fa62510e09 PM-3350 build.yml Added several fixes like not using MtouchUseLLVM on the iOS builds to fix they taking forever to build and some changes on the automation CI to do a debug build for the moment 2023-12-04 10:41:41 -03:00
Federico Maccaroni
65dc73495d PM-3350 build.yml added step to validate app for submitting into App Store and have better logs of it 2023-12-04 10:11:29 -03:00
Federico Maccaroni
02a2e41118 PM-3350 fix build.yml Bitwarden.ipa AppStore exported file 2023-12-04 10:10:18 -03:00
Federico Maccaroni
bd6f8295e7 PM-3350 iOS updated CFBundlerShortVersionString to latest one 2023.10.1 2023-12-04 10:01:56 -03:00
Federico Maccaroni
0a0cb7093b Merge branch 'master' into feature/maui-migration
# Conflicts:
#	.github/workflows/build.yml
2023-12-04 09:59:52 -03:00
Dinis Vieira
465e5eff76 PM-3349 PM-3350 Fix for text getting cut/truncated in both account switcher and ciphers/search lists
Issue is due to MAUI but can be avoided by using slightly different layout
2023-12-03 22:17:16 +00:00
Dinis Vieira
5b756aaf7a PM-3349 PM-3350 Added the ability for users to press "Continue" button as a fallback when using the Yubikey if the "SubmitCommand" doesn't trigger automatically. 2023-12-01 16:49:14 +00:00
Dinis Vieira
d168a7b750 PM-3349 PM-3350 Added workaround for More Options to work on Search and Groupings Page
Updated some code to MAUI Style also
2023-12-01 15:02:12 +00:00
Federico Maccaroni
7f4bbafe3c PM-3350 iOS applied workaround on the iOS Autofill and Share extension to maui embed the navigation page with its content page in the Window 2023-11-30 18:23:54 -03:00
Federico Maccaroni
a5804df6a3 PM-3350 iOS extensions TapGestureRecognizer try Window workaround 2023-11-29 18:42:39 -03:00
Federico Maccaroni
bfa2a51608 PM-3349 build.yml configured FDROID job for MAUI 2023-11-27 17:53:01 -03:00
Federico Maccaroni
32be08daae PM-3349 build.yml enabled android build workflow 2023-11-27 09:24:36 -03:00
Dinis Vieira
0a628cc8a8 PM-3349 PM-3350 Workaround to fix issues with text getting cropped/truncated when a Label has both Multiline and LinebreakMode set 2023-11-26 14:46:30 +00:00
Federico Maccaroni
80c424ed03 Update build.yml disabling iOS job to avoid long running process of publish until we can fix that 2023-11-25 10:17:48 -03:00
Federico Maccaroni
99fb5463cf PM-3350 iOS projs disable linking and set Newstandkit as weak framework 2023-11-24 17:47:12 -03:00
Federico Maccaroni
c5d941e1df PM-3350 added linkskip for iOS csprojs 2023-11-24 15:13:45 -03:00
Federico Maccaroni
3edfef6169 PM-3350 build.yml disable trimming on publish so it's faster 2023-11-24 13:59:09 -03:00
Federico Maccaroni
1c8742511a PM-3350 Added Document.Build.props to disable trimming on publish 2023-11-24 12:50:17 -03:00
Federico Maccaroni
8e424d6c05 PM-3350 build.yml changed image back to be macos-13 to see if the build is faster 2023-11-24 11:17:45 -03:00
Federico Maccaroni
390c303b90 PM-3350 build.yml try to fix ILLINK warnings and changed image to be macos-13-arm64 to see if the build is faster 2023-11-24 10:50:36 -03:00
Federico Maccaroni
443f7282b8 PM-3350 build.yml Upgraded iOS build to run on macos-13 image which has XCode 15, and set the XCode 15 version as currently the default one is 14.x 2023-11-23 18:05:58 -03:00
Federico Maccaroni
50109ee70b PM-3350 build.yml changed nuget restore for dotnet restore on iOS build to fix issue on restoring due to msbuild 2023-11-23 17:38:23 -03:00
Federico Maccaroni
9ffdfd51cc PM-3350 build.yml updated iOS build and ignore Android build to try the CI faster 2023-11-23 17:20:17 -03:00
Federico Maccaroni
04e409f3c6 PM-3349 build.yml add Android "prod" variant 2023-11-23 16:32:10 -03:00
Federico Maccaroni
6c143bad57 PM-3349 build.yml updated env helpers variables and set specific csproj to build on Android so not to build iOS extensions 2023-11-23 15:39:16 -03:00
Federico Maccaroni
286e18059a PM-3349 PM-3350 build.cake updated paths 2023-11-23 14:08:14 -03:00
Federico Maccaroni
553bf9ed0a PM-3349 build.yml commented verify format and just set qa as variant on MAUI Android for faster checks on CI 2023-11-23 13:08:18 -03:00
Federico Maccaroni
ddb27b52d3 PM-3349 build.yml update paths for MAUI Android 2023-11-23 12:49:29 -03:00
Federico Maccaroni
6c504aa710 PM-3349 Started to configure build.yml for MAUI Android 2023-11-23 12:18:31 -03:00
Dinis Vieira
62254aef8d PM-3349 PM-3350 Fixed issue where creating an account with weak/exposed password would get stuck after the Captcha (if a captcha is shown)
Changed App.xaml.cs NavigateImpl to be private
2023-11-21 22:39:44 +00:00
Dinis Vieira
06a0195a6d PM-3349 Added workaround for Android to avoid issues with setting MainPage when app is in background. They are now kept on a Queue to be executed after the app has resumed.
Updated some things on App.xaml.cs to the new MAUI style
2023-11-21 21:26:14 +00:00
Dinis Vieira
df2b0b21d5 PM-3350 Added workaround for iOS Avatar icon again. 2023-11-20 22:07:40 +00:00
Federico Maccaroni
e6b1bab860 PM-3350 Updated AppCenter package to latest version 5.0.3 and updated some things into MAUI style 2023-11-20 17:29:16 -03:00
Federico Maccaroni
ce41eb0578 PM-3350 Fixed account toolbar item and TitleView on SendAddOnlyPage, also removed comments on AvatarImageSource given the workaround is not needed anymore to draw the image successfully. 2023-11-20 16:09:23 -03:00
Federico Maccaroni
1a0b52d644 PM-3350 Fixed/Updated all MAUI-Migration TODOs 2023-11-20 13:10:03 -03:00
Federico Maccaroni
16ada4993c Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration
# Conflicts:
#	src/Core/Pages/Settings/OtherSettingsPageViewModel.cs
2023-11-20 10:49:42 -03:00
Federico Maccaroni
3795f3aa17 PM-3350 Added watchOS app to main project and fixed some csproj conditions for runtime identifiers on iOS. 2023-11-20 10:44:56 -03:00
Dinis Vieira
eceb506c77 Merge branch 'master' into feature/maui-migration
Fixed conflicts

# Conflicts:
#	src/App/Resources/AppResources.cs.Designer.cs
#	src/App/Resources/AppResources.da.Designer.cs
#	src/App/Resources/AppResources.de.Designer.cs
#	src/App/Resources/AppResources.es.Designer.cs
#	src/App/Resources/AppResources.fi.Designer.cs
#	src/App/Resources/AppResources.fr.Designer.cs
#	src/App/Resources/AppResources.hi.Designer.cs
#	src/App/Resources/AppResources.hr.Designer.cs
#	src/App/Resources/AppResources.hu.Designer.cs
#	src/App/Resources/AppResources.id.Designer.cs
#	src/App/Resources/AppResources.it.Designer.cs
#	src/App/Resources/AppResources.ja.Designer.cs
#	src/App/Resources/AppResources.nl.Designer.cs
#	src/App/Resources/AppResources.pl.Designer.cs
#	src/App/Resources/AppResources.pt-BR.Designer.cs
#	src/App/Resources/AppResources.pt-PT.Designer.cs
#	src/App/Resources/AppResources.ro.Designer.cs
#	src/App/Resources/AppResources.ru.Designer.cs
#	src/App/Resources/AppResources.sk.Designer.cs
#	src/App/Resources/AppResources.sv.Designer.cs
#	src/App/Resources/AppResources.th.Designer.cs
#	src/App/Resources/AppResources.tr.Designer.cs
#	src/App/Resources/AppResources.uk.Designer.cs
#	src/App/Resources/AppResources.vi.Designer.cs
#	src/App/Resources/AppResources.zh-Hans.Designer.cs
#	src/App/Resources/AppResources.zh-Hant.Designer.cs
#	src/Core/Controls/Settings/BaseSettingControlView.cs
#	src/Core/Pages/Accounts/EnvironmentPageViewModel.cs
#	src/Core/Pages/Accounts/HomePage.xaml.cs
#	src/Core/Pages/Accounts/HomePageViewModel.cs
#	src/Core/Pages/Accounts/SetPasswordPageViewModel.cs
#	src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs
#	src/Core/Pages/TabsPage.cs
#	src/Core/Services/StateMigrationService.cs
#	src/Core/Utilities/BoolToColorConverter.cs
2023-11-19 15:06:02 +00:00
Dinis Vieira
2c7870d660 PM-3349 PM-3350 Removed AsyncCommand "wrapper" and added AsyncRelayCommand directly in all ViewModels that were using the other one. 2023-11-16 22:31:01 +00:00
Dinis Vieira
f02b3415a3 PM-3350 Removed workaround for iOS issue with Avatar icon as it's now fixed in latest .Net8 release. 2023-11-14 23:24:10 +00:00
Federico Maccaroni
beda4e9ff8 PM-3350 Updated PCL Crypto to latest alpha version to fix "Dll not found NCrypt" issue 2023-11-14 18:43:23 -03:00
Federico Maccaroni
df4d89cd52 PM-3350 Fixed iOS Extensions navigation to several pages and improved avoiding duplicate calls to OnNavigatedTo 2023-11-14 13:43:59 -03:00
Federico Maccaroni
5f12bb9747 PM-3350 Fixed iOS Share extension lazy views loading and an issue with the avatar loading. Also discovered issue with TapGestureRecognizer not working on MAUI Embedding 2023-11-13 21:30:56 -03:00
Federico Maccaroni
5712639492 Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-11-13 14:44:24 -03:00
Federico Maccaroni
e0a3c301fb PM-3349 PM-3350 Changed SendViewCell and its binding to be directly against the ViewModel 2023-11-13 14:44:19 -03:00
Dinis Vieira
27306fe353 PM-3349 PM-3350 Added the partial MAUI Community Toolkit implementation for TouchEffect. This is a temporary solution until they finalize this and add it to their nuget package.
This allows implementing the LongPressCommand in AccountSwitchingOverlay and also have the "Ripple effect" animation when touching an item in Android
2023-11-13 09:59:54 +00:00
Dinis Vieira
a31f15559f PM-3350 Fix for colored html text on iOS 2023-11-11 15:05:51 +00:00
Dinis Vieira
0e75f3f5c8 PM-3349 Fix for HTML Label on Android. Color hex doesn't need to be cropped anymore. 2023-11-10 23:30:54 +00:00
Federico Maccaroni
363da063fa PM-3349 PM-3350 Changed AccountViewCell and its binding to be directly against the ViewModel 2023-11-10 17:30:08 -03:00
Federico Maccaroni
974a571455 PM-3349 PM-3350 Changed binding set for CipherViewCell so it updates accordingly 2023-11-10 14:41:41 -03:00
Dinis Vieira
e0c721098c PM-3349 PM-3350 Migrated remaining AutomationProperties to SemanticProperties.
All 'IsInAccessibleTree="True"' were deleted.
'IsInAccessibleTree="False"' were kept and stayed in code.
2023-11-09 23:01:04 +00:00
Federico Maccaroni
a86f6e3034 PM-3350 Added configurations for Release mode (no FDroid yet) 2023-11-09 17:15:02 -03:00
Federico Maccaroni
fe17288b99 Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-11-09 16:16:26 -03:00
Federico Maccaroni
7324da9d47 PM-3350 Fixed iOS extensions (iOS.Extension and iOS.ShareExtension) to load and commented argon2id from debug configuration until we have the .a compiled again with the new platform/arch 2023-11-09 16:16:17 -03:00
Dinis Vieira
69aa6fc044 PM-3349 PM-3350 Started using OnNavigatedTo/From instead of On(Dis)Appearing for LoginPage and LoginSSOPage to avoid the "Modal loading" issues in iOS
Also had to add IsInitialized logic to these pages because OnNavigatedTo can be called twice in some scenario.
Some minor migrations of Device to DeviceInfo was also done
2023-11-09 15:09:37 +00:00
Dinis Vieira
e840dc2e30 Merge branch 'master' into feature/maui-migration
Fixed conflicts and added null check on ForwardEmailDomainName

# Conflicts:
#	src/Core/Pages/Vault/CipherAddEditPage.xaml
#	src/Core/Pages/Vault/CipherDetailsPage.xaml
#	src/iOS.Core/Renderers/CollectionView/ExtendedGroupableItemsViewController.cs
2023-11-05 23:59:30 +00:00
Dinis Vieira
eb25ee5d1b PM-3349 Fix for Android buttons having all letters in Caps 2023-11-04 18:39:08 +00:00
Dinis Vieira
840f24dbe5 PM-3349 Fix for TabGestureRecognizer not working inside the StackLayout area of IconLabelButton 2023-11-04 18:08:44 +00:00
Dinis Vieira
c6309173ba PM-3349 PM-3350 Migrated IconLabelButton Frames to Borders to fix issue with TapGestureRecognizer in Android
Also fixed some minor "styles" for normal Button and IconLabelButton (both Android and iOS)
2023-11-04 17:48:59 +00:00
Dinis Vieira
946c465f0c PM-3349 Added Handler that enables the ExtendedDatePicker to get IsFocused events in Android. This is a workaround for fixing the current bug where it's not possible to select the "current day" in the expiration date of a Send.
Fix for TimePicker not displaying default Time Value
Updated some "Device" code to the new MAUI "DeviceInfo"
2023-11-02 02:32:12 +00:00
Dinis Vieira
e90409d842 PM-3350 Added Scrollview on HomePage so that the "Create account" button can be accessed in smaller devices like iPhone SE. 2023-10-29 21:34:33 +00:00
Dinis Vieira
484b5a5160 PM-3349 PM-3350 Minor change to HomePage to set fixed Image height otherwise it takes more space than it did in the old Xamarin Forms app.
Added HIdeSoftInputOnTapped on several pages (the ones with Entry controls) to allow hiding the keyboard when tapping "outside" of it. (just like we did in Xamarin Forms app)
2023-10-28 19:02:54 +01:00
Dinis Vieira
2688209752 PM-3350 Removed duplicate reference to LaunchScreen.storyboard 2023-10-27 17:19:20 +01:00
Dinis Vieira
53e0e55915 PM-3350 Quick workaround to allow 2nd factor auth to not get stuck in iOS in modals.
Updated some older "Device" code to the newer MAUI code.
2023-10-27 15:36:47 +01:00
Dinis Vieira
ca57948d9f PM-3350 Removed MAUI Splash Screen. Fixed iOS Privacy Screen logo (hardcoded image to avoid it getting cropped) 2023-10-27 11:41:21 +01:00
Dinis Vieira
aaf082faba PM-3349 Fixed white tint color not appearing on images added as MAU IImage SVG
PM-3349 PM-3350 Fix for Avatar text not adjusting to white/black color correctly
2023-10-25 22:20:01 +01:00
Dinis Vieira
e7aeb08cae PM-3350 Checked some [MAUI-Migration] and changed as needed.
TimePickerHandlerMappings: Remove old code for forcing the Wheel. After testing without it wheel picker is still used so this code shouldn't be needed anymore.
AppDelegate.ContinueUserActivity: Uncommented and changed the iOS ContinueUserActivity. It needs to call Platform.ContinueUserActivity according with Xamarin Essentials migration docs.
2023-10-24 23:37:18 +01:00
Dinis Vieira
f177968958 Checked some [MAUI-Migration] and deleted when it's working as intended.
SearchBarHandlerMapping: IME options working as intended
SliderHandlerMappings: The MAUI "replacement" for Color.Default seems to be White so the old use case doesn't seem to be needed anymore.
2023-10-24 22:25:05 +01:00
Dinis Vieira
f1d59210f9 PM-3349 PRM-3350 Replaced XZing with Camera.MAUI for QRCodes 2023-10-23 15:17:14 +01:00
Dinis Vieira
62213c0aaf PM-3350 Added some missing images in iOS 2023-10-20 13:33:39 +01:00
Dinis Vieira
8be8abb8fe PM-3350 Migrated some Device to DeviceInfo and added temporary workaround with some comments to be able to see the Generated Password on iOS 2023-10-20 00:04:19 +01:00
Dinis Vieira
174acbc558 PM-3349
Added Argon libraries for Android
minor change to gitignore so that the Argon x86 lib is not ignored on the Android platform
2023-10-18 15:41:20 +01:00
Dinis Vieira
4bcc7c0d71 Enabled argon2Id for iOS 2023-10-18 12:20:41 +01:00
Dinis Vieira
14b2960f30 PM-3350 Removed ButtonHandlerMappings and some other code related with fonts as MAUI is taking care of Accessibility and no custom code should be needed
Migrated SelectableLabelRenderer to Handler
Cleaned LabelHandlerMappings and added logic to migrate the CustomLabelRenderer
2023-10-18 01:21:19 +01:00
Dinis Vieira
455c3a257c PM-3350 Migrated the CustomViewCellRenderer for iOS 2023-10-17 22:23:04 +01:00
Dinis Vieira
8c623a2067 PM-3349 ToolbarHandler created for setting text on Android go back buttons. 2023-10-16 20:45:58 +01:00
Dinis Vieira
3cdf1c2f0e PM-3349 Replaced the FabShadowEffect with the new MAUI Shadow to fix the buggy shadows on the Android Fab Button. 2023-10-16 00:47:52 +01:00
Dinis Vieira
ce9503fa0c PM-3349 PM-3350
Added (migrated) CustomNavigationHandler (which should partially fix the AvatarIcon in the NavBar in iOS)
Added (migrated) CustomContentPageHandler (which should mostly place the AvatarIcon in the navBar in the correct place for iOS)
Added Task.Delay (workaround) to allow the Avatar to load in iOS on the LoginPage
Added workaround for iOS bug with the toolbar size (more info in comment in AvatarImageSource.cs)
Went through the AccountViewCell MAUI-Migration comments. (and deleted/added more comments as needed)
Migrated some Device calls to DeviceInfo and MainThread
Added (migrated) CustomTabbedHandler (for managing the iOS TabBar)
2023-10-15 22:06:26 +01:00
Dinis Vieira
2e4da1b87d hardcoded AccountViewCell Avatar image to 40x40 to avoid current iOS/Android bugs where they fill much larger space. 2023-10-13 18:49:30 +01:00
Dinis Vieira
d63a219272 PM-3349 Implemented HybridWebViewHandler for iOS 2023-10-07 23:44:24 +01:00
Dinis Vieira
c92cd90a97 PM-3349 Implemented HybridWebViewHandler for Android which enables 2nd factor auth flows
Ensured CustomTabbedPageHandler had it's DisconnectHandler called
Some minor code upgrades of older obsolete Xamarin Forms code.
2023-10-07 17:25:29 +01:00
Dinis Vieira
1dcd3a3daa PM-3349 Changed UseMauiApp init so that Android Handlers still get added 2023-10-07 16:56:08 +01:00
Federico Maccaroni
efb8763d3c Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-10-04 12:45:08 -03:00
Federico Maccaroni
90649d1c8b PM-3350 MAUI Migration Fix iOS Autofill extension 2023-10-04 12:35:43 -03:00
Dinis Vieira
828055791f PM-3349 Removed Deploy from iOS.Autofill to allow running Android
Changed MainApplication SpecialFolder.Personal to SpecialFolder.LocalApplicationData
2023-10-02 21:15:33 +01:00
Dinis Vieira
87eebda55f Changes to solution to hopefully fix Config Mappings 2023-10-02 16:40:31 +01:00
Dinis Vieira
7542d1ae1c fix conflicts 2023-10-02 16:08:09 +01:00
Federico Maccaroni
990de4ea4e PM-3349 PM-3350 MAUI Migration Start iOS extensions 2023-10-02 12:05:57 -03:00
Dinis Vieira
0dbc23f734 PM-3349 PM-3350 Add null checks on CipherDetailsPageVM to avoid crash opening Secure Notes. 2023-10-01 23:15:55 +01:00
Dinis Vieira
9f6c8601d3 TabBarEffect removed and it's behavior is now taken care of by CustomTabbedPageHandler 2023-10-01 22:32:37 +01:00
Dinis Vieira
8b7f9b9fb3 PM-3349: Android
Added CustomTabbedPageHandler for Android to handle the tab "reselection" for PopToRoot.
Commented support for Windows in App.csproj
Disabled Interpreter on Android to avoid very slow app in Debug (during Login for example)
Added some null checks that were causing crashes (on GeneratorPageVM and PickerVM)
Minor TabsPage cleanup
2023-10-01 15:35:04 +01:00
Federico Maccaroni
d17789d5ee PM-3349 PM-3350 MAUI Migration fix nullable bindings and fallbacks 2023-09-29 12:27:12 -03:00
Federico Maccaroni
b8f0747dd4 PM-3349 PM-3350 MAUI Migration fix nullable exception bindings and AsyncCommand canExecute null exception 2023-09-29 12:12:01 -03:00
Federico Maccaroni
8ef9443b1e PM-3349 PM-3350 MAUI Migration Initial 2023-09-29 11:02:19 -03:00
417 changed files with 8689 additions and 7231 deletions

25
.github/CODEOWNERS vendored
View File

@@ -1,21 +1,12 @@
# Please sort into logical groups with comment headers. Sort groups in order of specificity.
# For example, default owners should always be the first group.
# Sort lines alphabetically within these groups to avoid accidentally adding duplicates.
# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
#
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Default file owners
* @bitwarden/dept-development-mobile
# The following owners will be the default owners for everything in the repo.
# Unless a later match takes precedence
# @bitwarden/tech-leads
# DevOps for Actions and other workflow changes
.github/workflows @bitwarden/dept-devops
# DevOps for Version Bumping
src/App/Platforms/Android/AndroidManifest.xml
src/iOS.Autofill/Info.plist
src/iOS.Extension/Info.plist
src/iOS.ShareExtension/Info.plist
src/App/Platforms/iOS/Info.plist
@bitwarden/dept-development-mobile
## Auth team files ##
@@ -30,14 +21,14 @@ src/watchOS @bitwarden/team-vault-dev
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
## Crowdin Sync files ##
src/Core/Resources/Localization @bitwarden/team-tools-dev
src/App/Resources @bitwarden/team-tools-dev
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev
store/apple @bitwarden/team-tools-dev
store/google @bitwarden/team-tools-dev
## Locales ##
src/Core/Resources/Localization/AppResources.Designer.cs
src/Core/Resources/Localization/AppResources.resx
src/App/Resources/AppResources.Designer.cs
src/App/Resources/AppResources.resx
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
store/apple/en
store/google/en

17
.github/renovate.json vendored
View File

@@ -2,21 +2,22 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"github>bitwarden/renovate-config:pin-actions",
":combinePatchMinorReleases",
":dependencyDashboard",
":maintainLockFilesWeekly",
":pinAllExceptPeerDependencies",
":prConcurrentLimit10",
":rebaseStalePrs",
":separateMajorReleases",
"group:monorepos",
"schedule:weekends"
"schedule:weekends",
":separateMajorReleases"
],
"enabledManagers": ["github-actions", "npm", "nuget"],
"commitMessagePrefix": "[deps]:",
"commitMessageTopic": "{{depName}}",
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
"packageRules": [
{
"groupName": "cargo minor",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "gh minor",
"matchManagers": ["github-actions"],
@@ -31,6 +32,6 @@
"groupName": "nuget minor",
"matchManagers": ["nuget"],
"matchUpdateTypes": ["minor", "patch"]
}
},
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<EFBFBD>
 K<>Y#<23>(<28><><EFBFBD><EFBFBD>EI֐߄T?)l<><6C><EFBFBD><18><><10>"=<3D>|<7C>'e<><0E>m<EFBFBD>/~<7E><>' F<><46>><3E><><EFBFBD><EFBFBD>l<EFBFBD>b<EFBFBD>[<5B>+R<><52>iL<69><4C>"<22><><EFBFBD>~V:<3A><>p<EFBFBD>a<17>ڵel%8t<38><74><EFBFBD>y<<3C>n<EFBFBD><6E><EFBFBD>aU<61>w<16>JD<4A><44><1F><>We<57>9<EFBFBD><39><EFBFBD><EFBFBD><x8d<38>O<EFBFBD>j\<14>ד<EFBFBD><D793><EFBFBD>Vq<56><71>֋
Ǻ<EFBFBD>-<2D>#<23><><11><>]$<24>(<28>l,<2C>Br<42><02><>d<><64><EFBFBD>•a-<2D><><EFBFBD>:<3A><>:<3A><04>9b,!Em<02><19><>Qf<>D<EFBFBD>g<EFBFBD><06><0E>x(P<>ȡ~<7E>͹<EFBFBD><CDB9> <09><>[<06><>!:<3A>;f<><66>

Binary file not shown.

BIN
.github/secrets/play_creds.json.gpg vendored Normal file

Binary file not shown.

Binary file not shown.

29
.github/workflows/_cut_rc.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: Cut RC Branch
on:
workflow_call:
jobs:
cut-rc:
name: Cut RC branch
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Check if RC branch exists
run: |
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
echo "Remote RC branch exists."
echo "Please delete current RC branch before running again."
exit 1
fi
- name: Cut RC branch
run: |
git switch --quiet --create rc
git push --quiet --set-upstream origin rc

View File

@@ -1,5 +0,0 @@
---
name: Build Beta
on:
workflow_dispatch:

View File

@@ -31,7 +31,6 @@ jobs:
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
setup:
name: Setup
runs-on: ubuntu-22.04
@@ -59,7 +58,6 @@ jobs:
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi
android:
name: Android
runs-on: windows-2022
@@ -69,8 +67,7 @@ jobs:
matrix:
variant: ["prod", "qa"]
env:
android_folder_path: src\App\Platforms\Android
android_folder_path_bash: src/App/Platforms/Android
android_folder_path: src/App/Platforms/Android
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
@@ -78,14 +75,14 @@ jobs:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '8.0.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
# This step might be obsolete at some point as .NET MAUI workloads
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
@@ -96,8 +93,7 @@ jobs:
- name: Install Microsoft OpenJDK 11
run: |
choco install microsoft-openjdk11 --no-progress
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | `
Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Output "Java Home: $env:JAVA_HOME"
- name: Print environment
@@ -113,43 +109,39 @@ jobs:
with:
fetch-depth: 0
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Download secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p $HOME/secrets
mkdir -p ~/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name app_play-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_play-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name app_upload-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_upload-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name play_creds.json --file $HOME/secrets/play_creds.json --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash
- name: Download secrets - Google Services
- name: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }}
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name google-services.json --file ./${{ env.android_folder_path_bash }}/google-services.json --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.android_folder_path }}/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_folder_path_bash }}/AndroidManifest.xml
./${{ env.android_folder_path }}/AndroidManifest.xml
shell: bash
- name: Restore packages
@@ -158,75 +150,83 @@ jobs:
- name: Restore tools
run: dotnet tool restore
# - name: Run Core tests
# run: |
# dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" `
# /p:CustomConstants=UT
# - name: Verify Format
# run: dotnet tool run dotnet-format --check
# - name: Report test results
# uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0
# if: always()
# with:
# name: Test Results
# path: "**/test-results.trx"
# reporter: dotnet-trx
# fail-on-error: true
# - name: Run Core tests
# run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx"
#- name: Report test results
# uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0
# if: always()
# with:
# name: Test Results
# path: "**/test-results.trx"
# reporter: dotnet-trx
# fail-on-error: true
- name: Build Play Store publisher
if: ${{ matrix.variant == 'prod' }}
run: dotnet build .\store\google\Publisher\Publisher.csproj /p:Configuration=Release
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Setup Android build (${{ matrix.variant }})
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build & Sign Android
- name: Build Android
run: |
$configuration = "Release";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
dotnet build $projToBuild -c $configuration -f ${{ env.target-net-version }}-android
- name: Sign Android Build
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: |
$projToBuild = "$($env:GITHUB_WORKSPACE)/${{ env.main_app_project_path }}";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
$signingUploadKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_upload-keystore.jks"
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidPackageFormats=aab `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingUploadKeyStore `
/p:AndroidSigningKeyAlias=upload `
/p:AndroidSigningKeyPass="$($env:UPLOAD_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:UPLOAD_KEYSTORE_PASSWORD)" --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidPackageFormats=aab /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_upload-keystore.jks") /p:AndroidSigningKeyAlias=upload /p:AndroidSigningKeyPass="$($env:UPLOAD_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:UPLOAD_KEYSTORE_PASSWORD)" --no-restore
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.aab";
$signedAabDestPath = "$($env:GITHUB_WORKSPACE)\$($packageName).aab";
$signedAabPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
$signingPlayKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_play-keystore.jks"
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingPlayKeyStore `
/p:AndroidSigningKeyAlias=bitwarden `
/p:AndroidSigningKeyPass="$($env:PLAY_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:PLAY_KEYSTORE_PASSWORD)" --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_play-keystore.jks") /p:AndroidSigningKeyAlias=bitwarden /p:AndroidSigningKeyPass="$($env:PLAY_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:PLAY_KEYSTORE_PASSWORD)" --no-restore
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.apk";
$signedApkDestPath = "$($env:GITHUB_WORKSPACE)\$($packageName).apk";
Copy-Item $signedApkPath $signedApkDestPath
- name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
@@ -234,7 +234,7 @@ jobs:
- name: Upload Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
@@ -242,7 +242,7 @@ jobs:
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
@@ -262,7 +262,7 @@ jobs:
- name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt
@@ -270,7 +270,7 @@ jobs:
- name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
@@ -283,20 +283,20 @@ jobs:
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' ) }}
run: |
$publisherPath = "$($env:GITHUB_WORKSPACE)\store\google\Publisher\bin\Release\net8.0\Publisher.dll"
$credsPath = "$($HOME)\secrets\play_creds.json"
$aabPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden.aab"
$track = "internal"
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/net7.0/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
TRACK="internal"
dotnet $publisherPath $credsPath $aabPath $track
dotnet $PUBLISHER_PATH $CREDS_PATH $AAB_PATH $TRACK
shell: bash
f-droid:
name: F-Droid Build
runs-on: windows-2022
env:
android_folder_path: src\App\Platforms\Android
android_folder_path_bash: src/App/Platforms/Android
android_folder_path: src/App/Platforms/Android
android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml
steps:
- name: Setup NuGet
@@ -310,9 +310,9 @@ jobs:
dotnet-version: '8.0.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
# This step might be obsolete at some point as .NET MAUI workloads
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
@@ -337,25 +337,23 @@ jobs:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Download secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: app_fdroid-keystore.jks
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file ${{ env.android_folder_path_bash }}/$FILE --output none
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_fdroid-keystore.jks ./.github/secrets/app_fdroid-keystore.jks.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_manifest_path }}
@@ -363,16 +361,28 @@ jobs:
- name: Clean for F-Droid
run: |
$directoryBuildProps = $($env:GITHUB_WORKSPACE + "/Directory.Build.props");
$appPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
Write-Output "##### Back up project files"
# Write-Output "########################################"
# Write-Output "##### Clean Android and App"
# Write-Output "########################################"
# msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
# msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $directoryBuildProps $($directoryBuildProps + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
@@ -382,39 +392,80 @@ jobs:
$xml.Save($androidManifest);
Write-Output "##### Enabling FDROID constant"
# Write-Output "########################################"
# Write-Output "##### Uninstall from App.csproj"
# Write-Output "########################################"
(Get-Content $directoryBuildProps).Replace('<!-- <CustomConstants>FDROID</CustomConstants> -->', '<CustomConstants>FDROID</CustomConstants>') | Set-Content $directoryBuildProps
# $xml=New-Object XML;
# $xml.Load($appPath);
# $ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
# $ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
# $firebaseNode=$xml.SelectSingleNode(`
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
# $firebaseNode.ParentNode.RemoveChild($firebaseNode);
# $daggerNode=$xml.SelectSingleNode(`
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
# $daggerNode.ParentNode.RemoveChild($daggerNode);
# $safetyNetNode=$xml.SelectSingleNode(`
# "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
# $safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
# $xml.Save($appPath);
# Write-Output "########################################"
# Write-Output "##### Uninstall from Core.csproj"
# Write-Output "########################################"
# $xml=New-Object XML;
# $xml.Load($corePath);
# $appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
# $appCenterNode.ParentNode.RemoveChild($appCenterNode);
# $xml.Save($corePath);
- name: Restore packages
run: dotnet restore
- name: Build & Sign F-Droid
- name: Build for F-Droid
run: |
$configuration = "Release";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
Write-Output "########################################"
Write-Output "##### Build $configuration FDROID
Write-Output "########################################"
dotnet build $projToBuild -c $configuration -f ${{ env.target-net-version }}-android /p:CustomConstants="FDROID"
- name: Sign for F-Droid
env:
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
run: |
$projToBuild = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_project_path }}";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$packageName = "com.x8bit.bitwarden";
Write-Output "########################################"
Write-Output "##### Sign FDroid"
Write-Output "########################################"
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_fdroid-keystore.jks"
dotnet build $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
/p:AndroidSigningKeyAlias=bitwarden `
/p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" ` --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_fdroid-keystore.jks") /p:AndroidSigningKeyAlias=bitwarden /p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" /p:CustomConstants="FDROID" --no-restore
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\$($packageName)-Signed.apk";
$signedApkDestPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden-fdroid.apk";
$signedApkPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
@@ -426,13 +477,12 @@ jobs:
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt
if-no-files-found: error
ios:
name: Apple iOS
runs-on: macos-13
@@ -445,19 +495,19 @@ jobs:
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: 15.1
xcode-version: 15.0.1
- name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '8.0.x'
# This step might be obsolete at some point as .NET MAUI workloads
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
@@ -475,7 +525,7 @@ jobs:
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -486,71 +536,71 @@ jobs:
keyvault: "bitwarden-ci"
secrets: "appcenter-ios-token"
- name: Download Provisioning Profiles secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: profiles
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p $HOME/secrets
profiles=(
"dist_autofill.mobileprovision"
"dist_bitwarden.mobileprovision"
"dist_extension.mobileprovision"
"dist_share_extension.mobileprovision"
"dist_bitwarden_watch_app.mobileprovision"
"dist_bitwarden_watch_app_extension.mobileprovision"
)
mkdir -p ~/secrets
for FILE in "${profiles[@]}"
do
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
done
- name: Download Google Services secret
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: GoogleService-Info.plist
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file src/watchOS/bitwarden/$FILE --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/bitwarden-mobile-key.p12 ./.github/secrets/bitwarden-mobile-key.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/iphone-distribution-cert.p12 ./.github/secrets/iphone-distribution-cert.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_autofill.mobileprovision ./.github/secrets/dist_autofill.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_share_extension.mobileprovision \
./.github/secrets/dist_share_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app.mobileprovision \
./.github/secrets/dist_watch_app.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app_extension.mobileprovision \
./.github/secrets/dist_watch_app_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/watchOS/bitwarden/GoogleService-Info.plist ./.github/secrets/GoogleService-Info.plist.gpg
- name: Increment version
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "########################################"
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "########################################"
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
agvtool new-version -all $BUILD_NUMBER
- name: Update Entitlements
run: |
echo "########################################"
echo "##### Updating Entitlements"
echo "########################################"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
- name: Get certificates
run: |
mkdir -p $HOME/certificates
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/ios-distribution |
jq -r .value | base64 -d > $HOME/certificates/ios-distribution.p12
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \
-T /usr/bin/security
security import ~/secrets/bitwarden-mobile-key.p12 -k build.keychain -P $MOBILE_KEY_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security import ~/secrets/iphone-distribution-cert.p12 -k build.keychain -P $DIST_CERT_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Set up provisioning profiles
@@ -559,8 +609,8 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_bitwarden_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_bitwarden_watch_app_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
@@ -588,50 +638,74 @@ jobs:
- name: Bulid WatchApp
run: |
echo "########################################"
echo "##### Build WatchApp with Release Configuration"
echo "########################################"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "########################################"
echo "##### Done"
echo "########################################"
- name: Archive Build for App Store
run: |
echo "##### Archive for Release ios-arm64"
Write-Output "########################################"
Write-Output "##### Archive for Release ios-arm64
Write-Output "########################################"
dotnet publish ${{ env.main_app_project_path }} -c Release -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=ios-arm64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
shell: pwsh
- name: Archive Build for Mobile Automation
run: |
echo "##### Archive Debug for iossimulator-x64"
Write-Output "########################################"
Write-Output "##### Archive Debug for iossimulator-x64
Write-Output "########################################"
dotnet build ${{ env.main_app_project_path }} -c Debug -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=iossimulator-x64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
ls $HOME/Library/Developer/Xcode/Archives
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
shell: pwsh
- name: Export .ipa for App Store
env:
EXPORT_OPTIONS_PATH: ./.github/resources/export-options-app-store.plist
EXPORT_PATH: ./bitwarden-export
run: |
EXPORT_OPTIONS_PATH="./.github/resources/export-options-app-store.plist"
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
EXPORT_PATH="./bitwarden-export"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
- name: Export .app for Automation CI
env:
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
EXPORT_PATH: ./bitwarden-export
run: |
ARCHIVE_PATH="./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64"
EXPORT_PATH="./bitwarden-export"
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
- name: Copy all dSYMs files to upload
env:
EXPORT_PATH: ./bitwarden-export
WATCH_ARCHIVE_DSYMS_PATH: ./src/watchOS/bitwarden.xcarchive/dSYMs/
WATCH_DSYMS_EXPORT_PATH: ./bitwarden-export/Watch_dSYMs
run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
EXPORT_PATH="./bitwarden-export"
WATCH_ARCHIVE_DSYMS_PATH="./src/watchOS/bitwarden.xcarchive/dSYMs/"
WATCH_DSYMS_EXPORT_PATH="$EXPORT_PATH/Watch_dSYMs"
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
mkdir $WATCH_DSYMS_EXPORT_PATH
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: Bitwarden iOS
path: |
@@ -640,7 +714,7 @@ jobs:
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
@@ -674,7 +748,10 @@ jobs:
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: |
echo "########################################"
echo "##### Uploading Watch dSYMs to Firebase"
echo "########################################"
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \;
- name: Validate app in App Store
@@ -690,6 +767,7 @@ jobs:
run: |
xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
- name: Deploy to App Store
if: |
@@ -721,7 +799,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -733,13 +811,13 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
with:
config: crowdin.yml
crowdin_branch_name: main
crowdin_branch_name: main
upload_sources: true
upload_translations: false
@@ -757,14 +835,30 @@ jobs:
steps:
- name: Check if any job failed
if: |
(github.ref == 'refs/heads/main'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure')
run: exit 1
(github.ref == 'refs/heads/main')
|| (github.ref == 'refs/heads/rc')
|| (github.ref == 'refs/heads/hotfix-rc')
env:
CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }}
F_DROID_STATUS: ${{ needs.f-droid.result }}
IOS_STATUS: ${{ needs.ios.result }}
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$ANDROID_STATUS" = "failure" ]; then
exit 1
elif [ "$F_DROID_STATUS" = "failure" ]; then
exit 1
elif [ "$IOS_STATUS" = "failure" ]; then
exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
if: failure()
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

View File

@@ -1,53 +0,0 @@
---
name: Cleanup RC Branch
on:
push:
tags:
- v**
jobs:
delete-rc:
name: Delete RC Branch
runs-on: ubuntu-22.04
steps:
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve bot secrets
id: retrieve-bot-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout main
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
- name: Check if a RC branch exists
id: branch-check
run: |
hotfix_rc_branch_check=$(git ls-remote --heads origin hotfix-rc | wc -l)
rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${hotfix_rc_branch_check}" -gt 0 ]]; then
echo "hotfix-rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=hotfix-rc" >> $GITHUB_OUTPUT
elif [[ "${rc_branch_check}" -gt 0 ]]; then
echo "rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=rc" >> $GITHUB_OUTPUT
fi
- name: Delete RC branch
env:
BRANCH_NAME: ${{ steps.branch-check.outputs.name }}
run: |
if ! [[ -z "$BRANCH_NAME" ]]; then
git push --quiet origin --delete $BRANCH_NAME
echo "Deleted $BRANCH_NAME branch." | tee -a $GITHUB_STEP_SUMMARY
fi

View File

@@ -15,10 +15,10 @@ jobs:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -30,7 +30,7 @@ jobs:
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@@ -28,7 +28,7 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
if: inputs.release_type != 'Dry Run'
if: github.event.inputs.release_type != 'Dry Run'
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
@@ -38,15 +38,15 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@main
with:
release-type: ${{ inputs.release_type }}
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
file: src/App/Platforms/Android/AndroidManifest.xml
file: src/Android/Properties/AndroidManifest.xml
- name: Get branch name
id: branch
@@ -55,8 +55,8 @@ jobs:
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create GitHub deployment
if: ${{ inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
id: deployment
with:
token: '${{ secrets.GITHUB_TOKEN }}'
@@ -67,16 +67,16 @@ jobs:
- name: Download all artifacts
if: ${{ inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
- name: Dry Run - Download all artifacts
if: ${{ inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -86,8 +86,8 @@ jobs:
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release
if: ${{ inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
@@ -103,16 +103,16 @@ jobs:
draft: true
- name: Update deployment status to Success
if: ${{ inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure
if: ${{ inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'
@@ -126,11 +126,11 @@ jobs:
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Download F-Droid .apk artifact
if: ${{ inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -138,8 +138,8 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk
- name: Dry Run - Download F-Droid .apk artifact
if: ${{ inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -147,7 +147,7 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: '16.x'
@@ -176,19 +176,13 @@ jobs:
- name: Install Node dependencies
run: npm install
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Download secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name store_fdroid-keystore.jks --file ./store/fdroid/keystore.jks --output none
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
- name: Compile for F-Droid Store
env:
@@ -217,5 +211,5 @@ jobs:
cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages
if: ${{ inputs.release_type != 'Dry Run' }}
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: npm run deploy

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: 'Run stale action'
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
with:
stale-issue-label: 'needs-reply'
stale-pr-label: 'needs-changes'

View File

@@ -1,5 +1,5 @@
---
name: Auto Bump Mobile Version
name: Version Auto Bump
on:
push:
@@ -7,25 +7,34 @@ on:
- v**
jobs:
bump-version:
name: Bump Mobile Version
setup:
name: "Setup"
runs-on: ubuntu-22.04
outputs:
version_number: ${{ steps.version.outputs.new-version }}
steps:
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Checkout Branch
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Retrieve bot secrets
id: retrieve-bot-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Trigger Version Bump workflow
- name: Calculate bumped version
id: version
env:
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
RELEASE_TAG: ${{ github.ref }}
run: |
echo '{"cut_rc_branch": "false"}' | \
gh workflow run version-bump.yml --json --repo bitwarden/mobile
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
echo "Current Major: $CURR_MAJOR"
echo "Current Patch: $CURR_PATCH"
NEW_PATCH=$((CURR_PATCH+1))
NEW_VER=$CURR_MAJOR.$NEW_PATCH
echo "New Version: $NEW_VER"
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
name: Bump version to ${{ needs.setup.outputs.version_number }}
needs: setup
uses: ./.github/workflows/version-bump.yml
with:
version_number: ${{ needs.setup.outputs.version_number }}
secrets: inherit

View File

@@ -1,13 +1,13 @@
---
name: Version Bump
run-name: Version Bump - v${{ inputs.version_number }}
on:
workflow_dispatch:
inputs:
version_number_override:
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
required: false
type: string
version_number:
description: "New version (example: '2024.1.0')"
required: true
cut_rc_branch:
description: "Cut RC branch?"
default: true
@@ -15,36 +15,13 @@ on:
jobs:
bump_version:
name: Bump Version
name: "Bump Version to v${{ inputs.version_number }}"
runs-on: ubuntu-22.04
outputs:
version: ${{ steps.set-final-version-output.outputs.version }}
steps:
- name: Validate version input
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-check@main
with:
version: ${{ inputs.version_number_override }}
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Check if RC branch exists
if: ${{ inputs.cut_rc_branch == true }}
run: |
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
echo "Remote RC branch exists."
echo "Please delete current RC branch before running again."
exit 1
fi
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
@@ -55,46 +32,39 @@ jobs:
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout Branch
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
ref: main
repository: bitwarden/mobile
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Setup git
run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Create Version Branch
id: create-branch
run: |
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
git switch -c $NAME
echo "name=$NAME" >> $GITHUB_OUTPUT
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Get current version
id: current-version
run: |
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
run: sudo apt install -y libxml2-utils
- name: Verify input version
if: ${{ inputs.version_number_override != '' }}
env:
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
NEW_VERSION: ${{ inputs.version_number_override }}
NEW_VERSION: ${{ inputs.version_number }}
run: |
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/Android/Properties/AndroidManifest.xml)
# Error if version has not changed.
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
echo "Version has not changed."
@@ -110,93 +80,40 @@ jobs:
exit 1
fi
- name: Calculate next release version
if: ${{ inputs.version_number_override == '' }}
id: calculate-next-version
uses: bitwarden/gh-actions/version-next@main
with:
version: ${{ steps.current-version.outputs.version }}
- name: Bump Version - Android XML - Version Override
if: ${{ inputs.version_number_override != '' }}
id: bump-version-override
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ inputs.version_number_override }}
version: ${{ inputs.version_number }}
file_path: "src/Android/Properties/AndroidManifest.xml"
- name: Bump Version - Android XML - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
id: bump-version-automatic
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Autofill - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.Autofill
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Autofill - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Extension - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.Extension
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Extension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Extension - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Extension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.ShareExtension - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.ShareExtension
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.ShareExtension - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
- name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
version: ${{ inputs.version_number }}
file_path: "src/iOS/Info.plist"
- name: Bump Version - iOS - Version Override
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Set Job output
id: set-final-version-output
- name: Setup git
run: |
if [[ "${{ steps.bump-version-override.outcome }}" == "success" ]]; then
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
elif [[ "${{ steps.bump-version-automatic.outcome }}" == "success" ]]; then
echo "version=${{ steps.calculate-next-version.outputs.version }}" >> $GITHUB_OUTPUT
fi
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Check if version changed
id: version-changed
@@ -210,7 +127,7 @@ jobs:
- name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ steps.set-final-version-output.outputs.version }}" -a
run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
- name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
@@ -224,7 +141,7 @@ jobs:
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
TITLE: "Bump version to ${{ steps.set-final-version-output.outputs.version }}"
TITLE: "Bump version to ${{ inputs.version_number }}"
run: |
PR_URL=$(gh pr create --title "$TITLE" \
--base "main" \
@@ -240,58 +157,24 @@ jobs:
- [X] Other
## Objective
Automated version bump to ${{ steps.set-final-version-output.outputs.version }}")
Automated version bump to ${{ inputs.version_number }}")
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
- name: Approve PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr review $PR_NUMBER --approve
- name: Merge PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
cut_rc:
name: Cut RC branch
name: Cut RC Branch
if: ${{ inputs.cut_rc_branch == true }}
needs: bump_version
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Verify version has been updated
env:
NEW_VERSION: ${{ needs.bump_version.outputs.version }}
run: |
# Wait for version to change.
while : ; do
echo "Waiting for version to be updated..."
git pull --force
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
# If the versions don't match we continue the loop, otherwise we break out of the loop.
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
sleep 10
done
- name: Cut RC branch
run: |
git switch --quiet --create rc
git push --quiet --set-upstream origin rc
uses: ./.github/workflows/_cut_rc.yml
secrets: inherit

11
.github/workflows/workflow-linter.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: Workflow Linter
on:
pull_request:
paths:
- .github/workflows/**
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main

View File

@@ -1,16 +0,0 @@
<Project>
<PropertyGroup>
<MauiVersion>8.0.7</MauiVersion>
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
<IncludeBitwardenWatchOSApp>True</IncludeBitwardenWatchOSApp>
<Argon2IdLoadMtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</Argon2IdLoadMtouchExtraArgs>
<!-- Uncomment this when Unit Testing-->
<!-- <CustomConstants>UT</CustomConstants> -->
<!-- Uncomment this when building FDROID-->
<!-- <CustomConstants>FDROID</CustomConstants> -->
</PropertyGroup>
</Project>

View File

@@ -1,4 +1,4 @@
[![Github Workflow build on main](https://github.com/bitwarden/mobile/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:main)
[![Github Workflow build on master](https://github.com/bitwarden/mobile/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
@@ -6,7 +6,7 @@
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
The Bitwarden mobile application is written in C# using .NET MAUI.
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" />
@@ -20,6 +20,6 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
# Contribute
Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.

View File

@@ -5,7 +5,7 @@ VisualStudioVersion = 17.8.34112.27
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{971FDF07-E288-4239-B47A-E9E7E912193B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
EndProject
@@ -15,14 +15,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "test\Core.Test\Core.Test.csproj", "{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -35,7 +27,6 @@ Global
AppStore|iPhone = AppStore|iPhone
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
Ad-Hoc|iPhone = Ad-Hoc|iPhone
FDroid|Any CPU = FDroid|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{971FDF07-E288-4239-B47A-E9E7E912193B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -60,8 +51,6 @@ Global
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -82,8 +71,6 @@ Global
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -104,8 +91,6 @@ Global
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.Build.0 = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -126,8 +111,6 @@ Global
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -148,8 +131,6 @@ Global
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -162,52 +143,6 @@ Global
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.AppStore|iPhone.Build.0 = Release|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.Build.0 = Release|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.Build.0 = Release|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -220,14 +155,4 @@ Global
$0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = PrefixedHierarchical
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{971FDF07-E288-4239-B47A-E9E7E912193B} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{83449CC4-1F76-4CFE-92B1-D2E13A62506F} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{E71F3053-056C-4381-9638-048ED73BDFF6} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
EndGlobalSection
EndGlobal

View File

@@ -2,9 +2,9 @@ project_id_env: _CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN
preserve_hierarchy: true
files:
- source: /src/Core/Resources/Localization/AppResources.resx
dest: /src/Core/Resources/Localization/%original_file_name%
translation: /src/Core/Resources/Localization/AppResources.%two_letters_code%.resx
- source: /src/App/Resources/AppResources.resx
dest: /src/App/Resources/%original_file_name%
translation: /src/App/Resources/AppResources.%two_letters_code%.resx
update_option: update_as_unapproved
languages_mapping:
two_letters_code:

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="MAUI Nightly builds" value="https://pkgs.dev.azure.com/xamarin/public/_packaging/maui-nightly/nuget/v3/index.json" />
</packageSources>
</configuration>

2
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "bitwarden-mobile",
"version": "0.0.0",
"devDependencies": {
"gh-pages": "3.2.3"
"gh-pages": "^3.2.3"
}
},
"node_modules/array-union": {

View File

@@ -6,6 +6,6 @@
"clean:l10n": "git push origin --delete l10n_master"
},
"devDependencies": {
"gh-pages": "3.2.3"
"gh-pages": "^3.2.3"
}
}

View File

@@ -56,20 +56,18 @@
<CodesignKey>iPhone Developer</CodesignKey>
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
<UseInterpreter>true</UseInterpreter>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|iossimulator-x64'">
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
<!--TODO: add argon2id load when library is built with the corresponding architecture for iOS Simulator-->
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'">
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
<MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
<CreatePackage>false</CreatePackage>
<CodesignProvision>$(ReleaseCodesignProvision)</CodesignProvision>
<CodesignKey>$(ReleaseCodesignKey)</CodesignKey>
<CodesignProvision>Automatic:AppStore</CodesignProvision>
<CodesignKey>iPhone Distribution</CodesignKey>
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
<UseInterpreter>true</UseInterpreter>
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
<MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
</PropertyGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<!--This is needed for PCLCrypto to work correctly-->
@@ -157,15 +155,6 @@
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" />
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
<BundleResource Include="Platforms\iOS\Resources\logo.png" />
<BundleResource Include="Platforms\iOS\Resources\logo_white%402x.png" />
<BundleResource Include="Platforms\iOS\Resources\more_vert%402x.png" />
<BundleResource Include="Platforms\iOS\Resources\logo_white%403x.png" />
<BundleResource Include="Platforms\iOS\Resources\logo%403x.png" />
<BundleResource Include="Platforms\iOS\Resources\more_vert%403x.png" />
<BundleResource Include="Platforms\iOS\Resources\more_vert.png" />
<BundleResource Include="Platforms\iOS\Resources\logo_white.png" />
<BundleResource Include="Platforms\iOS\Resources\logo%402x.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" />
@@ -205,12 +194,15 @@
<MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF">
<BaseSize>24,24</BaseSize>
</MauiImage>
<MauiImage Include="Resources\search.svg" TintColor="#FFFFFFFF">
<BaseSize>24,24</BaseSize>
</MauiImage>
<MauiImage Include="Resources\send.svg">
<BaseSize>24,24</BaseSize>
</MauiImage>
<MauiImage Include="Resources\yubikey.png" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' AND '$(IncludeBitwardeniOSExtensions)' == 'True'">
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj">
<IsAppExtension>true</IsAppExtension>
<IsWatchApp>false</IsWatchApp>
@@ -224,15 +216,15 @@
<IsWatchApp>false</IsWatchApp>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND '$(IncludeBitwardenWatchOSApp)' == 'True'">
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'">watchsimulator</WatchAppConfiguration>
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'">watchos</WatchAppConfiguration>
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'">watchsimulator</WatchAppConfiguration>
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'">watchos</WatchAppConfiguration>
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND Exists('$(WatchAppBundleFullPath)') ">
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
</ItemGroup>
@@ -246,15 +238,4 @@
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
<GoogleServicesJson Include="Platforms\Android\google-services.json.enc" />
</ItemGroup>
<ItemGroup>
<None Remove="Platforms\iOS\Resources\logo.png" />
<None Remove="Platforms\iOS\Resources\logo_white%402x.png" />
<None Remove="Platforms\iOS\Resources\more_vert%402x.png" />
<None Remove="Platforms\iOS\Resources\logo_white%403x.png" />
<None Remove="Platforms\iOS\Resources\logo%403x.png" />
<None Remove="Platforms\iOS\Resources\more_vert%403x.png" />
<None Remove="Platforms\iOS\Resources\more_vert.png" />
<None Remove="Platforms\iOS\Resources\logo_white.png" />
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
#if IOS || MACCATALYST
using PlatformView = WebKit.WKWebView;
#elif ANDROID
using PlatformView = Android.Webkit.WebView;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
using Bit.App.Controls;
using Microsoft.Maui.Handlers;
namespace Bit.App.Handlers
{
public partial class HybridWebViewHandler
{
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
{
[nameof(HybridWebView.Uri)] = MapUri
};
public HybridWebViewHandler() : base(PropertyMapper)
{
}
}
}

View File

@@ -13,6 +13,7 @@
},
handlers =>
{
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
#if ANDROID
Bit.App.Handlers.EntryHandlerMappings.Setup();
Bit.App.Handlers.EditorHandlerMappings.Setup();
@@ -27,7 +28,6 @@
Bit.App.Handlers.ButtonHandlerMappings.Setup();
Bit.App.Handlers.ToolbarHandlerMappings.Setup();
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler));
#else

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2024.3.3" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.12.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
@@ -43,9 +43,6 @@
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />

View File

@@ -347,7 +347,7 @@ namespace Bit.Droid.Autofill
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
// "my vault" presentation) so we're including an empty one here
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, false));
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
}
var slice = CreateInlinePresentationSlice(
inlinePresentationSpec,

View File

@@ -1,6 +1,5 @@
using AndroidX.AppCompat.View.Menu;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Google.Android.Material.BottomNavigation;
using Microsoft.Maui.Handlers;
@@ -91,17 +90,7 @@ namespace Bit.App.Handlers
if(e.Item is MenuItemImpl item)
{
System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot.");
MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
await _tabbedPage.CurrentPage.Navigation.PopToRootAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
MainThread.BeginInvokeOnMainThread(async () => await _tabbedPage.CurrentPage.Navigation.PopToRootAsync());
}
}

View File

@@ -6,19 +6,10 @@ using AWebkit = Android.Webkit;
namespace Bit.App.Handlers
{
public class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
public partial class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
{
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
{
[nameof(HybridWebView.Uri)] = MapUri
};
public HybridWebViewHandler() : base(PropertyMapper)
{
}
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
{
}

View File

@@ -2,10 +2,7 @@
using Android.Graphics.Drawables;
using Android.OS;
using AndroidX.Core.Content.Resources;
using AndroidX.Core.Graphics;
using Bit.App.Droid.Utilities;
using Bit.App.Utilities;
using Microsoft.Maui.Platform;
namespace Bit.App.Handlers
{
@@ -40,31 +37,6 @@ namespace Bit.App.Handlers
};
handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
});
Microsoft.Maui.Handlers.SwitchHandler.Mapper.AppendToMapping(nameof(ISwitch.TrackColor), (handler, mauiSwitch) =>
{
var trackStates = new[]
{
new[] { Android.Resource.Attribute.StateChecked }, // checked
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
};
var selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.5f);
var unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.7f);
if (ThemeManager.UsingLightTheme)
{
selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.White.ToPlatform().ToArgb(), 0.7f);
unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.3f);
}
var trackColors = new int[]
{
selectedColor,
unselectedColor
};
handler.PlatformView.TrackTintList = new ColorStateList(trackStates, trackColors);
});
}
}
}

View File

@@ -74,11 +74,6 @@ namespace Bit.Droid
// this needs to be called here before base.OnCreate(...)
Intent?.Validate();
//We need to get and set the Options before calling OnCreate as that will "trigger" CreateWindow on App.xaml.cs
_appOptions = GetOptions();
//This does not replace existing Options in App.xaml.cs if it exists already. It only updates properties in Options related with Autofill/CreateSend/etc..
((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetAndroidOptions(_appOptions);
base.OnCreate(savedInstanceState);
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
@@ -94,6 +89,7 @@ namespace Bit.Droid
toplayout.FilterTouchesWhenObscured = true;
}
_appOptions = GetOptions();
CreateNotificationChannel();
DisableAndroidFontScale();

View File

@@ -158,7 +158,7 @@ namespace Bit.Droid
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>());
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService);
var userPinService = new UserPinService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);

View File

@@ -79,29 +79,24 @@ namespace Bit.Droid.Services
}
var context = Android.App.Application.Context;
var intent = context.PackageManager?.GetLaunchIntentForPackage(context.PackageName ?? string.Empty);
var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId);
if(intent != null && context.PackageManager != null && !string.IsNullOrEmpty(context.PackageName))
{
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
builder.SetContentIntent(pendingIntent)
.SetDeleteIntent(deletePendingIntent);
}
builder.SetContentTitle(title)
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetSmallIcon(Bit.Core.Resource.Drawable.ic_notification)
.SetColor((int)Android.Graphics.Color.White)
.SetDeleteIntent(deletePendingIntent)
.SetAutoCancel(true);
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
{
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);

View File

@@ -72,28 +72,17 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName)
{
try
{
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null;
}
catch (IntentSender.SendIntentException)
{
return false;
}
catch (Android.Util.AndroidException)
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null;
}
public async Task ShowLoadingAsync(string text)

View File

@@ -8,7 +8,6 @@ using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Accessibility;
using Java.Lang;
using Bit.App.Droid.Utilities;
namespace Bit.Droid.Tile
{
@@ -77,7 +76,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(AccessibilityActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("autofillTileClicked", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: true);
StartActivityAndCollapse(intent);
}
private void ShowConfigErrorDialog()

View File

@@ -1,8 +1,15 @@
using Android.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Service.QuickSettings;
using Bit.App.Droid.Utilities;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace Bit.Droid.Tile
@@ -55,7 +62,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("generatorTile", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
StartActivityAndCollapse(intent);
}
}
}

View File

@@ -1,8 +1,15 @@
using Android.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Service.QuickSettings;
using Bit.App.Droid.Utilities;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace Bit.Droid.Tile
@@ -56,7 +63,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("myVaultTile", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
StartActivityAndCollapse(intent);
}
}
}

View File

@@ -2,7 +2,6 @@
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Service.QuickSettings;
using Bit.App.Utilities;
namespace Bit.App.Droid.Utilities
@@ -65,26 +64,5 @@ namespace Bit.App.Droid.Utilities
return pendingIntentFlags;
}
public static void StartActivityAndCollapseWithIntent(this TileService service, Intent intent, bool isMutable)
{
//For Android 14+ We need to use PendingIntent instead of Intent directly. Older versions still need to use Intent.
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
{
service.StartActivityAndCollapse(intent);
return;
}
var pendingIntent = PendingIntent.GetActivity(
service.ApplicationContext,
0,
intent,
AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, isMutable)
);
if (pendingIntent == null)
{
return;
}
service.StartActivityAndCollapse(pendingIntent);
}
}
}

View File

@@ -15,7 +15,6 @@ using CoreNFC;
using Foundation;
using Microsoft.Maui.Platform;
using UIKit;
using UserNotifications;
using WatchConnectivity;
namespace Bit.iOS
@@ -42,78 +41,77 @@ namespace Bit.iOS
private IStateService _stateService;
private IEventService _eventService;
private readonly LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
private LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
try
InitApp();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
//LoadApplication(new App.App(null));
//iOSCoreHelpers.AppearanceAdjustments();
//ZXing.Net.Mobile.Forms.iOS.Platform.Init();
ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{
InitApp();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
try
{
try
if (message.Command == "startEventTimer")
{
if (message.Command == "startEventTimer")
StartEventTimer();
}
else if (message.Command == "stopEventTimer")
{
var task = StopEventTimerAsync();
}
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
MainThread.BeginInvokeOnMainThread(() =>
{
StartEventTimer();
iOSCoreHelpers.AppearanceAdjustments();
});
}
else if (message.Command == "listenYubiKeyOTP")
{
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
}
else if (message.Command == "unlocked")
{
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
{
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == "stopEventTimer")
}
else if (message.Command == "showAppExtension")
{
MainThread.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
}
else if (message.Command == "syncCompleted")
{
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
{
var task = StopEventTimerAsync();
}
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
iOSCoreHelpers.AppearanceAdjustments();
});
}
else if (message.Command == "listenYubiKeyOTP" && message.Data is bool listen)
{
iOSCoreHelpers.ListenYubiKey(listen, _deviceActionService, _nfcSession, _nfcDelegate);
}
else if (message.Command == "unlocked")
{
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
var success = data["successfully"] as bool?;
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
}
else if (message.Command == "showAppExtension")
}
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
message.Command == "restoredCipher")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
await MainThread.InvokeOnMainThreadAsync(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
}
else if (message.Command == "syncCompleted")
{
if (message.Data is Dictionary<string, object> data && data.TryGetValue("successfully", out var value))
{
var success = value as bool?;
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
}
}
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
message.Command == "restoredCipher")
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
return;
}
if (await ASHelpers.IdentitiesCanIncremental())
{
var cipherId = message.Data as string;
@@ -131,13 +129,11 @@ namespace Bit.iOS
}
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
}
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
return;
}
if (await ASHelpers.IdentitiesCanIncremental())
{
var identity = ASHelpers.ToCredentialIdentity(
@@ -152,145 +148,97 @@ namespace Bit.iOS
}
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
}
else if (message.Command == "logout")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
&& UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
}
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
&& _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == VaultTimeoutAction.Logout)
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
else
{
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == VaultTimeoutAction.Logout)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
else
{
await ASHelpers.ReplaceAllIdentities();
}
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
var finishedLaunching = base.FinishedLaunching(app, options);
var finishedLaunching = base.FinishedLaunching(app, options);
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
iOSCoreHelpers.AppearanceAdjustments();
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
iOSCoreHelpers.AppearanceAdjustments();
return finishedLaunching;
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
return finishedLaunching;
}
public override void OnResignActivation(UIApplication uiApplication)
{
try
if (UIApplication.SharedApplication.KeyWindow != null)
{
if (UIApplication.SharedApplication.KeyWindow != null)
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
Tag = SPLASH_VIEW_TAG
};
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
};
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
var imageView = new UIImageView(frame)
{
Image = logo,
Center = new CGPoint(view.Center.X, view.Center.Y - 30),
ContentMode = UIViewContentMode.ScaleAspectFit
};
view.AddSubview(backgroundView);
view.AddSubview(imageView);
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
}
base.OnResignActivation(uiApplication);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
Tag = SPLASH_VIEW_TAG
};
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
};
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
var imageView = new UIImageView(frame)
{
Image = logo,
Center = new CGPoint(view.Center.X, view.Center.Y - 30),
ContentMode = UIViewContentMode.ScaleAspectFit
};
view.AddSubview(backgroundView);
view.AddSubview(imageView);
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
}
base.OnResignActivation(uiApplication);
}
public override void DidEnterBackground(UIApplication uiApplication)
{
try
{
if (_stateService != null && _deviceActionService != null)
{
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
_messagingService?.Send("slept");
base.DidEnterBackground(uiApplication);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
_stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
_messagingService?.Send("slept");
base.DidEnterBackground(uiApplication);
}
public override async void OnActivated(UIApplication uiApplication)
public override void OnActivated(UIApplication uiApplication)
{
try
{
base.OnActivated(uiApplication);
base.OnActivated(uiApplication);
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
UIApplication.SharedApplication.KeyWindow?
.ViewWithTag(SPLASH_VIEW_TAG)?
.RemoveFromSuperview();
if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
await UNUserNotificationCenter.Current.SetBadgeCountAsync(0);
}
else
{
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
UIApplication.SharedApplication.KeyWindow?
.ViewWithTag(SPLASH_VIEW_TAG)?
.RemoveFromSuperview();
ThemeManager.UpdateThemeOnPagesAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
ThemeManager.UpdateThemeOnPagesAsync();
}
public override void WillEnterForeground(UIApplication uiApplication)
{
try
{
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
base.WillEnterForeground(uiApplication);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
base.WillEnterForeground(uiApplication);
}
[Export("application:openURL:sourceApplication:annotation:")]
@@ -301,30 +249,15 @@ namespace Bit.iOS
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
try
{
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return false;
}
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
}
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
UIApplicationRestorationHandler completionHandler)
{
try
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
{
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
{
return true;
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return true;
}
return base.ContinueUserActivity(application, userActivity, completionHandler);
}
@@ -332,68 +265,33 @@ namespace Bit.iOS
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
try
{
_pushHandler?.OnErrorReceived(error);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_pushHandler?.OnErrorReceived(error);
}
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
try
{
_pushHandler?.OnRegisteredSuccess(deviceToken);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_pushHandler?.OnRegisteredSuccess(deviceToken);
}
[Export("application:didRegisterUserNotificationSettings:")]
public void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings)
{
try
{
application.RegisterForRemoteNotifications();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
application.RegisterForRemoteNotifications();
}
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
try
{
_pushHandler?.OnMessageReceived(userInfo);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_pushHandler?.OnMessageReceived(userInfo);
}
[Export("application:didReceiveRemoteNotification:")]
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
try
{
_pushHandler?.OnMessageReceived(userInfo);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_pushHandler?.OnMessageReceived(userInfo);
}
public void InitApp()
@@ -406,6 +304,17 @@ namespace Bit.iOS
// Migration services
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
// Note: This might cause a race condition. Investigate more.
//Task.Run(() =>
//{
// FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
// FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
// {
// FadeAnimationEnabled = false,
// FadeAnimationForCachedImages = false
// });
//});
iOSCoreHelpers.RegisterLocalServices();
RegisterPush();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -419,7 +328,7 @@ namespace Bit.iOS
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
_messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.Bootstrap(ApplyManagedSettingsAsync);
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
}
private void RegisterPush()
@@ -464,45 +373,31 @@ namespace Bit.iOS
_eventTimer = null;
MainThread.BeginInvokeOnMainThread(() =>
{
try
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
{
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
{
_eventService?.UploadEventsAsync().FireAndForget();
});
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
var task = Task.Run(() => _eventService.UploadEventsAsync());
});
});
}
private async Task StopEventTimerAsync()
{
try
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
if (_eventBackgroundTaskId > 0)
{
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
if (_eventBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
}
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
});
await _eventService.UploadEventsAsync();
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
}
catch (Exception ex)
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
});
await _eventService.UploadEventsAsync();
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
}
private async Task ApplyManagedSettingsAsync()

View File

@@ -5,24 +5,15 @@ using Foundation;
using Microsoft.Maui.Handlers;
using WebKit;
namespace Bit.iOS.Core.Handlers
namespace Bit.App.Handlers
{
public class HybridWebViewHandler : ViewHandler<HybridWebView, WebKit.WKWebView>
public partial class HybridWebViewHandler : ViewHandler<HybridWebView, WebKit.WKWebView>
{
private const string JSFunction =
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
private WKUserContentController _userController;
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
{
[nameof(HybridWebView.Uri)] = MapUri
};
public HybridWebViewHandler() : base(PropertyMapper)
{
}
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
{
}

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key>
<string>2024.3.3</string>
<string>2023.12.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconName</key>

View File

@@ -1,27 +0,0 @@
{
"images": [
{
"appearances": [],
"scale": "1x",
"idiom": "universal",
"filename": "search.png"
},
{
"appearances": [],
"scale": "2x",
"idiom": "universal",
"filename": "search@2x.png"
},
{
"appearances": [],
"scale": "3x",
"idiom": "universal",
"filename": "search@3x.png"
}
],
"properties": {},
"info": {
"version": 1,
"author": ""
}
}

View File

@@ -5,8 +5,7 @@ namespace Bit.Core.Abstractions
{
public enum AwaiterPrecondition
{
EnvironmentUrlsInited,
AndroidWindowCreated
EnvironmentUrlsInited
}
public interface IConditionedAwaiterManager

View File

@@ -63,7 +63,5 @@ namespace Bit.Core.Abstractions
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
Task<MasterKey> GetOrDeriveMasterKeyAsync(string password, string userId = null);
Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey);
Task<string> HashAsync(string value, CryptoHashAlgorithm hashAlgorithm);
Task<bool> ValidateUriChecksumAsync(EncString remoteUriChecksum, string rawUri, string orgId, SymmetricCryptoKey key);
}
}

View File

@@ -9,9 +9,7 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Pages;
using Bit.Core.Services;
using Bit.Core.Utilities;
@@ -36,8 +34,6 @@ namespace Bit.App
private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService;
private readonly ILogger _logger;
private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests;
@@ -48,99 +44,6 @@ namespace Bit.App
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
private readonly Queue<Action> _onResumeActions = new Queue<Action>();
#if ANDROID
/*
* ** Workaround for our Android crashes when trying to use Autofill **
*
* This workaround works by managing the "Window Creation" ourselves.
* - If we get an AutofillExternalActivity we just create a "dummy" window/navigation page so that the activity can run without crashing. (no visible UI is needed)
* - If we get an FromAutofillFramework/Uri/Otp/CreateSend special Option request we create an Autofill Window
* - For everything else we use the default "mainWindow"
*/
public new static Page MainPage
{
get
{
return CurrentWindow?.Page;
}
set
{
if (CurrentWindow != null)
{
CurrentWindow.Page = value;
}
}
}
/// <summary>
/// Find the Current Active Window. There should only be one at any point in Android
/// </summary>
public static ResumeWindow CurrentWindow
{
get
{
return Application.Current?.Windows.OfType<ResumeWindow>().FirstOrDefault(w => w.IsActive);
}
}
/// <summary>
/// Allows setting Options from MainActivity before base.OnCreate
/// Note 1: This is only be used by Android due to way it's Lifecycle works
/// Note 2: This method does not replace existing Options in App.xaml.cs if it exists already.
/// It only updates properties in Options related with Autofill/CreateSend/etc..
/// </summary>
/// <param name="appOptions">Options created in Android MainActivity.cs</param>
public void SetAndroidOptions(AppOptions appOptions)
{
if (Options == null)
{
Options = appOptions ?? new AppOptions();
}
else if(appOptions != null)
{
Options.Uri = appOptions.Uri;
Options.MyVaultTile = appOptions.MyVaultTile;
Options.GeneratorTile = appOptions.GeneratorTile;
Options.FromAutofillFramework = appOptions.FromAutofillFramework;
Options.CreateSend = appOptions.CreateSend;
}
}
protected override Window CreateWindow(IActivationState activationState)
{
//When executing from AutofillExternalActivity we don't have "Options" so we need to filter "manually"
//In the AutofillExternalActivity we don't need to show any Page, so we just create a "dummy" Window with a NavigationPage to avoid crashing.
if (activationState != null
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
&& autofillFramework == "true"
&& activationState.State.ContainsKey("autofillFrameworkCipherId"))
{
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
}
_isResumed = true;
return new ResumeWindow(new NavigationPage(new AndroidNavigationRedirectPage(Options)));
}
#else
//iOS doesn't use the CreateWindow override used in Android so we just set the Application.Current.MainPage directly
public new static Page MainPage
{
get
{
return Application.Current?.MainPage;
}
set
{
if (Application.Current != null)
{
Application.Current.MainPage = value;
}
}
}
#endif
public App() : this(null)
{
}
@@ -164,157 +67,139 @@ namespace Bit.App
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_configService = ServiceContainer.Resolve<IConfigService>();
_logger = ServiceContainer.Resolve<ILogger>();
_accountsManager.Init(() => Options, this);
_broadcasterService.Subscribe(nameof(App), BroadcastServiceMessageCallbackAsync);
Bootstrap();
}
_broadcasterService.Subscribe(nameof(App), async (message) =>
{
try
{
if (message.Command == "showDialog")
{
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
MainThread.BeginInvokeOnMainThread(async () =>
{
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
ResumedAsync().FireAndForget();
}
}
else if (message.Command == "slept")
{
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
await SleptAsync();
}
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
Options.OtpData = new OtpData((string)message.Data);
}
private async void BroadcastServiceMessageCallbackAsync(Message message)
{
try
{
ArgumentNullException.ThrowIfNull(message);
if (message.Command == "showDialog")
{
var details = message.Data as DialogDetails;
ArgumentNullException.ThrowIfNull(details);
ArgumentNullException.ThrowIfNull(MainPage);
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
await MainThread.InvokeOnMainThreadAsync(ShowDialogAction);
async Task ShowDialogAction()
{
if (!string.IsNullOrWhiteSpace(details.CancelText))
MainThread.InvokeOnMainThreadAsync(async () =>
{
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
if (MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
{
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
}
}
});
}
}
#if IOS
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
ResumedAsync().FireAndForget();
}
else if (message.Command == "slept")
{
await SleptAsync();
}
#endif
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
else if (message.Command == "convertAccountToKeyConnector")
{
Options.OtpData = new OtpData((string)message.Data);
}
await MainThread.InvokeOnMainThreadAsync(ExecuteNavigationAction);
async Task ExecuteNavigationAction()
{
if (MainPage is TabsPage tabsPage)
MainThread.BeginInvokeOnMainThread(async () =>
{
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
ArgumentNullException.ThrowIfNull(tabsPage.Navigation.ModalStack);
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
{
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
}
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
else if (message.Command == Constants.ForceUpdatePassword)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
});
}
else if (message.Command == Constants.ForceSetPassword)
{
await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
}
}
else if (message.Command == "convertAccountToKeyConnector")
catch (Exception ex)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToRemoveMasterPasswordPageAction);
async Task NavigateToRemoveMasterPasswordPageAction()
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
}
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
else if (message.Command == Constants.ForceUpdatePassword)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToUpdateTempPasswordPageAction);
async Task NavigateToUpdateTempPasswordPageAction()
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
}
}
else if (message.Command == Constants.ForceSetPassword)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToSetPasswordPageAction);
void NavigateToSetPasswordPageAction()
{
MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)));
}
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
}
private async Task CheckPasswordlessLoginRequestsAsync()
@@ -329,6 +214,7 @@ namespace Bit.App
{
return;
}
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
if (notification == null)
{
@@ -399,52 +285,40 @@ namespace Bit.App
protected override async void OnStart()
{
try
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
_isResumed = true;
await ClearCacheIfNeededAsync();
Prime();
if (string.IsNullOrWhiteSpace(Options.Uri))
{
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
_isResumed = true;
await ClearCacheIfNeededAsync();
Prime();
if (string.IsNullOrWhiteSpace(Options.Uri))
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_stateService);
if (!updated)
{
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_stateService);
if (!updated)
{
SyncIfNeeded();
}
SyncIfNeeded();
}
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
#if ANDROID
}
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
if (DeviceInfo.Platform == DevicePlatform.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null;
#endif
}
await _configService.GetAsync();
_messagingService.Send("startEventTimer");
}
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
}
await _configService.GetAsync();
_messagingService.Send("startEventTimer");
}
#if ANDROID
protected override async void OnSleep()
#else
protected override void OnSleep()
#endif
{
try
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
_isResumed = false;
if (DeviceInfo.Platform == DevicePlatform.Android)
{
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
_isResumed = false;
#if ANDROID
var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked)
{
@@ -455,34 +329,20 @@ namespace Bit.App
ClearAutofillUri();
}
await SleptAsync();
#endif
}
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
}
}
protected override void OnResume()
{
try
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
_isResumed = true;
if (_pendingCheckPasswordlessLoginRequests)
{
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
_isResumed = true;
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
#if ANDROID
ResumedAsync().FireAndForget();
#endif
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
catch (Exception ex)
if (DeviceInfo.Platform == DevicePlatform.Android)
{
_logger?.Exception(ex);
throw;
ResumedAsync().FireAndForget();
}
}
@@ -569,22 +429,14 @@ namespace Bit.App
{
MainThread.BeginInvokeOnMainThread(() =>
{
try
Options.Uri = null;
if (isLocked)
{
Options.Uri = null;
if (isLocked)
{
App.MainPage = new NavigationPage(new LockPage());
}
else
{
App.MainPage = new TabsPage();
}
MainPage = new NavigationPage(new LockPage());
}
catch (Exception ex)
else
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
MainPage = new TabsPage();
}
});
});
@@ -609,13 +461,10 @@ namespace Bit.App
ThemeManager.SetTheme(Resources);
RequestedThemeChanged += (s, a) =>
{
UpdateThemeAsync().FireAndForget();
UpdateThemeAsync();
};
_isResumed = true;
#if IOS
//We only set the MainPage here for iOS. Android is using the CreateWindow override for the initial page.
App.MainPage = new NavigationPage(new HomePage(Options));
#endif
MainPage = new NavigationPage(new HomePage(Options));
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@@ -628,18 +477,11 @@ namespace Bit.App
}
Task.Run(async () =>
{
try
var lastSync = await _syncService.GetLastSyncAsync();
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
{
var lastSync = await _syncService.GetLastSyncAsync();
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
{
await Task.Delay(1000);
await _syncService.FullSyncAsync(false);
}
}
catch (Exception ex)
{
_logger.Exception(ex);
await Task.Delay(1000);
await _syncService.FullSyncAsync(false);
}
});
}
@@ -694,36 +536,36 @@ namespace Bit.App
switch (navTarget)
{
case NavigationTarget.HomeLogin:
App.MainPage = new NavigationPage(new HomePage(Options));
MainPage = new NavigationPage(new HomePage(Options));
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
App.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
}
break;
case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams)
{
App.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
}
else
{
App.MainPage = new NavigationPage(new LockPage(Options));
MainPage = new NavigationPage(new LockPage(Options));
}
break;
case NavigationTarget.Home:
App.MainPage = new TabsPage(Options);
MainPage = new TabsPage(Options);
break;
case NavigationTarget.AddEditCipher:
App.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
case NavigationTarget.OtpCipherSelection:
App.MainPage = new NavigationPage(new CipherSelectionPage(Options));
MainPage = new NavigationPage(new CipherSelectionPage(Options));
break;
case NavigationTarget.SendAddEdit:
App.MainPage = new NavigationPage(new SendAddEditPage(Options));
MainPage = new NavigationPage(new SendAddEditPage(Options));
break;
}
}

View File

@@ -47,7 +47,6 @@ namespace Bit.Core
public const string ConfigsKey = "configsKey";
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
public const string RegionEnvironment = "regionEnvironment";
public const string DuoCallback = "bitwarden://duo-callback";
/// <summary>
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
@@ -71,11 +70,10 @@ namespace Bit.Core
public const int Argon2Parallelism = 4;
public const int MasterPasswordMinimumChars = 12;
public const int CipherKeyRandomBytesLength = 64;
public const string CipherKeyEncryptionMinServerVersion = "2024.2.0";
public const string CipherKeyEncryptionMinServerVersion = "2023.9.1";
public const string DefaultFido2CredentialType = "public-key";
public const string DefaultFido2CredentialAlgorithm = "ECDSA";
public const string DefaultFido2CredentialCurve = "P-256";
public const int LatestStateVersion = 7;
public static readonly string[] AndroidAllClearCipherCacheKeys =
{

View File

@@ -12,14 +12,12 @@
BackgroundColor="#22000000"
Padding="0"
IsVisible="False">
<Grid
<VerticalStackLayout
x:Name="_accountListContainer"
VerticalOptions="Fill"
HorizontalOptions="Fill"
BackgroundColor="Transparent"
RowDefinitions="Auto, *">
HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent">
<Frame
Grid.Row="0"
Padding="0"
HorizontalOptions="Fill"
VerticalOptions="Start">
@@ -51,13 +49,12 @@
</ListView>
</Frame>
<BoxView
Grid.Row="1"
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="Fill">
VerticalOptions="FillAndExpand">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
</BoxView.GestureRecognizers>
</BoxView>
</Grid>
</VerticalStackLayout>
</ContentView>

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
<controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.AuthenticatorViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
xmlns:core="clr-namespace:Bit.Core"
StyleClass="list-row, list-row-platform"
HorizontalOptions="FillAndExpand"
@@ -13,32 +14,34 @@
RowSpacing="0"
Padding="0,10,0,0"
RowDefinitions="*,*">
<controls:BaseCipherViewCell.Resources>
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
<u:InverseBoolConverter x:Key="inverseBool" />
</controls:BaseCipherViewCell.Resources>
</Grid.Resources>
<controls:CachedImage
x:Name="_iconImage"
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
Success="Icon_Success"
Error="Icon_Error"
AutomationProperties.IsInAccessibleTree="False" />
<controls:IconLabel
x:Name="_iconPlaceholderImage"
Grid.Column="0"
Grid.RowSpan="2"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
<Label
@@ -46,7 +49,7 @@
Grid.Column="1"
Grid.Row="0"
VerticalTextAlignment="Center"
VerticalOptions="End"
VerticalOptions="Fill"
StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name}" />
@@ -55,7 +58,7 @@
Grid.Column="1"
Grid.Row="1"
VerticalTextAlignment="Center"
VerticalOptions="Start"
VerticalOptions="Fill"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" />
@@ -65,14 +68,11 @@
Grid.Column="2"
Grid.RowSpan="2"
HorizontalOptions="Fill"
WidthRequest="50"
HeightRequest="50"
VerticalOptions="CenterAndExpand" />
<Label
Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}"
BackgroundColor="Transparent"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
@@ -123,4 +123,4 @@
HorizontalOptions="Center"
VerticalOptions="Center"
SemanticProperties.Description="{u:I18n CopyTotp}" />
</controls:BaseCipherViewCell>
</controls:ExtendedGrid>

View File

@@ -1,14 +1,10 @@
namespace Bit.App.Controls
{
public partial class AuthenticatorViewCell : BaseCipherViewCell
public partial class AuthenticatorViewCell : ExtendedGrid
{
public AuthenticatorViewCell()
{
InitializeComponent();
}
protected override CachedImage Icon => _iconImage;
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
}
}

View File

@@ -1,62 +0,0 @@
using SkiaSharp;
namespace Bit.App.Controls
{
public class AvatarImageSource : StreamImageSource
{
private readonly string _text;
private readonly string _id;
private readonly string _color;
private readonly AvatarInfo _avatarInfo;
public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (obj is AvatarImageSource avatar)
{
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
}
return base.Equals(obj);
}
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
{
_id = userId;
_text = name;
if (string.IsNullOrWhiteSpace(_text))
{
_text = email;
}
_color = color;
//Workaround: [MAUI-Migration] There is currently a bug in MAUI where the actual size of the image is used instead of the size it should occupy in the Toolbar.
//This causes some issues with the position of the icon. As a workaround we make the icon smaller until this is fixed.
//Github issues: https://github.com/dotnet/maui/issues/12359 and https://github.com/dotnet/maui/pull/17120
_avatarInfo = new AvatarInfo(userId, name, email, color, DeviceInfo.Platform == DevicePlatform.iOS ? 20 : 50);
}
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
{
var result = Draw();
return Task.FromResult(result);
}
private Stream Draw()
{
using (var img = SKAvatarImageHelper.Draw(_avatarInfo))
{
var data = img.Encode(SKEncodedImageFormat.Png, 100);
return data?.AsStream(true);
}
}
}
}

View File

@@ -1,63 +0,0 @@
using Bit.Core.Utilities;
#nullable enable
namespace Bit.App.Controls
{
public struct AvatarInfo
{
private const string DEFAULT_BACKGROUND_COLOR = "#33ffffff";
public AvatarInfo(string? userId = null, string? name = null, string? email = null, string? color = null, int size = 50)
{
Size = size;
var text = string.IsNullOrWhiteSpace(name) ? email : name;
string? upperCaseText = null;
if (string.IsNullOrEmpty(text))
{
CharsToDraw = "..";
}
else if (text.Length > 1)
{
upperCaseText = text.ToUpper();
CharsToDraw = GetFirstLetters(upperCaseText, 2);
}
else
{
CharsToDraw = upperCaseText = text.ToUpper();
}
BackgroundColor = color ?? CoreHelpers.StringToColor(userId ?? upperCaseText, DEFAULT_BACKGROUND_COLOR);
TextColor = CoreHelpers.TextColorFromBgColor(BackgroundColor);
}
public string CharsToDraw { get; }
public string BackgroundColor { get; }
public string TextColor { get; }
public int Size { get; }
private static string GetFirstLetters(string data, int charCount)
{
var sanitizedData = data.Trim();
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 1 && charCount <= 2)
{
var text = string.Empty;
for (var i = 0; i < charCount; i++)
{
text += parts[i][0];
}
return text;
}
if (sanitizedData.Length > 2)
{
return sanitizedData.Substring(0, 2);
}
return sanitizedData;
}
}
}

View File

@@ -1,63 +0,0 @@
using SkiaSharp;
namespace Bit.App.Controls
{
public static class SKAvatarImageHelper
{
public static SKImage Draw(AvatarInfo avatarInfo)
{
using (var bitmap = new SKBitmap(avatarInfo.Size * 2,
avatarInfo.Size * 2,
SKImageInfo.PlatformColorType,
SKAlphaType.Premul))
{
using (var canvas = new SKCanvas(bitmap))
{
canvas.Clear(SKColors.Transparent);
using (var paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(avatarInfo.BackgroundColor)
})
{
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
var radius = midX - midX / 5;
using (var circlePaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(avatarInfo.BackgroundColor)
})
{
canvas.DrawCircle(midX, midY, radius, circlePaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
using (var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(avatarInfo.TextColor),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
})
{
var rect = new SKRect();
textPaint.MeasureText(avatarInfo.CharsToDraw, ref rect);
canvas.DrawText(avatarInfo.CharsToDraw, midX, midY + rect.Height / 2, textPaint);
return SKImage.FromBitmap(bitmap);
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,179 @@
using Bit.Core.Utilities;
using SkiaSharp;
namespace Bit.App.Controls
{
public class AvatarImageSource : StreamImageSource
{
private readonly string _text;
private readonly string _id;
private readonly string _color;
public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (obj is AvatarImageSource avatar)
{
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
}
return base.Equals(obj);
}
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
{
_id = userId;
_text = name;
if (string.IsNullOrWhiteSpace(_text))
{
_text = email;
}
_color = color;
}
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
{
var result = Draw();
return Task.FromResult(result);
}
private Stream Draw()
{
string chars;
string upperCaseText = null;
if (string.IsNullOrEmpty(_text))
{
chars = "..";
}
else if (_text?.Length > 1)
{
upperCaseText = _text.ToUpper();
chars = GetFirstLetters(upperCaseText, 2);
}
else
{
chars = upperCaseText = _text.ToUpper();
}
var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
var size = 50;
//Workaround: [MAUI-Migration] There is currently a bug in MAUI where the actual size of the image is used instead of the size it should occupy in the Toolbar.
//This causes some issues with the position of the icon. As a workaround we make the icon smaller until this is fixed.
//Github issues: https://github.com/dotnet/maui/issues/12359 and https://github.com/dotnet/maui/pull/17120
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
size = 20;
}
using (var bitmap = new SKBitmap(size * 2,
size * 2,
SKImageInfo.PlatformColorType,
SKAlphaType.Premul))
{
using (var canvas = new SKCanvas(bitmap))
{
canvas.Clear(SKColors.Transparent);
using (var paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor)
})
{
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
var radius = midX - midX / 5;
using (var circlePaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor)
})
{
canvas.DrawCircle(midX, midY, radius, circlePaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
using (var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
})
{
var rect = new SKRect();
textPaint.MeasureText(chars, ref rect);
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
using (var img = SKImage.FromBitmap(bitmap))
{
var data = img.Encode(SKEncodedImageFormat.Png, 100);
return data?.AsStream(true);
}
}
}
}
}
}
}
private string GetFirstLetters(string data, int charCount)
{
var sanitizedData = data.Trim();
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 1 && charCount <= 2)
{
var text = string.Empty;
for (var i = 0; i < charCount; i++)
{
text += parts[i][0];
}
return text;
}
if (sanitizedData.Length > 2)
{
return sanitizedData.Substring(0, 2);
}
return sanitizedData;
}
private Color StringToColor(string str)
{
if (str == null)
{
return Color.FromArgb("#33ffffff");
}
var hash = 0;
for (var i = 0; i < str.Length; i++)
{
hash = str[i] + ((hash << 5) - hash);
}
var color = "#FF";
for (var i = 0; i < 3; i++)
{
var value = (hash >> (i * 8)) & 0xff;
var base16 = "00" + Convert.ToString(value, 16);
color += base16.Substring(base16.Length - 2);
}
return Color.FromArgb(color);
}
}
}

View File

@@ -1,42 +0,0 @@
namespace Bit.App.Controls
{
#if !UT
public class CachedImage : FFImageLoading.Maui.CachedImage
{
}
#else
/// <summary>
/// Given that FFImageLoading package doesn't support net8.0 then for Unit tests projects to build and run correctly
/// we need to not include the reference to FFImageLoading and therefore wrap this class
/// to provide a stub one that does nothing so this project doesn't break and we can run the tests.
/// </summary>
public class CachedImage : View
{
public static readonly BindableProperty SourceProperty = BindableProperty.Create(
nameof(Source), typeof(ImageSource), typeof(CachedImage));
public static readonly BindableProperty AspectProperty = BindableProperty.Create(
nameof(Aspect), typeof(Aspect), typeof(CachedImage));
public bool BitmapOptimizations { get; set; }
public string ErrorPlaceholder { get; set; }
public string LoadingPlaceholder { get; set; }
public ImageSource Source
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public Aspect Aspect
{
get { return (Aspect)GetValue(AspectProperty); }
set { SetValue(AspectProperty, value); }
}
public bool IsLoading { get; set; }
public event EventHandler Success;
public event EventHandler Error;
}
#endif
}

View File

@@ -1,111 +0,0 @@
using Bit.App.Pages;
namespace Bit.App.Controls
{
public abstract class BaseCipherViewCell : ExtendedGrid
{
protected virtual CachedImage Icon { get; }
protected virtual IconLabel IconPlaceholder { get; }
// HACK: PM-5896 Fix for Background Crash on iOS
// While loading the cipher icon and the user sent the app to background
// the app was crashing sometimes when the "LoadingPlaceholder" or "ErrorPlaceholder"
// were being accessed, thus locked, and as soon the app got suspended by the OS
// the app would crash because there can't be any lock files by the app when it gets suspended.
// So, the approach has changed to reuse the IconLabel default icon to use it for these placeholders
// as well. In order to do that both icon controls change their visibility dynamically here reacting to
// CachedImage events and binding context changes.
protected override void OnBindingContextChanged()
{
Icon.Source = null;
if (BindingContext is CipherItemViewModel cipherItemVM)
{
Icon.Source = cipherItemVM.IconImageSource;
if (!cipherItemVM.IconImageSuccesfullyLoaded)
{
UpdateIconImages(cipherItemVM.ShowIconImage);
}
}
base.OnBindingContextChanged();
}
private void UpdateIconImages(bool showIcon)
{
MainThread.BeginInvokeOnMainThread(() =>
{
if (!showIcon)
{
Icon.IsVisible = false;
IconPlaceholder.IsVisible = true;
return;
}
IconPlaceholder.IsVisible = Icon.IsLoading;
});
}
#if !UT
public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs e)
{
if (BindingContext is CipherItemViewModel cipherItemVM)
{
cipherItemVM.IconImageSuccesfullyLoaded = true;
MainThread.BeginInvokeOnMainThread(() =>
{
Icon.IsVisible = cipherItemVM.ShowIconImage;
IconPlaceholder.IsVisible = !cipherItemVM.ShowIconImage;
});
}
}
public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e)
{
if (BindingContext is CipherItemViewModel cipherItemVM)
{
cipherItemVM.IconImageSuccesfullyLoaded = false;
}
MainThread.BeginInvokeOnMainThread(() =>
{
Icon.IsVisible = false;
IconPlaceholder.IsVisible = true;
});
}
#else
private void Icon_Success(object sender, EventArgs e) {}
private void Icon_Error(object sender, EventArgs e) {}
#endif
}
public class StubBaseCipherViewCellSoLinkerDoesntRemoveMethods : BaseCipherViewCell
{
protected override CachedImage Icon => new CachedImage();
protected override IconLabel IconPlaceholder => new IconLabel();
public static void CallThisSoLinkerDoesntRemoveMethods()
{
#if !UT
var stub = new StubBaseCipherViewCellSoLinkerDoesntRemoveMethods();
try
{
stub.Icon_Success(stub, new FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs(new FFImageLoading.Work.ImageInformation(), FFImageLoading.Work.LoadingResult.Disk));
}
catch (Exception)
{
}
try
{
stub.Icon_Error(stub, new FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs(new InvalidOperationException("stub")));
}
catch (Exception)
{
}
#endif
}
}
}

View File

@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
<controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.CipherViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Compat.Maui"
xmlns:core="clr-namespace:Bit.Core"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
@@ -29,32 +30,34 @@
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:CachedImage
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<ff:CachedImage
x:Name="_iconImage"
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Margin="9"
WidthRequest="22"
HeightRequest="22"
Aspect="AspectFit"
Success="Icon_Success"
Error="Icon_Error"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherWebsiteIcon" />
<controls:IconLabel
x:Name="_iconPlaceholderImage"
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -119,4 +122,4 @@
SemanticProperties.Description="{u:I18n Options}"
AutomationId="CipherOptionsButton" />
</controls:BaseCipherViewCell>
</controls:ExtendedGrid>

View File

@@ -5,7 +5,7 @@ using Bit.Core.Utilities;
namespace Bit.App.Controls
{
public partial class CipherViewCell : BaseCipherViewCell
public partial class CipherViewCell : ExtendedGrid
{
private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
@@ -23,10 +23,6 @@ namespace Bit.App.Controls
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
}
protected override CachedImage Icon => _iconImage;
protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
public ICommand ButtonCommand
{
get => GetValue(ButtonCommandProperty) as ICommand;

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@@ -17,7 +17,7 @@
LineBreakMode="TailTruncation" />
<controls:IconLabel
Text="{Binding Source={x:Static core:BitwardenIcons.ExternalLink}}"
Text="{Binding Source={x:Static core:BitwardenIcons.ShareSquare}}"
TextColor="{DynamicResource TextColor}"
HorizontalOptions="End"
VerticalOptions="Center"

View File

@@ -1,299 +0,0 @@
#if ANDROID
using System;
using System.Collections.Specialized;
using Android.App;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.Text;
using Android.Text.Style;
using Android.Widget;
using Microsoft.Maui;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using AGravityFlags = Android.Views.GravityFlags;
using ALayoutDirection = Android.Views.LayoutDirection;
using AppCompatAlertDialog = AndroidX.AppCompat.App.AlertDialog;
using AResource = Android.Resource;
using ATextAlignment = Android.Views.TextAlignment;
using ATextDirection = Android.Views.TextDirection;
namespace Bit.Core.Controls.Picker
{
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
// This is an adapted copy from https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Picker/PickerHandler.Android.cs
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>
{
AppCompatAlertDialog? _dialog;
protected override MauiPicker CreatePlatformView() =>
new MauiPicker(Context);
protected override void ConnectHandler(MauiPicker platformView)
{
platformView.FocusChange += OnFocusChange;
platformView.Click += OnClick;
base.ConnectHandler(platformView);
}
protected override void DisconnectHandler(MauiPicker platformView)
{
platformView.FocusChange -= OnFocusChange;
platformView.Click -= OnClick;
base.DisconnectHandler(platformView);
}
// This is a Android-specific mapping
public static void MapBackground(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateBackground(picker);
}
// TODO Uncomment me on NET8 [Obsolete]
public static void MapReload(IPickerHandler handler, IPicker picker, object? args) => Reload(handler);
internal static void MapItems(IPickerHandler handler, IPicker picker) => Reload(handler);
public static void MapTitle(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTitle(picker);
}
public static void MapTitleColor(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateTitleColor(picker);
}
public static void MapSelectedIndex(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateSelectedIndex(picker);
}
public static void MapCharacterSpacing(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateCharacterSpacing(picker);
}
public static void MapFont(IPickerHandler handler, IPicker picker)
{
var fontManager = handler.GetRequiredService<IFontManager>();
handler.PlatformView?.UpdateFont(picker, fontManager);
}
public static void MapHorizontalTextAlignment(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateHorizontalAlignment(picker.HorizontalTextAlignment);
}
public static void MapTextColor(IPickerHandler handler, IPicker picker)
{
handler.PlatformView.UpdateTextColor(picker);
}
public static void MapVerticalTextAlignment(IPickerHandler handler, IPicker picker)
{
handler.PlatformView?.UpdateVerticalAlignment(picker.VerticalTextAlignment);
}
void OnFocusChange(object? sender, global::Android.Views.View.FocusChangeEventArgs e)
{
if (PlatformView == null)
return;
if (e.HasFocus)
{
if (PlatformView.Clickable)
PlatformView.CallOnClick();
else
OnClick(PlatformView, EventArgs.Empty);
}
else if (_dialog != null)
{
_dialog.Hide();
_dialog = null;
}
}
void OnClick(object? sender, EventArgs e)
{
if (_dialog == null && VirtualView != null)
{
using (var builder = new AppCompatAlertDialog.Builder(Context))
{
if (VirtualView.TitleColor == null)
{
builder.SetTitle(VirtualView.Title ?? string.Empty);
}
else
{
var title = new SpannableString(VirtualView.Title ?? string.Empty);
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-android/issues/6962
title.SetSpan(new ForegroundColorSpan(VirtualView.TitleColor.ToPlatform()), 0, title.Length(), SpanTypes.ExclusiveExclusive);
#pragma warning restore CA1416
builder.SetTitle(title);
}
string[] items = VirtualView.GetItemsAsArray();
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
if (item == null)
items[i] = String.Empty;
}
builder.SetSingleChoiceItems(items, VirtualView.SelectedIndex, (s, e) =>
{
var selectedIndex = e.Which;
VirtualView.SelectedIndex = selectedIndex;
base.PlatformView?.UpdatePicker(VirtualView);
_dialog.Dismiss();
});
builder.SetNegativeButton(AResource.String.Cancel, (o, args) => { });
_dialog = builder.Create();
}
if (_dialog == null)
return;
_dialog.UpdateFlowDirection(PlatformView);
_dialog.SetCanceledOnTouchOutside(true);
_dialog.DismissEvent += (sender, args) =>
{
_dialog = null;
};
_dialog.Show();
}
}
static void Reload(IPickerHandler handler)
{
handler.PlatformView.UpdatePicker(handler.VirtualView);
}
}
public static class PickerExtensions
{
const AGravityFlags HorizontalGravityMask = AGravityFlags.CenterHorizontal | AGravityFlags.End | AGravityFlags.Start;
internal static void UpdatePicker(this MauiPicker platformPicker, IPicker picker)
{
platformPicker.Hint = picker.Title;
if (picker.SelectedIndex == -1 || picker.SelectedIndex >= picker.GetCount())
platformPicker.Text = null;
else
platformPicker.Text = picker.GetItem(picker.SelectedIndex);
}
internal static void UpdateHorizontalAlignment(this EditText view, TextAlignment alignment, AGravityFlags orMask = AGravityFlags.NoGravity)
{
if (!Rtl.IsSupported)
{
view.Gravity = (view.Gravity & ~HorizontalGravityMask) | alignment.ToHorizontalGravityFlags() | orMask;
}
else
view.TextAlignment = alignment.ToTextAlignment();
}
internal static AGravityFlags ToHorizontalGravityFlags(this TextAlignment alignment)
{
switch (alignment)
{
case TextAlignment.Center:
return AGravityFlags.CenterHorizontal;
case TextAlignment.End:
return AGravityFlags.End;
default:
return AGravityFlags.Start;
}
}
internal static ATextAlignment ToTextAlignment(this TextAlignment alignment)
{
switch (alignment)
{
case TextAlignment.Center:
return ATextAlignment.Center;
case TextAlignment.End:
return ATextAlignment.ViewEnd;
default:
return ATextAlignment.ViewStart;
}
}
internal static void UpdateFlowDirection(this AndroidX.AppCompat.App.AlertDialog alertDialog, MauiPicker platformPicker)
{
var platformLayoutDirection = platformPicker.LayoutDirection;
// Propagate the MauiPicker LayoutDirection to the AlertDialog
var dv = alertDialog.Window?.DecorView;
if (dv is not null)
dv.LayoutDirection = platformLayoutDirection;
var lv = alertDialog?.ListView;
if (lv is not null)
{
lv.LayoutDirection = platformLayoutDirection;
lv.TextDirection = platformLayoutDirection.ToTextDirection();
}
}
internal static ATextDirection ToTextDirection(this ALayoutDirection direction)
{
switch (direction)
{
case ALayoutDirection.Ltr:
return ATextDirection.Ltr;
case ALayoutDirection.Rtl:
return ATextDirection.Rtl;
default:
return ATextDirection.Inherit;
}
}
public static T GetRequiredService<T>(this IElementHandler handler)
where T : notnull
{
var services = handler.GetServiceProvider();
var service = services.GetRequiredService<T>();
return service;
}
public static IServiceProvider GetServiceProvider(this IElementHandler handler)
{
var context = handler.MauiContext ??
throw new InvalidOperationException($"Unable to find the context. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");
var services = context?.Services ??
throw new InvalidOperationException($"Unable to find the service provider. The {nameof(ElementHandler.MauiContext)} property should have been set by the host.");
return services;
}
}
static class Rtl
{
/// <summary>
/// True if /manifest/application@android:supportsRtl="true"
/// </summary>
public static readonly bool IsSupported =
(Android.App.Application.Context?.ApplicationInfo?.Flags & ApplicationInfoFlags.SupportsRtl) != 0;
}
}
#endif

View File

@@ -1,53 +0,0 @@
#if ANDROID
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
namespace Bit.Core.Controls.Picker
{
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
// This is a copy from https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/Picker/PickerHandler.cs
public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>, IPickerHandler
{
public static IPropertyMapper<IPicker, IPickerHandler> Mapper = new PropertyMapper<IPicker, PickerHandler>(ViewMapper)
{
#if __ANDROID__ || WINDOWS
[nameof(IPicker.Background)] = MapBackground,
#endif
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
[nameof(IPicker.Font)] = MapFont,
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
[nameof(IPicker.TextColor)] = MapTextColor,
[nameof(IPicker.Title)] = MapTitle,
[nameof(IPicker.TitleColor)] = MapTitleColor,
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
[nameof(IPicker.Items)] = MapItems,
};
public static CommandMapper<IPicker, IPickerHandler> CommandMapper = new(ViewCommandMapper)
{
};
public PickerHandler() : base(Mapper, CommandMapper)
{
}
public PickerHandler(IPropertyMapper? mapper)
: base(mapper ?? Mapper, CommandMapper)
{
}
public PickerHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
{
}
IPicker IPickerHandler.VirtualView => VirtualView;
Microsoft.Maui.Platform.MauiPicker IPickerHandler.PlatformView => PlatformView;
}
}
#endif

View File

@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--When running Unit tests we'll have the custom constant "UT" added, so in this manner we can add the net8.0 target we need for UT -->
<TargetFrameworks Condition="$(CustomConstants.Contains(UT))">net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
<TargetFrameworks Condition="!$(CustomConstants.Contains(UT))">net8.0-android;net8.0-ios</TargetFrameworks>
<TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
<RootNamespace>Bit.Core</RootNamespace>
<UseMaui>true</UseMaui>
@@ -43,11 +41,10 @@
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<!-- HACK: When running Unit Tests we cannot load FFImageLoading because it doesn't support "raw" net8.0 -->
<PackageReference Condition="!$(CustomConstants.Contains(UT))" Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
@@ -67,6 +64,7 @@
<Folder Include="Resources\Fonts\" />
<Folder Include="Effects\" />
<Folder Include="Resources\Raw\" />
<Folder Include="Pages\" />
<Folder Include="Behaviors\" />
<Folder Include="Controls\" />
<Folder Include="Lists\" />
@@ -75,9 +73,6 @@
<Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" />
<Folder Include="Resources\Localization\" />
<Folder Include="Controls\Picker\" />
<Folder Include="Controls\Avatar\" />
<Folder Include="Utilities\WebAuthenticatorMAUI\" />
</ItemGroup>
<ItemGroup>
<MauiImage Include="Resources\Images\dotnet_bot.svg">
@@ -91,23 +86,10 @@
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<Compile Update="Pages\AndroidNavigationRedirectPage.xaml.cs">
<DependentUpon>AndroidNavigationRedirectPage.xaml</DependentUpon>
</Compile>
<Compile Update="Resources\Localization\AppResources.Designer.cs">
<DependentUpon>AppResources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<MauiXaml Update="Pages\AndroidNavigationRedirectPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
</ItemGroup>
<ItemGroup>
<None Remove="Controls\Picker\" />
<None Remove="Controls\Avatar\" />
<None Remove="Utilities\WebAuthenticatorMAUI\" />
</ItemGroup>
</Project>

View File

@@ -6,18 +6,13 @@
public const string HELP_ABOUT_ORGANIZATIONS = "https://bitwarden.com/help/about-organizations/";
public const string HELP_FINGERPRINT_PHRASE = "https://bitwarden.com/help/fingerprint-phrase/";
public const string PRIVACY_POLICY = "https://bitwarden.com/privacy/";
public const string CONTACT_SUPPORT = "https://bitwarden.com/contact/";
/// <summary>
/// Link to go to settings website. Requires to pass website URL as parameter.
/// </summary>
public const string WEB_VAULT_SETTINGS_FORMAT = "{0}/#/settings";
/// <summary>
/// Link to go to individual vault import page. Requires to pass vault URL as parameter.
/// </summary>
public const string WEB_VAULT_TOOLS_IMPORT_FORMAT = "{0}/#/tools/import";
/// <summary>
/// General website, not in the full format of a URL given that this is used as parameter of string resources to be shown to the user.
/// </summary>

View File

@@ -1,12 +1,8 @@
using Bit.App.Controls;
using Camera.MAUI;
using Camera.MAUI;
using CommunityToolkit.Maui;
#if !UT
using FFImageLoading.Maui;
#endif
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Compatibility.Hosting;
using Microsoft.Maui.Handlers;
using SkiaSharp.Views.Maui.Controls.Hosting;
using AppEffects = Bit.App.Effects;
@@ -26,9 +22,7 @@ public static class MauiProgram
.UseMauiCompatibility()
.UseMauiCameraView()
.UseSkiaSharp()
#if !UT
.UseFFImageLoading()
#endif
.ConfigureEffects(effects =>
{
#if ANDROID
@@ -46,16 +40,6 @@ public static class MauiProgram
})
.ConfigureMauiHandlers(handlers =>
{
#if ANDROID
// HACK: Due to https://github.com/dotnet/maui/issues/19681 and not willing to use reflection to access
// the alert dialog, we need to redefine the PickerHandler implementation for a custom one of ours
// which handles showing the current selected item. Remove this workaround when MAUI releases a fix for this.
if (handlers.FirstOrDefault(h => h.ServiceType == typeof(Picker)) is ServiceDescriptor sd)
{
handlers.Remove(sd);
handlers.AddHandler(typeof(IPicker), typeof(Controls.Picker.PickerHandler));
}
#endif
customHandlers?.Invoke(handlers);
});
@@ -63,13 +47,6 @@ public static class MauiProgram
builder.Logging.AddDebug();
#endif
ExplicitlyPreventThingsGetRemovedBecauseOfLinker();
return builder;
}
private static void ExplicitlyPreventThingsGetRemovedBecauseOfLinker()
{
StubBaseCipherViewCellSoLinkerDoesntRemoveMethods.CallThisSoLinkerDoesntRemoveMethods();
}
}

View File

@@ -6,6 +6,5 @@ namespace Bit.Core.Models.Api
{
public string Uri { get; set; }
public UriMatchType? Match { get; set; }
public string UriChecksum { get; set; }
}
}

View File

@@ -11,11 +11,9 @@ namespace Bit.Core.Models.Data
{
Uri = data.Uri;
Match = data.Match;
UriChecksum = data.UriChecksum;
}
public string Uri { get; set; }
public UriMatchType? Match { get; set; }
public string UriChecksum { get; set; }
}
}

View File

@@ -115,7 +115,7 @@ namespace Bit.Core.Models.Domain
switch (Type)
{
case Enums.CipherType.Login:
model.Login = await Login.DecryptAsync(OrganizationId, Key == null, model.Key);
model.Login = await Login.DecryptAsync(OrganizationId, model.Key);
break;
case Enums.CipherType.SecureNote:
model.SecureNote = await SecureNote.DecryptAsync(OrganizationId, model.Key);

View File

@@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Domain
{
@@ -33,7 +31,7 @@ namespace Bit.Core.Models.Domain
public EncString Totp { get; set; }
public List<Fido2Credential> Fido2Credentials { get; set; }
public async Task<LoginView> DecryptAsync(string orgId, bool bypassUriChecksumValidation, SymmetricCryptoKey key = null)
public async Task<LoginView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
var view = await DecryptObjAsync(new LoginView(this), this, new HashSet<string>
{
@@ -43,15 +41,10 @@ namespace Bit.Core.Models.Domain
}, orgId, key);
if (Uris != null)
{
var cryptoService = ServiceContainer.Resolve<ICryptoService>();
view.Uris = new List<LoginUriView>();
foreach (var uri in Uris)
{
var loginUriView = await uri.DecryptAsync(orgId, key);
if (bypassUriChecksumValidation || await cryptoService.ValidateUriChecksumAsync(uri.UriChecksum, loginUriView.Uri, orgId, key))
{
view.Uris.Add(loginUriView);
}
view.Uris.Add(await uri.DecryptAsync(orgId, key));
}
}
if (Fido2Credentials != null)

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@@ -11,8 +10,7 @@ namespace Bit.Core.Models.Domain
{
private HashSet<string> _map = new HashSet<string>
{
nameof(Uri),
nameof(UriChecksum)
"Uri"
};
public LoginUri() { }
@@ -25,11 +23,10 @@ namespace Bit.Core.Models.Domain
public EncString Uri { get; set; }
public UriMatchType? Match { get; set; }
public EncString UriChecksum { get; set; }
public Task<LoginUriView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return DecryptObjAsync(new LoginUriView(this), this, _map.Where(m => m != nameof(UriChecksum)).ToHashSet<string>(), orgId, key);
return DecryptObjAsync(new LoginUriView(this), this, _map, orgId, key);
}
public LoginUriData ToLoginUriData()

View File

@@ -17,12 +17,10 @@ namespace Bit.Core.Models.Export
{
Match = obj.Match;
Uri = obj.Uri?.EncryptedString;
UriChecksum = obj.UriChecksum?.EncryptedString;
}
public UriMatchType? Match { get; set; }
public string Uri { get; set; }
public string UriChecksum { get; set; }
public static LoginUriView ToView(LoginUri req, LoginUriView view = null)
{

View File

@@ -27,7 +27,7 @@ namespace Bit.Core.Models.Request
Login = new LoginApi
{
Uris = cipher.Login.Uris?.Select(
u => new LoginUriApi { Match = u.Match, Uri = u.Uri?.EncryptedString, UriChecksum = u.UriChecksum?.EncryptedString }).ToList(),
u => new LoginUriApi { Match = u.Match, Uri = u.Uri?.EncryptedString }).ToList(),
Username = cipher.Login.Username?.EncryptedString,
Password = cipher.Login.Password?.EncryptedString,
PasswordRevisionDate = cipher.Login.PasswordRevisionDate,

View File

@@ -61,7 +61,6 @@
VerticalOptions="Start"
Margin="0,20,6,0"
Padding="16,0"
MinimumHeightRequest="45"
CornerRadius="2"
TextTransform="Uppercase"
Clicked="DeleteAccount_Clicked"/>
@@ -74,7 +73,6 @@
VerticalOptions="Start"
Margin="0,20,0,0"
Padding="16,0"
MinimumHeightRequest="45"
CornerRadius="2"
TextTransform="Uppercase"
Clicked="Close_Clicked" />

View File

@@ -21,6 +21,10 @@
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="MAUI APP"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n SelfHostedEnvironment, Header=True}"
StyleClass="box-header, box-header-platform" />
@@ -33,7 +37,7 @@
Text="{Binding BaseUrl}"
Keyboard="Url"
Placeholder="ex. https://bitwarden.company.com"
StyleClass="box-value, no-keyboard-auto-help"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}"
AutomationId="ServerUrlEntry"/>
@@ -55,7 +59,7 @@
x:Name="_webVaultEntry"
Text="{Binding WebVaultUrl}"
Keyboard="Url"
StyleClass="box-value, no-keyboard-auto-help"
StyleClass="box-value"
AutomationId="WebVaultUrlEntry"/>
</StackLayout>
<StackLayout StyleClass="box-row">
@@ -66,7 +70,7 @@
x:Name="_apiEntry"
Text="{Binding ApiUrl}"
Keyboard="Url"
StyleClass="box-value, no-keyboard-auto-help"
StyleClass="box-value"
AutomationId="ApiUrlEntry"/>
</StackLayout>
<StackLayout StyleClass="box-row">
@@ -77,7 +81,7 @@
x:Name="_identityEntry"
Text="{Binding IdentityUrl}"
Keyboard="Url"
StyleClass="box-value, no-keyboard-auto-help"
StyleClass="box-value"
AutomationId="IdentityUrlEntry"/>
</StackLayout>
<StackLayout StyleClass="box-row">
@@ -88,7 +92,7 @@
x:Name="_iconsEntry"
Text="{Binding IconsUrl}"
Keyboard="Url"
StyleClass="box-value, no-keyboard-auto-help"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}"
AutomationId="IconsUrlEntry"/>

View File

@@ -1,7 +1,6 @@
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Microsoft.Maui.Platform;
namespace Bit.App.Pages
{
@@ -27,7 +26,7 @@ namespace Bit.App.Pages
_apiEntry.ReturnCommand = new Command(() => _identityEntry.Focus());
_identityEntry.ReturnType = ReturnType.Next;
_identityEntry.ReturnCommand = new Command(() => _iconsEntry.Focus());
_vm.SubmitSuccessTask = () => MainThread.InvokeOnMainThreadAsync(SubmitSuccessAsync);
_vm.SubmitSuccessAction = () => MainThread.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
_vm.CloseAction = async () =>
{
await Navigation.PopModalAsync();
@@ -38,12 +37,6 @@ namespace Bit.App.Pages
{
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
await Navigation.PopModalAsync();
#if ANDROID
if (Platform.CurrentActivity.CurrentFocus != null)
{
Platform.CurrentActivity.HideKeyboard(Platform.CurrentActivity.CurrentFocus);
}
#endif
}
private void Close_Clicked(object sender, EventArgs e)

View File

@@ -44,7 +44,7 @@ namespace Bit.App.Pages
public string WebVaultUrl { get; set; }
public string IconsUrl { get; set; }
public string NotificationsUrls { get; set; }
public Func<Task> SubmitSuccessTask { get; set; }
public Action SubmitSuccessAction { get; set; }
public Action CloseAction { get; set; }
public async Task SubmitAsync()
@@ -73,10 +73,7 @@ namespace Bit.App.Pages
IconsUrl = resUrls.Icons;
NotificationsUrls = resUrls.Notifications;
if (SubmitSuccessTask != null)
{
await SubmitSuccessTask();
}
SubmitSuccessAction?.Invoke();
}
public bool ValidateUrls()

View File

@@ -9,7 +9,6 @@
x:DataType="pages:HomeViewModel"
HideSoftInputOnTapped="True"
x:Name="_page"
Loaded="HomePage_Loaded"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:HomeViewModel />
@@ -50,10 +49,11 @@
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value, no-keyboard-auto-help"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding ContinueCommand}"
AutomationId="EmailAddressEntry">
AutomationId="EmailAddressEntry"
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
@@ -72,7 +72,7 @@
Command="{Binding ShowEnvironmentPickerCommand}" />
</StackLayout.GestureRecognizers>
<Label
Margin="{OnPlatform Android='0,0,6,1', iOS='0,0,6,0'}"
Margin="0,0,6,0"
Text="{Binding RegionText}"
FontSize="13"
TextColor="{DynamicResource MutedColor}"

View File

@@ -1,8 +1,8 @@
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Microsoft.Maui.Platform;
namespace Bit.App.Pages
{
@@ -12,14 +12,12 @@ namespace Bit.App.Pages
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService;
private IConditionedAwaiterManager _conditionedAwaiterManager;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public HomePage(AppOptions appOptions = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_conditionedAwaiterManager = ServiceContainer.Resolve<IConditionedAwaiterManager>();
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
@@ -46,23 +44,6 @@ namespace Bit.App.Pages
}
}
public bool PerformNavigationOnAccountChangedOnLoad { get; internal set; }
void HomePage_Loaded(System.Object sender, System.EventArgs e)
{
#if ANDROID
// WORKAROUND: This is needed to fix the navigation when coming back from autofill when Accessibility Services is enabled
// See App.xaml.cs -> CreateWindow(...) for more info.
if (PerformNavigationOnAccountChangedOnLoad && ServiceContainer.TryResolve<IAccountsManager>(out var accountsManager))
{
PerformNavigationOnAccountChangedOnLoad = false;
accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
}
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.AndroidWindowCreated);
#endif
}
public async Task DismissRegisterPageAndLogInAsync(string email)
{
await Navigation.PopModalAsync();
@@ -157,7 +138,7 @@ namespace Bit.App.Pages
private async Task StartEnvironmentAsync()
{
await _accountListOverlay.HideAsync();
await _accountListOverlay.HideAsync();
var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -165,7 +165,7 @@ namespace Bit.App.Pages
await MainThread.InvokeOnMainThreadAsync(async () =>
{
var result = await _deviceActionService.Value.DisplayActionSheetAsync(AppResources.LoggingInOn, AppResources.Cancel, null, options);
var result = await Page.DisplayActionSheet(AppResources.LoggingInOn, AppResources.Cancel, null, options);
if (result is null || result == AppResources.Cancel)
{

View File

@@ -4,7 +4,6 @@ using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Core.Services;
namespace Bit.App.Pages
{
@@ -27,24 +26,16 @@ namespace Bit.App.Pages
_vm = BindingContext as LockPageViewModel;
_vm.CheckPendingAuthRequests = checkPendingAuthRequests;
_vm.Page = this;
_vm.UnlockedAction = () => MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
await UnlockedAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
});
_vm.UnlockedAction = () => MainThread.BeginInvokeOnMainThread(async () => await UnlockedAsync());
#if IOS
ToolbarItems.Add(_moreItem);
#else
ToolbarItems.Add(_logOut);
#endif
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
ToolbarItems.Add(_moreItem);
}
else
{
ToolbarItems.Add(_logOut);
}
}
public Entry SecretEntry
@@ -74,60 +65,52 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
try
base.OnAppearing();
_broadcasterService.Subscribe(nameof(LockPage), message =>
{
base.OnAppearing();
_broadcasterService.Subscribe(nameof(LockPage), message =>
if (message.Command == Constants.ClearSensitiveFields)
{
if (message.Command == Constants.ClearSensitiveFields)
{
MainThread.BeginInvokeOnMainThread(() => _vm?.ResetPinPasswordFields());
}
});
if (_appeared)
{
return;
}
_appeared = true;
_mainContent.Content = _mainLayout;
//Workaround: This delay allows the Avatar to correctly load on iOS. The cause of this issue is also likely connected with the race conditions issue when using loading modals in iOS
await Task.Delay(50);
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
await _vm.InitAsync();
_vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricEnabled)
{
RequestFocus(SecretEntry);
}
else
{
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
{
_passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false;
}
if (_autoPromptBiometric)
{
var tasks = Task.Run(async () =>
{
await Task.Delay(500);
await MainThread.InvokeOnMainThreadAsync(async () => await _vm.PromptBiometricAsync());
});
}
MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields);
}
});
if (_appeared)
{
return;
}
catch (Exception ex)
_appeared = true;
_mainContent.Content = _mainLayout;
//Workaround: This delay allows the Avatar to correctly load on iOS. The cause of this issue is also likely connected with the race conditions issue when using loading modals in iOS
await Task.Delay(50);
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
await _vm.InitAsync();
_vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricEnabled)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
RequestFocus(SecretEntry);
}
else
{
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
{
_passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false;
}
if (_autoPromptBiometric)
{
var tasks = Task.Run(async () =>
{
await Task.Delay(500);
MainThread.BeginInvokeOnMainThread(async () => await _vm.PromptBiometricAsync());
});
}
}
}
@@ -184,44 +167,27 @@ namespace Bit.App.Pages
private async void Biometric_Clicked(object sender, EventArgs e)
{
try
if (DoOnce())
{
if (DoOnce())
{
await _vm.PromptBiometricAsync();
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
await _vm.PromptBiometricAsync();
}
}
private async void More_Clicked(object sender, System.EventArgs e)
{
try
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
return;
}
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, AppResources.LogOut);
if (selection == AppResources.LogOut)
{
await _vm.LogOutAsync();
}
return;
}
catch (Exception ex)
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, AppResources.LogOut);
if (selection == AppResources.LogOut)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
await _vm.LogOutAsync();
}
}
@@ -233,7 +199,7 @@ namespace Bit.App.Pages
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
}
}

View File

@@ -245,9 +245,9 @@ namespace Bit.App.Pages
public async Task SubmitAsync()
{
ShowPassword = false;
try
{
ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinEnabled)
{
@@ -257,15 +257,12 @@ namespace Bit.App.Pages
{
await UnlockWithMasterPasswordAsync(kdfConfig);
}
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
catch (Exception ex)
{
HandleException(ex);
}
}
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)

View File

@@ -36,7 +36,7 @@ namespace Bit.App.Pages
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task StartLogInWithMasterPasswordAsync()

View File

@@ -3,7 +3,6 @@ using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
namespace Bit.App.Pages
@@ -75,7 +74,7 @@ namespace Bit.App.Pages
{
if (message.Command == Constants.ClearSensitiveFields)
{
MainThread.BeginInvokeOnMainThread(() => _vm?.ResetPasswordField());
MainThread.BeginInvokeOnMainThread(_vm.ResetPasswordField);
}
});
_mainContent.Content = _mainLayout;
@@ -189,20 +188,12 @@ namespace Bit.App.Pages
private async Task LogInSuccessAsync()
{
try
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task UpdateTempPasswordAsync()

View File

@@ -39,7 +39,7 @@
FontSize="Small"
FontAttributes="Bold"/>
<controls:MonoLabel
Text="{Binding LoginRequest.FingerprintPhrase}"
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
FontSize="Medium"
TextColor="{DynamicResource FingerprintPhrase}"
Margin="0,0,0,27"
@@ -85,6 +85,7 @@
<Button
Text="{u:I18n DenyLogIn}"
Command="{Binding RejectRequestCommand}"
StyleClass="btn-secundary"
AutomationId="DenyLoginButton" />
</StackLayout>

View File

@@ -41,7 +41,7 @@
FontSize="Small"
FontAttributes="Bold" />
<controls:MonoLabel
Text="{Binding FingerprintPhrase}"
FormattedText="{Binding FingerprintPhrase}"
FontSize="Small"
TextColor="{DynamicResource FingerprintPhrase}"
AutomationId="FingerprintPhraseValue" />

View File

@@ -1,7 +1,6 @@
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Services;
namespace Bit.App.Pages
{
@@ -49,20 +48,12 @@ namespace Bit.App.Pages
private async Task LogInSuccessAsync()
{
try
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task UpdateTempPasswordAsync()

View File

@@ -77,7 +77,6 @@ namespace Bit.App.Pages
{
_requestTimeCts?.Cancel();
_requestTimeCts?.Dispose();
_requestTimeCts = null;
}
catch (Exception ex)
{

View File

@@ -1,7 +1,6 @@
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
namespace Bit.App.Pages
@@ -21,7 +20,6 @@ namespace Bit.App.Pages
InitializeComponent();
_vm = BindingContext as LoginSsoPageViewModel;
_vm.Page = this;
_vm.FromIosExtension = _appOptions?.IosExtension ?? false;
_vm.StartTwoFactorAction = () => MainThread.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.StartSetPasswordAction = () =>
MainThread.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
@@ -91,30 +89,16 @@ namespace Bit.App.Pages
private async Task StartTwoFactorAsync()
{
try
{
RestoreAppOptionsFromCopy();
var page = new TwoFactorPage(true, _appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
RestoreAppOptionsFromCopy();
var page = new TwoFactorPage(true, _appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartSetPasswordAsync()
{
try
{
RestoreAppOptionsFromCopy();
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
RestoreAppOptionsFromCopy();
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
@@ -131,23 +115,16 @@ namespace Bit.App.Pages
private async Task SsoAuthSuccessAsync()
{
try
{
RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage();
RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage();
if (await _vaultTimeoutService.IsLockedAsync())
{
App.MainPage = new NavigationPage(new LockPage(_appOptions));
}
else
{
App.MainPage = new TabsPage(_appOptions, null);
}
}
catch (Exception ex)
if (await _vaultTimeoutService.IsLockedAsync())
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
}
else
{
Application.Current.MainPage = new TabsPage(_appOptions, null);
}
}
}

View File

@@ -14,17 +14,6 @@ using Bit.Core.Utilities;
using Microsoft.Maui.Authentication;
using Microsoft.Maui.Networking;
using NetworkAccess = Microsoft.Maui.Networking.NetworkAccess;
using Org.BouncyCastle.Asn1.Ocsp;
#if IOS
using AuthenticationServices;
using Foundation;
using UIKit;
using WebAuthenticator = Bit.Core.Utilities.MAUI.WebAuthenticator;
using WebAuthenticatorResult = Bit.Core.Utilities.MAUI.WebAuthenticatorResult;
using WebAuthenticatorOptions = Bit.Core.Utilities.MAUI.WebAuthenticatorOptions;
#endif
namespace Bit.App.Pages
{
@@ -74,8 +63,6 @@ namespace Bit.App.Pages
set => SetProperty(ref _orgIdentifier, value);
}
public bool FromIosExtension { get; set; }
public ICommand LogInCommand { get; }
public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; }
@@ -165,9 +152,6 @@ namespace Bit.App.Pages
CallbackUrl = new Uri(REDIRECT_URI),
Url = new Uri(url),
PrefersEphemeralWebBrowserSession = _useEphemeralWebBrowserSession,
#if IOS
ShouldUseSharedApplicationKeyWindow = FromIosExtension
#endif
});
var code = GetResultCode(authResult, state);

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