Skip to content

[MBL-3167] Video Feed Bottom Overlay Components #2824

Open
scottkicks wants to merge 12 commits intomainfrom
scott/videofeed/mbl-3167
Open

[MBL-3167] Video Feed Bottom Overlay Components #2824
scottkicks wants to merge 12 commits intomainfrom
scott/videofeed/mbl-3167

Conversation

@scottkicks
Copy link
Copy Markdown
Contributor

@scottkicks scottkicks commented Apr 6, 2026

📲 What

Adds a static bottom overlay components SwiftUI view to the VideoFeedViewController.
Includes all components from the designs except for the percent funded circle. The progress bar also isn't wire up to anything yet.

🤔 Why

This is where most of the campaign info will be

🛠 How

Building all overlay components via SwiftUI. Reminder that the main VideoFeedViewController is a UIViewController to help manage the video players later on. Found that SwiftUI wasn't the best at that in my SPIKE.

  • Add new icons and color
  • Create a VideoFeedOverlayView view and add a VideoFeedBottomOverlayView to it.
  • Add the new overlay view to VideoFeedCell (managed by the collection view in VideoFeedViewController.
  • Add snapshots

👀 See

Trello, screenshots, external resources?

After Large Text
simulator_screenshot_07F7D643-9EC7-4622-BD47-60DFC03393CD simulator_screenshot_14B8C907-285D-4B63-B8DB-2D8D71F1A32F

♿️ Accessibility

  • Works with VoiceOver
  • Supports Dynamic Type

✅ Acceptance criteria

  • Bottom overlay includes, campaign tags, CTA, creator, campaign name, and progress bar view
  • New colors and icons are added and being used

@kickstarter kickstarter deleted a comment from nativeksr Apr 6, 2026
@scottkicks scottkicks self-assigned this Apr 6, 2026
@scottkicks scottkicks marked this pull request as ready for review April 6, 2026 16:58
Copy link
Copy Markdown
Contributor

@amy-at-kickstarter amy-at-kickstarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few requested changes on VideoFeedCell (namely using contentConfiguration). These rest are non-blocking suggestions.

lightMode: .gray_550,
lightModeAlpha: 0.24,
darkMode: .gray_550,
darkModeAlpha: 0.25
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that the lightModeAlpha and darkModeAlpha are different by 0.01?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love a screenshot test 👍 Is it possible to set up the test so that the overlay has something dark behind it? It's a bit hard to see, as-is. (But not a blocker.)

height: device.deviceSize.height
))

cell.configureWith(value: .init(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I find it easier to read if the initializer is referenced by name, like

Suggested change
cell.configureWith(value: .init(
cell.configureWith(value: VideoFeedItem(


assertSnapshot(
matching: cell,
as: .image(perceptualPrecision: 0.98),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Does it need the lower precision to work?

static let reuseIdentifier = "VideoFeedCell"

private let titleLabel = UILabel()
private var overlayHostingController: UIHostingController<VideoFeedOverlayView>?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cell should use self.contentConfiguration instead of embedding a UIHostingController. Having embedded view controllers can be finicky to get working consistently, especially with cell recycling.

@@ -2,5 +2,22 @@ import Foundation

public struct VideoFeedItem {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to see this as a simple plain-ol'-swift struct.


var body: some View {
HStack(spacing: Constants.iconSpacing) {
Image(self.icon)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use Library.imageNamed(self.icon) instead of creating an image directly from the string. We do some bundle magic in our tests to get images working - changing this will make the pill icons show up in your screenshot tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh i'm not seeing imageNamed in the codebase anywhere

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, I told you the wrong name. It's Library.image(named:). See: https://github.com/kickstarter/ios-oss/blob/main/Library/Sources/Library/Library/Image.swift#L3-L9

FeedPillView(icon: "video-feed-clock-icon", text: self.item.secondaryPillText)
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(self.item.categoryPillText), \(self.item.secondaryPillText)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason the label is applied to the HStack and not to the individual pills?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its just my preference for VoiceOver. Like this it'll read as one "Film, 23 days left" which I feel like is a smoother experience than having a pause in between the two. I'm open to changing it since we don't have a defined VoiceOver pattern for things like this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense - can you throw that in a comment?

}

/// Custom frosted glass background view.
private struct FrostedGlassBackground: UIViewRepresentable {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a custom glass effect? Can we use glassEffect(in:) instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glassEffect is only available in ios 26, but I could check how it looks compared to the designs and add a version conditional

Copy link
Copy Markdown
Contributor Author

@scottkicks scottkicks Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok yeah glass effect doesn't offer much flexibility in terms of adjusting the amount of "frost", and by itself its a way too frosty compared to the designs, so I think a simple custom FrostedGlassBackground like this is the way to go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger that. Two thoughts:

  1. Should FrostedGlassBackground be public and in its own file, so we can reuse it?
  2. Can you throw this extra context in a comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea! My plan is to iteratively make changes like this as needed in this feature. I'll end up reusing it for the video overlay controls, so making it public will be the move, but I tend to prefer keeping each PR contained within the context of its changes, if that makes sense.

I'll definitely add a context comment

ZStack(alignment: .bottom) {
self.topGradient
.ignoresSafeArea()
/// Hidden so VoiceOver jumps straight to the interactive overlay.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this comment

Base automatically changed from scott/videofeed/mbl-3063 to main April 6, 2026 21:08
@scottkicks scottkicks changed the title Scott/videofeed/mbl 3167 [MBL-3167] Video Feed Bottom Overlay Components Apr 6, 2026
@nativeksr
Copy link
Copy Markdown
Collaborator

nativeksr commented Apr 7, 2026

SwiftLint found issues

Warnings

File Line Reason
VideoFeedBottomOverlayView.swift 93 Images that provide context should have an accessibility label or should be explicitly hidden from accessibility (accessibility_label_for_image)

Generated by 🚫 Danger

@scottkicks scottkicks force-pushed the scott/videofeed/mbl-3167 branch from f622180 to d390033 Compare April 7, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants