Skip to content

Commit

Permalink
Build Character Screen Info
Browse files Browse the repository at this point in the history
  • Loading branch information
Yazan98 committed Sep 5, 2023
1 parent 4ab195c commit fb94013
Show file tree
Hide file tree
Showing 30 changed files with 1,034 additions and 11 deletions.
88 changes: 86 additions & 2 deletions RM Client.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -864,8 +864,8 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "122"
endingLineNumber = "122"
landmarkName = "GetHomeScreenItemsVerticalListUseCase"
landmarkType = "3">
landmarkName = "getLocalCharactersBySectionName(sectionName:)"
landmarkType = "7">
<Locations>
<Location
uuid = "7377CE15-EB2F-43DC-98E9-5E3832CC507A - 850788540ea34959"
Expand Down
14 changes: 14 additions & 0 deletions RM Client/Core/UseCases/RmPropsUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// RmPropsUseCase.swift
// RM Client
//
// Created by Yazan Tarifi on 05/09/2023.
//

import Foundation

public class RmPropsUseCase<Props, Result>: RmUseCase<Result> {
public func onExecute(props: Props) {
// Child Class will Fill This
}
}
File renamed without changes.
File renamed without changes.
132 changes: 131 additions & 1 deletion RM Client/Features/Character/CharacterScreenViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@

import UIKit

class CharacterScreenViewController: RmBaseVC {
class CharacterScreenViewController: RmBaseVC, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

var id: Int64 = 0
var name: String = ""
var isOtherAction: Bool = false
private let viewModel: CharacterViewModel = CharacterViewModel()

@IBOutlet weak var loadingView: UIActivityIndicatorView!
@IBOutlet weak var screenContentCollectionView: UICollectionView!
@IBOutlet weak var otherNavigationCollectionView: UICollectionView!

public static func getInstance(id: Int64, name: String) -> CharacterScreenViewController {
let vc = CharacterScreenViewController()
Expand All @@ -19,8 +25,132 @@ class CharacterScreenViewController: RmBaseVC {
return vc
}

public static func getInstance(id: Int64, name: String, isOtherAction: Bool) -> CharacterScreenViewController {
let vc = CharacterScreenViewController()
vc.id = id
vc.name = name
vc.isOtherAction = isOtherAction
return vc
}

override func onScreenStarted() {
super.onScreenStarted()
if isOtherAction {
viewModel.onPerformAction(action: CharacterAction.GetCharacterInfoByOtherCharacters(id: id))
} else {
viewModel.onPerformAction(action: CharacterAction.GetCharacterInfoAction(id: id))
}
}

override func setupListeners() {
super.setupListeners()
viewModel.screenState.onSubscribe { items in
self.onSetupScreenCollectionView()
}

viewModel.loadingState.onSubscribe { status in
self.onLoadingState(view: self.loadingView, loadingState: status)
if status {
self.screenContentCollectionView.isHidden = true
}
}

viewModel.otherNavigationState.onSubscribe { list in

}
}

private func onSetupScreenCollectionView() {
self.screenContentCollectionView?.isHidden = false
self.screenContentCollectionView?.register(
UINib(nibName: "CharacterHeaderCollectionViewCell", bundle: nil),
forCellWithReuseIdentifier: CharacterItemConsts.CHARACTER_HEADER
)

self.screenContentCollectionView?.register(
UINib(nibName: "CharacterInfoItemCollectionViewCell", bundle: nil),
forCellWithReuseIdentifier: CharacterItemConsts.CHARACTER_INFO_ITEM
)

self.screenContentCollectionView?.register(
UINib(nibName: "CharacterOtherCollectionViewCell", bundle: nil),
forCellWithReuseIdentifier: CharacterItemConsts.CHARACTER_INFO_OTHER
)

self.screenContentCollectionView?.delegate = self
self.screenContentCollectionView?.dataSource = self
self.screenContentCollectionView?.reloadData()
}

func numberOfSections(in collectionView: UICollectionView) -> Int {
return viewModel.screenState.getValue()?.count ?? 0
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}

func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
let cell = viewModel.screenState.getValue()?[indexPath.section]
switch cell?.getIdentifire() {
case CharacterItemConsts.CHARACTER_HEADER:
let cellView = collectionView.dequeueReusableCell(
withReuseIdentifier: CharacterItemConsts.CHARACTER_HEADER,
for: indexPath
) as! CharacterHeaderCollectionViewCell

cellView.configure(item: cell as! CharacterHeaderItem)
return cellView

case CharacterItemConsts.CHARACTER_INFO_ITEM:
let cellView = collectionView.dequeueReusableCell(
withReuseIdentifier: CharacterItemConsts.CHARACTER_INFO_ITEM,
for: indexPath
) as! CharacterInfoItemCollectionViewCell

cellView.configure(item: cell as! CharacterItemInfo)
return cellView

case CharacterItemConsts.CHARACTER_INFO_OTHER:
let cellView = collectionView.dequeueReusableCell(
withReuseIdentifier: CharacterItemConsts.CHARACTER_INFO_OTHER,
for: indexPath
) as! CharacterOtherCollectionViewCell

cellView.onLoadData(item: cell as! CharacterOtherItem)
return cellView

default:
let cellView = collectionView.dequeueReusableCell(
withReuseIdentifier: CharacterItemConsts.CHARACTER_HEADER,
for: indexPath
) as! CharacterHeaderCollectionViewCell

cellView.configure(item: cell as! CharacterHeaderItem)
return cellView
}
}

func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
let cell = viewModel.screenState.getValue()?[indexPath.section]
switch cell?.getIdentifire() {
case CharacterItemConsts.CHARACTER_HEADER:
return CGSize(width: UIScreen.main.bounds.width - 2, height: 340)
case CharacterItemConsts.CHARACTER_INFO_ITEM:
return CGSize(width: UIScreen.main.bounds.width - 2, height: 50)
case CharacterItemConsts.CHARACTER_INFO_OTHER:
let itemsCount = (cell as! CharacterOtherItem).list.count
return CGSize(width: Int(UIScreen.main.bounds.width) - 2, height: 50 * itemsCount)
default:
return CGSize(width: 0, height: 0)
}
}

override func getTitle() -> String {
Expand Down
62 changes: 56 additions & 6 deletions RM Client/Features/Character/CharacterScreenViewController.xib
Original file line number Diff line number Diff line change
@@ -1,22 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CharacterScreenViewController" customModuleProvider="target">
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CharacterScreenViewController" customModule="RM_Client" customModuleProvider="target">
<connections>
<outlet property="loadingView" destination="aAH-7T-1qP" id="LNJ-Rp-BQa"/>
<outlet property="otherNavigationCollectionView" destination="7ue-JW-yFO" id="daz-qn-Le3"/>
<outlet property="screenContentCollectionView" destination="elx-8c-3DB" id="xkC-bW-Mke"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="elx-8c-3DB">
<rect key="frame" x="10" y="69" width="373" height="712"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="SIi-KU-B3e">
<size key="itemSize" width="128" height="128"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
</collectionView>
<collectionView hidden="YES" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="7ue-JW-yFO">
<rect key="frame" x="10" y="781" width="373" height="47"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="47" id="N0u-hM-oCK"/>
</constraints>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="xtv-ts-fGi">
<size key="itemSize" width="128" height="128"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
</collectionView>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="aAH-7T-1qP">
<rect key="frame" x="186.66666666666666" y="416" width="20" height="20"/>
</activityIndicatorView>
</subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="7ue-JW-yFO" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" constant="10" id="241-M6-cQU"/>
<constraint firstItem="7ue-JW-yFO" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="10" id="5W7-gl-LYp"/>
<constraint firstItem="elx-8c-3DB" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="10" id="Nz6-3S-CTI"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="7ue-JW-yFO" secondAttribute="trailing" constant="10" id="Ukx-ce-w7z"/>
<constraint firstItem="aAH-7T-1qP" firstAttribute="centerY" secondItem="i5M-Pr-FkT" secondAttribute="centerY" id="VVm-Si-xXU"/>
<constraint firstItem="7ue-JW-yFO" firstAttribute="top" secondItem="elx-8c-3DB" secondAttribute="bottom" id="ZhK-hD-gqD"/>
<constraint firstItem="aAH-7T-1qP" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="gNy-4h-1u0"/>
<constraint firstItem="elx-8c-3DB" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="10" id="hxi-3e-6jE"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="elx-8c-3DB" secondAttribute="trailing" constant="10" id="oh1-88-vzo"/>
</constraints>
<point key="canvasLocation" x="110.68702290076335" y="-11.267605633802818"/>
</view>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
24 changes: 24 additions & 0 deletions RM Client/Features/Character/ViewModel/CharacterAction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// CharacterAction.swift
// RM Client
//
// Created by Yazan Tarifi on 05/09/2023.
//

import Foundation

public class CharacterAction: RmAction {
public final class GetCharacterInfoAction: CharacterAction {
let id: Int64
init(id: Int64) {
self.id = id
}
}

public final class GetCharacterInfoByOtherCharacters: CharacterAction {
let id: Int64
init(id: Int64) {
self.id = id
}
}
}
77 changes: 77 additions & 0 deletions RM Client/Features/Character/ViewModel/CharacterViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// CharacterViewModel.swift
// RM Client
//
// Created by Yazan Tarifi on 05/09/2023.
//

import Foundation
import RealmSwift

public final class CharacterViewModel: RmBaseViewModel<CharacterAction> {

private var useCaseInstance: GetCharacterInfoUseCase? = nil
let screenState: RmViewModelState<[CharacterItem]> = RmViewModelState()
let loadingState: RmViewModelState<Bool> = RmViewModelState()
let otherNavigationState: RmViewModelState<[CharacterOtherInfoItem]> = RmViewModelState()

public override func onPerformAction(action: CharacterAction) {
if action is CharacterAction.GetCharacterInfoAction {
self.getScreenContent(
id: (action as! CharacterAction.GetCharacterInfoAction).id,
action: action
)
} else if action is CharacterAction.GetCharacterInfoByOtherCharacters {
let id = (action as! CharacterAction.GetCharacterInfoByOtherCharacters).id
self.getScreenContent(
id: id,
action: action
)

self.getOtherNavigationItems(id: id)
}
}

private func getOtherNavigationItems(id: Int64) {
getDispatchQueue().async { [weak self] in
self?.otherNavigationState.onPostValue(value: self?.getUseCase().getOtherCharactersList(id: id) ?? [])
}
}

private func getScreenContent(id: Int64, action: CharacterAction) {
let screenContent = screenState.getValue() ?? []
if screenContent.isEmpty {
getUseCase().onExecute(props: self.getUseCaseProp(
id: id,
action: action)
)
}
}

private func getUseCaseProp(id: Int64, action: CharacterAction) -> GetCharacterInfoUseCaseProps {
return GetCharacterInfoUseCaseProps(
id: id,
isInfoAction: action is CharacterAction.GetCharacterInfoAction
)
}

private func getUseCase() -> GetCharacterInfoUseCase {
if useCaseInstance == nil {
useCaseInstance = GetCharacterInfoUseCase(
dispatchQueue: getDispatchQueue(),
listener: GetCharacterInfoUseCaseListener(viewModel: self)
)
}

return useCaseInstance!
}

public override func getViewModelTitle() -> String {
return "CharacterViewModel"
}

deinit {
getUseCase().onDestroyUseCase()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// CharacterHeaderCollectionViewCell.swift
// RM Client
//
// Created by Yazan Tarifi on 05/09/2023.
//

import UIKit

class CharacterHeaderCollectionViewCell: UICollectionViewCell {

@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var deacriptionView: UILabel!
@IBOutlet weak var nameView: UILabel!

public func configure(item: CharacterHeaderItem) {
DispatchQueue.main.async { [weak self] in
self?.imageView?.layer.cornerRadius = 5
self?.imageView?.clipsToBounds = true
}

self.nameView.text = item.name
self.deacriptionView.text = "This is the Long Text Desctiption Providing a Multi Lines in each Character Page info as you can see the Multiple Lines in Labels \(item.description)"

guard let imageUrl = URL(string: item.image) else { return }
imageView?.load(url: imageUrl)
}
}
Loading

0 comments on commit fb94013

Please sign in to comment.