diff --git a/SodaLive/Sources/App/AppState.swift b/SodaLive/Sources/App/AppState.swift index a762fc0..7f69b1c 100644 --- a/SodaLive/Sources/App/AppState.swift +++ b/SodaLive/Sources/App/AppState.swift @@ -34,6 +34,8 @@ class AppState: ObservableObject { } } + @Published var eventPopup: EventItem? = nil + func setAppStep(step: AppStep) { switch step { case .splash, .main: diff --git a/SodaLive/Sources/Main/EventPopupDialogView.swift b/SodaLive/Sources/Main/EventPopupDialogView.swift new file mode 100644 index 0000000..e50ddf8 --- /dev/null +++ b/SodaLive/Sources/Main/EventPopupDialogView.swift @@ -0,0 +1,69 @@ +// +// EventPopupDialogView.swift +// SodaLive +// +// Created by klaus on 2023/08/11. +// + +import SwiftUI +import Kingfisher + +struct EventPopupDialogView: View { + + let eventPopup: EventItem + + var body: some View { + VStack(spacing: 13.3) { + KFImage(URL(string: eventPopup.popupImageUrl!)) + .resizable() + .frame(width: screenSize().width, height: screenSize().width) + .scaledToFill() + .cornerRadius(16.7, corners: [.topLeft, .topRight]) + .onTapGesture { + AppState + .shared + .setAppStep( + step: .eventDetail( + event: eventPopup + ) + ) + AppState.shared.eventPopup = nil + } + + HStack(spacing: 0) { + Text("다시보지 않기") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "eeeeee")) + .onTapGesture { + UserDefaults.set(eventPopup.id, forKey: .notShowingEventPopupId) + AppState.shared.eventPopup = nil + } + + Spacer() + + Text("닫기") + .font(.custom(Font.medium.rawValue, size: 14.7)) + .foregroundColor(Color(hex: "eeeeee")) + .onTapGesture { AppState.shared.eventPopup = nil } + } + .padding(.horizontal, 26.7) + .padding(.bottom, 13.3) + } + .background(Color(hex: "222222")) + .cornerRadius(16.7, corners: [.topLeft, .topRight]) + } +} + +struct EventPopupDialogView_Previews: PreviewProvider { + static var previews: some View { + EventPopupDialogView( + eventPopup: EventItem( + id: 1, + thumbnailImageUrl: "", + detailImageUrl: "", + popupImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + link: "" + ) + ) + } +} diff --git a/SodaLive/Sources/Main/Home/HomeView.swift b/SodaLive/Sources/Main/Home/HomeView.swift index 9484cd5..1ee19bb 100644 --- a/SodaLive/Sources/Main/Home/HomeView.swift +++ b/SodaLive/Sources/Main/Home/HomeView.swift @@ -58,12 +58,49 @@ struct HomeView: View { } } .onAppear { - + pushTokenUpdate() + viewModel.getMemberInfo() + viewModel.getEventPopup() + } + + if appState.isShowNotificationSettingsDialog { + NotificationSettingsDialog() + } + + if let eventItem = appState.eventPopup { + VStack(spacing: 0) { + Spacer() + + EventPopupDialogView(eventPopup: eventItem) + + if proxy.safeAreaInsets.bottom > 0 { + Rectangle() + .foregroundColor(Color(hex: "222222")) + .frame(width: proxy.size.width, height: 15.3) + } + } + .background(Color(hex: "222222").opacity(0.7)) + .onTapGesture { + AppState.shared.eventPopup = nil + } } } .edgesIgnoringSafeArea(.bottom) } } + + private func pushTokenUpdate() { + Messaging.messaging().token { token, error in + if let error = error { + DEBUG_LOG(error.localizedDescription) + } else { + if let token = token { + UserDefaults.set(token, forKey: .pushToken) + self.viewModel.pushTokenUpdate(pushToken: token) + } + } + } + } } struct HomeView_Previews: PreviewProvider { diff --git a/SodaLive/Sources/Main/Home/HomeViewModel.swift b/SodaLive/Sources/Main/Home/HomeViewModel.swift index e723b1b..0515dc6 100644 --- a/SodaLive/Sources/Main/Home/HomeViewModel.swift +++ b/SodaLive/Sources/Main/Home/HomeViewModel.swift @@ -13,10 +13,83 @@ final class HomeViewModel: ObservableObject { private var subscription = Set() private let userRepository = UserRepository() + private let eventRepository = EventRepository() enum CurrentTab: String { case content, live, explorer, message, mypage } @Published var currentTab: CurrentTab = .live + + func pushTokenUpdate(pushToken: String) { + userRepository.updatePushToken(pushToken: pushToken) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { _ in + } + .store(in: &subscription) + } + + func getMemberInfo() { + userRepository.getMemberInfo() + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { response in + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if let data = decoded.data, decoded.success { + UserDefaults.set(data.can, forKey: .can) + UserDefaults.set(data.isAuth, forKey: .auth) + UserDefaults.set(data.role.rawValue, forKey: .role) + if data.followingChannelLiveNotice == nil && data.followingChannelUploadContentNotice == nil && data.messageNotice == nil { + AppState.shared.isShowNotificationSettingsDialog = true + } + } + } catch { + print(error) + } + } + .store(in: &subscription) + } + + func getEventPopup() { + eventRepository.getEventPopup() + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { response in + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if let data = decoded.data, decoded.success { + if data.id != UserDefaults.int(forKey: .notShowingEventPopupId) { + AppState.shared.eventPopup = data + } + } + } catch { + } + } + .store(in: &subscription) + } } diff --git a/SodaLive/Sources/MyPage/Can/Payment/CanPaymentView.swift b/SodaLive/Sources/MyPage/Can/Payment/CanPaymentView.swift index 02caf99..275b718 100644 --- a/SodaLive/Sources/MyPage/Can/Payment/CanPaymentView.swift +++ b/SodaLive/Sources/MyPage/Can/Payment/CanPaymentView.swift @@ -173,6 +173,25 @@ struct CanPaymentView: View { .frame(width: proxy.size.width, height: 15.3) } } + .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .padding(.horizontal, 6.7) + .frame(width: geo.size.width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color(hex: "9970ff")) + .foregroundColor(Color.white) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + } + } .edgesIgnoringSafeArea(.bottom) } diff --git a/SodaLive/Sources/MyPage/Can/Payment/CanPgPaymentView.swift b/SodaLive/Sources/MyPage/Can/Payment/CanPgPaymentView.swift index 437bf12..11c793c 100644 --- a/SodaLive/Sources/MyPage/Can/Payment/CanPgPaymentView.swift +++ b/SodaLive/Sources/MyPage/Can/Payment/CanPgPaymentView.swift @@ -269,6 +269,25 @@ struct CanPgPaymentView: View { .frame(width: proxy.size.width, height: 15.3) } } + .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { + GeometryReader { geo in + HStack { + Spacer() + Text(viewModel.errorMessage) + .padding(.vertical, 13.3) + .padding(.horizontal, 6.7) + .frame(width: geo.size.width - 66.7, alignment: .center) + .font(.custom(Font.medium.rawValue, size: 12)) + .background(Color(hex: "9970ff")) + .foregroundColor(Color.white) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + .cornerRadius(20) + .padding(.top, 66.7) + Spacer() + } + } + } .edgesIgnoringSafeArea(.bottom) } } diff --git a/SodaLive/Sources/Settings/Notification/NotificationSettingsDialog.swift b/SodaLive/Sources/Settings/Notification/NotificationSettingsDialog.swift new file mode 100644 index 0000000..89a4fc5 --- /dev/null +++ b/SodaLive/Sources/Settings/Notification/NotificationSettingsDialog.swift @@ -0,0 +1,102 @@ +// +// NotificationSettingsDialog.swift +// SodaLive +// +// Created by klaus on 2023/08/11. +// + +import SwiftUI + +struct NotificationSettingsDialog: View { + + @StateObject var viewModel = NotificationSettingsDialogViewModel() + + var body: some View { + GeometryReader { geo in + ZStack { + Color.black + .opacity(0.5) + .frame(width: geo.size.width, height: geo.size.height) + + VStack(spacing: 0) { + HStack(spacing: 0) { + Text("라이브 알림") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + Image(viewModel.live ? "btn_toggle_on_big" : "btn_toggle_off_big") + .resizable() + .frame(width: 44, height: 27) + .onTapGesture { + viewModel.live.toggle() + } + } + + Rectangle() + .frame(height: 1) + .foregroundColor(Color(hex: "909090")) + .padding(.vertical, 13) + + HStack(spacing: 0) { + Text("콘텐츠 업로드 알림") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + Image(viewModel.uploadContent ? "btn_toggle_on_big" : "btn_toggle_off_big") + .resizable() + .frame(width: 44, height: 27) + .onTapGesture { + viewModel.uploadContent.toggle() + } + } + + Rectangle() + .frame(height: 1) + .foregroundColor(Color(hex: "909090")) + .padding(.vertical, 13) + + HStack(spacing: 0) { + Text("메시지 알림") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + Image(viewModel.message ? "btn_toggle_on_big" : "btn_toggle_off_big") + .resizable() + .frame(width: 44, height: 27) + .onTapGesture { + viewModel.message.toggle() + } + } + + Text("확인") + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(Color(hex: "ffffff")) + .padding(.vertical, 16) + .frame(width: (geo.size.width - 66.7) * 2 / 3) + .background(Color(hex: "9970ff")) + .cornerRadius(8) + .padding(.top, 33.3) + .onTapGesture { + viewModel.submit() + } + } + .padding(26.7) + .frame(width: geo.size.width - 26.7, alignment: .center) + .background(Color(hex: "222222")) + .cornerRadius(10) + } + } + } +} + +struct NotificationSettingsDialog_Previews: PreviewProvider { + static var previews: some View { + NotificationSettingsDialog() + } +} diff --git a/SodaLive/Sources/Settings/Notification/NotificationSettingsDialogViewModel.swift b/SodaLive/Sources/Settings/Notification/NotificationSettingsDialogViewModel.swift new file mode 100644 index 0000000..eba7ed5 --- /dev/null +++ b/SodaLive/Sources/Settings/Notification/NotificationSettingsDialogViewModel.swift @@ -0,0 +1,35 @@ +// +// NotificationSettingsDialogViewModel.swift +// SodaLive +// +// Created by klaus on 2023/08/11. +// + +import Foundation +import Combine + +final class NotificationSettingsDialogViewModel: ObservableObject { + + private let userRepository = UserRepository() + private var subscription = Set() + + @Published var live = true + @Published var uploadContent = true + @Published var message = true + + func submit() { + userRepository + .updateNotificationSettings(live: live, uploadContent: uploadContent, message: message) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { _ in + AppState.shared.isShowNotificationSettingsDialog = false + } + .store(in: &subscription) + } +} diff --git a/SodaLive/Sources/User/PushTokenUpdateRequest.swift b/SodaLive/Sources/User/PushTokenUpdateRequest.swift new file mode 100644 index 0000000..bcbc2b8 --- /dev/null +++ b/SodaLive/Sources/User/PushTokenUpdateRequest.swift @@ -0,0 +1,13 @@ +// +// PushTokenUpdateRequest.swift +// SodaLive +// +// Created by klaus on 2023/08/11. +// + +import Foundation + +struct PushTokenUpdateRequest: Encodable { + let pushToken: String + let container: String = "ios" +} diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift index 19da034..e30af07 100644 --- a/SodaLive/Sources/User/UserApi.swift +++ b/SodaLive/Sources/User/UserApi.swift @@ -19,6 +19,7 @@ enum UserApi { case logout case logoutAllDevice case signOut(request: SignOutRequest) + case updatePushToken(request: PushTokenUpdateRequest) } extension UserApi: TargetType { @@ -57,6 +58,9 @@ extension UserApi: TargetType { case .signOut: return "/member/sign_out" + + case .updatePushToken: + return "/member/push-token/update" } } @@ -67,6 +71,9 @@ extension UserApi: TargetType { case .searchUser, .getMypage, .getMemberInfo: return .get + + case .updatePushToken: + return .put } } @@ -95,6 +102,9 @@ extension UserApi: TargetType { case .signOut(let request): return .requestJSONEncodable(request) + + case .updatePushToken(let request): + return .requestJSONEncodable(request) } } diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift index 4c997a4..c281c1b 100644 --- a/SodaLive/Sources/User/UserRepository.swift +++ b/SodaLive/Sources/User/UserRepository.swift @@ -60,4 +60,8 @@ final class UserRepository { func signOut(reason: String, password: String) -> AnyPublisher { return api.requestPublisher(.signOut(request: SignOutRequest(reason: reason, password: password))) } + + func updatePushToken(pushToken: String) -> AnyPublisher { + return api.requestPublisher(.updatePushToken(request: PushTokenUpdateRequest(pushToken: pushToken))) + } }