Skip to content

Commit 461c9d0

Browse files
authored
Merge pull request #24 from VishwaiOSDev/feat/vishwa/download-progress-bar
feat: async progress bar
2 parents 474a960 + e014cbd commit 461c9d0

34 files changed

+672
-718
lines changed

Loadify.xcodeproj/project.pbxproj

Lines changed: 20 additions & 56 deletions
Large diffs are not rendered by default.

Loadify.xcodeproj/xcuserdata/vishweshwaran.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,84 @@
33
uuid = "3060FD4B-BC5F-4B01-9BBA-9EF176B0168D"
44
type = "1"
55
version = "2.0">
6+
<Breakpoints>
7+
<BreakpointProxy
8+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
9+
<BreakpointContent
10+
uuid = "C4EBE5E0-9EBF-4910-9F55-AA444CED162B"
11+
shouldBeEnabled = "Yes"
12+
ignoreCount = "0"
13+
continueAfterRunningActions = "No"
14+
filePath = "Loadify/Services/API/DetailsFetcher.swift"
15+
startingColumnNumber = "9223372036854775807"
16+
endingColumnNumber = "9223372036854775807"
17+
startingLineNumber = "21"
18+
endingLineNumber = "21"
19+
landmarkName = "loadDetails(for:to:)"
20+
landmarkType = "7">
21+
<Locations>
22+
<Location
23+
uuid = "C4EBE5E0-9EBF-4910-9F55-AA444CED162B - 5f19f898d5295893"
24+
shouldBeEnabled = "Yes"
25+
ignoreCount = "0"
26+
continueAfterRunningActions = "No"
27+
symbolName = "(1) suspend resume partial function for Loadify.DetailsFetcher.loadDetails&lt;&#x3c4;_0_0 where &#x3c4;_0_0: Swift.Decodable&gt;(for: Swift.String, to: Loadify.PlatformType) async throws -&gt; &#x3c4;_0_0"
28+
moduleName = "Loadify"
29+
usesParentBreakpointCondition = "Yes"
30+
urlString = "file:///Users/vishweshwaran/Developer/iOS/SwiftUI/Loadify-iOS/Loadify/Services/API/DetailsFetcher.swift"
31+
startingColumnNumber = "9223372036854775807"
32+
endingColumnNumber = "9223372036854775807"
33+
startingLineNumber = "21"
34+
endingLineNumber = "21"
35+
offsetFromSymbolStart = "264">
36+
</Location>
37+
<Location
38+
uuid = "C4EBE5E0-9EBF-4910-9F55-AA444CED162B - cebad7a4497636b4"
39+
shouldBeEnabled = "Yes"
40+
ignoreCount = "0"
41+
continueAfterRunningActions = "No"
42+
symbolName = "(2) await resume partial function for Loadify.DetailsFetcher.loadDetails&lt;&#x3c4;_0_0 where &#x3c4;_0_0: Swift.Decodable&gt;(for: Swift.String, to: Loadify.PlatformType) async throws -&gt; &#x3c4;_0_0"
43+
moduleName = "Loadify"
44+
usesParentBreakpointCondition = "Yes"
45+
urlString = "file:///Users/vishweshwaran/Developer/iOS/SwiftUI/Loadify-iOS/Loadify/Services/API/DetailsFetcher.swift"
46+
startingColumnNumber = "9223372036854775807"
47+
endingColumnNumber = "9223372036854775807"
48+
startingLineNumber = "21"
49+
endingLineNumber = "21"
50+
offsetFromSymbolStart = "40">
51+
</Location>
52+
<Location
53+
uuid = "C4EBE5E0-9EBF-4910-9F55-AA444CED162B - bf9608eb03561c95"
54+
shouldBeEnabled = "Yes"
55+
ignoreCount = "0"
56+
continueAfterRunningActions = "No"
57+
symbolName = "(3) suspend resume partial function for Loadify.DetailsFetcher.loadDetails&lt;&#x3c4;_0_0 where &#x3c4;_0_0: Swift.Decodable&gt;(for: Swift.String, to: Loadify.PlatformType) async throws -&gt; &#x3c4;_0_0"
58+
moduleName = "Loadify"
59+
usesParentBreakpointCondition = "Yes"
60+
urlString = "file:///Users/vishweshwaran/Developer/iOS/SwiftUI/Loadify-iOS/Loadify/Services/API/DetailsFetcher.swift"
61+
startingColumnNumber = "9223372036854775807"
62+
endingColumnNumber = "9223372036854775807"
63+
startingLineNumber = "21"
64+
endingLineNumber = "21"
65+
offsetFromSymbolStart = "84">
66+
</Location>
67+
<Location
68+
uuid = "C4EBE5E0-9EBF-4910-9F55-AA444CED162B - 8fce7123ea697e94"
69+
shouldBeEnabled = "Yes"
70+
ignoreCount = "0"
71+
continueAfterRunningActions = "No"
72+
symbolName = "(4) suspend resume partial function for Loadify.DetailsFetcher.loadDetails&lt;&#x3c4;_0_0 where &#x3c4;_0_0: Swift.Decodable&gt;(for: Swift.String, to: Loadify.PlatformType) async throws -&gt; &#x3c4;_0_0"
73+
moduleName = "Loadify"
74+
usesParentBreakpointCondition = "Yes"
75+
urlString = "file:///Users/vishweshwaran/Developer/iOS/SwiftUI/Loadify-iOS/Loadify/Services/API/DetailsFetcher.swift"
76+
startingColumnNumber = "9223372036854775807"
77+
endingColumnNumber = "9223372036854775807"
78+
startingLineNumber = "21"
79+
endingLineNumber = "21"
80+
offsetFromSymbolStart = "68">
81+
</Location>
82+
</Locations>
83+
</BreakpointContent>
84+
</BreakpointProxy>
85+
</Breakpoints>
686
</Bucket>

Loadify/App/Enums/Errors.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ enum DownloadError: Error, LocalizedError {
1313

1414
var errorDescription: String? {
1515
switch self {
16-
case .notCompatible: return "This video is not compatible to save"
16+
case .notCompatible:
17+
return "This video is not compatible to save"
1718
}
1819
}
1920
}
@@ -25,8 +26,10 @@ enum PhotosError: Error, LocalizedError {
2526

2627
var errorDescription: String? {
2728
switch self {
28-
case .permissionDenied: return "Please grant permission to photos"
29-
case .insufficientStorage: return "There is no enough storage to save this video"
29+
case .permissionDenied:
30+
return "Please grant permission to photos"
31+
case .insufficientStorage:
32+
return "There is no enough storage to save this video"
3033
}
3134
}
3235
}

Loadify/App/Enums/VideoQuality.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ enum VideoQuality: String, CaseIterable {
1616

1717
var description: String {
1818
switch self {
19-
case .none: return "Select Video Quality"
20-
case .low: return "Low - 320p"
21-
case .medium: return "Medium - 720p"
22-
case .high: return "High - 1080p"
19+
case .none:
20+
return "Select Video Quality"
21+
case .low:
22+
return "Low - 320p"
23+
case .medium:
24+
return "Medium - 720p"
25+
case .high:
26+
return "High - 1080p"
2327
}
2428
}
2529

Loadify/Others/Extensions/URLSession+Extension.swift

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,40 @@ import Foundation
99

1010
extension URLSession: URLSessionProtocol {
1111

12-
func fetchData(for request: URLRequest) async throws -> (Data, HTTPURLResponse) {
12+
func fetch(for request: URLRequest) async throws -> (Data, HTTPURLResponse) {
1313
let (data, response) = try await data(for: request)
1414

15-
guard let httpResponse = response as? HTTPURLResponse else {
16-
throw NetworkError.invalidResponse(message: nil)
17-
}
18-
19-
try handleStateBasedOnStatusCode(for: httpResponse.statusCode)
15+
let httpResponse = try response.handleStatusCodeAndReturnHTTPResponse()
2016

2117
return (data, httpResponse)
2218
}
2319

24-
func downloadData(for request: URLRequest) async throws -> (URL, HTTPURLResponse) {
25-
let (data, response) = try await download(for: request)
26-
27-
guard let httpResponse = response as? HTTPURLResponse else {
28-
throw NetworkError.invalidResponse(message: nil)
20+
func download(for request: URLRequest) {
21+
let task = downloadTask(with: request)
22+
task.resume()
23+
}
24+
25+
func finishAndInvalidate() {
26+
self.finishTasksAndInvalidate()
27+
}
28+
}
29+
30+
extension URLResponse {
31+
32+
var httpResponse: HTTPURLResponse {
33+
get throws {
34+
guard let httpResponse = self as? HTTPURLResponse else {
35+
throw NetworkError.unknownError(message: nil)
36+
}
37+
38+
return httpResponse
2939
}
30-
31-
try handleStateBasedOnStatusCode(for: httpResponse.statusCode)
32-
33-
return (data, httpResponse)
3440
}
3541

36-
private func handleStateBasedOnStatusCode(for statusCode: Int) throws {
37-
switch statusCode {
42+
func handleStatusCodeAndReturnHTTPResponse() throws -> HTTPURLResponse {
43+
let response = try httpResponse
44+
45+
switch response.statusCode {
3846
case 200...299:
3947
break
4048
case 400:
@@ -50,5 +58,7 @@ extension URLSession: URLSessionProtocol {
5058
default:
5159
throw NetworkError.unknownError(message: "An unknown error occurred.")
5260
}
61+
62+
return response
5363
}
5464
}

Loadify/Others/Extensions/View+Extension.swift

Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ extension View {
1818
}
1919

2020
func hideKeyboard() {
21-
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
21+
UIApplication.shared.sendAction(
22+
#selector(UIResponder.resignFirstResponder),
23+
to: nil,
24+
from: nil,
25+
for: nil
26+
)
2227
}
2328

2429
/// Applies the given transform if the given condition evaluates to `true`.
@@ -90,23 +95,6 @@ extension View {
9095
}
9196
}
9297

93-
/// This will return an Alert from LoaderKit.
94-
/// This functions helps you to present `Alert` on top of the View Hierarchy
95-
/// - Parameters:
96-
/// - isPresented: Bool to indicate to show Alert on the View heriracy
97-
/// - content: Closure to show `Alert` and display alert message
98-
func showAlert<T: View>(isPresented: Binding<Bool>, for duration: TimeInterval = 2.5, content: () -> T) -> some View {
99-
ZStack {
100-
self.allowsHitTesting(!isPresented.wrappedValue)
101-
if isPresented.wrappedValue {
102-
content()
103-
.dismiss(delay: duration) {
104-
isPresented.wrappedValue = false
105-
}
106-
}
107-
}
108-
}
109-
11098
func permissionAlert(isPresented: Binding<Bool>) -> some View {
11199
let title = LoadifyTexts.photosAccessTitle
112100
let message = LoadifyTexts.photosAccessSubtitle
@@ -129,48 +117,6 @@ extension View {
129117
}
130118
}
131119

132-
/// This will return a loader from LoaderKit.
133-
/// This functions helps you to register loader in the rootView and can be acessible by creating and instance of the **LoaderViewAction**
134-
/// - Parameters:
135-
/// - loaderAction: Lifecycle of the loaderAction
136-
/// - showOverlay: Bool property to add black background behind the loader when it is active. By default it is false
137-
/// - options: This gives some View of type LoaderView
138-
@available(*, deprecated, message: "use showLoader instead.")
139-
func addLoaderView(
140-
for loaderAction: LoaderViewAction,
141-
showOverlay: Bool = false,
142-
options: LoaderOptions = LoaderOptions()
143-
) -> some View {
144-
ZStack {
145-
self
146-
if loaderAction.isLoading {
147-
LoaderView(
148-
title: loaderAction.title,
149-
subTitle: loaderAction.subTitle,
150-
showOverlay: showOverlay,
151-
options: options
152-
)
153-
}
154-
}
155-
}
156-
157-
@available(*, deprecated, message: "use showAlert instead.")
158-
func addAlertView(
159-
for alertAction: AlertViewAction
160-
) -> some View {
161-
ZStack {
162-
self
163-
if alertAction.isShowing {
164-
AlertView(
165-
title: alertAction.title,
166-
subTitle: alertAction.subTitle,
167-
showOverlay: alertAction.showOverlay,
168-
options: alertAction.options
169-
)
170-
}
171-
}
172-
}
173-
174120
/// Uses VisualEffectsView to blur the background beneath the views
175121
func loaderBackground(colors: [Color]? = nil) -> some View {
176122
modifier(LoaderBackground(colors: colors))

Loadify/Others/LoadifyConstants.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ struct Loadify {
3131

3232
struct Texts {
3333
static let loading = "LOADING"
34-
static let downloading = "DOWNLOADING"
3534
static let downloadedTitle = "Downloaded Successfully"
36-
static let downloadedSubtitle = "Added to photos"
35+
3736
static let loadifySubTitle = "Download YouTube Videos, Shorts, Instagram Reels, and Posts with Ease!"
37+
3838
static let photosAccessTitle = "Photos access is required"
3939
static let photosAccessSubtitle = "To enable access, go to Settings > Privacy > Photos and turn on All Photos access for this app."
40+
4041
static let tryAgain: [String] = ["Please try again later", "Oops, something went wrong", "There was an error. Please try again later"]
42+
4143
static let noInternet = "No Internet Connection"
4244
static let noInternetMessage = "Make sure your device is connected to the internet"
4345
}
Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// CustomButtonStyle.swift
3-
//
3+
//
44
//
55
// Created by Vishweshwaran on 30/10/22.
66
//
@@ -9,27 +9,72 @@ import SwiftUI
99

1010
struct CustomButtonStyle: ButtonStyle {
1111

12+
var progress: Binding<Double>?
1213
var buttonColor: Color
1314
var cornerRadius: CGFloat = 10
14-
var isDisabled: Bool = false
15+
var isDisabled: Bool
16+
var downloadFailed: Bool
1517

1618
init(
19+
progress: Binding<Double>? = nil,
1720
buttonColor: Color = LoadifyColors.blueAccent,
1821
cornerRadius: CGFloat = 10,
19-
isDisabled: Bool = false
22+
isDisabled: Bool = false,
23+
downloadFailed: Bool
2024
) {
25+
self.progress = progress
2126
self.buttonColor = buttonColor
2227
self.cornerRadius = cornerRadius
2328
self.isDisabled = isDisabled
29+
self.downloadFailed = downloadFailed
2430
}
2531

2632
func makeBody(configuration: Configuration) -> some View {
27-
configuration.label
28-
.foregroundColor(isDisabled ? .white.opacity(0.5) : .white)
33+
let label = configuration.label
34+
let foregroundColor = isDisabled ? Color.white.opacity(0.5) : Color.white
35+
36+
let button = label
37+
.foregroundColor(foregroundColor)
2938
.frame(maxWidth: Loadify.maxWidth, maxHeight: 56)
39+
.background(downloadFailed ? LoadifyColors.errorRed : Color.clear)
40+
.background(
41+
progress.map { ProgressBar(progress: $0.wrappedValue) }
42+
.foregroundStyle(LoadifyColors.successGreen)
43+
)
3044
.background(isDisabled ? buttonColor.opacity(0.5) : buttonColor)
3145
.cornerRadius(cornerRadius)
3246
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
47+
.onChange(of: progress?.wrappedValue) {
48+
if $0 == 1.0 {
49+
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
50+
self.progress?.wrappedValue = .zero
51+
}
52+
}
53+
}
54+
55+
return button
56+
}
57+
}
58+
59+
fileprivate struct ProgressBar: Shape {
60+
61+
var progress: CGFloat
62+
63+
var animatableData: CGFloat {
64+
get { return progress }
65+
set { self.progress = newValue }
66+
}
67+
68+
func path(in rect: CGRect) -> Path {
69+
var path = Path()
70+
71+
path.move(to: CGPoint(x: 0, y: 0))
72+
path.addLine(to: CGPoint(x: rect.width * progress, y: 0))
73+
path.addLine(to: CGPoint(x: rect.width * progress, y: rect.height))
74+
path.addLine(to: CGPoint(x: 0, y: rect.height))
75+
path.closeSubpath()
76+
77+
return path
3378
}
3479
}
3580

@@ -40,6 +85,12 @@ struct CustomButtonStyle_Previews: PreviewProvider {
4085
Text("Download Now")
4186
}
4287
.padding()
43-
.buttonStyle(CustomButtonStyle(isDisabled: false))
88+
.buttonStyle(
89+
CustomButtonStyle(
90+
progress: .constant(0.2),
91+
isDisabled: false,
92+
downloadFailed: true
93+
)
94+
)
4495
}
4596
}

0 commit comments

Comments
 (0)