Skip to content

Commit

Permalink
Merge pull request #564 from wakmusic/491-LyricHighlighting
Browse files Browse the repository at this point in the history
πŸ”€ :: (#491) 가사 ν•˜μ΄λΌμ΄νŒ… κ΅¬ν˜„
  • Loading branch information
KangTaeHoon authored Jun 10, 2024
2 parents 129d133 + df2342a commit a0130f5
Show file tree
Hide file tree
Showing 57 changed files with 1,531 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,11 @@ public extension TargetDependency.SPM {

// MARK: Native SPM
static let YouTubePlayerKit = TargetDependency.package(product: "YouTubePlayerKit")
// static let Amplify = TargetDependency.package(product: "Amplify")
// static let AWSCognitoAuthPlugin = TargetDependency.package(product: "AWSCognitoAuthPlugin")
// static let AWSS3StoragePlugin = TargetDependency.package(product:"AWSS3StoragePlugin")
}

public extension Package {
static let YouTubePlayerKit = Package.remote(
url: "https://github.com/SvenTiigi/YouTubePlayerKit.git",
requirement: .upToNextMajor(from: "1.3.1")
)

/*
static let Amplify = Package.remote(
url: "https://github.com/aws-amplify/amplify-swift.git",
requirement: .upToNextMajor(from: "2.10.0")
)
*/
}

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension ModulePaths: MicroTargetPathConvertable {

public extension ModulePaths {
enum Feature: String, MicroTargetPathConvertable {
case LyricHighlightingFeature
case MyInfoFeature
case MusicDetailFeature
case PlaylistFeature
Expand Down
10 changes: 10 additions & 0 deletions Projects/App/Sources/Application/AppComponent+Songs.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import BaseFeature
import HomeFeature
import LyricHighlightingFeature
import LyricHighlightingFeatureInterface
import SongsDomain
import SongsDomainInterface

Expand All @@ -16,6 +18,14 @@ public extension AppComponent {
NewSongsContentComponent(parent: self)
}

var lyricHighlightingFactory: any LyricHighlightingFactory {
LyricHighlightingComponent(parent: self)
}

var lyricDecoratingComponent: LyricDecoratingComponent {
LyricDecoratingComponent(parent: self)
}

var remoteSongsDataSource: any RemoteSongsDataSource {
shared {
RemoteSongsDataSourceImpl(keychain: keychain)
Expand Down
230 changes: 139 additions & 91 deletions Projects/App/Sources/Application/NeedleGenerated.swift

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Projects/App/Support/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@
<string>$(WMDOMAIN_NOTICE)</string>
<key>WMDOMAIN_PLAYLIST</key>
<string>$(WMDOMAIN_PLAYLIST)</string>
<key>WMDOMAIN_QNA</key>
<string>$(WMDOMAIN_QNA)</string>
<key>WMDOMAIN_FAQ</key>
<string>$(WMDOMAIN_FAQ)</string>
<key>WMDOMAIN_SONGS</key>
<string>$(WMDOMAIN_SONGS)</string>
<key>WMDOMAIN_USER</key>
Expand Down
4 changes: 2 additions & 2 deletions Projects/Domains/BaseDomain/Sources/WMAPI/SecretURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public func WMDOMAIN_LIKE() -> String {
return config(key: "WMDOMAIN_LIKE")
}

public func WMDOMAIN_QNA() -> String {
return config(key: "WMDOMAIN_QNA")
public func WMDOMAIN_FAQ() -> String {
return config(key: "WMDOMAIN_FAQ")
}

public func WMDOMAIN_NOTICE() -> String {
Expand Down
2 changes: 1 addition & 1 deletion Projects/Domains/BaseDomain/Sources/WMAPI/WMAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extension WMDomain {
case .naver:
return "/v1/nid/me"
case .faq:
return WMDOMAIN_QNA()
return WMDOMAIN_FAQ()
case .notice:
return WMDOMAIN_NOTICE()
case .app:
Expand Down
6 changes: 3 additions & 3 deletions Projects/Domains/FaqDomain/Interface/Entity/FaqEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ public struct FaqEntity: Equatable {
public init(
category: String,
question: String,
description: String,
answer: String,
isOpen: Bool
) {
self.category = category
self.question = question
self.description = description
self.answer = answer
self.isOpen = isOpen
}

public let category, question, description: String
public let category, question, answer: String
public var isOpen: Bool
}
4 changes: 2 additions & 2 deletions Projects/Domains/FaqDomain/Sources/API/FaqAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ public enum FaqAPI {

extension FaqAPI: WMAPI {
public var domain: WMDomain {
.faq
return .faq
}

public var urlPath: String {
switch self {
case .fetchFaqCategories:
return "/categories"
case .fetchFaq:
return ""
return "/list"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import FaqDomainInterface
import Foundation

public struct FaqResponseDTO: Decodable {
public let question, description: String
public let question, answer: String
public let category: String

private enum CodingKeys: String, CodingKey {
case question, description, category
case question, answer, category
}
}

Expand All @@ -23,7 +23,7 @@ public extension FaqResponseDTO {
FaqEntity(
category: category,
question: question,
description: description,
answer: answer,
isOpen: false
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public struct LyricsEntity: Equatable {
}

public let text: String
public var isHighlighting: Bool = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import UIKit

@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let viewController = UIViewController()
viewController.view.backgroundColor = .yellow
window?.rootViewController = viewController
window?.makeKeyAndVisible()

return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import UIKit

public protocol LyricHighlightingFactory {
func makeView(model: LyricHighlightingRequiredModel) -> UIViewController
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

public struct LyricHighlightingRequiredModel {
public let songID: String
public let title: String
public let artist: String
public let highlightingItems: [String]

/// 가사 ν•˜μ΄λΌμ΄νŒ… 화면에 μ£Όμž…ν•΄μ€˜μ•Όν•  ν•„μˆ˜ μ•„μ΄ν…œ
public init(
songID: String, // λ…Έλž˜ 고유 ID
title: String, // λ…Έλž˜ 제λͺ©
artist: String, // μ•„ν‹°μŠ€νŠΈ 이름
highlightingItems: [String] = [] // λ°μ½”λ ˆμ΄νŒ… ν™”λ©΄μœΌλ‘œ λ„˜μ–΄κ°ˆ λ•Œλ§Œ λ„£μœΌλ©΄λ©λ‹ˆλ‹€.
) {
self.songID = songID
self.title = title
self.artist = artist
self.highlightingItems = highlightingItems
}
}
30 changes: 30 additions & 0 deletions Projects/Features/LyricHighlightingFeature/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import DependencyPlugin
import ProjectDescription
import ProjectDescriptionHelpers

let project = Project.module(
name: ModulePaths.Feature.LyricHighlightingFeature.rawValue,
targets: [
.interface(
module: .feature(.LyricHighlightingFeature)
),
.implements(
module: .feature(.LyricHighlightingFeature),
dependencies: [
.feature(target: .BaseFeature),
.feature(target: .LyricHighlightingFeature, type: .interface),
.domain(target: .SongsDomain, type: .interface)
]
),
.tests(
module: .feature(.LyricHighlightingFeature),
dependencies: [.feature(target: .LyricHighlightingFeature)]
),
.demo(
module: .feature(.LyricHighlightingFeature),
dependencies: [
.feature(target: .LyricHighlightingFeature)
]
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation
import LyricHighlightingFeatureInterface
import NeedleFoundation

public protocol LyricDecoratingDependency: Dependency {}

public final class LyricDecoratingComponent: Component<LyricDecoratingDependency> {
public func makeView(model: LyricHighlightingRequiredModel) -> LyricDecoratingViewController {
let viewModel = LyricDecoratingViewModel(model: model)
return LyricDecoratingViewController(viewModel: viewModel)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation
import LyricHighlightingFeatureInterface
import NeedleFoundation
import SongsDomainInterface
import UIKit

public protocol LyricHighlightingDependency: Dependency {
var fetchLyricsUseCase: any FetchLyricsUseCase { get }
var lyricDecoratingComponent: LyricDecoratingComponent { get }
var lyricHighlightingFactory: any LyricHighlightingFactory { get }
}

public final class LyricHighlightingComponent: Component<LyricHighlightingDependency>, LyricHighlightingFactory {
public func makeView(model: LyricHighlightingRequiredModel) -> UIViewController {
let viewModel = LyricHighlightingViewModel(model: model, fetchLyricsUseCase: dependency.fetchLyricsUseCase)
return LyricHighlightingViewController(
viewModel: viewModel,
lyricDecoratingComponent: dependency.lyricDecoratingComponent
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import DesignSystem
import Foundation
import RxCocoa
import RxSwift
import UIKit

extension LyricDecoratingViewController {
func inputBind() {
input.fetchBackgroundImage.onNext(())

collectionView.rx
.setDelegate(self)
.disposed(by: disposeBag)

collectionView.rx.itemSelected
.do(onNext: { [collectionView] indexPath in
collectionView.scrollToItem(
at: indexPath,
at: .centeredHorizontally,
animated: true
)
})
.bind(to: input.didTapBackground)
.disposed(by: disposeBag)

backButton.rx.tap
.bind(with: self) { owner, _ in
owner.navigationController?.popViewController(animated: true)
}
.disposed(by: disposeBag)

saveButton.rx.tap
.bind(with: self) { owner, _ in
let activityViewController = UIActivityViewController(
activityItems: [owner.decorateShareContentView.asImage(size: .init(width: 960, height: 960))],
applicationActivities: nil
)
activityViewController.popoverPresentationController?.sourceRect = CGRect(
x: owner.view.bounds.midX,
y: owner.view.bounds.midY,
width: 0,
height: 0
)
activityViewController.popoverPresentationController?.permittedArrowDirections = []
owner.present(activityViewController, animated: true, completion: nil)
}
.disposed(by: disposeBag)
}

func outputBind() {
output.updateSongTitle
.bind(with: self, onNext: { owner, song in
owner.songTitleLabel.text = song
owner.songTitleLabel.setTextWithAttributes(kernValue: -0.5, alignment: .center)
})
.disposed(by: disposeBag)

output.updateArtist
.bind(with: self, onNext: { owner, artist in
owner.artistLabel.text = artist
owner.artistLabel.setTextWithAttributes(kernValue: -0.5, alignment: .center)
})
.disposed(by: disposeBag)

output.dataSource
.skip(1)
.do(onNext: { [decorateImageView, indicator] entities in
decorateImageView.backgroundColor = entities.filter { $0.isSelected }.first?
.imageColor ?? DesignSystemAsset.PrimaryColorV2.point.color
indicator.stopAnimating()
})
.bind(to: collectionView.rx.items) { collectionView, index, model in
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "\(LyricDecoratingCell.self)",
for: IndexPath(item: index, section: 0)
) as? LyricDecoratingCell else {
return UICollectionViewCell()
}
cell.update(model: model, index: index)
return cell
}
.disposed(by: disposeBag)

output.highlightingItems
.filter { !$0.isEmpty }
.map { lyric in
let style = NSMutableParagraphStyle()
style.alignment = .center
style.lineSpacing = 8
let attributedString = NSMutableAttributedString(
string: lyric,
attributes: [
.font: DesignSystemFontFamily.Pretendard.medium.font(size: 18),
.foregroundColor: UIColor.white,
.kern: -0.5,
.paragraphStyle: style
]
)
return attributedString
}
.bind(to: highlightingLyricLabel.rx.attributedText)
.disposed(by: disposeBag)
}
}
Loading

0 comments on commit a0130f5

Please sign in to comment.