Skip to content

Commit

Permalink
Add updating screenshots functionality.
Browse files Browse the repository at this point in the history
Add ability to enter screenshot name while capturing from floating button.
  • Loading branch information
serhii-londar committed Nov 14, 2024
1 parent 50b9006 commit 9e2054d
Show file tree
Hide file tree
Showing 9 changed files with 456 additions and 26 deletions.
10 changes: 5 additions & 5 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
PODS:
- BaseAPI (0.2.1)
- BaseAPI (0.2.2)
- CrowdinSDK (1.9.0):
- CrowdinSDK/Core (= 1.9.0)
- CrowdinSDK/CrowdinProvider (= 1.9.0)
- CrowdinSDK/Core (1.9.0):
- CrowdinSDK/CrowdinFileSystem
- CrowdinSDK/CrowdinAPI (1.9.0):
- BaseAPI (~> 0.2.1)
- BaseAPI (~> 0.2.2)
- CrowdinSDK/Core
- CrowdinSDK/CrowdinFileSystem (1.9.0)
- CrowdinSDK/CrowdinProvider (1.9.0):
Expand All @@ -18,7 +18,7 @@ PODS:
- CrowdinSDK/CrowdinAPI
- CrowdinSDK/CrowdinProvider
- CrowdinSDK/LoginFeature (1.9.0):
- BaseAPI (~> 0.2.1)
- BaseAPI (~> 0.2.2)
- CrowdinSDK/Core
- CrowdinSDK/CrowdinAPI
- CrowdinSDK/CrowdinProvider
Expand Down Expand Up @@ -79,8 +79,8 @@ EXTERNAL SOURCES:
:path: "../"

SPEC CHECKSUMS:
BaseAPI: 7a3abac9fa1e19147a5c87dcfbb1829a584cd1ca
CrowdinSDK: 65fd7989c86e5ff79c8734979bc61510238d8725
BaseAPI: 7d1c79778a5c85f8e05f5e5c9d7f9be6474a53eb
CrowdinSDK: 176f6db0a3681cbcf5a1c64bb2f8e264a042c0a6
DebugSwift: f766d934affddea9ffe36b0cf2631cd28311481f
Realm: 490aad28f1360e58fc22256d5d686d3a36525346
RealmSwift: f6a9b56d747bbdd7931de1835896c5f024b6898a
Expand Down
45 changes: 45 additions & 0 deletions Sources/CrowdinSDK/CrowdinAPI/CrowdinAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,51 @@ class CrowdinAPI: BaseAPI {
}
}

func cw_put<T: Decodable>(url: String, parameters: [String: String]? = nil, headers: [String: String]? = nil, body: Data?, completion: @escaping (T?, Error?) -> Swift.Void) {
self.put(url: url, parameters: parameters, headers: addDefaultHeaders(to: headers), body: body, completion: { data, response, error in
if self.isUnautorized(response: response) {
CrowdinAPILog.logRequest(method: RequestMethod.POST.rawValue, url: url, parameters: parameters, headers: self.addDefaultHeaders(to: headers), body: body, responseData: data)
NotificationCenter.default.post(name: .CrowdinAPIUnautorizedNotification, object: nil)
return
}
guard let data = data else {
completion(nil, error)
return
}

CrowdinAPILog.logRequest(method: RequestMethod.POST.rawValue, url: url, parameters: parameters, headers: self.addDefaultHeaders(to: headers), body: body, responseData: data)

do {
let response = try JSONDecoder().decode(T.self, from: data)
completion(response, error)
} catch {
print(String(data: data, encoding: .utf8) ?? "Data is empty")
completion(nil, error)
}
})
}

func cw_putSync<T: Decodable>(url: String, parameters: [String: String]? = nil, headers: [String: String]? = nil, body: Data?) -> (T?, Error?) {
let result = self.put(url: url, parameters: parameters, headers: addDefaultHeaders(to: headers), body: body)
CrowdinAPILog.logRequest(method: RequestMethod.POST.rawValue, url: url, parameters: parameters, headers: addDefaultHeaders(to: headers), body: body, responseData: result.data)

if self.isUnautorized(response: result.response) {
NotificationCenter.default.post(name: .CrowdinAPIUnautorizedNotification, object: nil)
return (nil, nil);
}
guard let data = result.data else {
return (nil, result.error)
}

do {
let response = try JSONDecoder().decode(T.self, from: data)
return (response, result.error)
} catch {
print(String(data: data, encoding: .utf8) ?? "Data is empty")
return (nil, error)
}
}

func cw_get<T: Decodable>(url: String, parameters: [String: String]? = nil, headers: [String: String]? = nil, completion: @escaping (T?, Error?) -> Swift.Void) {
self.get(url: url, parameters: parameters, headers: addDefaultHeaders(to: headers), completion: { data, response, error in
if self.isUnautorized(response: response) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//
// ScreenshotsListResponse.swift
// Pods
//
// Created by Serhii Londar on 10.11.2024.
//

import Foundation

// MARK: - ScreenshotsListResponse
struct ScreenshotsListResponse: Codable, Hashable {
let data: [ScreenshotsListResponseDatum]
let pagination: ScreenshotsListResponsePagination

enum CodingKeys: String, CodingKey {
case data = "data"
case pagination = "pagination"
}
}

//
// Hashable or Equatable:
// The compiler will not be able to synthesize the implementation of Hashable or Equatable
// for types that require the use of JSONAny, nor will the implementation of Hashable be
// synthesized for types that have collections (such as arrays or dictionaries).

// MARK: - ScreenshotsListResponseDatum
struct ScreenshotsListResponseDatum: Codable, Hashable {
let data: ScreenshotsListResponseData

enum CodingKeys: String, CodingKey {
case data = "data"
}
}

//
// Hashable or Equatable:
// The compiler will not be able to synthesize the implementation of Hashable or Equatable
// for types that require the use of JSONAny, nor will the implementation of Hashable be
// synthesized for types that have collections (such as arrays or dictionaries).

// MARK: - ScreenshotsListResponseData
struct ScreenshotsListResponseData: Codable, Hashable {
let id: Int
let userID: Int
let url: String
let webURL: String
let name: String
let size: ScreenshotsListResponseSize
let tagsCount: Int
let tags: [ScreenshotsListResponseTag]
let labels: [Int]
let labelIDS: [Int]
let createdAt: String
let updatedAt: String

enum CodingKeys: String, CodingKey {
case id = "id"
case userID = "userId"
case url = "url"
case webURL = "webUrl"
case name = "name"
case size = "size"
case tagsCount = "tagsCount"
case tags = "tags"
case labels = "labels"
case labelIDS = "labelIds"
case createdAt = "createdAt"
case updatedAt = "updatedAt"
}
}

//
// Hashable or Equatable:
// The compiler will not be able to synthesize the implementation of Hashable or Equatable
// for types that require the use of JSONAny, nor will the implementation of Hashable be
// synthesized for types that have collections (such as arrays or dictionaries).

// MARK: - ScreenshotsListResponseSize
struct ScreenshotsListResponseSize: Codable, Hashable {
let width: Int
let height: Int

enum CodingKeys: String, CodingKey {
case width = "width"
case height = "height"
}
}

//
// Hashable or Equatable:
// The compiler will not be able to synthesize the implementation of Hashable or Equatable
// for types that require the use of JSONAny, nor will the implementation of Hashable be
// synthesized for types that have collections (such as arrays or dictionaries).

// MARK: - ScreenshotsListResponseTag
struct ScreenshotsListResponseTag: Codable, Hashable {
let id: Int
let screenshotID: Int
let stringID: Int
let position: ScreenshotsListResponsePosition
let createdAt: String

enum CodingKeys: String, CodingKey {
case id = "id"
case screenshotID = "screenshotId"
case stringID = "stringId"
case position = "position"
case createdAt = "createdAt"
}
}

//
// Hashable or Equatable:
// The compiler will not be able to synthesize the implementation of Hashable or Equatable
// for types that require the use of JSONAny, nor will the implementation of Hashable be
// synthesized for types that have collections (such as arrays or dictionaries).

// MARK: - ScreenshotsListResponsePosition
struct ScreenshotsListResponsePosition: Codable, Hashable {
let x: Int
let y: Int
let width: Int
let height: Int

enum CodingKeys: String, CodingKey {
case x = "x"
case y = "y"
case width = "width"
case height = "height"
}
}

//
// Hashable or Equatable:
// The compiler will not be able to synthesize the implementation of Hashable or Equatable
// for types that require the use of JSONAny, nor will the implementation of Hashable be
// synthesized for types that have collections (such as arrays or dictionaries).

// MARK: - ScreenshotsListResponsePagination
struct ScreenshotsListResponsePagination: Codable, Hashable {
let offset: Int
let limit: Int

enum CodingKeys: String, CodingKey {
case offset = "offset"
case limit = "limit"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// UpdateScreenshotRequest.swift
// Pods
//
// Created by Serhii Londar on 09.11.2024.
//


struct UpdateScreenshotRequest: Codable {
let storageId: Int
let name: String
var usePreviousTags: Bool = true
}
32 changes: 32 additions & 0 deletions Sources/CrowdinSDK/CrowdinAPI/ScreenshotsAPI/ScreenshotsAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ class ScreenshotsAPI: CrowdinAPI {
self.cw_post(url: url, headers: headers, body: requestData, completion: completion)
}

func updateScreenshot(projectId: Int, screnshotId: Int, storageId: Int, name: String, usePreviousTags: Bool = false, completion: @escaping (CreateScreenshotResponse?, Error?) -> Void) {
let request = UpdateScreenshotRequest(storageId: storageId, name: name, usePreviousTags: usePreviousTags)
let requestData = try? JSONEncoder().encode(request)
let url = baseUrl(with: projectId) + "/" + String(screnshotId)
let headers = [RequestHeaderFields.contentType.rawValue: "application/json"]
self.cw_put(url: url, headers: headers, body: requestData, completion: completion)
}

func createScreenshotTags(projectId: Int, screenshotId: Int, frames: [(id: Int, rect: CGRect)], completion: @escaping (CreateScreenshotTagResponse?, Error?) -> Void) {
var elements = [CreateScreenshotTagRequestElement]()
for frame in frames {
Expand All @@ -39,4 +47,28 @@ class ScreenshotsAPI: CrowdinAPI {
let headers = [RequestHeaderFields.contentType.rawValue: "application/json"]
self.cw_post(url: url, headers: headers, body: requestData, completion: completion)
}

enum ListScreenshotsParameters: String {
case search
case orderBy
case limit
case offset
}

func listScreenshots(projectId: Int, query: String, completion: @escaping (ScreenshotsListResponse?, Error?) -> Void) {
let parameters = [
ListScreenshotsParameters.search.rawValue: query,
ListScreenshotsParameters.orderBy.rawValue: "createdAt desc,updatedAt desc",
ListScreenshotsParameters.offset.rawValue: "0",
ListScreenshotsParameters.limit.rawValue: "1"
]
let url = baseUrl(with: projectId)
self.cw_get(url: url, parameters: parameters, completion: completion)
}
}

extension String {
func urlEncoded() -> String {
return self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import Foundation

#if !os(watchOS)

/// Extension that adds screenshot capture functionality to CrowdinSDK
extension CrowdinSDK {
/// Initializes the screenshot feature with the current SDK configuration.
/// This method sets up the screenshot feature if it's enabled in the configuration,
/// creating necessary uploaders and processors, and configuring method swizzling.
@objc class func initializeScreenshotFeature() {
guard let config = CrowdinSDK.config else { return }
if config.screenshotsEnabled {
Expand All @@ -20,6 +24,12 @@ extension CrowdinSDK {
}
}

/// Captures a screenshot of the current top view controller and upload it to Crowdin.
/// - Parameters:
/// - name: The name to be assigned to the screenshot.
/// - success: A closure to be called when the screenshot is successfully captured and uploaded.
/// - errorHandler: A closure to be called if an error occurs during the process.
/// The closure receives an optional Error parameter indicating what went wrong.
public class func captureScreenshot(name: String, success: @escaping (() -> Void), errorHandler: @escaping ((Error?) -> Void)) {
guard let screenshotFeature = ScreenshotFeature.shared else {
errorHandler(NSError(domain: "Screenshots feature disabled", code: defaultCrowdinErrorCode, userInfo: nil))
Expand All @@ -28,13 +38,53 @@ extension CrowdinSDK {
screenshotFeature.captureScreenshot(name: name, success: success, errorHandler: errorHandler)
}

/// Captures a screenshot of a specific view and upload it to Crowdin.
/// - Parameters:
/// - view: The view to capture in the screenshot.
/// - name: The name to be assigned to the screenshot.
/// - success: A closure to be called when the screenshot is successfully captured and uploaded.
/// - errorHandler: A closure to be called if an error occurs during the process.
/// The closure receives an optional Error parameter indicating what went wrong.
public class func captureScreenshot(view: View, name: String, success: @escaping (() -> Void), errorHandler: @escaping ((Error?) -> Void)) {
guard let screenshotFeature = ScreenshotFeature.shared else {
errorHandler(NSError(domain: "Screenshots feature disabled", code: defaultCrowdinErrorCode, userInfo: nil))
return
}
screenshotFeature.captureScreenshot(view: view, name: name, success: success, errorHandler: errorHandler)
}

/// Captures a screenshot of the current top view controller and updates it if it already exists in Crowdin.
/// If several screnshots with passed name exist it will update the newest one.
/// If screenshot with fiven name not exist new one will be created.
/// - Parameters:
/// - name: The name to be assigned to the screenshot.
/// - success: A closure to be called when the screenshot is successfully captured and updated.
/// - errorHandler: A closure to be called if an error occurs during the process.
/// The closure receives an optional Error parameter indicating what went wrong.
public class func captureAndUpdateScreenshot(name: String, success: @escaping (() -> Void), errorHandler: @escaping ((Error?) -> Void)) {
guard let screenshotFeature = ScreenshotFeature.shared else {
errorHandler(NSError(domain: "Screenshots feature disabled", code: defaultCrowdinErrorCode, userInfo: nil))
return
}
screenshotFeature.captureScreenshot(name: name, success: success, errorHandler: errorHandler)
}

/// Captures a screenshot of a specific view and updates it if it already exists in Crowdin.
/// If several screnshots with passed name exist it will update the newest one.
/// If screenshot with fiven name not exist new one will be created.
/// - Parameters:
/// - view: The view to capture in the screenshot.
/// - name: The name to be assigned to the screenshot.
/// - success: A closure to be called when the screenshot is successfully captured and updated.
/// - errorHandler: A closure to be called if an error occurs during the process.
/// The closure receives an optional Error parameter indicating what went wrong.
public class func captureAndUpdateScreenshot(view: View, name: String, success: @escaping (() -> Void), errorHandler: @escaping ((Error?) -> Void)) {
guard let screenshotFeature = ScreenshotFeature.shared else {
errorHandler(NSError(domain: "Screenshots feature disabled", code: defaultCrowdinErrorCode, userInfo: nil))
return
}
screenshotFeature.captureScreenshot(view: view, name: name, success: success, errorHandler: errorHandler)
}
}

#endif
Loading

0 comments on commit 9e2054d

Please sign in to comment.