Skip to content
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
4 changes: 4 additions & 0 deletions iOS/Layover/Layover.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
836C338B2B15D22C00ECAFB0 /* PlaybackConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 836C338A2B15D22C00ECAFB0 /* PlaybackConfigurator.swift */; };
836C33912B17629400ECAFB0 /* MapRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 836C33902B17629400ECAFB0 /* MapRouter.swift */; };
83957AA62B20F94C00B3BA8A /* MockPlaybackWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83957AA52B20F94C00B3BA8A /* MockPlaybackWorker.swift */; };
839F1DF82B62AEDA0071C622 /* LOTextLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839F1DF72B62AEDA0071C622 /* LOTextLabel.swift */; };
83C35E1B2B108C3500D8DD5C /* PlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C35E1A2B108C3500D8DD5C /* PlaybackView.swift */; };
83C35E1E2B10923C00D8DD5C /* PlaybackCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C35E1D2B10923C00D8DD5C /* PlaybackCell.swift */; };
FC0E80242B1A0BBB00EF56D6 /* UploadPostPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0E801E2B1A0BBB00EF56D6 /* UploadPostPresenter.swift */; };
Expand Down Expand Up @@ -422,6 +423,7 @@
836C33902B17629400ECAFB0 /* MapRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRouter.swift; sourceTree = "<group>"; };
836C33972B1843BE00ECAFB0 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = "<group>"; };
83957AA52B20F94C00B3BA8A /* MockPlaybackWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPlaybackWorker.swift; sourceTree = "<group>"; };
839F1DF72B62AEDA0071C622 /* LOTextLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOTextLabel.swift; sourceTree = "<group>"; };
83C35E1A2B108C3500D8DD5C /* PlaybackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackView.swift; sourceTree = "<group>"; };
83C35E1D2B10923C00D8DD5C /* PlaybackCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackCell.swift; sourceTree = "<group>"; };
FC0E801E2B1A0BBB00EF56D6 /* UploadPostPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostPresenter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1191,6 +1193,7 @@
8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */,
8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */,
8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */,
839F1DF72B62AEDA0071C622 /* LOTextLabel.swift */,
);
path = DesignSystem;
sourceTree = "<group>";
Expand Down Expand Up @@ -1561,6 +1564,7 @@
198167A42B20583D0032F563 /* SettingInteractor.swift in Sources */,
FC4E0C1D2B28977000152596 /* CurrentLocationManager.swift in Sources */,
83C35E1B2B108C3500D8DD5C /* PlaybackView.swift in Sources */,
839F1DF82B62AEDA0071C622 /* LOTextLabel.swift in Sources */,
FC4E0C192B28955400152596 /* LocationFetcher.swift in Sources */,
835A61A02B068115002F22A5 /* PlaybackModels.swift in Sources */,
FC0E80292B1A0BBB00EF56D6 /* UploadPostInteractor.swift in Sources */,
Expand Down
39 changes: 39 additions & 0 deletions iOS/Layover/Layover/DesignSystem/LOTextLabel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// LOTextLabel.swift
// Layover
//
// Created by 황지웅 on 1/25/24.
// Copyright © 2024 CodeBomber. All rights reserved.
//

import UIKit

final class LOTextLabel: UILabel {

private var padding = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)

convenience init(padding: UIEdgeInsets) {
self.init()
self.padding = padding
setUI()
}

override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: padding))
}

override var intrinsicContentSize: CGSize {
var contentSize = super.intrinsicContentSize
contentSize.height += padding.top + padding.bottom
contentSize.width += padding.left + padding.right

return contentSize
}

private func setUI() {
layer.cornerRadius = 8
layer.borderWidth = 1
layer.borderColor = UIColor.grey500.cgColor
backgroundColor = UIColor.clear
}
}
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 @@ -75,11 +76,12 @@ final class UploadPostViewController: BaseViewController {
return imageLabel
}()

private let currentAddressLabel: UILabel = {
let label = UILabel()
private let currentAddressLabel: LOTextLabel = {
let label = LOTextLabel(padding: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16))
label.font = .loFont(type: .body2)
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
label.isUserInteractionEnabled = true
return label
}()

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

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

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

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

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

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

}

extension UploadPostViewController: UITextFieldDelegate {
Expand Down Expand Up @@ -321,4 +333,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)
}
}
Loading