Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix New Tab Page UI issues on iOS 15 #3198

Merged
merged 5 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions DuckDuckGo/NewTabPageSettingsSectionItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,22 @@ struct NewTabPageSettingsSectionItemView: View {
.foregroundStyle(Color(designSystemColor: .textPrimary))
.daxBodyRegular()
})
.toggleStyle(SwitchToggleStyle(tint: Color(designSystemColor: .accent)))

Divider()
}
.applyListRowInsets()
}
}

private extension View {
@ViewBuilder
func applyListRowInsets() -> some View {
if #available(iOS 16, *) {
self
} else {
listRowInsets(EdgeInsets(top: 0, leading: -24, bottom: 0, trailing: 8))
}
}
}

Expand Down
144 changes: 64 additions & 80 deletions DuckDuckGo/NewTabPageSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,81 +26,78 @@ struct NewTabPageSettingsView: View {
@ObservedObject var shortcutsSettingsModel: NewTabPageShortcutsSettingsModel
@ObservedObject var sectionsSettingsModel: NewTabPageSectionsSettingsModel

// Arbitrary high value is required to acomodate for the content size
@State var listHeight: CGFloat = 5000

@State var firstSectionFrame: CGRect = .zero
@State var lastSectionFrame: CGRect = .zero
@State var listHeight: CGFloat = Metrics.initialListHeight

var body: some View {
mainView
.applyBackground()
.tintIfAvailable(Color(designSystemColor: .accent))
.navigationTitle(UserText.newTabPageSettingsTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(UserText.navigationTitleDone) {
dismiss()
.applyBackground()
.tintIfAvailable(Color(designSystemColor: .accent))
.navigationTitle(UserText.newTabPageSettingsTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(UserText.navigationTitleDone) {
dismiss()
}
.tint(Color(designSystemColor: .textPrimary))
}
.tint(Color(designSystemColor: .textPrimary))
}
}
}

// MARK: Views

@ViewBuilder
private var mainView: some View {
if sectionsSettingsModel.enabledItems.contains(.shortcuts) {
ScrollView {
VStack {
sectionsList(withFrameUpdates: true)
.withoutScroll()
.frame(height: listHeight)

EditableShortcutsView(model: shortcutsSettingsModel)
.padding(.horizontal, Metrics.horizontalPadding)
GeometryReader { geometry in
ScrollView {
VStack {
sectionsList(withFrameUpdates: true, geometry: geometry)
.withoutScroll()
.frame(height: listHeight)

EditableShortcutsView(model: shortcutsSettingsModel)
.padding(.horizontal, Metrics.horizontalPadding)
.coordinateSpace(name: Constant.scrollCoordinateSpaceName)
}
}
}
.coordinateSpace(name: Constant.scrollCoordinateSpace)
} else {
sectionsList(withFrameUpdates: false)
}
}

@ViewBuilder
private func sectionsList(withFrameUpdates: Bool) -> some View {
List {
Section {
sectionsSettingsContentView
} header: {
Text(UserText.newTabPageSettingsSectionsHeaderTitle)
.if(withFrameUpdates) {
$0.onFrameUpdate(in: Constant.scrollCoordinateSpace, using: FirstSectionFrameKey.self) { frame in
self.firstSectionFrame = frame
updateListHeight()
}
}
} footer: {
Text(UserText.newTabPageSettingsSectionsDescription)
}

if sectionsSettingsModel.enabledItems.contains(.shortcuts) {
private func sectionsList(withFrameUpdates: Bool, geometry: GeometryProxy? = nil) -> some View {
List {
Section {
sectionsSettingsContentView
} header: {
Text(UserText.newTabPageSettingsShortcutsHeaderTitle)
.if(withFrameUpdates) {
$0.onFrameUpdate(in: Constant.scrollCoordinateSpace, using: LastSectionFrameKey.self) { frame in
self.lastSectionFrame = frame
updateListHeight()
}
}
Text(UserText.newTabPageSettingsSectionsHeaderTitle)
} footer: {
Text(UserText.newTabPageSettingsSectionsDescription)
}

if sectionsSettingsModel.enabledItems.contains(.shortcuts) {
Section {
} header: {
Text(UserText.newTabPageSettingsShortcutsHeaderTitle)
} footer: {
Rectangle().fill(.clear).frame(minHeight: 0.1)
}
.anchorPreference(key: ListBottomKey.self, value: .bottom, transform: { anchor in
let y = geometry?[anchor].y ?? 0
return y
})
}
}
}
.applyInsetGroupedListStyle()
.environment(\.editMode, .constant(.active))
.onPreferenceChange(ListBottomKey.self, perform: { position in
guard self.listHeight == Metrics.initialListHeight else { return }

self.listHeight = max(0, position)
})
.applyInsetGroupedListStyle()
.environment(\.editMode, .constant(.active))
}

@ViewBuilder
Expand All @@ -109,36 +106,37 @@ struct NewTabPageSettingsView: View {
switch setting.item {
case .favorites:
NewTabPageSettingsSectionItemView(title: "Favorites",
iconResource: .favorite24,
isEnabled: setting.isEnabled)
iconResource: .favorite24,
isEnabled: setting.isEnabled)
case .shortcuts:
NewTabPageSettingsSectionItemView(title: "Shortcuts",
iconResource: .shortcut24,
isEnabled: setting.isEnabled)
iconResource: .shortcut24,
isEnabled: setting.isEnabled)
}
}.onMove(perform: { indices, newOffset in
sectionsSettingsModel.moveItems(from: indices, to: newOffset)
})
}

// MARK: -

private func updateListHeight() {
guard firstSectionFrame != .zero, lastSectionFrame != .zero else { return }

let newHeight = lastSectionFrame.maxY - firstSectionFrame.minY + Metrics.defaultListTopPadding
self.listHeight = max(0, newHeight)
}
}

private struct Constant {
static let scrollCoordinateSpaceName = "Scroll"
static let scrollCoordinateSpace = CoordinateSpace.named(scrollCoordinateSpaceName)
}

private extension CoordinateSpace {
static let scroll = CoordinateSpace.named(Constant.scrollCoordinateSpaceName)
}

private struct Metrics {
static let defaultListTopPadding = 24.0
static let horizontalPadding = 16.0
static let initialListHeight = 5000.0
}

private struct ListBottomKey: PreferenceKey {
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
static var defaultValue: CGFloat = Metrics.initialListHeight
}

#Preview {
Expand All @@ -149,17 +147,3 @@ private struct Metrics {
)
}
}

private struct FirstSectionFrameKey: PreferenceKey {
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
static var defaultValue: CGRect = .zero
}

private struct LastSectionFrameKey: PreferenceKey {
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
static var defaultValue: CGRect = .zero
}
8 changes: 8 additions & 0 deletions DuckDuckGo/ShortcutAccessoryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import SwiftUI
struct ShortcutAccessoryView: View {

let accessoryType: ShortcutAccessoryType
let expectedSize: CGSize

var body: some View {
Circle()
Expand All @@ -32,6 +33,13 @@ struct ShortcutAccessoryView: View {
.aspectRatio(contentMode: .fit)
}
.shadow(color: .shade(0.15), radius: 1, y: 1)
.frame(width: expectedSize.width, height: expectedSize.height)
}
}

extension ShortcutAccessoryView {
init(accessoryType: ShortcutAccessoryType) {
self.init(accessoryType: accessoryType, expectedSize: CGSize(width: 24, height: 24))
}
}

Expand Down
28 changes: 21 additions & 7 deletions DuckDuckGo/ShortcutItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ struct ShortcutItemView: View {
var body: some View {
VStack(spacing: 6) {
ShortcutIconView(shortcut: shortcut)
.overlay(alignment: .topTrailing) {
if let accessoryType {
ShortcutAccessoryView(accessoryType: accessoryType)
.frame(width: Constant.accessorySize)
.offset(Constant.accessoryOffset)
.overlay(alignment: .topTrailing) {
if let accessoryType {
ShortcutAccessoryView(accessoryType: accessoryType)
.alignedForOverlay(edgeSize: Constant.accessorySize)
}
}
}

Text(shortcut.name)
.font(Font.system(size: 12))
.lineLimit(2)
Expand All @@ -45,7 +45,6 @@ struct ShortcutItemView: View {

private enum Constant {
static let accessorySize = 24.0
static let accessoryOffset = CGSize(width: 6, height: -6)
}
}

Expand Down Expand Up @@ -99,6 +98,21 @@ private extension NewTabPageShortcut {
}
}

private extension ShortcutAccessoryView {
@ViewBuilder func alignedForOverlay(edgeSize: CGFloat) -> some View {
let offset = CGSize(width: edgeSize/4.0, height: -edgeSize/4.0)
let size = CGSize(width: edgeSize, height: edgeSize)

if #available(iOS 16, *) {
frame(width: edgeSize)
.offset(offset)
} else {
frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
.offset(offset)
}
}
}

#Preview {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 86))], content: {
Expand Down
11 changes: 4 additions & 7 deletions DuckDuckGo/ViewExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,13 @@ extension View {
}

extension View {
/// Disables scroll in a backwards-compatible way.
///
/// Keep in mind fallback version may have unforeseen consequences.
/// Verify if it does not break anything else for you.
/// Disables scroll if allowed by system version
@ViewBuilder
func withoutScroll() -> some View {
func withoutScroll(_ isScrollDisabled: Bool = true) -> some View {
if #available(iOS 16, *) {
scrollDisabled(true)
scrollDisabled(isScrollDisabled)
} else {
gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local), including: .gesture)
self
}
}
}
Expand Down
Loading