Skip to content

Commit

Permalink
Feat: Christmas icon announcement
Browse files Browse the repository at this point in the history
  • Loading branch information
autoreleasefool committed Nov 30, 2023
1 parent 0c821c8 commit 75caba1
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 1 deletion.
20 changes: 20 additions & 0 deletions ios/Approach/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let package = Package(
.library(name: "AddressLookupFeature", targets: ["AddressLookupFeature"]),
.library(name: "AlleyEditorFeature", targets: ["AlleyEditorFeature"]),
.library(name: "AlleysListFeature", targets: ["AlleysListFeature"]),
.library(name: "AnnouncementsFeature", targets: ["AnnouncementsFeature"]),
.library(name: "AppFeature", targets: ["AppFeature"]),
.library(name: "ArchiveListFeature", targets: ["ArchiveListFeature"]),
.library(name: "AvatarEditorFeature", targets: ["AvatarEditorFeature"]),
Expand Down Expand Up @@ -237,6 +238,24 @@ let package = Package(
"AlleysListFeature",
]
),
.target(
name: "AnnouncementsFeature",
dependencies: [
"AnalyticsServiceInterface",
"FeatureActionLibrary",
"LoggingServiceInterface",
"PreferenceServiceInterface",
"SwiftUIExtensionsLibrary",
"ViewsLibrary",
]
),
.testTarget(
name: "AnnouncementsFeatureTests",
dependencies: [
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
"AnnouncementsFeature",
]
),
.target(
name: "AppFeature",
dependencies: [
Expand Down Expand Up @@ -309,6 +328,7 @@ let package = Package(
.target(
name: "BowlersListFeature",
dependencies: [
"AnnouncementsFeature",
"BowlerEditorFeature",
"LeaguesListFeature",
"QuickLaunchRepositoryInterface",
Expand Down
6 changes: 5 additions & 1 deletion ios/Approach/Package.swift.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ features = [ "AlleyEditor" ]
services = [ "FeatureFlags" ]
libraries = [ "ResourceList" ]

[features.Announcements]
services = [ "Preference" ]
libraries = [ "SwiftUIExtensions", "Views" ]

[features.App]
features = [ "AccessoriesOverview", "BowlersList", "Onboarding", "Settings", "StatisticsOverview" ]
services = [ "AppInfo" ]
Expand All @@ -46,7 +50,7 @@ features = [ "Form" ]
repositories = [ "Bowlers" ]

[features.BowlersList]
features = [ "BowlerEditor", "LeaguesList" ]
features = [ "Announcements", "BowlerEditor", "LeaguesList" ]
repositories = [ "QuickLaunch" ]

[features.Errors]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public protocol GameSessionTrackableEvent: TrackableEvent {

extension Analytics {
public enum Alley {}
public enum Announcement {}
public enum App {}
public enum Bowler {}
public enum Feature {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
extension Analytics.Announcement {
public struct ChristmasAnnouncementShown: TrackableEvent {
public let name = "Announcement.ChristmasAnnouncementShown"
public let payload: [String: String]? = nil

public init() {}
}
}
97 changes: 97 additions & 0 deletions ios/Approach/Sources/AnnouncementsFeature/Announcements.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import AssetsLibrary
import ComposableArchitecture
import FeatureActionLibrary
import PreferenceServiceInterface
import SwiftUI
import SwiftUIExtensionsLibrary

@Reducer
public struct Announcements: Reducer {
public struct State: Equatable {
@PresentationState public var christmas: Christmas2023Announcement.State?

public init() {}
}

public enum Action: FeatureAction, Equatable {
public enum ViewAction: Equatable {
case onFirstAppear
case didFinishDismissingAnnouncement
}

public enum InternalAction: Equatable {
case showChristmasAnnouncement
case christmas(PresentationAction<Christmas2023Announcement.Action>)
}

public enum DelegateAction: Equatable {}

case view(ViewAction)
case delegate(DelegateAction)
case `internal`(InternalAction)
}

public init() {}

@Dependency(\.preferences) var preferences

public var body: some ReducerOf<Self> {
Reduce<State, Action> { state, action in
switch action {
case let .view(viewAction):
switch viewAction {
case .onFirstAppear:
return .run { send in
if Christmas2023Announcement.meetsExpectationsToShow() {
await send(.internal(.showChristmasAnnouncement))
}
}

case .didFinishDismissingAnnouncement:
return .run { _ in preferences.setKey(.announcementChristmasBanner2023Hidden, toBool: true) }
}

case let .internal(internalAction):
switch internalAction {
case .showChristmasAnnouncement:
state.christmas = .init()
return .none

case let .christmas(.presented(.delegate(delegateAction))):
switch delegateAction {
case .openAppIconSettings:
return .none
}

case .christmas(.presented(.view)), .christmas(.presented(.internal)), .christmas(.dismiss):
return .none
}

case .delegate:
return .none
}
}
.ifLet(\.$christmas, action: /Action.internal..Action.InternalAction.christmas) {
Christmas2023Announcement()
}
}
}

// MARK: - View

extension View {
public func announcements(
store: Store<Announcements.State, Announcements.Action>
) -> some View {
self
.onFirstAppear { store.send(.view(.onFirstAppear)) }
.sheet(
store: store.scope(state: \.$christmas, action: { .internal(.christmas($0)) }),
onDismiss: { store.send(.view(.didFinishDismissingAnnouncement)) },
content: { store in
Christmas2023AnnouncementView(store: store)
.presentationDetents([.medium])
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import AnalyticsServiceInterface
import AssetsLibrary
import ComposableArchitecture
import FeatureActionLibrary
import PreferenceServiceInterface
import StringsLibrary
import SwiftUI
import ViewsLibrary

public struct Christmas2023Announcement: Reducer {
static func meetsExpectationsToShow() -> Bool {
@Dependency(\.preferences) var preferences
let christmas2023AnnouncementHiddden = preferences.bool(forKey: .announcementChristmasBanner2023Hidden) ?? false
guard !christmas2023AnnouncementHiddden else { return false }

@Dependency(\.date) var date
// Before January 1, 2024
return date() < Date(timeIntervalSince1970: 1704067200)
}

public struct State: Equatable {}
public enum Action: FeatureAction, Equatable {
public enum ViewAction: Equatable {
case onFirstAppear
case didTapOpenAppSettings
case didTapDismiss
}
public enum DelegateAction: Equatable {
case openAppIconSettings
}
public enum InternalAction: Equatable {}

case view(ViewAction)
case delegate(DelegateAction)
case `internal`(InternalAction)
}

@Dependency(\.dismiss) var dismiss

init() {}

public var body: some ReducerOf<Self> {
Reduce<State, Action> { state, action in
switch action {
case let .view(viewAction):
switch viewAction {
case .onFirstAppear:
return .none

case .didTapOpenAppSettings:
return .concatenate(
.send(.delegate(.openAppIconSettings)),
.run { _ in await dismiss() }
)

case .didTapDismiss:
return .run { _ in await dismiss() }
}

case let .internal(internalAction):
switch internalAction {
case .never:
return .none
}

case .delegate:
return .none
}
}

AnalyticsReducer<State, Action> { _, action in
switch action {
case .view(.onFirstAppear):
return Analytics.Announcement.ChristmasAnnouncementShown()
default:
return nil
}
}
}
}

public struct Christmas2023AnnouncementView: View {
let store: StoreOf<Christmas2023Announcement>

public var body: some View {
VStack(spacing: 0) {
Spacer()

Text(Strings.Announcement.Christmas2023.title)
.font(.headline)
.multilineTextAlignment(.center)

Image(uiImage: UIImage(named: AppIcon.christmas.rawValue) ?? UIImage())
.resizable()
.scaledToFit()
.frame(width: .extraLargeIcon)
.cornerRadius(.standardRadius)
.shadow(radius: .standardRadius)
.padding(.horizontal, .smallSpacing)
.padding(.vertical, .standardSpacing)

Text(Strings.Announcement.Christmas2023.message)
.font(.body)
.multilineTextAlignment(.center)

Spacer()

Button { store.send(.view(.didTapOpenAppSettings)) } label: {
Text(Strings.Announcement.Christmas2023.openSettings)
.frame(maxWidth: .infinity)
}
.modifier(PrimaryButton())
.padding(.bottom, .smallSpacing)

Button { store.send(.view(.didTapDismiss)) } label: {
Text(Strings.Action.dismiss)
.frame(maxWidth: .infinity)
.padding(.vertical, .smallSpacing)
}
}
.onFirstAppear { store.send(.view(.onFirstAppear)) }
.padding()
}
}
4 changes: 4 additions & 0 deletions ios/Approach/Sources/AppFeature/TabbedContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ public struct TabbedContent: Reducer {

case let .internal(internalAction):
switch internalAction {
case .bowlersList(.internal(.announcements(.internal(.christmas(.presented(.delegate(.openAppIconSettings))))))):
state.selectedTab = .settings
return state.settings.showAppIconList().map { .internal(.settings($0)) }

case let .didChangeTabs(tabs):
state.tabs = tabs
return .none
Expand Down
16 changes: 16 additions & 0 deletions ios/Approach/Sources/BowlersListFeature/BowlersList.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AnalyticsServiceInterface
import AnnouncementsFeature
import AssetsLibrary
import BowlerEditorFeature
import BowlersRepositoryInterface
Expand Down Expand Up @@ -44,6 +45,7 @@ public struct BowlersList: Reducer {
public var quickLaunch: QuickLaunchSource?

public var errors: Errors<ErrorID>.State = .init()
public var announcements: Announcements.State = .init()

@PresentationState public var destination: Destination.State?

Expand Down Expand Up @@ -100,6 +102,7 @@ public struct BowlersList: Reducer {
case widgets(StatisticsWidgetLayout.Action)
case destination(PresentationAction<Destination.Action>)
case errors(Errors<ErrorID>.Action)
case announcements(Announcements.Action)
}

case view(ViewAction)
Expand Down Expand Up @@ -173,6 +176,10 @@ public struct BowlersList: Reducer {
Errors()
}

Scope(state: \.announcements, action: /Action.internal..Action.InternalAction.announcements) {
Announcements()
}

Reduce<State, Action> { state, action in
switch action {
case let .view(viewAction):
Expand Down Expand Up @@ -258,6 +265,12 @@ public struct BowlersList: Reducer {
return .none
}

case let .announcements(.delegate(delegateAction)):
switch delegateAction {
case .never:
return .none
}

case let .destination(.presented(.seriesEditor(.delegate(delegateAction)))):
switch delegateAction {
case let .didFinishCreating(created):
Expand Down Expand Up @@ -329,6 +342,9 @@ public struct BowlersList: Reducer {
case .errors(.view), .errors(.internal):
return .none

case .announcements(.view), .announcements(.internal):
return .none

case .destination(.dismiss),
.destination(.presented(.editor(.internal))),
.destination(.presented(.editor(.view))),
Expand Down
2 changes: 2 additions & 0 deletions ios/Approach/Sources/BowlersListFeature/BowlersListView.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AnnouncementsFeature
import AssetsLibrary
import BowlerEditorFeature
import ComposableArchitecture
Expand Down Expand Up @@ -62,6 +63,7 @@ public struct BowlersListView: View {
.onAppear { viewStore.send(.onAppear) }
})
.errors(store: store.scope(state: \.errors, action: { .internal(.errors($0)) }))
.announcements(store: store.scope(state: \.announcements, action: { .internal(.announcements($0)) }))
.bowlerEditor(store.scope(state: \.$destination, action: { .internal(.destination($0)) }))
.sortOrder(store.scope(state: \.$destination, action: { .internal(.destination($0)) }))
.seriesEditor(store.scope(state: \.$destination, action: { .internal(.destination($0)) }))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ public enum PreferenceKey: String {

// MARK: - Data Management
case dataLastExportDate // default: 0

// MARK: - Announcements
case announcementChristmasBanner2023Hidden // default: false
}
Loading

0 comments on commit 75caba1

Please sign in to comment.