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

🎉 [iOS] TipKit 🎉 #113

Merged
merged 2 commits into from
Aug 26, 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
14 changes: 14 additions & 0 deletions ios/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import KeychainProvider
import NetworkProvider
import OSLog
import SharedDomain
import TipKit
import UIKit
import UIToolkit
import UserDefaultsProvider
Expand Down Expand Up @@ -42,6 +43,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
// Setup Cache capacity
setupCacheCapacity()

// Setup Tips
setupTips()

// Register for remote notifications
application.registerForRemoteNotifications()

Expand Down Expand Up @@ -133,6 +137,16 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
URLCache.shared.memoryCapacity = 10_000_000 // ~10 MB memory space
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
}

// MARK: Setup Tips
private func setupTips() {
guard #available(iOS 17, *) else { return }

// Reset DataStore has to be called before configure
// You don't usually need to reset DataStore because you wanna show the tip only once
try? Tips.resetDatastore()
try? Tips.configure()
}
}

extension AppDelegate: UNUserNotificationCenterDelegate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum Recipe: String, CaseIterable {
case images = "Images"
case maps = "Maps"
case slidingButton = "SlidingButton"
case tipKit = "TipKit"
}

final class RecipesViewModel: BaseViewModel, ViewModel, ObservableObject {
Expand Down Expand Up @@ -58,6 +59,7 @@ final class RecipesViewModel: BaseViewModel, ViewModel, ObservableObject {
case .images: flowController?.handleFlow(RecipesFlow.recipes(.showImages))
case .maps: flowController?.handleFlow(RecipesFlow.recipes(.showMaps))
case .slidingButton: flowController?.handleFlow(RecipesFlow.recipes(.showSlidingButton))
case .tipKit: flowController?.handleFlow(RecipesFlow.recipes(.showTipKitExample))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum RecipesFlow: Flow, Equatable {
case showImages
case showMaps
case showSlidingButton
case showTipKitExample
}
}

Expand Down Expand Up @@ -47,6 +48,7 @@ extension RecipesFlowController {
case .showImages: showImages()
case .showMaps: showMaps()
case .showSlidingButton: showSlidingButton()
case .showTipKitExample: showTipKitExample()
}
}

Expand Down Expand Up @@ -91,4 +93,12 @@ extension RecipesFlowController {
let vc = BaseHostingController(rootView: SlidingButtonView(viewModel: vm))
navigationController.show(vc, sender: nil)
}

private func showTipKitExample() {
guard #available(iOS 17, *) else { return }

let vm = ExampleTipKitViewModel(flowController: self)
let vc = BaseHostingController(rootView: ExampleTipKitView(viewModel: vm))
navigationController.show(vc, sender: nil)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// Created by David Kadlček on 24.07.2024
// Copyright © 2024 Matee. All rights reserved.
//

import SwiftUI
import TipKit
import UIToolkit

@available(iOS 17, *)
struct ExampleTipKitView: View {

@ObservedObject private var viewModel: ExampleTipKitViewModel

private var actionTip = ActionTip()

init(viewModel: ExampleTipKitViewModel) {
self.viewModel = viewModel
}

var body: some View {
VStack(spacing: 32) {
Text(L10n.recipe_tipkit_title)
.frame(maxWidth: .infinity, alignment: .center)
.font(.largeTitle)
.popoverTip(PopoverTip())

VStack {
Text(L10n.recipe_tipkit_inline_tip)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.headline)

TipView(InlineTip())
}

VStack {
HStack {
Text(L10n.recipe_tipkit_remaining_title(viewModel.state.remainingTapsToShowTip))

if viewModel.state.remainingTapsToShowTip > 0 {
Button(
action: {
viewModel.onIntent(.tapToShowTip)
}, label: {
Text(L10n.recipe_tipkit_decrease_button_title)
}
)
DavidKadlcek marked this conversation as resolved.
Show resolved Hide resolved
}
}

VStack {
Text(L10n.recipe_tipkit_rule_tip)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.headline)

TipView(RuleTip())
}

VStack {
Text(L10n.recipe_tipkit_action_tip)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.headline)

TipView(actionTip) { action in
switch action.id {
case ActionTip.Actions.pop.id:
viewModel.onIntent(.pop)
case ActionTip.Actions.close.id:
actionTip.invalidate(reason: .actionPerformed)
default:
break
}
}
}
.padding(.top)
}

Spacer()
}
.padding()
.lifecycle(viewModel)
}
}

#Preview {
if #available(iOS 17, *) {
return ExampleTipKitView(viewModel: ExampleTipKitViewModel(flowController: nil))
} else {
return EmptyView()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Created by David Kadlček on 24.07.2024
// Copyright © 2024 Matee. All rights reserved.
//

import Foundation
import UIToolkit
import TipKit

class ExampleTipKitViewModel: BaseViewModel, ObservableObject, ViewModel {

// MARK: Dependencies
private weak var flowController: FlowController?

init(flowController: FlowController?) {
self.flowController = flowController
super.init()

if #available(iOS 17, *) {
state.remainigTapsToShowTip = 3 - RuleTip.remainToShow.donations.count
}
}

// MARK: State

@Published private(set) var state: State = State()

struct State {
var remainingTapsToShowTip: Int = 3
}

// MARK: Intent
enum Intent {
case pop
case tapToShowTip
}

func onIntent(_ intent: Intent) {
executeTask(Task {
switch intent {
case .pop: flowController?.pop()
case .tapToShowTip: await handleTapToShowTip()
}
})
}

private func handleTapToShowTip() async {
guard #available(iOS 17, *) else { return }

state.remainingTapsToShowTip -= 1
await RuleTip.remainToShow.donate()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Created by David Kadlček on 24.07.2024
// Copyright © 2024 Matee. All rights reserved.
//

import Foundation
import TipKit
import UIToolkit

@available(iOS 17, *)
struct ActionTip: Tip {

enum Actions {
case pop
case close

var id: String {
switch self {
case .pop:
"pop-screen"
case .close:
"close-popup"
}
}
}

var title: Text {
Text(L10n.recipe_tipkit_action_tip_title)
}

var actions: [Action] {
Action(id: Actions.pop.id, title: "Pop screen")
Action(id: Actions.close.id, title: "Close popup")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Created by David Kadlček on 24.07.2024
// Copyright © 2024 Matee. All rights reserved.
//

import Foundation
import TipKit
import UIToolkit

@available(iOS 17, *)
struct InlineTip: Tip {

var title: Text {
Text(L10n.recipe_tipkit_inline_tip_title)
}

var message: Text? {
Text(L10n.recipe_tipkit_inline_tip_message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Created by David Kadlček on 24.07.2024
// Copyright © 2024 Matee. All rights reserved.
//

import Foundation
import TipKit
import UIToolkit

@available(iOS 17, *)
struct PopoverTip: Tip {

var title: Text {
Text(L10n.recipe_tipkit_popover_tip_title)
}

var message: Text? {
Text(L10n.recipe_tipkit_popover_tip_message)
}

var image: Image? {
Asset.Images.brandLogo.image
}

var options: [any TipOption] {
[Tips.MaxDisplayCount(1)]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Created by David Kadlček on 24.07.2024
// Copyright © 2024 Matee. All rights reserved.
//

import Foundation
import TipKit
import UIToolkit

@available(iOS 17, *)
struct RuleTip: Tip {

/// Event Rule
static let remainToShow = Event(id: "number-value")

var title: Text {
Text(L10n.recipe_tipkit_rule_tip_title)
}

var message: Text? {
Text(L10n.recipe_tipkit_rule_tip_message)
}

var rules: [Rule] {
[
#Rule(Self.remainToShow) { $0.donations.count > 2 }
]
}

var options: [any TipOption] = [
IgnoresDisplayFrequency(true)
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ public enum AppTheme {
public static let whisperMessage = Font.system(size: 13.0, weight: .medium)
public static let whisperMessageUIKit = UIFont.systemFont(ofSize: 13.0, weight: .medium)
}

public enum Images {
public static let arrowShapeBackward = Image(systemName: "arrowshape.backward")
}
}
Loading
Loading