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

Feat [#47] background schedule&FamilyPicker 연결 #48

Merged
merged 9 commits into from
Jun 2, 2024
32 changes: 10 additions & 22 deletions HMH_iOS/HMH_iOS/Global/Application/MidnightTaskScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,29 @@
// Created by 이지희 on 5/26/24.
//

import BackgroundTasks
import CoreData
import Foundation

class MidnightTaskScheduler {
class MidnightTaskScheduler: ObservableObject {
var timer: Timer?

init() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.refresh", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
scheduleMidnightTask()
}

func scheduleMidnightTask() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
request.earliestBeginDate = nextMidnight() // 다음 자정에 실행되도록 설정
let nextMidnight = nextMidnightDate()
let interval = nextMidnight.timeIntervalSinceNow

do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Unable to submit task: \(error)")
}
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(executeMidnightTask), userInfo: nil, repeats: false)
print("Timer scheduled to fire at: \(nextMidnight)")
}

func handleAppRefresh(task: BGAppRefreshTask) {
// 데이터 저장 작업 실행
@objc func executeMidnightTask() {
saveDataToLocalDatabase()

// 작업이 끝나면 새로 예약
scheduleMidnightTask()

task.setTaskCompleted(success: true)
}

func nextMidnight() -> Date {
func nextMidnightDate() -> Date {
let now = Date()
var calendar = Calendar.current
calendar.timeZone = TimeZone.current
Expand All @@ -49,7 +37,7 @@ class MidnightTaskScheduler {
}

func saveDataToLocalDatabase() {

print("Data saved to local database.")
// 백그라운드에서 실행할 API 연결
}
}

21 changes: 16 additions & 5 deletions HMH_iOS/HMH_iOS/Global/HMH_iOSApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@ enum AppState: String {
case home
}


@main
struct HMH_iOSApp: App {
@StateObject var loginViewModel = LoginViewModel()
@StateObject var userManager = UserManager.shared

@StateObject private var scheduler = MidnightTaskScheduler()

let kakaoAPIKey = Bundle.main.infoDictionary?["KAKAO_API_KEY"] as! String
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate


@Environment(\.scenePhase) private var scenePhase

init() {
KakaoSDK.initSDK(appKey: kakaoAPIKey)
}

var body: some Scene {
WindowGroup {
ZStack {
Expand All @@ -48,13 +50,22 @@ struct HMH_iOSApp: App {
case .login:
LoginView(viewModel: loginViewModel)
.onOpenURL { url in
if (AuthApi.isKakaoTalkLoginUrl(url)) {
if AuthApi.isKakaoTalkLoginUrl(url) {
_ = AuthController.handleOpenUrl(url: url)
}
}
}
}
}
}
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .background:
print("App moved to background.")
scheduler.scheduleMidnightTask()
@unknown default:
break
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ final class ChallengeViewModel: ObservableObject {
@Published var subTitleString = ""
@Published var challengeType: ChallengeType = .empty

@StateObject var screenViewModel = ScreenTimeViewModel()

enum PointStatus {
static let unearned = "UNEARNED"
static let earned = "EARNED"
Expand Down Expand Up @@ -57,18 +59,15 @@ final class ChallengeViewModel: ObservableObject {
}
}

func createChallenge(bundle: [String]) {

let dto = CreateChallengeRequestDTO(period: 7, goalTime: 1204928)
Providers.challengeProvider.request(target: .createChallenge(data: dto), instance: BaseResponse<EmptyResponseDTO>.self) { result in
print(result)
}
func addApp(appGoalTime: Int) {
var applist: [Apps] = []

bundle.forEach { bundleid in
applist.append(Apps(appCode: bundleid, goalTime: 10000000))
screenViewModel.selectedApp.applications.forEach { app in
applist.append(Apps(appCode: app.localizedDisplayName ?? "basic name", goalTime: appGoalTime))
}

screenViewModel.handleStartDeviceActivityMonitoring(includeUsageThreshold: true, interval: appGoalTime)

Providers.challengeProvider.request(target: .addApp(data: AddAppRequestDTO(apps: applist)), instance: BaseResponse<EmptyResponseDTO>.self) { result in
print(result)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ final class ScreenTimeViewModel: ObservableObject {
}

@Published var sharedHasScreenTimePermission = false
var hashVaule: [Int] = []
@Published var hashVaule: [String] = []


@MainActor func updateSelectedApp(newSelection: FamilyActivitySelection) {
DispatchQueue.main.async {
self.selectedApp = newSelection
print(self.selectedApp.jsonString())
}
}

func saveHashValue() {
selectedApp.applicationTokens.forEach { app in
hashVaule.append(app.hashValue)
}

}

func requestAuthorization() {
Expand Down Expand Up @@ -71,22 +71,23 @@ final class ScreenTimeViewModel: ObservableObject {
func handleStartDeviceActivityMonitoring(includeUsageThreshold: Bool = true, interval: Int) {
//datacomponent타입을 써야함
let dateComponents = Calendar.current.dateComponents([.hour, .minute, .second], from: Date())
let minute: Int = interval / 60000

// 새 스케쥴 시간 설정
let schedule = DeviceActivitySchedule(
intervalStart: DateComponents(hour: dateComponents.hour, minute: dateComponents.minute, second: dateComponents.second),
intervalEnd: DateComponents(hour: 23, minute: 59, second: 59),
repeats: false,
//warning Time 설정해야 알람
warningTime: DateComponents(minute: 1)
warningTime: DateComponents(minute: minute - 1 ) // 여기는 전체 시간 가까워질 때
)
//새 이벤트 생성
let event = DeviceActivityEvent(
applications: selectedApp.applicationTokens,
categories: selectedApp.categoryTokens,
webDomains: selectedApp.webDomainTokens,
//threshold - 이 시간이 되면 특정한 event가 발생 deviceactivitymonitor에 eventdidreachthreshold
threshold: DateComponents(minute : interval)
threshold: DateComponents(minute : minute)
)

do {
Expand Down Expand Up @@ -142,6 +143,36 @@ final class ScreenTimeViewModel: ObservableObject {


extension FamilyActivitySelection: RawRepresentable {
func jsonString() -> String? {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(self)
guard let jsonString = String(data: data, encoding: .utf8) else {
print("Error encoding FamilyActivitySelection")
return nil
}
return jsonString
} catch {
print("Error encoding FamilyActivitySelection: \(error)")
return nil
}
}

static func from(jsonString: String) -> FamilyActivitySelection? {
guard let data = jsonString.data(using: .utf8) else {
print("Error converting string to Data")
return nil
}

let decoder = JSONDecoder()
do {
let familyActivitySelection = try decoder.decode(FamilyActivitySelection.self, from: data)
return familyActivitySelection
} catch {
print("Error decoding FamilyActivitySelection: \(error)")
return nil
}
}
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(FamilyActivitySelection.self, from: data)
Expand Down
15 changes: 1 addition & 14 deletions HMH_iOS/HMH_iOS/Presentation/Challenge/Views/ChallengeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ struct ChallengeView: View {
@StateObject var screenTimeViewModel = ScreenTimeViewModel()
@ObservedObject var viewModel: ChallengeViewModel
@State var list = [AppDeviceActivity]()
@State var isPresented = false
@State private var selection = FamilyActivitySelection()
@State private var isExpanded = false


Expand Down Expand Up @@ -126,22 +124,11 @@ extension ChallengeView {
.padding(.horizontal, 20)
DeviceActivityReport(context, filter: filter)
.frame(height: 72 * CGFloat(screenTimeViewModel.selectedApp.applicationTokens.count))
Button(action: {
isPresented = true
}, label: {
NavigationLink(destination: OnboardingContentView(isChallengeMode: true, onboardingState: 4)) {
Image(.addAppButton)
})
.familyActivityPicker(isPresented: $isPresented,
selection: $selection)
.onChange(of: selection) { newSelection in
screenTimeViewModel.updateSelectedApp(newSelection: newSelection)
screenTimeViewModel.saveHashValue()
// TODO: 챌린지 만드는 시점에 설정
// screenTimeViewModel.handleStartDeviceActivityMonitoring(interval: 1)
}
}
.onAppear() {
selection = screenTimeViewModel.selectedApp
filter = DeviceActivityFilter(
segment: .daily(
during: Calendar.current.dateInterval(
Expand Down
35 changes: 21 additions & 14 deletions HMH_iOS/HMH_iOS/Presentation/Home/Views/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,10 @@ struct HomeView: View {
showBackButton: false,
showPointButton: false, point: 0)
.background(.blackground)
.onAppear {
screenTimeViewModel.requestAuthorization()

appFilter = DeviceActivityFilter(
segment: .daily(
during: Calendar.current.dateInterval(
of: .day, for: .now
) ?? DateInterval()
),
users: .all,
devices: .init([.iPhone]),
applications: screenTimeViewModel.selectedApp.applicationTokens,
categories: screenTimeViewModel.selectedApp.categoryTokens
)
.task {
await loadData()
}

}
}

Expand All @@ -65,9 +54,27 @@ extension HomeView {
.padding(.bottom, 20)
}
}

@MainActor
func loadData() async {
screenTimeViewModel.requestAuthorization()

appFilter = DeviceActivityFilter(
segment: .daily(
during: Calendar.current.dateInterval(
of: .day, for: .now
) ?? DateInterval()
),
users: .all,
devices: .init([.iPhone]),
applications: screenTimeViewModel.selectedApp.applicationTokens,
categories: screenTimeViewModel.selectedApp.categoryTokens
)
}
}



//#Preview {
// HomeView()
//}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ class LoginViewModel: NSObject, ObservableObject {
@Published var isLoading: Bool = true

func handleSplashScreen() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.refreshToken()
self.isLoading = false
}
self.isLoading = false
}

private func refreshToken() {
Expand Down
Loading