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

[CAT-48] 로컬 푸시 기능 대응 #12

Merged
merged 4 commits into from
Jul 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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public struct UserNotificationClient {
case didReceiveResponse(Notification.Response, completionHandler: @Sendable () -> Void)
case openSettingsForNotification(Notification?)
case willPresentNotification(
Notification, completionHandler: @Sendable (UNNotificationPresentationOptions) -> Void
Notification,
completionHandler: @Sendable (UNNotificationPresentationOptions) -> Void
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import Dependencies

extension UserNotificationClient: DependencyKey {
public static let liveValue = Self(
add: { try await UNUserNotificationCenter.current().add($0) },
add: {
try await UNUserNotificationCenter.current().add($0)
},
delegate: {
AsyncStream { continuation in
let delegate = Delegate(continuation: continuation)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "ICON_DEMO.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
<rect key="frame" x="0.0" y="626.5" width="375" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PushService 모듈 데모앱" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="SfN-ll-jLj"/>
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="x7j-FC-K8j"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
80 changes: 80 additions & 0 deletions Projects/Domain/PushService/Example/Sources/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// ContentView.swift
// PushService
//
// Created by devMinseok on 7/24/24.
//

import SwiftUI

import UserNotificationClientInterface
import PushService

import Dependencies

struct ContentView: View {
@State var selectedInterval: Int = 0
@Dependency(\.userNotificationClient) var userNotificationClient

var body: some View {
VStack {
Picker("test", selection: $selectedInterval) {
ForEach(0..<100, id: \.self) { interval in
Text("\(interval)초")
}
}
.pickerStyle(WheelPickerStyle())

Button {
sendLocalPushNotification()
} label: {
Text("로컬 푸시 발송")
}
}
.padding()
.onAppear {
notiAuthHandler()
requestNotiAuth()
}
}

func notiAuthHandler() {
let userNotificationEventStream = self.userNotificationClient.delegate()
Task {
for await event in userNotificationEventStream {
switch event {
case let .didReceiveResponse(_, completionHandler):
completionHandler()
case .openSettingsForNotification(_):

Check warning on line 48 in Projects/Domain/PushService/Example/Sources/ContentView.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Arguments can be omitted when matching enums with associated values if they are not used (empty_enum_arguments)
break
case let .willPresentNotification(_, completionHandler):
completionHandler([.banner, .list, .sound])
}
}
}
}

func requestNotiAuth() {
Task {
try await userNotificationClient.requestAuthorization([.alert, .badge, .sound])
}
}

func sendLocalPushNotification() {
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: TimeInterval(selectedInterval),
repeats: false
)
Task {
try await scheduleNotification(
userNotificationClient: self.userNotificationClient,
contentType: .test,
trigger: trigger
)
}
}
}

#Preview {
ContentView()
}
17 changes: 17 additions & 0 deletions Projects/Domain/PushService/Example/Sources/PushServiceApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// PushServiceApp.swift
// PushService
//
// Created by devMinseok on 7/24/24.
//

import SwiftUI

@main
struct PushServiceApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
12 changes: 12 additions & 0 deletions Projects/Domain/PushService/Interface/PushServiceInterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// PushServiceInterface.swift
// PushService
//
// Created by <#T##Author name#> on 7/24/24.
//

import Foundation

public struct PushServiceInterface {
public init() {}
}
3 changes: 2 additions & 1 deletion Projects/Domain/PushService/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ let project: Project = .makeTMABasedProject(
module: Domain.PushService,
scripts: [],
targets: [
.sources
.sources,
.example
],
dependencies: [
.sources: [
Expand Down
1 change: 1 addition & 0 deletions Projects/Domain/PushService/Resources/dummy.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dummy file
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// LocalPushNotificationContent.swift
// PushService
//
// Created by devMinseok on 7/24/24.
// Copyright © 2024 PomoNyang. All rights reserved.
//

import Foundation

public enum LocalPushNotificationContent {
case test

public var title: String {
switch self {
case .test:
return "테스트 집중"
}
}

public var body: String {
switch self {
case .test:
return "\(10)시 까지 집중해봐요!"
}
}

public var identifier: String {
switch self {
case .test:
return "group1"
}
}
}
20 changes: 20 additions & 0 deletions Projects/Domain/PushService/Sources/PushServiceInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//

import Foundation
import UserNotifications

import UserNotificationClientInterface

public func getPushNotificationContent(from userInfo: [AnyHashable: Any]) -> PushNotificationContent? {
guard let data = try? JSONSerialization.data(withJSONObject: userInfo) else {
Expand All @@ -15,3 +18,20 @@
let pushNotiContent = try? JSONDecoder().decode(PushNotificationContent.self, from: data)
return pushNotiContent
}

public func scheduleNotification(
userNotificationClient: UserNotificationClient,
contentType: LocalPushNotificationContent,
trigger: UNNotificationTrigger
) async throws -> Void {

Check warning on line 26 in Projects/Domain/PushService/Sources/PushServiceInterface.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Returning Void in a function declaration is redundant (redundant_void_return)
let content = UNMutableNotificationContent()
content.title = contentType.title
content.body = contentType.body
content.sound = UNNotificationSound.default
let request = UNNotificationRequest(
identifier: contentType.identifier,
content: content,
trigger: trigger
)
return try await userNotificationClient.add(request)
}
12 changes: 12 additions & 0 deletions Projects/Domain/PushService/Testing/PushServiceTesting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// PushServiceTesting.swift
// PushService
//
// Created by <#T##Author name#> on 7/24/24.
//

import Foundation

public struct PushServiceTesting {
public init() {}
}
33 changes: 33 additions & 0 deletions Projects/Domain/PushService/Tests/PushServiceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// PushServiceTests.swift
// PushService
//
// Created by <#T##Author name#> on 7/24/24.
//

import XCTest

final class PushServiceTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
// Any test you write for XCTest can be annotated as throws and async.
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
}

func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
2 changes: 1 addition & 1 deletion Projects/Feature/Feature/Sources/AppCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public struct AppCore {
private func core(_ state: inout State, _ action: Action) -> EffectOf<Self> {
switch action {
case .onAppear:
let isLoggedIn = false
let isLoggedIn = true
if isLoggedIn { // 로그인 판단
state.home = HomeCore.State()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public struct HomeCore {

public enum Action {
case onAppear
case localPushButtonTapped
}

public var body: some ReducerOf<Self> {
Expand Down
3 changes: 3 additions & 0 deletions Projects/Feature/HomeFeature/Interface/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ public struct HomeView: View {
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
.onAppear {
store.send(.onAppear)
}
}
}
28 changes: 26 additions & 2 deletions Projects/Feature/HomeFeature/Sources/HomeCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,40 @@
// Copyright © 2024 PomoNyang. All rights reserved.
//

import UserNotifications

import HomeFeatureInterface
import PushService
import UserNotificationClientInterface

import ComposableArchitecture

extension HomeCore {
public init() {
let reducer = Reduce<State, Action> { _, action in
@Dependency(\.userNotificationClient) var userNotificationClient

let reducer = Reduce<State, Action> {
_,

Check warning on line 22 in Projects/Feature/HomeFeature/Sources/HomeCore.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Closure parameters should be on the same line as opening brace (closure_parameter_position)
action in

Check warning on line 23 in Projects/Feature/HomeFeature/Sources/HomeCore.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Closure parameters should be on the same line as opening brace (closure_parameter_position)
switch action {
case .onAppear:
return .none
return .run { send in

Check warning on line 26 in Projects/Feature/HomeFeature/Sources/HomeCore.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Unused parameter in a closure should be replaced with _ (unused_closure_parameter)
_ = try await userNotificationClient.requestAuthorization([.alert, .badge, .sound])
}

case .localPushButtonTapped:
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
return .run { send in

Check warning on line 32 in Projects/Feature/HomeFeature/Sources/HomeCore.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Unused parameter in a closure should be replaced with _ (unused_closure_parameter)
do {
try await scheduleNotification(
userNotificationClient: userNotificationClient,
contentType: .test,
trigger: trigger
)
} catch {
print(error)
}
}
}
}
self.init(reducer: reducer)
Expand Down
Loading
Loading