Skip to content

Commit

Permalink
Merge pull request #23 from shinrenpan/develop
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
shinrenpan authored Sep 24, 2024
2 parents 9072a5e + 9fefbc3 commit bf0e4fc
Show file tree
Hide file tree
Showing 38 changed files with 1,138 additions and 1,033 deletions.
9 changes: 9 additions & 0 deletions Sources/App/TabBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ protocol ScrollToTopable: UIViewController {
}

final class TabBarController: UITabBarController {
init() {
super.init(nibName: nil, bundle: nil)
traitOverrides.horizontalSizeClass = .compact
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let sameTab = selectedViewController?.tabBarItem == item

Expand Down
2 changes: 1 addition & 1 deletion Sources/Extensions/ParserConfiguration+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension ParserConfiguration {
)
}

static func makeParseImages(comic: Comic, episode: Comic.Episode) -> Self {
static func images(comic: Comic, episode: Comic.Episode) -> Self {
let uri = "https://tw.manhuagui.com/comic/\(comic.id)/\(episode.id).html"
let urlComponents = URLComponents(string: uri)!

Expand Down
62 changes: 62 additions & 0 deletions Sources/MVVVR/Detail/DetailModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// DetailModel.swift
//
// Created by Shinren Pan on 2024/5/22.
//

import UIKit

enum DetailModel {
typealias DataSource = UICollectionViewDiffableDataSource<Int, Episode>
typealias Snapshot = NSDiffableDataSourceSnapshot<Int, Episode>
typealias CellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Episode>
}

// MARK: - Action

extension DetailModel {
enum Action {
case loadCache
case loadRemote
case tapFavorite
}
}

// MARK: - State

extension DetailModel {
enum State {
case none
case cacheLoaded(response: CacheLoadedResponse)
case remoteLoaded(response: RemoteLoadedResponse)
case favoriteUpdated(response: FavoriteUpdatedResponse)
}

struct CacheLoadedResponse {
let comic: Comic
let episodes: [Episode]
}

struct RemoteLoadedResponse {
let comic: Comic
let episodes: [Episode]
}

struct FavoriteUpdatedResponse {
let comic: Comic
}
}

// MARK: - Models

extension DetailModel {
final class Episode: NSObject {
let data: Comic.Episode
let selected: Bool

init(data: Comic.Episode, selected: Bool) {
self.data = data
self.selected = selected
}
}
}
65 changes: 0 additions & 65 deletions Sources/MVVVR/Detail/DetailModels.swift

This file was deleted.

123 changes: 66 additions & 57 deletions Sources/MVVVR/Detail/DetailVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ final class DetailVC: UIViewController {
let vm: DetailVM
let router = DetailRouter()
var binding: Set<AnyCancellable> = .init()
lazy var dataSource = makeDataSource()
var firstInit = true

lazy var dataSource = makeDataSource()

init(comic: Comic) {
self.vm = .init(comic: comic)
super.init(nibName: nil, bundle: nil)
Expand All @@ -34,7 +34,6 @@ final class DetailVC: UIViewController {

override func viewIsAppearing(_ animated: Bool) {
super.viewIsAppearing(animated)
stateFavoriteUpdated()
vm.doAction(.loadCache)
}
}
Expand All @@ -59,12 +58,12 @@ private extension DetailVC {
switch state {
case .none:
stateNone()
case let .cacheLoaded(episodes):
stateCacheLoaded(episodes: episodes)
case let .remoteLoaded(episodes):
stateRemoteLoaded(episodes: episodes)
case .favoriteUpdated:
stateFavoriteUpdated()
case let .cacheLoaded(response):
stateCacheLoaded(response: response)
case let .remoteLoaded(response):
stateRemoteLoaded(response: response)
case let .favoriteUpdated(response):
stateFavoriteUpdated(response: response)
}
}.store(in: &binding)
}
Expand Down Expand Up @@ -92,67 +91,53 @@ private extension DetailVC {

func stateNone() {}

func stateCacheLoaded(episodes: [DetailModels.DisplayEpisode]) {
vo.header.reloadUI(comic: vm.model.comic)

var snapshot = DetailModels.Snapshot()
snapshot.appendSections([.main])
snapshot.appendItems(episodes, toSection: .main)

func stateCacheLoaded(response: DetailModel.CacheLoadedResponse) {
vo.reloadHeader(comic: response.comic)
vo.reloadFavoriteUI(comic: response.comic)

let episodes = response.episodes
var snapshot = DetailModel.Snapshot()
snapshot.appendSections([0])
snapshot.appendItems(episodes, toSection: 0)

dataSource.apply(snapshot) { [weak self] in
guard let self else { return }

if firstInit {
firstInit = false
LoadingView.show()
vm.doAction(.loadRemote)
}
else {
updateWatchedUI()
}
updateAfterCacheLoaded()
}
}

func stateRemoteLoaded(episodes: [DetailModels.DisplayEpisode]) {
func stateRemoteLoaded(response: DetailModel.RemoteLoadedResponse) {
LoadingView.hide()

vo.header.reloadUI(comic: vm.model.comic)

var snapshot = DetailModels.Snapshot()
snapshot.appendSections([.main])
snapshot.appendItems(episodes, toSection: .main)
vo.reloadHeader(comic: response.comic)
vo.reloadFavoriteUI(comic: response.comic)

let episodes = response.episodes
var snapshot = DetailModel.Snapshot()
snapshot.appendSections([0])
snapshot.appendItems(episodes, toSection: 0)

dataSource.apply(snapshot) { [weak self] in
guard let self else { return }

if episodes.isEmpty {
var content = Self.makeError()
content.buttonProperties.primaryAction = makeReloadAction()
contentUnavailableConfiguration = content
}
else {
contentUnavailableConfiguration = nil
updateWatchedUI()
}
updateAfterRemoveLoaded(isEmpty: episodes.isEmpty)
}
}

func stateFavoriteUpdated() {
let imgNamed = vm.model.comic.favorited ? "star.fill" : "star"
let image = UIImage(systemName: imgNamed)
vo.favoriteItem.image = image
func stateFavoriteUpdated(response: DetailModel.FavoriteUpdatedResponse) {
vo.reloadFavoriteUI(comic: response.comic)
}

func makeCell() -> DetailModels.CellRegistration {
.init { cell, _, item in
// MARK: - Make Something

func makeCell() -> DetailModel.CellRegistration {
.init { cell, _, episode in
var config = UIListContentConfiguration.cell()
config.text = item.data.title
config.text = episode.data.title
cell.contentConfiguration = config
cell.accessories = item.selected ? [.checkmark()] : []
cell.accessories = episode.selected ? [.checkmark()] : []
}
}

func makeDataSource() -> DetailModels.DataSource {
func makeDataSource() -> DetailModel.DataSource {
let cell = makeCell()

return .init(collectionView: vo.list) { collectionView, indexPath, itemIdentifier in
Expand All @@ -175,15 +160,39 @@ private extension DetailVC {

// MARK: - Update Something

func updateWatchedUI() {
func updateAfterCacheLoaded() {
if firstInit {
firstInit = false
LoadingView.show()
vm.doAction(.loadRemote)
}
else {
vo.scrollListToWatched(indexPath: getWatchedIndexPath())
}
}

func updateAfterRemoveLoaded(isEmpty: Bool) {
if isEmpty {
var content = Self.makeError()
content.buttonProperties.primaryAction = makeReloadAction()
contentUnavailableConfiguration = content
}
else {
contentUnavailableConfiguration = nil
vo.scrollListToWatched(indexPath: getWatchedIndexPath())
}
}

// MARK: - Get Something

func getWatchedIndexPath() -> IndexPath? {
let items = dataSource.snapshot().itemIdentifiers

guard let row = items.firstIndex(where: { $0.selected }) else {
return
guard let index = items.firstIndex(where: { $0.selected }) else {
return nil
}

let indexPath = IndexPath(item: row, section: 0)
vo.list.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
return .init(item: index, section: 0)
}
}

Expand All @@ -195,6 +204,6 @@ extension DetailVC: UICollectionViewDelegate {
return
}

router.toReader(comic: vm.model.comic, episode: episode.data)
router.toReader(comic: vm.comic, episode: episode.data)
}
}
Loading

0 comments on commit bf0e4fc

Please sign in to comment.