Skip to content

Commit

Permalink
Merge pull request #70 from mash-up-kr/feature/#68-app-update-version
Browse files Browse the repository at this point in the history
[FEAT] 앱 업데이트 기능 연결, 스켈레톤 적용
  • Loading branch information
hryeong66 authored Aug 22, 2024
2 parents 8eec5f1 + f81c147 commit 32e507e
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 37 deletions.
6 changes: 4 additions & 2 deletions Projects/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ let project = Project.configure(
"CFBundleDevelopmentRegion": "ko_KR",
"CFBundleShortVersionString": "1.1.1",
"CFBundleVersion": "1",
"UILaunchStoryboardName": "launch"
"UILaunchStoryboardName": "launch",
"UIUserInterfaceStyle": "Light" // 다크모드 방지
]),
sources: "Sources/**",
resources: "Resources/**",
Expand Down Expand Up @@ -54,7 +55,8 @@ let project = Project.configure(
product: .app,
bundleId: "ppac.farmeme.App",
infoPlist: .extendingDefault(with: [
"UILaunchStoryboardName": "launch"
"UILaunchStoryboardName": "launch",
"UIUserInterfaceStyle": "Light" // 다크모드 방지
]),
sources: "Sources/**",
resources: "Resources/**",
Expand Down
9 changes: 5 additions & 4 deletions Projects/Core/DesignSystem/Sources/MainTab/MainTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ public struct TabItemView: View {
.padding(40)
}

var needPlayLottieView: Bool {
return !isAnimationFinished && isSelected
}
// var needPlayLottieView: Bool {
// return !isAnimationFinished && isSelected
// }

var tabItemImageView: some View {
needPlayLottieView ? tabLottieView : tabImageView
//needPlayLottieView ? tabLottieView : tabImageView
tabImageView
}

var tabLottieView: AnyView {
Expand Down
27 changes: 26 additions & 1 deletion Projects/Core/DesignSystem/Sources/View/Meme/MemeImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
//

import SwiftUI
import ResourceKit
import Kingfisher
import SkeletonUI

public struct MemeImageView: View {

// MARK: - Properties

private let imageUrlString: String

@State private var isImageLoaded: Bool = false
// MARK: - Initializers

public init(imageUrlString: String) {
Expand All @@ -24,6 +26,14 @@ public struct MemeImageView: View {

public var body: some View {
KFImage(URL(string: imageUrlString))
.placeholder {
skeletonView
}
.onSuccess { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
isImageLoaded = true
}
}
.resizable()
.loadDiskFileSynchronously()
.cacheMemoryOnly()
Expand All @@ -32,4 +42,19 @@ public struct MemeImageView: View {
.aspectRatio(0.9375, contentMode: .fit)
.cornerRadius(10)
}

var skeletonView: some View {
EmptyView()
.skeleton(
with: !isImageLoaded,
animation: .linear(duration: 2, delay: 0, speed: 1),
appearance: .gradient(
.linear,
color: Color.Skeleton.secondary,
background: Color.Skeleton.primary,
radius: 1
),
shape: .rounded(.radius(12))
)
}
}
31 changes: 30 additions & 1 deletion Projects/Core/DesignSystem/Sources/View/Meme/MemeItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SwiftUI
import ResourceKit
import PPACModels
import Kingfisher
import SkeletonUI

public struct MemeItemView: View {
private let memeDetail: MemeDetail
Expand All @@ -31,6 +32,7 @@ public struct MemeItemView: View {
.onTapGesture {
memeClickHandler?(memeDetail)
}

MemeItemInfoView(memeName: memeDetail.title, reaction: memeDetail.reaction)
}
}
Expand All @@ -40,6 +42,7 @@ struct MemeItemViewWithButton: View {
@State private var imageHeight: CGFloat = .zero
private let memeCopyHandler: ((MemeDetail) -> ())?
private let memeDetail: MemeDetail
@State private var isImageLoaded: Bool = false

init(memeDetail: MemeDetail, memeCopyHandler: ((MemeDetail) -> ())?) {
self.memeDetail = memeDetail
Expand Down Expand Up @@ -74,15 +77,17 @@ struct MemeItemViewWithButton: View {
struct ResizableMemeImageView: View {
let imageUrlString: String
@Binding var imageHeight: CGFloat
@State private var isImageLoaded: Bool = false

var body: some View {
GeometryReader { geometry in
VStack {
ZStack {
KFImage(URL(string: imageUrlString))
.resizable()
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.onSuccess { result in
guard result.image.size.width > 0 else { return }
let ratio = geometry.size.width / result.image.size.width
let newHeight = result.image.size.height * ratio
if newHeight < 80 {
Expand All @@ -92,12 +97,36 @@ struct ResizableMemeImageView: View {
} else {
imageHeight = newHeight
}
isImageLoaded = true
}
.cornerRadius(12)
.frame(height: imageHeight)
.opacity(isImageLoaded ? 1 : 0) // 이미지 로드 완료 전에 투명하게 처리

if !isImageLoaded {
skeletonView
.onAppear {
imageHeight = geometry.size.width
}
}
}
}
}

var skeletonView: some View {
EmptyView()
.skeleton(
with: !isImageLoaded,
animation: .linear(duration: 2, delay: 0, speed: 1),
appearance: .gradient(
.linear,
color: Color.Skeleton.secondary,
background: Color.Skeleton.primary,
radius: 1
),
shape: .rounded(.radius(12))
)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct SplashView: View {
.foregroundStyle(Color.Text.primary)
}
.opacity(viewModel.state.isVisible ? 1 : 0)
.animation(.easeInOut(duration: 3.0), value: viewModel.state.isVisible)
.animation(.easeInOut(duration: 0.7), value: viewModel.state.isVisible)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class SplashViewModel: ViewModelType, ObservableObject {
let userDetail = try await self.checkUserInfoUseCase.execute()
self.updateMemeLevel(to: userDetail.level)
self.state = State(isVisible: false)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { [weak self] in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.7) { [weak self] in
self?.router?.showMainTabView(userDetail: userDetail)
}
} catch(let error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,48 @@ extension MemeDetail: HorizontalMemeItemProtocol {}
struct MemeSimpleItemView: View, HorizontalMemeItemViewProtocol {
typealias Item = MemeDetail
let memeDetail: MemeDetail
@State private var isImageLoaded: Bool = false

init(item memeDetail: MemeDetail) {
self.memeDetail = memeDetail
}

var body: some View {
MemeImageView(imageUrlString: memeDetail.imageUrlString)
public var body: some View {
ZStack {
KFImage(URL(string: memeDetail.imageUrlString))
.resizable()
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.onSuccess { _ in
isImageLoaded = true
}
.aspectRatio(contentMode: .fill)
.frame(width: 120, height: 120, alignment: .center)
.cornerRadius(12)
.opacity(isImageLoaded ? 1 : 0) // 이미지 로드 완료 전에 투명하게 처리

if !isImageLoaded {
skeletonView
}
}
}

var skeletonView: some View {
EmptyView()
.skeleton(
with: !isImageLoaded,
animation: .linear(duration: 2, delay: 0, speed: 1),
appearance: .gradient(
.linear,
color: Color.Skeleton.secondary,
background: Color.Skeleton.primary,
radius: 1
),
shape: .rounded(.radius(12))
)
.frame(width: 120, height: 120, alignment: .center)
}

}

//#Preview {
Expand Down
30 changes: 28 additions & 2 deletions Projects/Features/Setting/Sources/SettingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ public struct SettingView: View {
.padding(.top, 50)

memeLogoImage
appNameAndVersion
.padding(.bottom, 50)
appDescriptionView

Divider()
.padding(.horizontal, 10)
Expand Down Expand Up @@ -69,6 +68,19 @@ public struct SettingView: View {
.padding(.top, 50)
}

var appDescriptionView: some View {
VStack {
if viewModel.state.needUpdate {
appNameAndVersion
.padding(.bottom, 16)
appUpdateButton
} else {
appNameAndVersion
}
}
.padding(.bottom, 50)
}

var appNameAndVersion: some View {
VStack {
Text("파밈")
Expand All @@ -81,6 +93,20 @@ public struct SettingView: View {
}
}

var appUpdateButton: some View {
Link(destination: viewModel.appStoreUrl) {
Text("앱 업데이트하기")
.font(Font.Body.Large.semiBold)
.foregroundStyle(Color.Text.inverse)
.padding(.vertical, 12)
.padding(.horizontal, 16)
.background {
RoundedCorners(radius: 10, corners: .allCorners)
.foregroundStyle(Color.Background.primary)
}
}
}

var settingListView: some View {
ForEach(viewModel.state.settingList) { settingType in
SettingListItemView(
Expand Down
61 changes: 38 additions & 23 deletions Projects/Features/Setting/Sources/SettingViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final public class SettingViewModel: ViewModelType, ObservableObject {

public enum Action {
case naviBackButtonTapped
case checkNeedUpdate
}

public struct State {
Expand All @@ -28,14 +29,20 @@ final public class SettingViewModel: ViewModelType, ObservableObject {
// MARK: - Properties
weak var router: SettingRouting?
@Published public var state: State
private let appId: String = "6532618484"
var appStoreUrl: URL {
return URL(string: "itms-apps://itunes.apple.com/app/\(appId)")!
}

// MARK: - Initializers
public init(router: SettingRouting? = nil) {
self.router = router
self.state = State()
self.state.needUpdate = self.checkNeedUpdate()
self.state.currnetAppVersion = self.getCurrentAppVersion()

self.state.currnetAppVersion = "v." + self.getCurrentAppVersion()
self.initSettingList()

self.dispatch(type: .checkNeedUpdate)
}

private func initSettingList() {
Expand All @@ -50,11 +57,14 @@ final public class SettingViewModel: ViewModelType, ObservableObject {
}

// MARK: - Methods
@MainActor
public func dispatch(type: Action) {
switch type {
case .naviBackButtonTapped:
router?.popView()
Task { @MainActor in
switch type {
case .naviBackButtonTapped:
router?.popView()
case .checkNeedUpdate:
self.state.needUpdate = await checkNeedUpdate()
}
}
}

Expand All @@ -63,30 +73,35 @@ final public class SettingViewModel: ViewModelType, ObservableObject {
return currentVersion ?? ""
}

private func checkNeedUpdate() -> Bool {
guard let appStoreVersion = self.getLatestVersion() else { return false }
let currentVersion = getCurrentAppVersion()
print("Setting | currentVersion = \(currentVersion)")
print("Setting | appStoreVersion = \(appStoreVersion)")

private func checkNeedUpdate() async -> Bool {
guard let appStoreVersion = await self.getLatestVersion() else { return false }
let currentVersion = self.getCurrentAppVersion()
return currentVersion != appStoreVersion
}

private func getLatestVersion() -> String? {
// guard
// let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
// let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleIdentifier)"),
// let data = try? Data(contentsOf: url),
// let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
// let results = json["results"] as? [[String: Any]], !results.isEmpty,
// let appStoreVersion = results[0]["version"] as? String else {
// return nil
// }
// return appStoreVersion
private func getLatestVersion() async -> String? {
guard let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleIdentifier)&country=kr") else {
return nil
}

do {
let (data, _) = try await URLSession.shared.data(from: url)
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let results = json["results"] as? [[String: Any]], !results.isEmpty,
let appStoreVersion = results[0]["version"] as? String {
return appStoreVersion
}
} catch {
print("Failed to fetch latest version: \(error)")
}

return nil
}

}


public struct SettingType: Identifiable {
public let id = UUID()
public let url: String
Expand Down

0 comments on commit 32e507e

Please sign in to comment.