-
Notifications
You must be signed in to change notification settings - Fork 2
refactor(ui): InsetHeaderScrollView, shared tab swipes #535
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
Open
pwltr
wants to merge
2
commits into
master
Choose a base branch
from
fix/sticky-nav
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
...trations/lightbulb.imageset/Contents.json → ...s/lightbulb-figure.imageset/Contents.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+9.39 KB
...it/Assets.xcassets/Illustrations/lightbulb-figure.imageset/lightbulb-figure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions
15
Bitkit/Assets.xcassets/icons/lightbulb.imageset/Contents.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "lightbulb.pdf", | ||
| "idiom" : "universal" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| }, | ||
| "properties" : { | ||
| "template-rendering-intent" : "template" | ||
| } | ||
| } | ||
File renamed without changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import SwiftUI | ||
|
|
||
| /// Shared gradient tile used by suggestions widget and shop discover | ||
| struct Card: View { | ||
| let title: String | ||
| let description: String | ||
| let imageName: String | ||
| let accentColor: Color | ||
| let onTap: () -> Void | ||
| let onDismiss: (() -> Void)? | ||
|
|
||
| init( | ||
| title: String, | ||
| description: String, | ||
| imageName: String, | ||
| accentColor: Color, | ||
| onTap: @escaping () -> Void, | ||
| onDismiss: (() -> Void)? = nil | ||
| ) { | ||
| self.title = title | ||
| self.description = description | ||
| self.imageName = imageName | ||
| self.accentColor = accentColor | ||
| self.onTap = onTap | ||
| self.onDismiss = onDismiss | ||
| } | ||
|
|
||
| var body: some View { | ||
| ZStack(alignment: .topTrailing) { | ||
| Button(action: onTap) { | ||
| VStack(alignment: .leading, spacing: 0) { | ||
| Spacer() | ||
|
|
||
| Image(imageName) | ||
| .resizable() | ||
| .scaledToFit() | ||
| .frame(width: 96, height: 96) | ||
| .frame(maxWidth: .infinity, alignment: .center) | ||
|
|
||
| Text(title) | ||
| .font(.custom(Fonts.black, size: 20)) | ||
| .lineLimit(1) | ||
| .kerning(-0.5) | ||
| .textCase(.uppercase) | ||
| .padding(.top, 4) | ||
|
|
||
| CaptionBText(description) | ||
| } | ||
| .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) | ||
| .padding(.vertical, 12) | ||
| .padding(.horizontal, 16) | ||
| .background( | ||
| RoundedRectangle(cornerRadius: 16) | ||
| .fill( | ||
| LinearGradient( | ||
| gradient: Gradient(stops: [ | ||
| .init(color: accentColor, location: 0.0), | ||
| .init(color: Color.black.opacity(0.1), location: 0.9), | ||
| .init(color: Color.black, location: 1.0), | ||
| ]), | ||
| startPoint: .top, | ||
| endPoint: .bottom | ||
| ) | ||
| ) | ||
| ) | ||
| } | ||
| .buttonStyle(.plain) | ||
|
|
||
| if let onDismiss { | ||
| Button(action: onDismiss) { | ||
| Image("x-mark") | ||
| .resizable() | ||
| .aspectRatio(contentMode: .fit) | ||
| .foregroundColor(.textSecondary) | ||
| .frame(width: 16, height: 16) | ||
| .padding(8) | ||
| } | ||
| .padding(8) | ||
| .accessibilityIdentifier("SuggestionDismiss") | ||
| .accessibility(label: Text("Dismiss \(title)")) | ||
| .buttonStyle(.plain) | ||
| } | ||
| } | ||
| } | ||
| } |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import SwiftUI | ||
|
|
||
| // MARK: - InsetHeaderScrollView | ||
|
|
||
| // Measured top header (`safeAreaInset`) and scroll content with `minHeight` to fill the viewport below it. | ||
| // Optional `scrollModifier` for refresh, margins, etc. | ||
|
|
||
| private enum HeaderHeightPreferenceKey: PreferenceKey { | ||
| static var defaultValue: CGFloat = 0 | ||
|
|
||
| static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { | ||
| let next = nextValue() | ||
| guard next > 0 else { return } | ||
| value = next | ||
| } | ||
| } | ||
|
|
||
| private struct HeaderHeightMeasure: View { | ||
| var body: some View { | ||
| GeometryReader { proxy in | ||
| Color.clear.preference(key: HeaderHeightPreferenceKey.self, value: proxy.size.height) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| struct InsetHeaderScrollView<Header: View, Content: View, ScrollModifier: ViewModifier>: View { | ||
| let header: () -> Header | ||
| let content: () -> Content | ||
| let scrollModifier: ScrollModifier | ||
|
|
||
| @State private var headerHeight: CGFloat = 0 | ||
|
|
||
| init( | ||
| header: @escaping () -> Header, | ||
| content: @escaping () -> Content, | ||
| scrollModifier: ScrollModifier = EmptyModifier() | ||
| ) { | ||
| self.header = header | ||
| self.content = content | ||
| self.scrollModifier = scrollModifier | ||
| } | ||
|
|
||
| var body: some View { | ||
| GeometryReader { geo in | ||
| ScrollView(showsIndicators: false) { | ||
| content() | ||
| .frame(minHeight: contentMinHeight(in: geo), alignment: .top) | ||
| } | ||
| .safeAreaInset(edge: .top, spacing: 0) { | ||
| header().background(HeaderHeightMeasure()) | ||
| } | ||
| .modifier(scrollModifier) | ||
| .onPreferenceChange(HeaderHeightPreferenceKey.self) { newValue in | ||
| if newValue > 0 { headerHeight = newValue } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Before the first header measurement, use full height so `minHeight` is non-negative. | ||
| private func contentMinHeight(in geo: GeometryProxy) -> CGFloat { | ||
| let insetTop = headerHeight > 0 ? headerHeight : geo.size.height | ||
| return max(0, geo.size.height - insetTop) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import SwiftUI | ||
|
|
||
| // Swipe-to-go-back (nav stack) and horizontal swipes between `SegmentedControl` tabs. | ||
|
|
||
| extension View { | ||
| /// Controls whether the interactive swipe-back gesture is enabled on this screen. | ||
| /// Use `.allowSwipeBack(false)` on screens that use a custom header without a back button | ||
| /// (e.g. `SheetHeader` with default `showBackButton: false`) so users can't swipe to dismiss. | ||
| /// Default is `true`; only apply this modifier when you want to disable the gesture. | ||
| func allowSwipeBack(_ allowed: Bool) -> some View { | ||
| modifier(AllowSwipeBackModifier(allowed: allowed)) | ||
| } | ||
|
|
||
| // MARK: Segmented tab swipes | ||
|
|
||
| /// Swipe left/right to move between adjacent tabs (same order as `T.allCases` / `SegmentedControl`). | ||
| func swipeSegmentedTabs<T: Hashable & CaseIterable>( | ||
| selection: Binding<T>, | ||
| minimumDragDistance: CGFloat = 20, | ||
| swipeThreshold: CGFloat = 50, | ||
| animation: Animation = .easeInOut(duration: 0.2) | ||
| ) -> some View { | ||
| highPriorityGesture( | ||
| DragGesture(minimumDistance: minimumDragDistance, coordinateSpace: .local) | ||
| .onEnded { value in | ||
| let horizontalAmount = value.translation.width | ||
| let verticalAmount = value.translation.height | ||
| guard abs(horizontalAmount) > abs(verticalAmount) else { return } | ||
|
|
||
| let tabs = Array(T.allCases) | ||
| guard let currentIndex = tabs.firstIndex(of: selection.wrappedValue) else { return } | ||
|
|
||
| if horizontalAmount < -swipeThreshold, currentIndex < tabs.count - 1 { | ||
| withAnimation(animation) { | ||
| selection.wrappedValue = tabs[currentIndex + 1] | ||
| } | ||
| } else if horizontalAmount > swipeThreshold, currentIndex > 0 { | ||
| withAnimation(animation) { | ||
| selection.wrappedValue = tabs[currentIndex - 1] | ||
| } | ||
| } | ||
| } | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private struct AllowSwipeBackModifier: ViewModifier { | ||
| let allowed: Bool | ||
|
|
||
| func body(content: Content) -> some View { | ||
| content | ||
| .onAppear { SwipeBackState.allowSwipeBack = allowed } | ||
| .onDisappear { SwipeBackState.allowSwipeBack = true } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This image set declares
"filename": "lightbulb.pdf", but the commit only addslightbulb.pngin that folder, so the catalog entry points to a non-existent file. As a result,Image("lightbulb")(for example in profile hint UIs) can render as missing at runtime or fail asset validation in build tooling. Update the filename tolightbulb.png(or add the referenced PDF) so the asset resolves correctly.Useful? React with 👍 / 👎.