Skip to content

Commit

Permalink
Add FXIOS-8357 [v124] Login autofill improvements bottom sheet with l…
Browse files Browse the repository at this point in the history
…ogin information (#18866)

Add FXIOS-8357 [v124] Login autofill improvements bottom sheet with login information
---------

Co-authored-by: Nishant Bhasin <nish.bhasin@gmail.com>
Co-authored-by: Sophie <5545720+dataports@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 29, 2024
1 parent 426dbe5 commit 647aecd
Show file tree
Hide file tree
Showing 27 changed files with 796 additions and 157 deletions.
40 changes: 32 additions & 8 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -858,8 +858,8 @@
8AFCE50929DE136300B1B253 /* MockLaunchFinishedLoadingDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE50829DE136300B1B253 /* MockLaunchFinishedLoadingDelegate.swift */; };
8AFE4C2127480D0C00B97C65 /* LegacyTabTrayViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE4C2027480D0B00B97C65 /* LegacyTabTrayViewControllerTests.swift */; };
8C19532E2B85E7AE00761B20 /* SelfSizingHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C19532D2B85E7AE00761B20 /* SelfSizingHostingController.swift */; };
8C1953302B85E7EC00761B20 /* AutoFillFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C19532F2B85E7EC00761B20 /* AutoFillFooterView.swift */; };
8C1953322B85EAB500761B20 /* AutoFillHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1953312B85EAB500761B20 /* AutoFillHeaderView.swift */; };
8C1953302B85E7EC00761B20 /* AutofillFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C19532F2B85E7EC00761B20 /* AutofillFooterView.swift */; };
8C1953322B85EAB500761B20 /* AutofillHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1953312B85EAB500761B20 /* AutofillHeaderView.swift */; };
8C29627C2B1F473800571655 /* AdEventsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C29627B2B1F473800571655 /* AdEventsResponse.swift */; };
8C44A9D22A6A99FE009A1AA7 /* ShoppingProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C44A9D12A6A99FE009A1AA7 /* ShoppingProduct.swift */; };
8C46E1B72B2209F000F56521 /* FakespotAdsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C46E1B62B2209F000F56521 /* FakespotAdsEvent.swift */; };
Expand All @@ -873,6 +873,12 @@
8CAF29A02AA5E76B00DC3486 /* FakespotMessageCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAF299F2AA5E76B00DC3486 /* FakespotMessageCardView.swift */; };
8CBDE8E32AB09804001985BF /* ProductAnalyzeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CBDE8E22AB09804001985BF /* ProductAnalyzeResponse.swift */; };
8CCCB08B2AE26B5C0073ADB9 /* ReportResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CCCB08A2AE26B5C0073ADB9 /* ReportResponse.swift */; };
8CCD74732B90A945008F919B /* LoginListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CCD74722B90A945008F919B /* LoginListViewModelTests.swift */; };
8CE1E4322B8C76AE0026530B /* LoginStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE1E4312B8C76AE0026530B /* LoginStorage.swift */; };
8CE1E4372B8C76C80026530B /* LoginAutofillView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE1E4332B8C76C80026530B /* LoginAutofillView.swift */; };
8CE1E4382B8C76C80026530B /* LoginListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE1E4342B8C76C80026530B /* LoginListViewModel.swift */; };
8CE1E4392B8C76C80026530B /* LoginCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE1E4352B8C76C80026530B /* LoginCellView.swift */; };
8CE1E43A2B8C76C80026530B /* LoginListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE1E4362B8C76C80026530B /* LoginListView.swift */; };
8CFD56882AAF057D003157A6 /* SwitchFakespotProduction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFD56872AAF057D003157A6 /* SwitchFakespotProduction.swift */; };
8CFD56892AAF06D3003157A6 /* ShoppingProductTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6DA7D02A6FE78F00DE264F /* ShoppingProductTests.swift */; };
8D8251811F4DE67F00780643 /* AdvancedAccountSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8251721F4DE67E00780643 /* AdvancedAccountSettingViewController.swift */; };
Expand Down Expand Up @@ -5937,8 +5943,8 @@
8BFA4413BB71963DB0E69F82 /* ses */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ses; path = ses.lproj/AuthenticationManager.strings; sourceTree = "<group>"; };
8BFD47109E07F897D604AEBE /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uz; path = uz.lproj/ClearPrivateData.strings; sourceTree = "<group>"; };
8C19532D2B85E7AE00761B20 /* SelfSizingHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingHostingController.swift; sourceTree = "<group>"; };
8C19532F2B85E7EC00761B20 /* AutoFillFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoFillFooterView.swift; sourceTree = "<group>"; };
8C1953312B85EAB500761B20 /* AutoFillHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoFillHeaderView.swift; sourceTree = "<group>"; };
8C19532F2B85E7EC00761B20 /* AutofillFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillFooterView.swift; sourceTree = "<group>"; };
8C1953312B85EAB500761B20 /* AutofillHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillHeaderView.swift; sourceTree = "<group>"; };
8C264BF5A1C7B2B6378D4DFF /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Today.strings; sourceTree = "<group>"; };
8C29627B2B1F473800571655 /* AdEventsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdEventsResponse.swift; sourceTree = "<group>"; };
8C44A9D12A6A99FE009A1AA7 /* ShoppingProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShoppingProduct.swift; sourceTree = "<group>"; };
Expand All @@ -5958,8 +5964,14 @@
8CBDE8E22AB09804001985BF /* ProductAnalyzeResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductAnalyzeResponse.swift; sourceTree = "<group>"; };
8CCA45EFA01D41F54715FBCC /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/AuthenticationManager.strings; sourceTree = "<group>"; };
8CCCB08A2AE26B5C0073ADB9 /* ReportResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResponse.swift; sourceTree = "<group>"; };
8CCD74722B90A945008F919B /* LoginListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginListViewModelTests.swift; sourceTree = "<group>"; };
8CD04F1485CE61A3B3237248 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/3DTouchActions.strings; sourceTree = "<group>"; };
8CE041B18298F668144B225A /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Menu.strings; sourceTree = "<group>"; };
8CE1E4312B8C76AE0026530B /* LoginStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginStorage.swift; sourceTree = "<group>"; };
8CE1E4332B8C76C80026530B /* LoginAutofillView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginAutofillView.swift; sourceTree = "<group>"; };
8CE1E4342B8C76C80026530B /* LoginListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginListViewModel.swift; sourceTree = "<group>"; };
8CE1E4352B8C76C80026530B /* LoginCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginCellView.swift; sourceTree = "<group>"; };
8CE1E4362B8C76C80026530B /* LoginListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginListView.swift; sourceTree = "<group>"; };
8CFD56872AAF057D003157A6 /* SwitchFakespotProduction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchFakespotProduction.swift; sourceTree = "<group>"; };
8D1340A4B7E4ACBF43347178 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/LoginManager.strings; sourceTree = "<group>"; };
8D194DCF9E244D29DA68FDE5 /* az */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = az; path = az.lproj/Shared.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8842,10 +8854,15 @@
43D00490296FC44B00CB0F31 /* Autofill */ = {
isa = PBXGroup;
children = (
8CE1E4352B8C76C80026530B /* LoginCellView.swift */,
8CE1E4362B8C76C80026530B /* LoginListView.swift */,
8CE1E4332B8C76C80026530B /* LoginAutofillView.swift */,
8CE1E4342B8C76C80026530B /* LoginListViewModel.swift */,
8CE1E4312B8C76AE0026530B /* LoginStorage.swift */,
B2981F892B71AD7A00132C1B /* AutofillAccessoryViewButtonItem.swift */,
8C19532D2B85E7AE00761B20 /* SelfSizingHostingController.swift */,
8C19532F2B85E7EC00761B20 /* AutoFillFooterView.swift */,
8C1953312B85EAB500761B20 /* AutoFillHeaderView.swift */,
8C19532F2B85E7EC00761B20 /* AutofillFooterView.swift */,
8C1953312B85EAB500761B20 /* AutofillHeaderView.swift */,
B2FEA6892B460CEC0058E616 /* Address */,
43D00491296FC46E00CB0F31 /* CreditCard */,
);
Expand Down Expand Up @@ -9977,6 +9994,7 @@
isa = PBXGroup;
children = (
439B78172A09721600CAAE37 /* FormAutofillHelperTests.swift */,
8CCD74722B90A945008F919B /* LoginListViewModelTests.swift */,
);
path = Autofill;
sourceTree = "<group>";
Expand Down Expand Up @@ -13528,7 +13546,7 @@
8C46E1B72B2209F000F56521 /* FakespotAdsEvent.swift in Sources */,
396E38F11EE0C8EC00CC180F /* FxAPushMessageHandler.swift in Sources */,
8A76B01629F6EB3900A82607 /* ScreenshotService.swift in Sources */,
8C1953322B85EAB500761B20 /* AutoFillHeaderView.swift in Sources */,
8C1953322B85EAB500761B20 /* AutofillHeaderView.swift in Sources */,
8C19532E2B85E7AE00761B20 /* SelfSizingHostingController.swift in Sources */,
E4CD9F6D1A77DD2800318571 /* ReaderModeStyleViewController.swift in Sources */,
E13E9AB52AAB0FB5001A0E9D /* FakespotViewModel.swift in Sources */,
Expand Down Expand Up @@ -13673,6 +13691,7 @@
C88E7A602A05551B0072E638 /* NimbusOnboardingFeatureLayerProtocol.swift in Sources */,
8CFD56882AAF057D003157A6 /* SwitchFakespotProduction.swift in Sources */,
C40046FA1CF8E0B200B08303 /* BackForwardListAnimator.swift in Sources */,
8CE1E4372B8C76C80026530B /* LoginAutofillView.swift in Sources */,
DD31E0FB1B382B520077078A /* TabPrintPageRenderer.swift in Sources */,
D81E45131F82C56D004EFFBA /* NewTabContentSettingsViewController.swift in Sources */,
E4CD9E911A6897FB00318571 /* ReaderMode.swift in Sources */,
Expand Down Expand Up @@ -13704,10 +13723,12 @@
CEFA977E1FAA6B490016F365 /* SyncContentSettingsViewController.swift in Sources */,
C8CD80DC2A1E8C970097C3AE /* OnboardingTelemetryUtility.swift in Sources */,
96C11E9B2864C2DD00840E7C /* DependencyHelper.swift in Sources */,
8CE1E4382B8C76C80026530B /* LoginListViewModel.swift in Sources */,
F18859502A3E454E0004AA7B /* EnhancedTrackingProtectionCoordinator.swift in Sources */,
2386E4E624F8358E0072EF17 /* HomepageMessageCard.swift in Sources */,
21618A8C2A438A0900A5189E /* ActiveScreenAction.swift in Sources */,
E15DE7C2293A7AED00B32667 /* PhotonActionSheetLineSeparator.swift in Sources */,
8CE1E43A2B8C76C80026530B /* LoginListView.swift in Sources */,
8A832A9429DC99BA0025D5DD /* LaunchScreenViewController.swift in Sources */,
21E77E4E2AA8BA5200FABA10 /* TabTrayViewController.swift in Sources */,
43BDBBFE2752FA8600254DE4 /* LegacyTabCell.swift in Sources */,
Expand Down Expand Up @@ -13746,6 +13767,7 @@
74821FC51DB56A2500EEEA72 /* OpenWithSettingsViewController.swift in Sources */,
C84656012887A0F700861B4A /* WallpaperMetadataUtility.swift in Sources */,
E1B04A9D28E20A8300670E54 /* InstructionsView.swift in Sources */,
8CE1E4392B8C76C80026530B /* LoginCellView.swift in Sources */,
D3B6923D1B9F9444004B87A4 /* FindInPageBar.swift in Sources */,
1D5CBF492B17E3CB0001D033 /* NotificationPayloads.swift in Sources */,
2F44FC721A9E840300FD20CC /* SettingsNavigationController.swift in Sources */,
Expand Down Expand Up @@ -14028,7 +14050,7 @@
43F7952525795F69005AEE40 /* SearchTelemetry.swift in Sources */,
E65075541E37F6FC006961AC /* LegacyDynamicFontHelper.swift in Sources */,
8ADAE4242A33A126007BF926 /* StudiesToggleSetting.swift in Sources */,
8C1953302B85E7EC00761B20 /* AutoFillFooterView.swift in Sources */,
8C1953302B85E7EC00761B20 /* AutofillFooterView.swift in Sources */,
C82CDD47233E8996002E2743 /* Tab+ChangeUserAgent.swift in Sources */,
81122E212B221AC0003DD9F8 /* SearchScreenState.swift in Sources */,
1DA6F6512B48B42900BB5AD6 /* WindowEventCoordinator.swift in Sources */,
Expand Down Expand Up @@ -14100,6 +14122,7 @@
392ED7E41D0AEF56009D9B62 /* NewTabAccessors.swift in Sources */,
1D2F68AD2ACB266300524B92 /* RemoteTabsPanelState.swift in Sources */,
8A9AC465276CEC4E0047F5B0 /* JumpBackInCell.swift in Sources */,
8CE1E4322B8C76AE0026530B /* LoginStorage.swift in Sources */,
4347B398298D6D7B0045F677 /* CreditCardTableViewModel.swift in Sources */,
1DFE57FD27BADD7D0025DE58 /* HomepageViewModel.swift in Sources */,
D3A9949D1A3686BD008AD1AC /* Tab.swift in Sources */,
Expand Down Expand Up @@ -14378,6 +14401,7 @@
C869916328918C36007ACC5C /* WallpaperNetworkingModuleTests.swift in Sources */,
B640467E29B9B58200C5C7B6 /* TabLocationViewTests.swift in Sources */,
8ABA9C8D28931223002C0077 /* MockDispatchQueue.swift in Sources */,
8CCD74732B90A945008F919B /* LoginListViewModelTests.swift in Sources */,
C81C66C429F00D1000F6422F /* UserActivityRouteTests.swift in Sources */,
8AFCE50529DDF38300B1B253 /* LaunchScreenViewControllerTests.swift in Sources */,
D3FA777B1A43B2990010CD32 /* SearchTests.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions firefox-ios/Client/AccessoryViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ class AccessoryViewProvider: UIView, Themeable {
})
accessoryView.accessibilityTraits = .button
accessoryView.accessibilityLabel = .PasswordAutofill.UseSavedPasswordFromKeyboard
accessoryView.accessibilityIdentifier = AccessibilityIdentifiers.Autofill.footerPrimaryAction
accessoryView.isAccessibilityElement = true
return accessoryView
}()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public struct AccessibilityIdentifiers {
static let previousButton = "KeyboardAccessory.previousButton"
static let addressAutofillButton = "KeyboardAccessory.addressAutofillButton"
static let creditCardAutofillButton = "KeyboardAccessory.creditCardAutofillButton"
static let loginAutofillButton = "KeyboardAccessory.loginAutofillButton"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,12 @@ class BrowserCoordinator: BaseCoordinator,
)
}

@MainActor
func showSavedLoginAutofill(tabURL: URL, currentRequestId: String) {
let bottomSheetCoordinator = makeCredentialAutofillCoordinator()
bottomSheetCoordinator.showSavedLoginAutofill(tabURL: tabURL, currentRequestId: currentRequestId)
}

func showAddressAutofill(frame: WKFrameInfo?) {
let bottomSheetCoordinator = makeAddressAutofillCoordinator()
bottomSheetCoordinator.showAddressAutofill(frame: frame)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ protocol BrowserNavigationHandler: AnyObject, QRCodeNavigationHandler {
frame: WKFrameInfo?,
alertContainer: UIView)

/// Displays an autofill interface for saved logins, allowing the user to select from stored login credentials
/// to autofill login forms on the specified web page.
func showSavedLoginAutofill(tabURL: URL, currentRequestId: String)

/// Shows an AddressAutofill view for selecting addresses in order to autofill forms.
func showAddressAutofill(frame: WKFrameInfo?)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,59 @@ class CredentialAutofillCoordinator: BaseCoordinator {
}
}

@MainActor
func showSavedLoginAutofill(tabURL: URL, currentRequestId: String) {
let viewModel = LoginListViewModel(
tabURL: tabURL,
loginStorage: profile.logins,
logger: logger,
onLoginCellTap: { [weak self] login in
guard let self else { return }
let rustLoginsEncryption = RustLoginEncryptionKeys()

guard let currentTab = self.tabManager.selectedTab,
let decryptLogin = rustLoginsEncryption.decryptSecureFields(login: login)
else {
router.dismiss(animated: true)
parentCoordinator?.didFinish(from: self)
return
}

LoginsHelper.fillLoginDetails(
with: currentTab,
loginData: LoginInjectionData(
requestId: currentRequestId,
logins: [LoginItem(
username: decryptLogin.secFields.username,
password: decryptLogin.secFields.password,
hostname: decryptLogin.fields.origin
)]
)
)

router.dismiss(animated: true)
parentCoordinator?.didFinish(from: self)
},
manageLoginInfoAction: { [weak self] in
guard let self else { return }
parentCoordinator?.show(settings: .password)
parentCoordinator?.didFinish(from: self)
}
)
let loginAutofillView = LoginAutofillView(viewModel: viewModel)

let viewController = SelfSizingHostingController(rootView: loginAutofillView)

var bottomSheetViewModel = BottomSheetViewModel(closeButtonA11yLabel: .CloseButtonTitle)
bottomSheetViewModel.shouldDismissForTapOutside = false

let bottomSheetVC = BottomSheetViewController(
viewModel: bottomSheetViewModel,
childViewController: viewController
)
router.present(bottomSheetVC)
}

func showPassCodeController() {
let passwordController = DevicePasscodeRequiredViewController()
passwordController.profile = profile
Expand Down
2 changes: 2 additions & 0 deletions firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum NimbusFeatureFlagID: String, CaseIterable {
case inactiveTabs
case isToolbarCFREnabled
case jumpBackIn
case loginAutofill
case nightMode
case preferSwitchToOpenTabOverDuplicate
case reduxSearchSettings
Expand Down Expand Up @@ -71,6 +72,7 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar {
.fakespotFeature,
.fakespotProductAds,
.isToolbarCFREnabled,
.loginAutofill,
.nightMode,
.preferSwitchToOpenTabOverDuplicate,
.reduxSearchSettings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct AddressAutoFillBottomSheetView: View {

var body: some View {
VStack {
AutoFillHeaderView(title: .Addresses.BottomSheet.UseASavedAddress)
AutofillHeaderView(title: .Addresses.BottomSheet.UseASavedAddress)
Spacer()
AddressScrollView(viewModel: addressListViewModel)
Spacer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import SwiftUI
import Common

struct AutoFillFooterView: View {
// Constants for UI layout and styling adapted for LoginAutoFill feature
struct AutofillFooterView: View {
// Constants for UI layout and styling adapted for LoginAutofill feature
private enum UX {
static let actionButtonFontSize: CGFloat = 16
static let actionButtonLeadingSpace: CGFloat = 0
Expand Down Expand Up @@ -59,13 +59,11 @@ struct AutoFillFooterView: View {
}
}

struct AutoFillFooterView_Previews: PreviewProvider {
static var previews: some View {
AutoFillFooterView(
title: "Manage Login Info",
primaryAction: { }
)
.previewLayout(.sizeThatFits)
.padding()
}
#Preview {
AutofillFooterView(
title: "Manage Login Info",
primaryAction: { }
)
.previewLayout(.sizeThatFits)
.padding()
}
Loading

0 comments on commit 647aecd

Please sign in to comment.