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: 위치 선택 기능 추가 #356

Merged
merged 6 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
77 changes: 67 additions & 10 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ protocol UploadPostBusinessLogic {
func fetchTags()
func editTags(with request: UploadPostModels.EditTags.Request)
func fetchThumbnailImage() async
func fetchCurrentAddress() async
func fetchCurrentAddress() async -> UploadPostModels.AddressInfo?
func canUploadPost(request: UploadPostModels.CanUploadPost.Request)
func uploadPost(request: UploadPostModels.UploadPost.Request)
func fetchVideoAddress() async -> UploadPostModels.AddressInfo?
func fetchAddresses() async
func showActionSheet()
func selectAddress(with request: UploadPostModels.SelectAddress.Request)
}

protocol UploadPostDataStore {
Expand All @@ -45,6 +49,8 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
var videoURL: URL?
var isMuted: Bool?
var tags: [String]? = []
var videoAddress: Models.AddressInfo?
var currentAddress: Models.AddressInfo?

// MARK: - Object LifeCycle

Expand Down Expand Up @@ -80,23 +86,44 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
}
}

func fetchCurrentAddress() async {
guard let location = locationManager.getCurrentLocation() else { return }
func fetchCurrentAddress() async -> UploadPostModels.AddressInfo? {
guard let location = locationManager.getCurrentLocation() else { return nil }
let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier
let locale = Locale(identifier: localeIdentifier)
do {
let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last
let administrativeArea = address?.administrativeArea
let locality = address?.locality
let subLocality = address?.subLocality
let response = Models.FetchCurrentAddress.Response(administrativeArea: administrativeArea,
locality: locality,
subLocality: subLocality)
await MainActor.run {
presenter?.presentCurrentAddress(with: response)
}
return Models.AddressInfo(
administrativeArea: administrativeArea,
locality: locality,
subLocality: subLocality)
} catch {
os_log(.error, log: .data, "Failed to fetch Current Address with error: %@", error.localizedDescription)
return nil
}
}

func fetchVideoAddress() async -> UploadPostModels.AddressInfo? {
guard let videoURL,
let videoLocation = await worker?.loadVideoLocation(videoURL: videoURL),
let location = locationManager.getVideoLocation(latitude: videoLocation.latitude, longitude: videoLocation.longitude)
else { return nil }
let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier
let locale = Locale(identifier: localeIdentifier)
do {
let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last
let administrativeArea = address?.administrativeArea
let locality = address?.locality
let subLocality = address?.subLocality
return Models.AddressInfo(
administrativeArea: administrativeArea,
locality: locality,
subLocality: subLocality)
} catch {
os_log(.error, log: .data, "Failed to fetch Video Address with error: %@", error.localizedDescription)
return nil
}
}

Expand Down Expand Up @@ -127,6 +154,37 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
}
}

func fetchAddresses() async {
async let currentAddressInfo = fetchCurrentAddress()
async let videoAddressInfo = fetchVideoAddress()

videoAddress = await videoAddressInfo
currentAddress = await currentAddressInfo

let response: Models.FetchCurrentAddress.Response = Models.FetchCurrentAddress.Response(addressInfo: [ videoAddress, currentAddress].compactMap { $0 })
await MainActor.run {
presenter?.presentCurrentAddress(with: response)
}
}

func selectAddress(with request: UploadPostModels.SelectAddress.Request) {
var response: Models.FetchCurrentAddress.Response
switch request.addressType {
case .video:
guard let videoAddress else { return }
response = Models.FetchCurrentAddress.Response(addressInfo: [videoAddress])
case .current:
guard let currentAddress else { return }
response = Models.FetchCurrentAddress.Response(addressInfo: [currentAddress])
}
presenter?.presentCurrentAddress(with: response)
}

func showActionSheet() {
let response: Models.ShowActionSheet.Response = Models.ShowActionSheet.Response(videoAddress: videoAddress, currentAddress: currentAddress)
presenter?.presentShowActionSheet(with: response)
}

private func exportVideoWithoutAudio(at url: URL) async {
let composition = AVMutableComposition()
let sourceAsset = AVURLAsset(url: url)
Expand Down Expand Up @@ -158,5 +216,4 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD
os_log(.error, log: .data, "Failed to extract Video Without Audio with error: %@", error.localizedDescription)
}
}

}
47 changes: 43 additions & 4 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ enum UploadPostModels {
static let titleMaxLength: Int = 15
static let contentMaxLength: Int = 50

struct VideoAddress {
let latitude: Double
let longitude: Double
}

struct AddressInfo {
let administrativeArea: String?
let locality: String?
let subLocality: String?
}

enum AddressType {
case video
case current
}

enum CanUploadPost {
struct Request {
let title: String?
Expand Down Expand Up @@ -61,9 +77,7 @@ enum UploadPostModels {

}
struct Response {
let administrativeArea: String?
let locality: String?
let subLocality: String?
let addressInfo: [AddressInfo]
}
struct ViewModel {
let fullAddress: String
Expand All @@ -79,7 +93,32 @@ enum UploadPostModels {
struct Response {

}
struct VideModel {
struct ViewModel {

}
}

enum ShowActionSheet {
struct Request {

}
struct Response {
let videoAddress: AddressInfo?
let currentAddress: AddressInfo?
}
struct ViewModel {
let addressTypes: [AddressType]
}
}

enum SelectAddress {
struct Request {
let addressType: AddressType
}
struct Response {

}
struct ViewModel {

}
}
Expand Down
21 changes: 16 additions & 5 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protocol UploadPostPresentationLogic {
func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response)
func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response)
func presentUnsupportedFormatAlert()
func presentShowActionSheet(with response: UploadPostModels.ShowActionSheet.Response)
}

final class UploadPostPresenter: UploadPostPresentationLogic {
Expand All @@ -34,19 +35,18 @@ final class UploadPostPresenter: UploadPostPresentationLogic {

func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) {
let addresses: [String] = [
response.administrativeArea,
response.locality,
response.subLocality]
response.addressInfo.first?.administrativeArea,
response.addressInfo.first?.locality,
response.addressInfo.first?.subLocality]
.compactMap { $0 }

var fullAddress: [String] = []

for address in addresses {
if !fullAddress.contains(address) {
fullAddress.append(address)
}
}

let viewModel = Models.FetchCurrentAddress.ViewModel(fullAddress: fullAddress.joined(separator: " "))
viewController?.displayCurrentAddress(viewModel: viewModel)
}
Expand All @@ -59,4 +59,15 @@ final class UploadPostPresenter: UploadPostPresentationLogic {
func presentUnsupportedFormatAlert() {
viewController?.displayUnsupportedFormatAlert()
}

func presentShowActionSheet(with response: UploadPostModels.ShowActionSheet.Response) {
var addressTypes: [Models.AddressType] = []
if response.videoAddress != nil {
addressTypes.append(.video)
}
if response.currentAddress != nil {
addressTypes.append(.current)
}
viewController?.displayActionSheet(viewModel: Models.ShowActionSheet.ViewModel(addressTypes: addressTypes))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protocol UploadPostDisplayLogic: AnyObject {
func displayCurrentAddress(viewModel: UploadPostModels.FetchCurrentAddress.ViewModel)
func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel)
func displayUnsupportedFormatAlert()
func displayActionSheet(viewModel: UploadPostModels.ShowActionSheet.ViewModel)
}

final class UploadPostViewController: BaseViewController {
Expand Down Expand Up @@ -142,6 +143,7 @@ final class UploadPostViewController: BaseViewController {
setConstraints()
setDelegation()
addTarget()
addLocationTarget()
fetchPostInfo()
}

Expand Down Expand Up @@ -176,7 +178,7 @@ final class UploadPostViewController: BaseViewController {

private func fetchPostInfo() {
Task {
await interactor?.fetchCurrentAddress()
await interactor?.fetchAddresses()
await interactor?.fetchThumbnailImage()
}
}
Expand Down Expand Up @@ -247,6 +249,11 @@ final class UploadPostViewController: BaseViewController {
scrollView.addGestureRecognizer(singleTapGestureRecognizer)
}

private func addLocationTarget() {
let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(locationDidTap))
locationImageLabel.addGestureRecognizer(singleTapGestureRecognizer)
}

@objc private func titleTextChanged() {
interactor?.canUploadPost(request: Models.CanUploadPost.Request(title: titleTextField.text))
}
Expand All @@ -270,6 +277,10 @@ final class UploadPostViewController: BaseViewController {
router?.routeToBack()
}

@objc private func locationDidTap() {
interactor?.showActionSheet()
}

}

extension UploadPostViewController: UITextFieldDelegate {
Expand Down Expand Up @@ -321,4 +332,21 @@ extension UploadPostViewController: UploadPostDisplayLogic {
Toast.shared.showToast(message: "지원하지 않는 파일 형식이에요 😢")
}

func displayActionSheet(viewModel: UploadPostModels.ShowActionSheet.ViewModel) {
let actionSheet = UIAlertController(title: "주소 선택", message: "원하는 위치의 주소를 선택하세요.", preferredStyle: .actionSheet)
for type in viewModel.addressTypes {
switch type {
case .video:
actionSheet.addAction(UIAlertAction(title: "영상 위치", style: .default, handler: { _ in
self.interactor?.selectAddress(with: Models.SelectAddress.Request(addressType: type))
}))
case .current:
actionSheet.addAction(UIAlertAction(title: "현재 위치", style: .default, handler: { _ in
self.interactor?.selectAddress(with: Models.SelectAddress.Request(addressType: type))
}))
}
}
actionSheet.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil))
present(actionSheet, animated: true, completion: nil)
}
}
23 changes: 23 additions & 0 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
//

import UIKit
import AVFoundation

import OSLog

protocol UploadPostWorkerProtocol {
func uploadPost(with request: UploadPost) async -> UploadPostDTO?
func uploadVideo(with request: UploadVideoRequestDTO, videoURL: URL) async -> Bool
func loadVideoLocation(videoURL: URL) async -> UploadPostModels.VideoAddress?
}

final class UploadPostWorker: NSObject, UploadPostWorkerProtocol {
Expand Down Expand Up @@ -64,6 +66,27 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol {
}
}

func loadVideoLocation(videoURL: URL) async -> UploadPostModels.VideoAddress? {
let asset = AVAsset(url: videoURL)
let metadata = try? await asset.load(.metadata)
guard let metadata else { return nil }
for meta in metadata {
if meta.commonKey == AVMetadataKey.commonKeyLocation {
let location = try? await meta.load(.stringValue)?.split(separator: "+")
if location?.count ?? 0 < 2 {
return nil
}
guard let latitudeString = location?[0],
let longitudeString = location?[1],
let latitude = Double(latitudeString),
let longitude = Double(longitudeString)
else { return nil }
return Models.VideoAddress(latitude: latitude, longitude: longitude)
}
}
return nil
}

}

extension UploadPostWorker: URLSessionTaskDelegate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ final class CurrentLocationManager: NSObject {
return CLLocation(latitude: space.latitude, longitude: space.longitude)
}

func getVideoLocation(latitude: Double?, longitude: Double?) -> CLLocation? {
guard let latitude,
let longitude else { return nil }
return CLLocation(latitude: latitude, longitude: longitude)
}

func getAuthorizationStatus() -> CLAuthorizationStatus {
return locationFetcher.authorizationStatus
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ final class MockUploadPostWorker: UploadPostWorkerProtocol {
return true
}

func loadVideoLocation(videoURL: URL) async -> Layover.UploadPostModels.VideoAddress? {
nil
}

}
Loading
Loading