Skip to content
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
2 changes: 1 addition & 1 deletion Knock.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "Knock"
spec.version = "1.2.0"
spec.version = "1.2.1"
spec.summary = "An SDK to build in-app notifications experiences in Swift with Knock.."

spec.description = <<-DESC
Expand Down
1 change: 0 additions & 1 deletion Sources/Components/Feed/FeedNotificationRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import SwiftUI
import WebKit
import UIKit


extension Knock {
Expand Down
93 changes: 33 additions & 60 deletions Sources/Components/Feed/InAppFeedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,7 @@ extension Knock {

public var body: some View {
VStack(alignment: .leading, spacing: .zero) {
VStack(alignment: .leading, spacing: .zero) {
if let title = theme.titleString {
Text(title)
.font(theme.titleFont)
.foregroundStyle(theme.titleColor)
.padding(.horizontal, 24)
}

if viewModel.filterOptions.count > 1 {
filterTabView()
.padding(.bottom, 12)
}

if let topButtons = viewModel.topButtonActions {
topActionButtonsView(topButtons: topButtons)
.padding(.bottom, 12)
Divider()
}
}
.background(theme.upperBackgroundColor)
topSectionView()

ZStack(alignment: .bottom) {
Group {
Expand Down Expand Up @@ -78,16 +59,18 @@ extension Knock {
viewModel.feedItemRowTapped(item: item)
}
.swipeActions(edge: .trailing) {
if let config = theme.rowTheme.swipeLeftConfig {
Knock.SwipeButton(config: config) {
viewModel.didSwipeRow(item: item, swipeAction: config.action)
if let config = theme.rowTheme.archiveSwipeConfig {
let useInverse = item.archived_at != nil
Knock.SwipeButton(config: config, useInverse: useInverse) {
viewModel.didSwipeRow(item: item, swipeAction: config.action, useInverse: useInverse)
}
}
}
.swipeActions(edge: .leading) {
if let config = theme.rowTheme.swipeRightConfig {
Knock.SwipeButton(config: config) {
viewModel.didSwipeRow(item: item, swipeAction: config.action)
if let config = theme.rowTheme.markAsReadSwipeConfig {
let useInverse = item.read_at != nil
Knock.SwipeButton(config: config, useInverse: useInverse) {
viewModel.didSwipeRow(item: item, swipeAction: config.action, useInverse: useInverse)
}
}
}
Expand Down Expand Up @@ -127,6 +110,30 @@ extension Knock {
}
}

@ViewBuilder
private func topSectionView() -> some View {
VStack(alignment: .leading, spacing: .zero) {
if let title = theme.titleString {
Text(title)
.font(theme.titleFont)
.foregroundStyle(theme.titleColor)
.padding(.horizontal, 24)
}

if viewModel.filterOptions.count > 1 {
FilterBarView(filters: viewModel.filterOptions, selectedFilter: $viewModel.currentFilter)
.padding(.bottom, 12)
}

if let topButtons = viewModel.topButtonActions {
topActionButtonsView(topButtons: topButtons)
.padding(.bottom, 12)
Divider()
}
}
.background(theme.upperBackgroundColor)
}

@ViewBuilder
private func lastRowView() -> some View {
HStack {
Expand All @@ -144,40 +151,6 @@ extension Knock {
}
}

@ViewBuilder
private func filterTabView() -> some View {
ZStack(alignment: .bottom) {
Divider()
.frame(height: 1)
.background(KnockColor.Gray.gray4)

HStack(spacing: .zero
) {
ForEach(viewModel.filterOptions, id: \.self) { option in
Text(option.title)
.font(.knock2.weight(.medium))
.foregroundColor(option == viewModel.currentFilter ? KnockColor.Accent.accent11 : KnockColor.Gray.gray11)
.padding(.vertical, 10)
.padding(.horizontal, 16)
.overlay(
Rectangle()
.frame(height: 1)
.foregroundColor(option == viewModel.currentFilter ? KnockColor.Accent.accent9 : .clear),
alignment: .bottom
)
.onTapGesture {
withAnimation {
viewModel.currentFilter = option
}
}
}
Spacer()
}
.padding(.horizontal, 24)
}

}

@ViewBuilder
private func topActionButtonsView(topButtons: [Knock.FeedTopActionButtonType]) -> some View {
HStack(alignment: .center, spacing: 12) {
Expand Down
22 changes: 8 additions & 14 deletions Sources/Components/Feed/InAppFeedViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import Combine
extension Knock {
public class InAppFeedViewModel: ObservableObject {
@Published public var feed: Knock.Feed = Knock.Feed() /// The current feed data.
@Published public var currentTenantId: String? /// The tenant ID associated with the current feed.
@Published public var filterOptions: [InAppFeedFilter] /// Available filter options for the feed.
@Published public var topButtonActions: [Knock.FeedTopActionButtonType]? /// Actions available at the top of the feed interface.
@Published internal var brandingRequired: Bool = true
Expand All @@ -23,8 +22,8 @@ extension Knock {
}

public var feedClientOptions: Knock.FeedClientOptions /// Configuration options for feed.
public let didTapFeedItemButtonPublisher = PassthroughSubject<String, Never>() /// Publisher for feed item button tap events.
public let didTapFeedItemRowPublisher = PassthroughSubject<Knock.FeedItem, Never>() /// Publisher for feed item row tap events.
public var didTapFeedItemButtonPublisher = PassthroughSubject<String, Never>() /// Publisher for feed item button tap events.
public var didTapFeedItemRowPublisher = PassthroughSubject<Knock.FeedItem, Never>() /// Publisher for feed item row tap events.

public var shouldHideArchived: Bool {
(feedClientOptions.archived == .exclude || feedClientOptions.archived == nil)
Expand All @@ -36,19 +35,15 @@ extension Knock {

public init(
feedClientOptions: Knock.FeedClientOptions = .init(),
currentTenantId: String? = nil,
currentFilter: InAppFeedFilter? = nil,
filterOptions: [InAppFeedFilter]? = nil,
topButtonActions: [Knock.FeedTopActionButtonType]? = [.markAllAsRead(), .archiveRead()]
) {
self.feedClientOptions = feedClientOptions
self.currentTenantId = currentTenantId ?? feedClientOptions.tenant
self.filterOptions = filterOptions ?? [.init(scope: .all), .init(scope: .unread), .init(scope: .archived)]
self.currentFilter = currentFilter ?? filterOptions?.first ?? .init(scope: .all)
self.topButtonActions = topButtonActions

self.feedClientOptions.status = self.currentFilter.scope
self.feedClientOptions.tenant = self.currentTenantId
}

// MARK: Public Methods
Expand Down Expand Up @@ -129,7 +124,7 @@ extension Knock {
default: break
}

let feedOptions = Knock.FeedClientOptions(status: archivedScope, tenant: currentTenantId, has_tenant: feedClientOptions.has_tenant, archived: feedClientOptions.archived)
let feedOptions = Knock.FeedClientOptions(status: archivedScope, tenant: feedClientOptions.tenant, has_tenant: feedClientOptions.has_tenant, archived: feedClientOptions.archived)
do {
_ = try await Knock.shared.feedManager?.makeBulkStatusUpdate(type: updatedStatus, options: feedOptions)
await optimisticallyBulkUpdateStatus(updatedStatus: updatedStatus, archivedScope: archivedScope)
Expand All @@ -142,7 +137,7 @@ extension Knock {
switch updatedStatus {
case .seen: guard item.seen_at == nil else { return }
case .read: guard item.read_at == nil else { return }
case .interacted: guard item.inserted_at == nil else { return }
case .interacted: guard item.interacted_at == nil else { return }
case .archived: guard item.archived_at == nil else { return }
case .unread: guard item.read_at != nil else { return }
case .unseen: guard item.seen_at != nil else { return }
Expand Down Expand Up @@ -173,12 +168,11 @@ extension Knock {

// MARK: Button/Swipe Interactions

public func didSwipeRow(item: Knock.FeedItem, swipeAction: FeedNotificationRowSwipeAction) {
public func didSwipeRow(item: Knock.FeedItem, swipeAction: FeedNotificationRowSwipeAction, useInverse: Bool) {
Task {
switch swipeAction {
case .archive: await updateMessageEngagementStatus(item, updatedStatus: .archived)
case .markAsRead: await updateMessageEngagementStatus(item, updatedStatus: .read)
case .markAsUnread: await updateMessageEngagementStatus(item, updatedStatus: .unread)
case .archive: await updateMessageEngagementStatus(item, updatedStatus: useInverse ? .unarchived : .archived)
case .markAsRead: await updateMessageEngagementStatus(item, updatedStatus: useInverse ? .unread : .read)
}
}
}
Expand Down Expand Up @@ -249,7 +243,7 @@ extension Knock {

private func shouldArchive(item: Knock.FeedItem, scope: Knock.FeedItemScope) -> Bool {
switch scope {
case .interacted: return item.inserted_at != nil
case .interacted: return item.interacted_at != nil
case .unread: return item.read_at == nil
case .read: return item.read_at != nil
case .unseen: return item.seen_at == nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,39 @@ extension Knock {
public enum FeedNotificationRowSwipeAction {
case archive
case markAsRead
case markAsUnread

public var defaultTitle: String {
switch self {
case .archive: return "Archive"
case .markAsRead: return "Read"
case .markAsUnread: return "Unread"
}
}

public var defaultInverseTitle: String {
switch self {
case .archive: return "Unarchive"
case .markAsRead: return "Unread"
}
}

public var defaultImage: Image {
switch self {
case .archive: return Image(systemName: "archivebox")
case .markAsRead: return Image(systemName: "envelope.open")
case .markAsUnread: return Image(systemName: "envelope")
}
}

public var defaultInverseImage: Image {
switch self {
case .archive: return Image(systemName: "archivebox")
case .markAsRead: return Image(systemName: "envelope")
}
}

public var defaultSwipeColor: Color {
switch self {
case .archive: return KnockColor.Green.green9
case .markAsRead: return KnockColor.Blue.blue9
case .markAsUnread: return KnockColor.Blue.blue9
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/Components/Feed/Support/ActionButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extension Knock {
Button(action: action) {
Text(title)
.font(config.font)
.lineLimit(1)
.foregroundStyle(config.textColor)
.padding(.vertical, 8)
.frame(maxWidth: config.fillAvailableSpace ? .infinity : .none)
Expand Down Expand Up @@ -81,4 +82,5 @@ extension Knock {
Knock.ActionButton(title: "Secondary", config: Knock.ActionButton.Style.secondary.defaultConfig) {}
Knock.ActionButton(title: "Tertiary", config: Knock.ActionButton.Style.tertiary.defaultConfig) {}
}
.padding()
}
49 changes: 49 additions & 0 deletions Sources/Components/Feed/Support/FilterBarView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// FilterBarView.swift
//
//
// Created by Matt Gardner on 6/24/24.
//

import Foundation
import SwiftUI

extension Knock {
struct FilterBarView: View {
var filters: [InAppFeedFilter]
@Binding var selectedFilter: InAppFeedFilter
let theme: FilterBarTheme = FilterBarTheme()

var body: some View {
ZStack(alignment: .bottom) {
Divider()
.frame(height: 1)
.background(KnockColor.Gray.gray4)

HStack(spacing: .zero
) {
ForEach(filters, id: \.self) { option in
Text(option.title)
.font(theme.font)
.foregroundColor(option == selectedFilter ? theme.selectedColor : theme.unselectedColor)
.padding(.vertical, 10)
.padding(.horizontal, 16)
.overlay(
Rectangle()
.frame(height: 1)
.foregroundColor(option == selectedFilter ? theme.selectedColor : .clear),
alignment: .bottom
)
.onTapGesture {
withAnimation {
selectedFilter = option
}
}
}
Spacer()
}
.padding(.horizontal, 24)
}
}
}
}
16 changes: 14 additions & 2 deletions Sources/Components/Feed/Support/SwipeButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ import SwiftUI
extension Knock {
struct SwipeButton: View {
let config: SwipeButtonConfig
let useInverse: Bool
let action: () -> Void

var body: some View {
Button(action: action) {
VStack(alignment: .center, spacing: 10) {
config.image
Text(config.title)
if useInverse {
config.inverseImage
} else {
config.image
}
Text(useInverse ? config.inverseTitle : config.title)
.font(config.titleFont)
.foregroundStyle(config.titleColor)
}
Expand All @@ -27,26 +33,32 @@ extension Knock {
public struct SwipeButtonConfig {
public var action: Knock.FeedNotificationRowSwipeAction
public var title: String
public var inverseTitle: String
public var titleFont: Font
public var titleColor: Color
public var image: Image
public var inverseImage: Image
public var swipeColor: Color
public var showIcon: Bool

public init(
action: Knock.FeedNotificationRowSwipeAction,
title: String? = nil,
inverseTitle: String? = nil,
titleFont: Font? = nil,
titleColor: Color? = nil,
image: Image? = nil,
inverseImage: Image? = nil,
swipeColor: Color? = nil,
showIcon: Bool = true
) {
self.action = action
self.title = title ?? action.defaultTitle
self.inverseTitle = inverseTitle ?? action.defaultInverseTitle
self.titleFont = titleFont ?? .knock2.weight(.medium)
self.titleColor = titleColor ?? .white
self.image = image ?? action.defaultImage
self.inverseImage = inverseImage ?? action.defaultInverseImage
self.swipeColor = swipeColor ?? action.defaultSwipeColor
self.showIcon = showIcon
}
Expand Down
Loading