Skip to content

Commit

Permalink
Merge pull request #944 from fumiyasac/feature/favorite_heartmark_tra…
Browse files Browse the repository at this point in the history
…nsition
  • Loading branch information
Ryoya Ito authored Sep 9, 2024
2 parents 3f6345e + 8f53b72 commit aa38547
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 36 deletions.
53 changes: 37 additions & 16 deletions app-ios/Sources/CommonComponents/Timetable/TimetableCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@ public struct TimetableCard: View {
let timetableItem: TimetableItem
let isFavorite: Bool
let onTap: (TimetableItem) -> Void
let onTapFavorite: (TimetableItem) -> Void
let onTapFavorite: (TimetableItem, CGPoint?) -> Void

public init(
timetableItem: TimetableItem,
isFavorite: Bool,
onTap: @escaping (TimetableItem) -> Void,
onTapFavorite: @escaping (TimetableItem) -> Void
onTapFavorite: @escaping (TimetableItem, CGPoint?) -> Void
) {
self.timetableItem = timetableItem
self.isFavorite = isFavorite
self.onTap = onTap
self.onTapFavorite = onTapFavorite
}

// MEMO: Used to adjust margins by Orientation.
@Environment(\.horizontalSizeClass) var heightSizeClass
@Environment(\.verticalSizeClass) var widthSizeClass

public var body: some View {
Button {
onTap(timetableItem)
Expand All @@ -35,20 +39,29 @@ public struct TimetableCard: View {
LanguageTag(label)
}
Spacer()
Button {
onTapFavorite(timetableItem)
} label: {
Image(isFavorite ? .icFavoriteFill : .icFavoriteOutline)
.resizable()
.renderingMode(.template)
.foregroundColor(
isFavorite ?
AssetColors.Primary.primaryFixed.swiftUIColor
:
AssetColors.Surface.onSurfaceVariant.swiftUIColor
)
.frame(width: 24, height: 24)

// [NOTE] In order to calculate the value from GeometryReader, it is supported by assigning DragGesture to the Image element instead of Button.
HStack {
GeometryReader { geometry in
// MEMO: Since the coordinate values ​​are based on the inside of the View, ".local" is specified.
let localGeometry = geometry.frame(in: .local)
Image(isFavorite ? .icFavoriteFill : .icFavoriteOutline)
.resizable()
.renderingMode(.template)
.foregroundColor(
isFavorite ? AssetColors.Primary.primaryFixed.swiftUIColor : AssetColors.Surface.onSurfaceVariant.swiftUIColor
)
.frame(width: 24, height: 24)
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global).onEnded { dragGesture in
// MEMO: The offset value in the Y-axis direction is subtracted for adjustment (decided by device orientation).
let adjustedLocationPoint = CGPoint(x: dragGesture.location.x, y: dragGesture.location.y - calculateTopMarginByDevideOrietation())
onTapFavorite(timetableItem, adjustedLocationPoint)
})
// MEMO: To adjust horizontal position, I'm subtracting half the size of Image (-12).
.position(x: localGeometry.maxX - 12, y: localGeometry.midY)
}
}
.frame(height: 24, alignment: .trailing)
.sensoryFeedback(.impact, trigger: isFavorite) { _, newValue in newValue }
}

Expand Down Expand Up @@ -85,6 +98,14 @@ public struct TimetableCard: View {
.overlay(RoundedRectangle(cornerRadius: 4).stroke(AssetColors.Outline.outlineVariant.swiftUIColor, lineWidth: 1))
}
}

private func calculateTopMarginByDevideOrietation() -> CGFloat {
if widthSizeClass == .regular && heightSizeClass == .compact {
return CGFloat(128)
} else {
return CGFloat(96)
}
}
}

#Preview {
Expand All @@ -93,7 +114,7 @@ public struct TimetableCard: View {
timetableItem: TimetableItem.Session.companion.fake(),
isFavorite: true,
onTap: { _ in },
onTapFavorite: { _ in }
onTapFavorite: { _,_ in }
)
.padding(.horizontal, 16)
}
Expand Down
2 changes: 1 addition & 1 deletion app-ios/Sources/FavoriteFeature/FavoriteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public struct FavoriteView: View {
isFavorite: timetableItemWithFavorite.isFavorited
) { _ in
store.send(.view(.timetableItemTapped(timetableItemWithFavorite)))
} onTapFavorite: { _ in
} onTapFavorite: { _, _ in
store.send(.view(.toggleFavoriteTapped(timetableItem.id)))
}
}
Expand Down
2 changes: 1 addition & 1 deletion app-ios/Sources/SearchFeature/SearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct SearchView: View {
isFavorite: timetableItemWithFavorite.isFavorited
) { _ in
store.send(.view(.timetableItemTapped(timetableItemWithFavorite)))
} onTapFavorite: { _ in
} onTapFavorite: { _, _ in
store.send(.view(.toggleFavoriteTapped(timetableItem.id)))
}
}
Expand Down
106 changes: 88 additions & 18 deletions app-ios/Sources/TimetableFeature/TimetableListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,29 +81,99 @@ public struct TimetableView: View {
}
}

@MainActor
struct TimetableListView: View {
private let store: StoreOf<TimetableReducer>

init(store: StoreOf<TimetableReducer>) {
self.store = store
}

// MEMO: A variable that stores the value of Animation variation. (Only 0 or 1)
@State private var animationProgress: CGFloat = 0
// MEMO: Select target targetTimetableItemId & targetLocationPoint (for Animation).
@State private var targetTimetableItemId: TimetableItemId?
@State private var targetLocationPoint: CGPoint?

var body: some View {
ScrollView{
LazyVStack(spacing: 0) {
ForEach(store.timetableItems, id: \.self) { item in
TimeGroupMiniList(contents: item, onItemTap: { item in
store.send(.view(.timetableItemTapped(item)))
}, onFavoriteTap: {
store.send(.view(.favoriteTapped($0)))
})
}
}.scrollContentBackground(.hidden)
.onAppear {
store.send(.view(.onAppear))
}.background(AssetColors.Surface.surface.swiftUIColor)

bottomTabBarPadding
ZStack {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(store.timetableItems, id: \.self) { item in
TimeGroupMiniList(contents: item, onItemTap: { item in
store.send(.view(.timetableItemTapped(item)))
}, onFavoriteTap: { timetableItemWithFavorite, adjustedLocationPoint in

store.send(.view(.favoriteTapped(timetableItemWithFavorite)))

// MEMO: When "isFavorited" flag is false, this view executes animation.
if timetableItemWithFavorite.isFavorited == false {
toggleFavorite(timetableItem: timetableItemWithFavorite.timetableItem, adjustedLocationPoint: adjustedLocationPoint)
}
})
}
}.scrollContentBackground(.hidden)
.onAppear {
store.send(.view(.onAppear))
}.background(AssetColors.Surface.surface.swiftUIColor)
bottomTabBarPadding
}

// MEMO: Stack the Image elements that will be animated using ZStack.
makeHeartAnimationView()
}
}

@ViewBuilder
private func makeHeartAnimationView() -> some View {
GeometryReader { geometry in
if targetTimetableItemId != nil {
Image(systemName: "heart.fill")
.foregroundColor(
AssetColors.Primary.primaryFixed.swiftUIColor
)
.frame(width: 24, height: 24)
.position(animationPosition(geometry: geometry))
.opacity(1 - animationProgress)
.zIndex(99)
}
}
}

private func animationPosition(geometry: GeometryProxy) -> CGPoint {

// MEMO: Get the value calculated from both the default and .global GeometryReader.
let globalGeometrySize = geometry.frame(in: .global).size
let defaultGeometrySize = geometry.size

// MEMO: Calculate the offset value in the Y-axis direction using GeometryReader.
let startPositionY = targetLocationPoint?.y ?? 0
let endPositionY = defaultGeometrySize.height - 25
let targetY = startPositionY + (endPositionY - startPositionY) * animationProgress

// MEMO: Calculate the offset value in the X-axis direction using GeometryReader.
let adjustedPositionX = animationProgress * (globalGeometrySize.width / 2 - globalGeometrySize.width + 50)
let targetX = defaultGeometrySize.width - 50 + adjustedPositionX

return CGPoint(x: targetX, y: targetY)
}

private func toggleFavorite(timetableItem: TimetableItem, adjustedLocationPoint: CGPoint?) {

targetLocationPoint = adjustedLocationPoint
targetTimetableItemId = timetableItem.id

// MEMO: Execute animation.
if targetTimetableItemId != nil {
withAnimation(.easeOut(duration: 1)) {
animationProgress = 1
}
Task {
try await Task.sleep(nanoseconds: 1_000_000_000)
targetTimetableItemId = nil
targetLocationPoint = nil
animationProgress = 0
}
}
}
}
Expand Down Expand Up @@ -177,7 +247,7 @@ struct TimetableGridView: View {
struct TimeGroupMiniList: View {
let contents: TimetableTimeGroupItems
let onItemTap: (TimetableItemWithFavorite) -> Void
let onFavoriteTap: (TimetableItemWithFavorite) -> Void
let onFavoriteTap: (TimetableItemWithFavorite, CGPoint?) -> Void

var body: some View {
HStack(spacing: 16) {
Expand All @@ -195,8 +265,8 @@ struct TimeGroupMiniList: View {
onTap: {_ in
onItemTap(item)
},
onTapFavorite: { _ in
onFavoriteTap(item)
onTapFavorite: { _, point in
onFavoriteTap(item, point)
})
}
}
Expand Down

0 comments on commit aa38547

Please sign in to comment.