diff --git a/firefox-ios/Client/Application/AccessibilityIdentifiers.swift b/firefox-ios/Client/Application/AccessibilityIdentifiers.swift index 5e29af128c1c5..1054ab38511a9 100644 --- a/firefox-ios/Client/Application/AccessibilityIdentifiers.swift +++ b/firefox-ios/Client/Application/AccessibilityIdentifiers.swift @@ -649,6 +649,7 @@ struct AccessibilityIdentifiers { struct Addresses { static let title = "Addresses" static let addAddress = "Add address" + static let addressCell = "AddressCell" } } diff --git a/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift b/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift index 18620d2805299..f484aacbf57d7 100644 --- a/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift +++ b/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift @@ -64,3 +64,37 @@ struct AddressAutofillSettingsView: View { viewBackground = Color(color.layer1) } } + +struct ListHeaderPadding: ViewModifier { + let isLandscape: Bool + let paddingSize: CGFloat + + func body(content: Content) -> some View { + let isPad = UIDevice().userInterfaceIdiom == .pad + let shouldAddPadding = !isLandscape || (isPad && isLandscape) + if #available(iOS 26.0, *) { + if shouldAddPadding { + content.padding(.leading, paddingSize) + } else { + content + } + } else { + content + } + } +} + +struct ListItemIconPadding: ViewModifier { + let isLandscape: Bool + let paddingSize: CGFloat + + func body(content: Content) -> some View { + let isPad = UIDevice().userInterfaceIdiom == .pad + let shouldAddPadding = !isLandscape || (isPad && isLandscape) + if #available(iOS 26.0, *), shouldAddPadding { + content.padding(.leading, paddingSize) + } else { + content + } + } +} diff --git a/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift b/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift index 50fa0a29e8cd9..ac3f07468eceb 100644 --- a/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift +++ b/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift @@ -11,6 +11,15 @@ import struct MozillaAppServices.Address /// A view representing a cell displaying address information. struct AddressCellView: View { + // MARK: - Constants + + private enum UX { + static let listIconPadding: CGFloat = -8 + static let hStackSpacing: CGFloat = 24 + static let vStackSpacing: CGFloat = 0 + static let dividerHeight: CGFloat = 1 + } + // MARK: - Properties let windowUUID: WindowUUID @@ -20,6 +29,12 @@ struct AddressCellView: View { @State private var textColor: Color = .clear @State private var customLightGray: Color = .clear @State private var iconPrimary: Color = .clear + @State private var backgroundColor: Color = .clear + @State private var highlightColor: Color = .clear + + @State private var isHighlighted = false + @GestureState private var isPressing = false + @State private var isLandscape = false private(set) var address: Address private(set) var onTap: () -> Void @@ -27,41 +42,63 @@ struct AddressCellView: View { // MARK: - Body var body: some View { - Button(action: onTap) { - VStack(alignment: .leading, spacing: 0) { - HStack(alignment: .midIconAndLabel, spacing: 24) { - Image(StandardImageIdentifiers.Large.location) - .renderingMode(.template) - .padding(.leading, 16) - .foregroundColor(iconPrimary) - .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } - VStack(alignment: .leading) { - if !address.name.isEmpty { - Text(address.name) - .font(.body) - .foregroundColor(textColor) - .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } - } - if !address.streetAddress.isEmpty { - Text(address.streetAddress) - .font(.subheadline) - .foregroundColor(customLightGray) - } - if !address.addressCityStateZipcode.isEmpty { - Text(address.addressCityStateZipcode) - .font(.subheadline) - .foregroundColor(customLightGray) - } + VStack(spacing: UX.vStackSpacing) { + HStack(alignment: .midIconAndLabel, spacing: UX.hStackSpacing) { + Image(decorative: StandardImageIdentifiers.Large.location) + .renderingMode(.template) + .modifier(ListItemIconPadding(isLandscape: isLandscape, + paddingSize: UX.listIconPadding)) + .foregroundColor(iconPrimary) + .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } + VStack(alignment: .leading) { + if !address.name.isEmpty { + Text(address.name) + .font(.body) + .foregroundColor(textColor) + .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } + } + if !address.streetAddress.isEmpty { + Text(address.streetAddress) + .font(.subheadline) + .foregroundColor(customLightGray) + } + if !address.addressCityStateZipcode.isEmpty { + Text(address.addressCityStateZipcode) + .font(.subheadline) + .foregroundColor(customLightGray) } - Spacer() } + Spacer() } .padding() - Spacer().frame(height: 0) - Divider().frame(height: 1) + Divider().frame(height: UX.dividerHeight) + } + .contentShape(Rectangle()) + .onChange(of: isPressing) { pressing in + if pressing { + isHighlighted = true + } else { + withAnimation(.easeOut(duration: 0.2)) { + isHighlighted = false + } + } } + .gesture( + DragGesture(minimumDistance: 0) + .updating($isPressing) { _, state, _ in state = true } + .onEnded { value in + let isWithinTapBounds = abs(value.translation.width) < 20 + && abs(value.translation.height) < 20 + if isWithinTapBounds { + onTap() + } + } + ) .listRowInsets(EdgeInsets()) - .buttonStyle(AddressButtonStyle(theme: themeManager.getCurrentTheme(for: windowUUID))) + .listRowBackground( + (isHighlighted ? highlightColor : backgroundColor) + .edgesIgnoringSafeArea([.leading, .trailing]) + ) .listRowSeparator(.hidden) .onAppear { applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) @@ -70,7 +107,14 @@ struct AddressCellView: View { guard let uuid = notification.windowUUID, uuid == windowUUID else { return } applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + isLandscape = UIDevice.current.orientation.isLandscape + } + .accessibilityElement(children: .ignore) + .accessibilityIdentifier(AccessibilityIdentifiers.Settings.Address.Addresses.addressCell) .accessibilityLabel(address.a11ySettingsRow) + .accessibilityAddTraits(.isButton) + .accessibilityAction { onTap() } } // MARK: - Theme Application @@ -82,18 +126,7 @@ struct AddressCellView: View { textColor = Color(color.textPrimary) customLightGray = Color(color.textSecondary) iconPrimary = Color(color.iconPrimary) - } -} - -// MARK: - CustomButtonStyle - -/// A address button style with a specific theme. -struct AddressButtonStyle: ButtonStyle { - let theme: Theme - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .background(configuration.isPressed ? Color(theme.colors.layer1) : Color(theme.colors.layer2)) - .foregroundColor(.white) + backgroundColor = Color(color.layer2) + highlightColor = Color(color.layer5Hover) } } diff --git a/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift b/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift index 54068b21eec56..4b159196d5c89 100644 --- a/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift +++ b/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift @@ -19,6 +19,7 @@ struct AddressListView: View { static let titleFontSize: CGFloat = 22 static let subtitleFontSize: CGFloat = 16 static let contentUnavailableViewTopPadding: CGFloat = 125 + static let listPadding: CGFloat = -8 } // MARK: - Properties @@ -34,13 +35,17 @@ struct AddressListView: View { @State var imageColor: Color = .clear @State var listColor: Color = .clear + @State private var isLandscape = false + // MARK: - Body var body: some View { Group { if viewModel.showSection { List { - Section(header: Text(String.Addresses.Settings.SavedAddressesSectionTitle)) { + Section(header: Text(String.Addresses.Settings.SavedAddressesSectionTitle) + .modifier(ListHeaderPadding(isLandscape: isLandscape, + paddingSize: UX.listPadding))) { ForEach(viewModel.addresses, id: \.self) { address in AddressCellView( windowUUID: windowUUID, @@ -86,6 +91,9 @@ struct AddressListView: View { guard let uuid = notification.windowUUID, uuid == windowUUID else { return } applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + isLandscape = UIDevice.current.orientation.isLandscape + } .onDisappear { viewModel.editAddressWebViewManager.teardownWebView() } diff --git a/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift b/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift index 92e67264d4046..879af016990c9 100644 --- a/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift +++ b/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift @@ -50,7 +50,8 @@ struct ColoredListStyle: ViewModifier { .scrollContentBackground(.hidden) .background(backgroundColor) } else { - content.listStyle(.plain) + content + .listStyle(.plain) } } } diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift index cc5f9eccb3f5c..06b14ffd5d1da 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift @@ -57,7 +57,7 @@ final class AddressScreen { } func reachEditAndRemoveAddress() { - app.collectionViews.cells.buttons.staticTexts.firstMatch.tapWithRetry() + app.collectionViews.cells.buttons[AccessibilityIdentifiers.Settings.Address.Addresses.addressCell].tapWithRetry() // Update the all addresses fields let buttonEdit = sel.BUTTON_EDIT.element(in: app) buttonEdit.waitAndTap()