Skip to content

Commit

Permalink
Merge pull request #155 from YAPP-Github/fix/#152-fetch-url-image
Browse files Browse the repository at this point in the history
Fix/#152 ์ธ์Šคํƒ€๊ทธ๋žจ ์ด๋ฏธ์ง€ ์กฐํšŒ ๋ฌธ์ œ ์ˆ˜์ •
  • Loading branch information
ShapeKim98 authored Nov 18, 2024
2 parents b502179 + 1375cc9 commit 00a99c7
Show file tree
Hide file tree
Showing 30 changed files with 763 additions and 183 deletions.
1 change: 0 additions & 1 deletion Projects/App/ShareExtension/Sources/ShareRootFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ struct ShareRootFeature {
.ifLet(\.intro, action: \.intro) { IntroFeature() }
.ifLet(\.contentSetting, action: \.contentSetting) { ContentSettingFeature() }
.forEach(\.path, action: \.path)
._printChanges()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,14 @@ import SwiftSoup

extension SwiftSoupClient: DependencyKey {
public static let liveValue: Self = {
let provider = SwiftSoupProvider()

return Self(
parseOGTitleAndImage: { url, completion in
guard let html = try? String(contentsOf: url),
let document = try? SwiftSoup.parse(html) else {
await completion()
return (nil, nil)
}

let title = try? document.select("meta[property=og:title]").first()?.attr("content")
let imageURL = try? document.select("meta[property=og:image]").first()?.attr("content")

guard title != nil || imageURL != nil else {
var request = URLRequest(url: url)
request.setValue(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
forHTTPHeaderField: "User-Agent"
)

guard let data = try? await URLSession.shared.data(for: request).0,
let html = String(data: data, encoding: .utf8),
let document = try? SwiftSoup.parse(html) else {
return (nil, nil)
}

let title = try? document.select("meta[property=og:title]").first()?.attr("content")
let imageURL = try? document.select("meta[property=og:image]").first()?.attr("content")

await completion()

return (title, imageURL)
}

await completion()

return (title, imageURL)
parseOGTitle: { url in
try await provider.parseOGTitle(url)
},
parseOGImageURL: { url in
try await provider.parseOGImageURL(url)
}
)
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import DependenciesMacros

@DependencyClient
public struct SwiftSoupClient {
public var parseOGTitleAndImage: @Sendable (
_ url: URL,
_ completion: @Sendable () async -> Void
) async -> (String?, String?) = { _, _ in (nil , nil) }
public var parseOGTitle: @Sendable (
_ url: URL
) async throws -> String? = { _ in nil }

public var parseOGImageURL: @Sendable (
_ url: URL
) async throws -> String? = { _ in nil }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// SwiftSoupProvider.swift
// CoreKit
//
// Created by ๊น€๋„ํ˜• on 11/17/24.
//

import SwiftUI
import SwiftSoup

final class SwiftSoupProvider {
func parseOGTitle(_ url: URL) async throws -> String? {
try await parseOGMeta(url: url, type: "og:title")
}

func parseOGImageURL(_ url: URL) async throws -> String? {
try await parseOGMeta(url: url, type: "og:image")
}

func parseOGMeta(url: URL, type: String) async throws -> String? {
let html = try String(contentsOf: url)
let document = try SwiftSoup.parse(html)

if let metaData = try document.select("meta[property=\(type)]").first()?.attr("content") {
return metaData
} else {
var request = URLRequest(url: url)
request.setValue(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
forHTTPHeaderField: "User-Agent"
)

let (data, _) = try await URLSession.shared.data(for: request)
guard let html = String(data: data, encoding: .utf8) else {
return nil
}
let document = try SwiftSoup.parse(html)
let metaData = try document.select("meta[property=\(type)]").first()?.attr("content")

return metaData
}
}
}
16 changes: 8 additions & 8 deletions Projects/DSKit/Sources/Components/PokitLinkCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ public struct PokitLinkCard<Item: PokitLinkCardItem>: View {
private let link: Item
private let action: () -> Void
private let kebabAction: (() -> Void)?
private let fetchMetaData: (() -> Void)?

public init(
link: Item,
action: @escaping () -> Void,
kebabAction: (() -> Void)? = nil
kebabAction: (() -> Void)? = nil,
fetchMetaData: (() -> Void)? = nil
) {
self.link = link
self.action = action
self.kebabAction = kebabAction
self.fetchMetaData = fetchMetaData
}

public var body: some View {
Expand Down Expand Up @@ -110,18 +113,15 @@ public struct PokitLinkCard<Item: PokitLinkCardItem>: View {

@MainActor
private func thumbleNail(url: URL) -> some View {
var request = URLRequest(url: url)
request.setValue(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
forHTTPHeaderField: "User-Agent"
)

return LazyImage(request: .init(urlRequest: request)) { phase in
LazyImage(url: url) { phase in
Group {
if let image = phase.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
} else if phase.error != nil {
placeholder
.onAppear { fetchMetaData?() }
} else {
placeholder
}
Expand Down
2 changes: 2 additions & 0 deletions Projects/DSKit/Sources/Components/PokitLinkPreview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public struct PokitLinkPreview: View {
if let image = phase.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
} else {
PokitSpinner()
.foregroundStyle(.pokit(.icon(.brand)))
Expand All @@ -56,6 +57,7 @@ public struct PokitLinkPreview: View {
Spacer()
}
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.clipped()
.background {
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(.pokit(.bg(.base)))
Expand Down
2 changes: 1 addition & 1 deletion Projects/Domain/Sources/Base/BaseContentItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public struct BaseContentItem: Identifiable, Equatable, PokitLinkCardItem, Sorta
public let categoryName: String
public let categoryId: Int
public let title: String
public let thumbNail: String
public var thumbNail: String
public let data: String
public let domain: String
public let createdAt: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import Foundation

import ComposableArchitecture
import FeatureContentCard
import Domain
import CoreKit
import DSKit
Expand Down Expand Up @@ -51,16 +52,7 @@ public struct CategoryDetailFeature {
}
return identifiedArray
}
var contents: IdentifiedArrayOf<BaseContentItem>? {
guard let contentList = domain.contentList.data else {
return nil
}
var identifiedArray = IdentifiedArrayOf<BaseContentItem>()
contentList.forEach { content in
identifiedArray.append(content)
}
return identifiedArray
}
var contents: IdentifiedArrayOf<ContentCardFeature.State> = []
var kebobSelectedType: PokitDeleteBottomSheet.SheetType?
var selectedContentItem: BaseContentItem?
var shareSheetItem: BaseContentItem? = nil
Expand All @@ -73,6 +65,7 @@ public struct CategoryDetailFeature {
var hasNext: Bool {
domain.contentList.hasNext
}
var isLoading: Bool = true

public init(category: BaseCategoryItem) {
self.domain = .init(categpry: category)
Expand All @@ -86,6 +79,7 @@ public struct CategoryDetailFeature {
case async(AsyncAction)
case scope(ScopeAction)
case delegate(DelegateAction)
case contents(IdentifiedActionOf<ContentCardFeature>)

@CasePathable
public enum View: BindableAction, Equatable {
Expand Down Expand Up @@ -121,10 +115,11 @@ public struct CategoryDetailFeature {
case ํด๋ฆฝ๋ณด๋“œ_๊ฐ์ง€
}

public enum ScopeAction: Equatable {
public enum ScopeAction {
case categoryBottomSheet(PokitBottomSheet.Delegate)
case categoryDeleteBottomSheet(PokitDeleteBottomSheet.Delegate)
case filterBottomSheet(CategoryFilterSheet.Delegate)
case contents(IdentifiedActionOf<ContentCardFeature>)
}

public enum DelegateAction: Equatable {
Expand Down Expand Up @@ -163,13 +158,19 @@ public struct CategoryDetailFeature {
/// - Delegate
case .delegate(let delegateAction):
return handleDelegateAction(delegateAction, state: &state)

case .contents(let contentsAction):
return .send(.scope(.contents(contentsAction)))
}
}

/// - Reducer body
public var body: some ReducerOf<Self> {
BindingReducer(action: \.view)
Reduce(self.core)
.forEach(\.contents, action: \.contents) {
ContentCardFeature()
}
}
}
//MARK: - FeatureAction Effect
Expand All @@ -191,7 +192,7 @@ private extension CategoryDetailFeature {
case .์นดํ…Œ๊ณ ๋ฆฌ_์„ ํƒํ–ˆ์„๋•Œ(let item):
state.domain.category = item
return .run { send in
await send(.inner(.pagenation_์ดˆ๊ธฐํ™”))
await send(.inner(.pagenation_์ดˆ๊ธฐํ™”), animation: .pokitDissolve)
await send(.async(.์นดํ…Œ๊ณ ๋ฆฌ_๋‚ด_์ปจํ…์ธ _๋ชฉ๋ก_์กฐํšŒ_API))
await send(.inner(.์นดํ…Œ๊ณ ๋ฆฌ_์„ ํƒ_์‹œํŠธ_ํ™œ์„ฑํ™”(false)))
}
Expand Down Expand Up @@ -248,10 +249,17 @@ private extension CategoryDetailFeature {

case .์นดํ…Œ๊ณ ๋ฆฌ_๋‚ด_์ปจํ…์ธ _๋ชฉ๋ก_์กฐํšŒ_API_๋ฐ˜์˜(let contentList):
state.domain.contentList = contentList

var identifiedArray = IdentifiedArrayOf<ContentCardFeature.State>()
contentList.data?.forEach { identifiedArray.append(.init(content: $0)) }
state.contents = identifiedArray

state.isLoading = false
return .none

case let .์ปจํ…์ธ _์‚ญ์ œ_API_๋ฐ˜์˜(id):
state.domain.contentList.data?.removeAll { $0.id == id }
state.contents.removeAll { $0.content.id == id }
state.domain.category.contentCount -= 1
state.selectedContentItem = nil
state.isPokitDeleteSheetPresented = false
Expand All @@ -264,11 +272,15 @@ private extension CategoryDetailFeature {

state.domain.contentList = contentList
state.domain.contentList.data = list + newList
newList.forEach { state.contents.append(.init(content: $0)) }

return .none

case .pagenation_์ดˆ๊ธฐํ™”:
state.domain.pageable.page = 0
state.domain.contentList.data = nil
state.isLoading = true
state.contents.removeAll()
return .none
}
}
Expand Down Expand Up @@ -459,6 +471,15 @@ private extension CategoryDetailFeature {
.send(.async(.์นดํ…Œ๊ณ ๋ฆฌ_๋‚ด_์ปจํ…์ธ _๋ชฉ๋ก_์กฐํšŒ_API))
)
}

case let .contents(.element(id: _, action: .delegate(.์ปจํ…์ธ _ํ•ญ๋ชฉ_๋ˆŒ๋ €์„๋•Œ(content)))):
return .send(.delegate(.contentItemTapped(content)))
case let .contents(.element(id: _, action: .delegate(.์ปจํ…์ธ _ํ•ญ๋ชฉ_์ผ€๋ฐฅ_๋ฒ„ํŠผ_๋ˆŒ๋ €์„๋•Œ(content)))):
state.kebobSelectedType = .๋งํฌ์‚ญ์ œ
state.selectedContentItem = content
return .send(.inner(.์นดํ…Œ๊ณ ๋ฆฌ_์‹œํŠธ_ํ™œ์„ฑํ™”(true)))
case .contents:
return .none
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import SwiftUI

import ComposableArchitecture
import FeatureContentCard
import Domain
import DSKit
import Util
Expand Down Expand Up @@ -136,8 +137,8 @@ private extension CategoryDetailView {

var contentScrollView: some View {
Group {
if let contents = store.contents {
if contents.isEmpty {
if !store.isLoading {
if store.contents.isEmpty {
VStack {
PokitCaution(
image: .empty,
Expand All @@ -151,17 +152,17 @@ private extension CategoryDetailView {
} else {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 0) {
ForEach(contents) { content in
let isFirst = content == contents.first
let isLast = content == contents.last
ForEach(
store.scope(state: \.contents, action: \.contents)
) { store in
let isFirst = store.state.id == self.store.contents.first?.id
let isLast = store.state.id == self.store.contents.last?.id

PokitLinkCard(
link: content,
action: { send(.์ปจํ…์ธ _ํ•ญ๋ชฉ_๋ˆŒ๋ €์„๋•Œ(content)) },
kebabAction: { send(.์นดํ…Œ๊ณ ๋ฆฌ_์ผ€๋ฐฅ_๋ฒ„ํŠผ_๋ˆŒ๋ €์„๋•Œ(.๋งํฌ์‚ญ์ œ, selectedItem: content)) }
ContentCardView(
store: store,
isFirst: isFirst,
isLast: isLast
)
.divider(isFirst: isFirst, isLast: isLast)
.pokitScrollTransition(.opacity)
}

if store.hasNext {
Expand Down
Loading

0 comments on commit 00a99c7

Please sign in to comment.