Skip to content

Commit

Permalink
Merge pull request #223 from teamterning/Feat/#222
Browse files Browse the repository at this point in the history
[Feat] #222 - λ°°λ„ˆ API 호좜둜 λ³€κ²½ ν–ˆμŠ΅λ‹ˆλ‹€.
  • Loading branch information
thingineeer authored Nov 15, 2024
2 parents 4d64830 + 33d65d9 commit 05bc7ea
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension SearchTargetType: TargetType {
case .getMostScrapDatas:
return "/search/scraps"
case .getAdvertiseDatas:
return "/search/advertisement"
return "/search/banners"
}
}

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

// MARK: - AdvertisementsModel

struct Advertisement {
let image: UIImage
let url: String
struct AdvertisementModel: Codable {
let banners: [BannerModel]
}

struct BannerModel: Codable {
let imageUrl: String
let link: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ final class TNCalendarViewModel: ViewModelType {
let successMessageTracker = PublishSubject<String>()

let monthData = input.fetchMonthDataTrigger
.distinctUntilChanged()
.flatMapLatest { date -> Observable<[CalendarScrapModel]> in
let year = Calendar.current.component(.year, from: date)
let month = Calendar.current.component(.month, from: date)
Expand All @@ -76,6 +77,7 @@ final class TNCalendarViewModel: ViewModelType {
.asDriver(onErrorJustReturn: ())

let monthlyList = input.fetchMonthlyListTrigger
.distinctUntilChanged()
.flatMapLatest { date -> Observable<[CalendarAnnouncementModel]> in
let year = Calendar.current.component(.year, from: date)
let month = Calendar.current.component(.month, from: date)
Expand All @@ -99,6 +101,7 @@ final class TNCalendarViewModel: ViewModelType {
.asDriver(onErrorJustReturn: ())

let dailyData = input.fetchDailyDataTrigger
.distinctUntilChanged()
.flatMapLatest { date -> Observable<[AnnouncementModel]> in
let dateString = self.dateFormatter.string(from: date)
return self.calendarRepository.fetchDailyData(for: dateString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ extension AdvertisementCollectionViewCell {
// MARK: - Bind

extension AdvertisementCollectionViewCell {
func bind(with advertisement: UIImage) {
advertisementImageView.image = advertisement
func bind(with advertisement: String) {
advertisementImageView.setImage(with: advertisement)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class SearchView: UIView {

var viewsNum: [RecommendAnnouncement]?
var scrapsNum: [RecommendAnnouncement]?
var advertisements: [AdvertisementModel]?

// MARK: - UIComponents

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ final class SearchViewController: UIViewController {
private let screenWidth = UIScreen.main.bounds.width
private lazy var initialBannerCount: CGFloat = CGFloat(viewModel.advertisements.count - 2)

private let currentPageRelay = BehaviorRelay<Int>(value: 0)

// MARK: - UI Components

private let rootView = SearchView()
Expand Down Expand Up @@ -54,7 +56,6 @@ final class SearchViewController: UIViewController {
setDelegate()
setAddTarget()
bindViewModel()
setAdvertisements()
}

override func viewWillAppear(_ animated: Bool) {
Expand Down Expand Up @@ -152,6 +153,7 @@ extension SearchViewController {

private func setAdvertisements() {
// μ›λž˜ κ΄‘κ³  배열을 λ³΅μ‚¬ν•˜μ—¬ 처음과 끝에 μΆ”κ°€ (infite carousel)

if let firstAd = viewModel.advertisements.first, let lastAd = viewModel.advertisements.last {
viewModel.advertisements.insert(lastAd, at: 0)
viewModel.advertisements.append(firstAd)
Expand All @@ -175,6 +177,7 @@ extension SearchViewController {
jobDetailVC.internshipAnnouncementId.accept(internshipId)
navigationController?.pushViewController(jobDetailVC, animated: true)
}

}

// MARK: - @objc func
Expand All @@ -188,6 +191,10 @@ extension SearchViewController {
@objc
private func autoScroll() {
let collectionView = rootView.advertisementCollectionView

// 배열이 λΉ„μ–΄ μžˆμ§€ μ•Šμ€μ§€ 확인
guard !collectionView.indexPathsForVisibleItems.isEmpty else { return }

let visibleItem = collectionView.indexPathsForVisibleItems[0].item
let nextItem = visibleItem + 1
let initialAdCounts = viewModel.advertisements.count - 2
Expand All @@ -200,8 +207,9 @@ extension SearchViewController {
}
}

rootView.pageControl.currentPage = visibleItem % initialAdCounts
currentPageRelay.accept(visibleItem % initialAdCounts)
}

}

// MARK: - Bind
Expand All @@ -217,7 +225,10 @@ extension SearchViewController {

output.announcements
.drive(onNext: { [weak self] advertisements in
self?.viewModel.advertisements = advertisements
self?.rootView.pageControl.numberOfPages = advertisements.count
self?.rootView.advertisementCollectionView.reloadData()
self?.setAdvertisements()
})
.disposed(by: disposeBag)

Expand All @@ -240,6 +251,11 @@ extension SearchViewController {
self?.pushToSearchResultView()
})
.disposed(by: disposeBag)

currentPageRelay
.distinctUntilChanged()
.bind(to: rootView.pageControl.rx.currentPage)
.disposed(by: disposeBag)
}
}

Expand Down Expand Up @@ -274,7 +290,7 @@ extension SearchViewController: UICollectionViewDataSource {
return UICollectionViewCell()
}

cell.bind(with: viewModel.advertisements[indexPath.row].image)
cell.bind(with: viewModel.advertisements[indexPath.row].imageUrl)
return cell
} else {
if indexPath.section == 0 {
Expand All @@ -300,17 +316,26 @@ extension SearchViewController: UICollectionViewDataSource {
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

stopTimer()
startTimer()

if scrollView.contentOffset.x == 0 { // 첫번째 λ°°λ„ˆ κ°€ 보이면 (λ°°λ„ˆ 갯수) 번째 index의 λ°°λ„ˆκ°―μˆ˜ 으둜 μ΄λ™μ‹œν‚€κΈ°
scrollView.setContentOffset(.init(x: screenWidth * initialBannerCount, y: scrollView.contentOffset.y), animated: false)
} else if scrollView.contentOffset.x == screenWidth * (initialBannerCount + 1) { // λ§ˆμ§€λ§‰ 1이 보이면 1번째 index의 1둜 이동
scrollView.setContentOffset(.init(x: screenWidth, y: scrollView.contentOffset.y), animated: false)
}
let pageWidth = screenWidth
let maxPage = Int(initialBannerCount)
let currentOffset = scrollView.contentOffset.x
let currentPage = Int(currentOffset / pageWidth)

rootView.pageControl.currentPage = Int(scrollView.contentOffset.x / scrollView.frame.maxX) - (Int(initialBannerCount) - 1)
if currentPage == 0 {
// 첫 번째 κ°€μ§œ λ°°λ„ˆ (λ§ˆμ§€λ§‰ μ‹€μ œ λ°°λ„ˆλ‘œ 이동)
scrollView.setContentOffset(CGPoint(x: pageWidth * CGFloat(maxPage), y: 0), animated: false)
currentPageRelay.accept(maxPage - 1)
} else if currentPage == maxPage + 1 {
// λ§ˆμ§€λ§‰ κ°€μ§œ λ°°λ„ˆ (첫 번째 μ‹€μ œ λ°°λ„ˆλ‘œ 이동)
scrollView.setContentOffset(CGPoint(x: pageWidth, y: 0), animated: false)
currentPageRelay.accept(0)
} else {
// κ·Έ μ™Έμ˜ 경우
currentPageRelay.accept(currentPage - 1)
}
}
}

Expand Down Expand Up @@ -349,7 +374,7 @@ extension SearchViewController: UICollectionViewDelegate {

if collectionView == rootView.advertisementCollectionView {
let advertisement = viewModel.advertisements[indexPath.item]
if let url = URL(string: advertisement.url) {
if let url = URL(string: advertisement.link) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import UIKit
import RxSwift
import RxCocoa

import RxMoya

final class SearchViewModel: ViewModelType {

// MARK: - Properties

private let searchProvider = Providers.searchProvider

var advertisements: [Advertisement] = []
var advertisements: [BannerModel] = []

// MARK: - Input

Expand All @@ -28,7 +30,7 @@ final class SearchViewModel: ViewModelType {
// MARK: - Output

struct Output {
let announcements: Driver<[Advertisement]>
let announcements: Driver<[BannerModel]>
let recommendedByViews: Driver<[RecommendAnnouncement]>
let recommendedByScraps: Driver<[RecommendAnnouncement]>
let searchTapped: Driver<Void>
Expand All @@ -38,13 +40,13 @@ final class SearchViewModel: ViewModelType {

func transform(input: Input, disposeBag: DisposeBag) -> Output {
let announcements = input.viewDidLoad
.do(onNext: {
self.advertisements = [
Advertisement(image: .imgAd1, url: "https://www.instagram.com/terning_official/"),
Advertisement(image: .imgAd2, url: "https://forms.gle/4btEwEbUQ3JSjTKP7")
]
})
.map { self.advertisements }
.flatMapLatest { _ in
self.fetchAdveriseData()
.do(onNext: { fetchedAdvertisements in
self.advertisements = fetchedAdvertisements
})
.catchAndReturn([])
}
.asDriver(onErrorJustReturn: [])

let recommendedByViews = input.viewDidLoad
Expand Down Expand Up @@ -149,4 +151,40 @@ extension SearchViewModel {
}
}
}

private func fetchAdveriseData() -> Observable<[BannerModel]> {
return Observable.create { observer in
let request = self.searchProvider.request(.getAdvertiseDatas) { result in
switch result {
case .success(let response):
let status = response.statusCode
if 200..<300 ~= status {
do {
let responseDto = try response.map(BaseResponse<AdvertisementModel>.self)
if let banners = responseDto.result?.banners {
observer.onNext(banners)
observer.onCompleted()
} else {
print("No banner data available")
observer.onError(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "λ°°λ„ˆ 데이터 μ—†μŒ"]))
}
} catch {
print(error.localizedDescription)
observer.onError(error)
}
} else if status >= 400 {
print("Server error with status code: \(status)")
observer.onError(NSError(domain: "", code: status, userInfo: [NSLocalizedDescriptionKey: "μ—λŸ¬ μƒνƒœ μ½”λ“œ: \(status)"]))
}
case .failure(let error):
print("Network error: \(error.localizedDescription)")
observer.onError(error)
}
}

return Disposables.create {
request.cancel()
}
}
}
}

0 comments on commit 05bc7ea

Please sign in to comment.