Skip to content

Commit bd43405

Browse files
committed
feat: implement paste feature to search and gif preview
1 parent f181fdb commit bd43405

File tree

9 files changed

+129
-44
lines changed

9 files changed

+129
-44
lines changed

Giffy/Features/Detail/DetailReducer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ public struct DetailReducer {
3434
public struct State: Equatable {
3535
var items: [Giffy]
3636
var item: Giffy
37+
var isPasted: Bool = false
3738
init(items: [Giffy]) {
3839
self.items = items
3940
self.item = items.first(where: \.isHighlighted)!
41+
self.isPasted = item.image.data != nil
4042
}
4143

4244
var isFavorited: Bool = false
@@ -103,7 +105,7 @@ public struct DetailReducer {
103105

104106
case .failedShare:
105107
state.isLoading = false
106-
Toaster.error(message: "Can't share the GIF").show()
108+
Toaster.error(message: Localizable.toastFailedShare.tr()).show()
107109
return .none
108110

109111
case .success(let isFavorited):

Giffy/Features/Detail/DetailView.swift

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,32 @@ struct DetailView: View {
3131
let mainFrame = geometry.frame(in: .global)
3232
let imageWidth = CGFloat(item.image.width) * 1.5
3333
let imageHeight = CGFloat(item.image.height) * 1.5
34+
let fullWidth = UIScreen.main.bounds.width - 16
35+
let fullHeight = UIScreen.main.bounds.height - 16
3436

3537
ScrollView(showsIndicators: false) {
3638
ZStack {
3739
AnimatedGradientBackground()
3840
.frame(width: mainFrame.width, height: mainFrame.height)
3941
.cornerRadius(40)
4042

41-
GIFView(
42-
url: URL(string: item.image.url),
43-
contentMode: .fit
44-
)
43+
Group {
44+
if let data = item.image.data {
45+
GIFView(
46+
data: data,
47+
contentMode: .fill
48+
)
49+
} else {
50+
GIFView(
51+
url: URL(string: item.image.url),
52+
contentMode: .fit
53+
)
54+
}
55+
}
4556
.scaledToFill()
4657
.frame(
47-
width: min(imageWidth, UIScreen.main.bounds.width - 16),
48-
height: min(imageHeight, UIScreen.main.bounds.height - 30)
58+
width: item.image.data != nil ? fullWidth : min(imageWidth, fullWidth),
59+
height: item.image.data != nil ? fullWidth : min(imageHeight, fullHeight)
4960
)
5061
.clipShape(.rect(cornerRadius: 20))
5162
}
@@ -108,45 +119,47 @@ struct DetailView: View {
108119
)
109120
}
110121

111-
ToolbarItem(placement: .navigationBarTrailing) {
112-
if viewStore.state.isLoading {
113-
ProgressView()
114-
.tint(.Theme.green)
115-
.font(.system(size: 80))
116-
.frame(width: 10, height: 10)
117-
} else {
122+
if !viewStore.isPasted {
123+
ToolbarItem(placement: .navigationBarTrailing) {
124+
if viewStore.state.isLoading {
125+
ProgressView()
126+
.tint(.Theme.green)
127+
.font(.system(size: 80))
128+
.frame(width: 10, height: 10)
129+
} else {
130+
IconButton(
131+
iconName: viewStore.state.shareImage != nil ? "doc.on.clipboard.fill" : "doc.on.clipboard",
132+
tint: .Theme.green,
133+
size: 15,
134+
onClick: {
135+
viewStore.send(.prepareShare)
136+
}
137+
)
138+
.tapScaleEffect()
139+
}
140+
}
141+
142+
ToolbarItem(placement: .navigationBarTrailing) {
118143
IconButton(
119-
iconName: viewStore.state.shareImage != nil ? "doc.on.clipboard.fill" : "doc.on.clipboard",
120-
tint: .Theme.green,
121-
size: 15,
144+
iconName: viewStore.state.isFavorited ? "heart.fill" : "heart",
145+
tint: .Theme.blueSky,
122146
onClick: {
123-
viewStore.send(.prepareShare)
147+
if viewStore.state.isFavorited {
148+
viewStore.send(.removeFavorite)
149+
} else {
150+
viewStore.send(.addFavorite)
151+
viewStore.send(.startLiveActivity(viewStore.state))
152+
153+
let middleX: CGFloat = CGFloat((window?.screen.bounds.width ?? 0.0) / 2)
154+
let middleY: CGFloat = CGFloat((window?.screen.bounds.height ?? 0.0) / 2)
155+
viewStore.send(.displayHeart(location: .init(x: middleX, y: middleY)))
156+
}
124157
}
125158
)
159+
.animation(.easeInOut(duration: 0.2), value: viewStore.state.isFavorited)
126160
.tapScaleEffect()
127161
}
128162
}
129-
130-
ToolbarItem(placement: .navigationBarTrailing) {
131-
IconButton(
132-
iconName: viewStore.state.isFavorited ? "heart.fill" : "heart",
133-
tint: .Theme.blueSky,
134-
onClick: {
135-
if viewStore.state.isFavorited {
136-
viewStore.send(.removeFavorite)
137-
} else {
138-
viewStore.send(.addFavorite)
139-
viewStore.send(.startLiveActivity(viewStore.state))
140-
141-
let middleX: CGFloat = CGFloat((window?.screen.bounds.width ?? 0.0) / 2)
142-
let middleY: CGFloat = CGFloat((window?.screen.bounds.height ?? 0.0) / 2)
143-
viewStore.send(.displayHeart(location: .init(x: middleX, y: middleY)))
144-
}
145-
}
146-
)
147-
.animation(.easeInOut(duration: 0.2), value: viewStore.state.isFavorited)
148-
.tapScaleEffect()
149-
}
150163
}
151164
}
152165
}

Giffy/Features/Search/SearchField.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
import SwiftUI
99
import CommonUI
10+
import UniformTypeIdentifiers
1011

1112
struct SearchField: View {
1213

1314
@State private var query = ""
1415
var initialQuery: String = ""
1516
var onQueryChange: ((String) -> Void)?
16-
17+
var onPaste: ((String?, Data?) -> Void)?
18+
1719
var body: some View {
1820
VStack(alignment: .leading) {
1921
HStack {
@@ -47,6 +49,34 @@ struct SearchField: View {
4749
}.padding(.trailing, 12)
4850
}
4951

52+
Button(action: {
53+
if let gifData = UIPasteboard.general.data(forPasteboardType: UTType.gif.identifier) {
54+
onPaste?(nil, gifData)
55+
} else if let string = UIPasteboard.general.string {
56+
onPaste?(string, nil)
57+
query = string
58+
} else {
59+
Toaster.error(message: Localizable.labelFailedCopy.tr()).show()
60+
}
61+
}) {
62+
Image(systemName: "doc.on.clipboard")
63+
.resizable()
64+
.foregroundColor(.white)
65+
.frame(width: 20, height: 20)
66+
.background(
67+
LinearGradient(
68+
gradient: Gradient(
69+
colors: [.Theme.green, .Theme.blueSky]
70+
),
71+
startPoint: .bottomTrailing,
72+
endPoint: .topLeading
73+
)
74+
.frame(width: 40, height: 40)
75+
.cornerRadius(5, corners: [.topLeft, .bottomLeft])
76+
)
77+
.padding(.trailing, 10)
78+
}
79+
5080
Button(action: {
5181
onQueryChange?(query)
5282
}) {

Giffy/Features/Search/SearchReducer.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public struct SearchReducer {
7171

7272
public enum Action {
7373
case initialFetch
74+
case onGIFPasted(data: Data?)
7475
case fetch(request: String)
7576
case success(response: [Giffy])
7677
case failed(error: Error)
@@ -86,6 +87,16 @@ public struct SearchReducer {
8687
return .run { send in
8788
await send(.fetch(request: greetingText))
8889
}
90+
91+
case let .onGIFPasted(data):
92+
let giffy = Giffy(
93+
id: UUID().uuidString,
94+
image: .init(data: data, height: "", width: ""),
95+
isFavorite: true,
96+
isHighlighted: true
97+
)
98+
router.present(.detail(items: [giffy]))
99+
return .none
89100

90101
case .fetch(let query):
91102
state.searchText = query

Giffy/Features/Search/SearchView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ struct SearchView: View {
2222
SearchField(initialQuery: tabState.searchQuery) { query in
2323
tabState.searchQuery = query
2424
viewStore.send(.fetch(request: query))
25+
} onPaste: { string, gifData in
26+
if let string {
27+
tabState.searchQuery = string
28+
viewStore.send(.fetch(request: string))
29+
} else if let gifData {
30+
viewStore.send(.onGIFPasted(data: gifData))
31+
}
2532
}.padding(.horizontal, 16)
2633

2734
if !viewStore.state.isLoading {

Modules/Common/Common/Sources/Common/Domain/Model/Giffy.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// Giphy.swift
3-
//
3+
//
44
//
55
// Created by Uwais Alqadri on 10/17/21.
66
//
@@ -49,17 +49,33 @@ public struct Giffy: Equatable, Hashable {
4949
}
5050

5151
public func setFavorite(_ state: Bool) -> Giffy {
52-
return Giffy(id: id, url: url, rating: rating, username: username, title: title, trendingDateTime: trendingDateTime, image: image, isFavorite: state)
52+
return Giffy(
53+
id: id,
54+
url: url,
55+
rating: rating,
56+
username: username,
57+
title: title,
58+
trendingDateTime: trendingDateTime,
59+
image: image,
60+
isFavorite: state
61+
)
5362
}
5463
}
5564

5665
public struct ImageOriginal {
5766
public var url: String
67+
public var data: Data?
5868
public var height: String
5969
public var width: String
6070

61-
public init(url: String = "", height: String = "", width: String = "") {
71+
public init(
72+
url: String = "",
73+
data: Data? = nil,
74+
height: String = "",
75+
width: String = ""
76+
) {
6277
self.url = url
78+
self.data = data
6379
self.height = height
6480
self.width = width
6581
}

Modules/CommonUI/CommonUI/Sources/CommonUI/Assets/en.lproj/Localizable.strings

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"trending.title" = "Trending";
1313
"detail.title" = "Detail";
1414
"trending.today_popular" = "Today's Trending";
15-
"search.placeholder" = "Search all GIFs and Stickers";
15+
"search.placeholder" = "Search or Paste GIFs";
1616
"search.loading" = "Searching for the GIFs... Maybe you should try a different keyword";
1717
"favorite.empty_message" = "Favorite is empty!";
1818
"trending.date" = "Trending Date";
@@ -21,4 +21,6 @@
2121
"button.delete" = "Delete";
2222
"error.favorite_check" = "Can't determine if the GIF is favorited";
2323
"toast.copied" = "Copied";
24+
"toast.failed_copy" = "Failed to Copy";
2425
"share.option" = "Share via";
26+
"toast.failed_share" = "Can't share the GIF";

Modules/CommonUI/CommonUI/Sources/CommonUI/Assets/id.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@
2121
"button.delete" = "Hapus";
2222
"error.favorite_check" = "Tidak dapat memastikan apakah GIF difavoritkan";
2323
"toast.copied" = "Disalin";
24+
"toast.failed_copy" = "Gagal Disalin";
2425
"share.option" = "Kirim melalui";
26+
"toast.failed_share" = "Can't share the GIF";

Modules/CommonUI/CommonUI/Sources/CommonUI/Utils/Localizable.generated.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ public enum Localizable: String {
2020
case labelSearching = "search.loading"
2121
case titleBackgroundRemoval = "common.background_removal"
2222
case labelCopied = "toast.copied"
23+
case labelFailedCopy = "toast.failed_copy"
2324
case labelShareVia = "share.option"
2425
case actionSelectPhotoLibrary = "modal.select_photo_library"
2526
case actionDelete = "button.delete"
2627
case errorCheckFavorite = "error.favorite_check"
28+
case toastFailedShare = "toast.failed_share"
2729

2830
public func tr() -> String {
2931
localized

0 commit comments

Comments
 (0)