From 3a55499d1f5d65fa9a28ba34a9cb023b519b54b2 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar Date: Fri, 3 May 2024 09:44:01 +0300 Subject: [PATCH 1/2] chore: remove Toast --- .../CrowdinSDK/CrowdinSDK/Utils/Toast.swift | 810 ------------------ .../SettingsView+UITableView.swift | 34 +- .../Settings/SettingsView/SettingsView.swift | 58 +- 3 files changed, 40 insertions(+), 862 deletions(-) delete mode 100644 Sources/CrowdinSDK/CrowdinSDK/Utils/Toast.swift diff --git a/Sources/CrowdinSDK/CrowdinSDK/Utils/Toast.swift b/Sources/CrowdinSDK/CrowdinSDK/Utils/Toast.swift deleted file mode 100644 index dc7f4fa8..00000000 --- a/Sources/CrowdinSDK/CrowdinSDK/Utils/Toast.swift +++ /dev/null @@ -1,810 +0,0 @@ -// -// Toast.swift -// Toast-Swift -// -// Copyright (c) 2015-2019 Charles Scalesse. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#if os(iOS) || os(tvOS) - -import UIKit -import ObjectiveC - -/** - Toast is a Swift extension that adds toast notifications to the `UIView` object class. - It is intended to be simple, lightweight, and easy to use. Most toast notifications - can be triggered with a single line of code. - - The `makeToast` methods create a new view and then display it as toast. - - The `showToast` methods display any view as toast. - - */ -public extension UIView { - - /** - Keys used for associated objects. - */ - private struct ToastKeys { - static var timer = "com.toast-swift.timer" - static var duration = "com.toast-swift.duration" - static var point = "com.toast-swift.point" - static var completion = "com.toast-swift.completion" - static var activeToasts = "com.toast-swift.activeToasts" - static var activityView = "com.toast-swift.activityView" - static var queue = "com.toast-swift.queue" - } - - /** - Swift closures can't be directly associated with objects via the - Objective-C runtime, so the (ugly) solution is to wrap them in a - class that can be used with associated objects. - */ - private class ToastCompletionWrapper { - let completion: ((Bool) -> Void)? - - init(_ completion: ((Bool) -> Void)?) { - self.completion = completion - } - } - - private enum ToastError: Error { - case missingParameters - } - - private var activeToasts: NSMutableArray { - get { - if let activeToasts = objc_getAssociatedObject(self, &ToastKeys.activeToasts) as? NSMutableArray { - return activeToasts - } else { - let activeToasts = NSMutableArray() - objc_setAssociatedObject(self, &ToastKeys.activeToasts, activeToasts, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return activeToasts - } - } - } - - private var queue: NSMutableArray { - get { - if let queue = objc_getAssociatedObject(self, &ToastKeys.queue) as? NSMutableArray { - return queue - } else { - let queue = NSMutableArray() - objc_setAssociatedObject(self, &ToastKeys.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return queue - } - } - } - - // MARK: - Make Toast Methods - - /** - Creates and presents a new toast view. - - @param message The message to be displayed - @param duration The toast duration - @param position The toast's position - @param title The title - @param image The image - @param style The style. The shared style will be used when nil - @param completion The completion closure, executed after the toast view disappears. - didTap will be `true` if the toast view was dismissed from a tap. - */ - func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, title: String? = nil, image: UIImage? = nil, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)? = nil) { - do { - let toast = try toastViewForMessage(message, title: title, image: image, style: style) - showToast(toast, duration: duration, position: position, completion: completion) - } catch ToastError.missingParameters { - print("Error: message, title, and image are all nil") - } catch {} - } - - /** - Creates a new toast view and presents it at a given center point. - - @param message The message to be displayed - @param duration The toast duration - @param point The toast's center point - @param title The title - @param image The image - @param style The style. The shared style will be used when nil - @param completion The completion closure, executed after the toast view disappears. - didTap will be `true` if the toast view was dismissed from a tap. - */ - func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, title: String?, image: UIImage?, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)?) { - do { - let toast = try toastViewForMessage(message, title: title, image: image, style: style) - showToast(toast, duration: duration, point: point, completion: completion) - } catch ToastError.missingParameters { - print("Error: message, title, and image cannot all be nil") - } catch {} - } - - // MARK: - Show Toast Methods - - /** - Displays any view as toast at a provided position and duration. The completion closure - executes when the toast view completes. `didTap` will be `true` if the toast view was - dismissed from a tap. - - @param toast The view to be displayed as toast - @param duration The notification duration - @param position The toast's position - @param completion The completion block, executed after the toast view disappears. - didTap will be `true` if the toast view was dismissed from a tap. - */ - func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, completion: ((_ didTap: Bool) -> Void)? = nil) { - let point = position.centerPoint(forToast: toast, inSuperview: self) - showToast(toast, duration: duration, point: point, completion: completion) - } - - /** - Displays any view as toast at a provided center point and duration. The completion closure - executes when the toast view completes. `didTap` will be `true` if the toast view was - dismissed from a tap. - - @param toast The view to be displayed as toast - @param duration The notification duration - @param point The toast's center point - @param completion The completion block, executed after the toast view disappears. - didTap will be `true` if the toast view was dismissed from a tap. - */ - func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, completion: ((_ didTap: Bool) -> Void)? = nil) { - objc_setAssociatedObject(toast, &ToastKeys.completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - if ToastManager.shared.isQueueEnabled, activeToasts.count > 0 { - objc_setAssociatedObject(toast, &ToastKeys.duration, NSNumber(value: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); - objc_setAssociatedObject(toast, &ToastKeys.point, NSValue(cgPoint: point), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - queue.add(toast) - } else { - showToast(toast, duration: duration, point: point) - } - } - - // MARK: - Hide Toast Methods - - /** - Hides the active toast. If there are multiple toasts active in a view, this method - hides the oldest toast (the first of the toasts to have been presented). - - @see `hideAllToasts()` to remove all active toasts from a view. - - @warning This method has no effect on activity toasts. Use `hideToastActivity` to - hide activity toasts. - - */ - func hideToast() { - guard let activeToast = activeToasts.firstObject as? UIView else { return } - hideToast(activeToast) - } - - /** - Hides an active toast. - - @param toast The active toast view to dismiss. Any toast that is currently being displayed - on the screen is considered active. - - @warning this does not clear a toast view that is currently waiting in the queue. - */ - func hideToast(_ toast: UIView) { - guard activeToasts.contains(toast) else { return } - hideToast(toast, fromTap: false) - } - - /** - Hides all toast views. - - @param includeActivity If `true`, toast activity will also be hidden. Default is `false`. - @param clearQueue If `true`, removes all toast views from the queue. Default is `true`. - */ - func hideAllToasts(includeActivity: Bool = false, clearQueue: Bool = true) { - if clearQueue { - clearToastQueue() - } - - activeToasts.compactMap { $0 as? UIView } - .forEach { hideToast($0) } - - if includeActivity { - hideToastActivity() - } - } - - /** - Removes all toast views from the queue. This has no effect on toast views that are - active. Use `hideAllToasts(clearQueue:)` to hide the active toasts views and clear - the queue. - */ - func clearToastQueue() { - queue.removeAllObjects() - } - - // MARK: - Activity Methods - - /** - Creates and displays a new toast activity indicator view at a specified position. - - @warning Only one toast activity indicator view can be presented per superview. Subsequent - calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called. - - @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast - activity views can be presented and dismissed while toast views are being displayed. - `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods. - - @param position The toast's position - */ - func makeToastActivity(_ position: ToastPosition) { - // sanity - guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return } - - let toast = createToastActivityView() - let point = position.centerPoint(forToast: toast, inSuperview: self) - makeToastActivity(toast, point: point) - } - - /** - Creates and displays a new toast activity indicator view at a specified position. - - @warning Only one toast activity indicator view can be presented per superview. Subsequent - calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called. - - @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast - activity views can be presented and dismissed while toast views are being displayed. - `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods. - - @param point The toast's center point - */ - func makeToastActivity(_ point: CGPoint) { - // sanity - guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return } - - let toast = createToastActivityView() - makeToastActivity(toast, point: point) - } - - /** - Dismisses the active toast activity indicator view. - */ - func hideToastActivity() { - if let toast = objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView { - UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: { - toast.alpha = 0.0 - }) { _ in - toast.removeFromSuperview() - objc_setAssociatedObject(self, &ToastKeys.activityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - } - - // MARK: - Private Activity Methods - - private func makeToastActivity(_ toast: UIView, point: CGPoint) { - toast.alpha = 0.0 - toast.center = point - - objc_setAssociatedObject(self, &ToastKeys.activityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - - if let topVC = UIApplication.getTopViewController() { - topVC.view.addSubview(toast) - } else { - self.addSubview(toast) - } - - UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: .curveEaseOut, animations: { - toast.alpha = 1.0 - }) - } - - private func createToastActivityView() -> UIView { - let style = ToastManager.shared.style - - let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height)) - activityView.backgroundColor = style.activityBackgroundColor - activityView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] - activityView.layer.cornerRadius = style.cornerRadius - - if style.displayShadow { - activityView.layer.shadowColor = style.shadowColor.cgColor - activityView.layer.shadowOpacity = style.shadowOpacity - activityView.layer.shadowRadius = style.shadowRadius - activityView.layer.shadowOffset = style.shadowOffset - } - - let activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge) - activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0) - activityView.addSubview(activityIndicatorView) - activityIndicatorView.color = style.activityIndicatorColor - activityIndicatorView.startAnimating() - - return activityView - } - - // MARK: - Private Show/Hide Methods - - private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) { - toast.center = point - toast.alpha = 0.0 - - if ToastManager.shared.isTapToDismissEnabled { - let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:))) - toast.addGestureRecognizer(recognizer) - toast.isUserInteractionEnabled = true -#if os(iOS) - toast.isExclusiveTouch = true -#endif - } - - activeToasts.add(toast) - - toast.cw_present() - - UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: { - toast.alpha = 1.0 - }) { _ in - let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerDidFinish(_:)), userInfo: toast, repeats: false) - RunLoop.main.add(timer, forMode: .common) - objc_setAssociatedObject(toast, &ToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - - private func hideToast(_ toast: UIView, fromTap: Bool) { - if let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer { - timer.invalidate() - } - - UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: { - toast.alpha = 0.0 - }) { _ in - toast.removeFromSuperview() - self.activeToasts.remove(toast) - - if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.completion) as? ToastCompletionWrapper, let completion = wrapper.completion { - completion(fromTap) - } - - if let nextToast = self.queue.firstObject as? UIView, let duration = objc_getAssociatedObject(nextToast, &ToastKeys.duration) as? NSNumber, let point = objc_getAssociatedObject(nextToast, &ToastKeys.point) as? NSValue { - self.queue.removeObject(at: 0) - self.showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue) - } - } - } - - // MARK: - Events - - @objc - private func handleToastTapped(_ recognizer: UITapGestureRecognizer) { - guard let toast = recognizer.view else { return } - hideToast(toast, fromTap: true) - } - - @objc - private func toastTimerDidFinish(_ timer: Timer) { - guard let toast = timer.userInfo as? UIView else { return } - hideToast(toast) - } - - // MARK: - Toast Construction - - /** - Creates a new toast view with any combination of message, title, and image. - The look and feel is configured via the style. Unlike the `makeToast` methods, - this method does not present the toast view automatically. One of the `showToast` - methods must be used to present the resulting view. - - @warning if message, title, and image are all nil, this method will throw - `ToastError.missingParameters` - - @param message The message to be displayed - @param title The title - @param image The image - @param style The style. The shared style will be used when nil - @throws `ToastError.missingParameters` when message, title, and image are all nil - @return The newly created toast view - */ - func toastViewForMessage(_ message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView { - // sanity - guard message != nil || title != nil || image != nil else { - throw ToastError.missingParameters - } - - var messageLabel: UILabel? - var titleLabel: UILabel? - var imageView: UIImageView? - - let wrapperView = UIView() - wrapperView.backgroundColor = style.backgroundColor - wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] - wrapperView.layer.cornerRadius = style.cornerRadius - - if style.displayShadow { - wrapperView.layer.shadowColor = UIColor.black.cgColor - wrapperView.layer.shadowOpacity = style.shadowOpacity - wrapperView.layer.shadowRadius = style.shadowRadius - wrapperView.layer.shadowOffset = style.shadowOffset - } - - if let image = image { - imageView = UIImageView(image: image) - imageView?.contentMode = .scaleAspectFit - imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height) - } - - var imageRect = CGRect.zero - - if let imageView = imageView { - imageRect.origin.x = style.horizontalPadding - imageRect.origin.y = style.verticalPadding - imageRect.size.width = imageView.bounds.size.width - imageRect.size.height = imageView.bounds.size.height - } - - if let title = title { - titleLabel = UILabel() - titleLabel?.numberOfLines = style.titleNumberOfLines - titleLabel?.font = style.titleFont - titleLabel?.textAlignment = style.titleAlignment - titleLabel?.lineBreakMode = .byTruncatingTail - titleLabel?.textColor = style.titleColor - titleLabel?.backgroundColor = UIColor.clear - titleLabel?.text = title; - - let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage) - let titleSize = titleLabel?.sizeThatFits(maxTitleSize) - if let titleSize = titleSize { - titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height) - } - } - - if let message = message { - messageLabel = UILabel() - messageLabel?.text = message - messageLabel?.numberOfLines = style.messageNumberOfLines - messageLabel?.font = style.messageFont - messageLabel?.textAlignment = style.messageAlignment - messageLabel?.lineBreakMode = .byTruncatingTail; - messageLabel?.textColor = style.messageColor - messageLabel?.backgroundColor = UIColor.clear - - let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage) - let messageSize = messageLabel?.sizeThatFits(maxMessageSize) - if let messageSize = messageSize { - let actualWidth = min(messageSize.width, maxMessageSize.width) - let actualHeight = min(messageSize.height, maxMessageSize.height) - messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight) - } - } - - var titleRect = CGRect.zero - - if let titleLabel = titleLabel { - titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding - titleRect.origin.y = style.verticalPadding - titleRect.size.width = titleLabel.bounds.size.width - titleRect.size.height = titleLabel.bounds.size.height - } - - var messageRect = CGRect.zero - - if let messageLabel = messageLabel { - messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding - messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding - messageRect.size.width = messageLabel.bounds.size.width - messageRect.size.height = messageLabel.bounds.size.height - } - - let longerWidth = max(titleRect.size.width, messageRect.size.width) - let longerX = max(titleRect.origin.x, messageRect.origin.x) - let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding)) - let wrapperHeight = max((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0))) - - wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight) - - if let titleLabel = titleLabel { - titleRect.size.width = longerWidth - titleLabel.frame = titleRect - wrapperView.addSubview(titleLabel) - } - - if let messageLabel = messageLabel { - messageRect.size.width = longerWidth - messageLabel.frame = messageRect - wrapperView.addSubview(messageLabel) - } - - if let imageView = imageView { - wrapperView.addSubview(imageView) - } - - return wrapperView - } - -} - -// MARK: - Toast Style -/** - `ToastStyle` instances define the look and feel for toast views created via the - `makeToast` methods as well for toast views created directly with - `toastViewForMessage(message:title:image:style:)`. - @warning `ToastStyle` offers relatively simple styling options for the default - toast view. If you require a toast view with more complex UI, it probably makes more - sense to create your own custom UIView subclass and present it with the `showToast` - methods. -*/ -public struct ToastStyle { - - public init() {} - - /** - The background color. Default is `.black` at 80% opacity. - */ - public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8) - - /** - The title color. Default is `UIColor.whiteColor()`. - */ - public var titleColor: UIColor = .white - - /** - The message color. Default is `.white`. - */ - public var messageColor: UIColor = .white - - /** - A percentage value from 0.0 to 1.0, representing the maximum width of the toast - view relative to it's superview. Default is 0.8 (80% of the superview's width). - */ - public var maxWidthPercentage: CGFloat = 0.8 { - didSet { - maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0) - } - } - - /** - A percentage value from 0.0 to 1.0, representing the maximum height of the toast - view relative to it's superview. Default is 0.8 (80% of the superview's height). - */ - public var maxHeightPercentage: CGFloat = 0.8 { - didSet { - maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0) - } - } - - /** - The spacing from the horizontal edge of the toast view to the content. When an image - is present, this is also used as the padding between the image and the text. - Default is 10.0. - - */ - public var horizontalPadding: CGFloat = 10.0 - - /** - The spacing from the vertical edge of the toast view to the content. When a title - is present, this is also used as the padding between the title and the message. - Default is 10.0. On iOS11+, this value is added added to the `safeAreaInset.top` - and `safeAreaInsets.bottom`. - */ - public var verticalPadding: CGFloat = 10.0 - - /** - The corner radius. Default is 10.0. - */ - public var cornerRadius: CGFloat = 10.0; - - /** - The title font. Default is `.boldSystemFont(16.0)`. - */ - public var titleFont: UIFont = .boldSystemFont(ofSize: 16.0) - - /** - The message font. Default is `.systemFont(ofSize: 16.0)`. - */ - public var messageFont: UIFont = .systemFont(ofSize: 16.0) - - /** - The title text alignment. Default is `NSTextAlignment.Left`. - */ - public var titleAlignment: NSTextAlignment = .left - - /** - The message text alignment. Default is `NSTextAlignment.Left`. - */ - public var messageAlignment: NSTextAlignment = .center - - /** - The maximum number of lines for the title. The default is 0 (no limit). - */ - public var titleNumberOfLines = 0 - - /** - The maximum number of lines for the message. The default is 0 (no limit). - */ - public var messageNumberOfLines = 0 - - /** - Enable or disable a shadow on the toast view. Default is `false`. - */ - public var displayShadow = false - - /** - The shadow color. Default is `.black`. - */ - public var shadowColor: UIColor = .black - - /** - A value from 0.0 to 1.0, representing the opacity of the shadow. - Default is 0.8 (80% opacity). - */ - public var shadowOpacity: Float = 0.8 { - didSet { - shadowOpacity = max(min(shadowOpacity, 1.0), 0.0) - } - } - - /** - The shadow radius. Default is 6.0. - */ - public var shadowRadius: CGFloat = 6.0 - - /** - The shadow offset. The default is 4 x 4. - */ - public var shadowOffset = CGSize(width: 4.0, height: 4.0) - - /** - The image size. The default is 80 x 80. - */ - public var imageSize = CGSize(width: 80.0, height: 80.0) - - /** - The size of the toast activity view when `makeToastActivity(position:)` is called. - Default is 100 x 100. - */ - public var activitySize = CGSize(width: 100.0, height: 100.0) - - /** - The fade in/out animation duration. Default is 0.2. - */ - public var fadeDuration: TimeInterval = 0.2 - - /** - Activity indicator color. Default is `.white`. - */ - public var activityIndicatorColor: UIColor = .white - - /** - Activity background color. Default is `.black` at 80% opacity. - */ - public var activityBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8) - -} - -// MARK: - Toast Manager -/** - `ToastManager` provides general configuration options for all toast - notifications. Backed by a singleton instance. -*/ -public class ToastManager { - - /** - The `ToastManager` singleton instance. - - */ - public static let shared = ToastManager() - - /** - The shared style. Used whenever toastViewForMessage(message:title:image:style:) is called - with with a nil style. - - */ - public var style = ToastStyle() - - /** - Enables or disables tap to dismiss on toast views. Default is `true`. - - */ - public var isTapToDismissEnabled = true - - /** - Enables or disables queueing behavior for toast views. When `true`, - toast views will appear one after the other. When `false`, multiple toast - views will appear at the same time (potentially overlapping depending - on their positions). This has no effect on the toast activity view, - which operates independently of normal toast views. Default is `false`. - - */ - public var isQueueEnabled = false - - /** - The default duration. Used for the `makeToast` and - `showToast` methods that don't require an explicit duration. - Default is 3.0. - - */ - public var duration: TimeInterval = 3.0 - - /** - Sets the default position. Used for the `makeToast` and - `showToast` methods that don't require an explicit position. - Default is `ToastPosition.Bottom`. - - */ - public var position: ToastPosition = .bottom - -} - -// MARK: - ToastPosition -public enum ToastPosition { - case top - case center - case bottom - - fileprivate func centerPoint(forToast toast: UIView, inSuperview superview: UIView) -> CGPoint { - let topPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.top - let bottomPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.bottom - let bottomOffset: CGFloat = 50 - let deviceBounds = UIScreen.main.bounds - switch self { - case .top: - return CGPoint(x: deviceBounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + topPadding) - case .center: - return CGPoint(x: deviceBounds.size.width / 2.0, y: deviceBounds.size.height / 2.0) - case .bottom: - return CGPoint(x: deviceBounds.size.width / 2.0, y: (deviceBounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding - bottomOffset) - } - } -} - -// MARK: - Private UIView Extensions -private extension UIView { - - var csSafeAreaInsets: UIEdgeInsets { - if #available(iOS 11.0, *) { - if #available(tvOS 11.0, *) { - return self.safeAreaInsets - } else { - return .zero - } - } else { - return .zero - } - } - -} - -extension UIApplication { - - class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { - - if let nav = base as? UINavigationController { - return getTopViewController(base: nav.visibleViewController) - - } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController { - return getTopViewController(base: selected) - - } else if let presented = base?.presentedViewController { - return getTopViewController(base: presented) - } - return base - } -} - -#endif diff --git a/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift b/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift index ba8b6959..5b0670c5 100644 --- a/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift +++ b/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift @@ -12,7 +12,7 @@ import UIKit extension SettingsView { func setupCells() { cells = [] - + if let loginFeature = LoginFeature.shared { let logInItemView = SettingsItemView(frame: .zero) if !LoginFeature.isLogined { @@ -23,13 +23,11 @@ extension SettingsView { self?.reloadData() self?.reloadUI() } - let message = "Successfully logined" + let message = "Logged in" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message)) - self?.showToast(message) }, error: { [weak self] error in let message = "Login error - \(error.localizedDescription)" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .error, message: message)) - self?.showToast(message) }) self?.isHidden = false self?.reloadData() @@ -44,41 +42,42 @@ extension SettingsView { logInItemView.statusView.isHidden = false cells.append(logInItemView) } - + let reloadItemView = SettingsItemView(frame: .zero) reloadItemView.action = { [weak self] in RefreshLocalizationFeature.refreshLocalization() + let message = RealtimeUpdateFeature.shared?.enabled == true ? "Localization fetched from Crowdin project" : "Localization fetched from distribution" - self?.showToast(message) + CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message)) } + reloadItemView.title = "Reload translations" cells.append(reloadItemView) - + if LoginFeature.isLogined { if var feature = RealtimeUpdateFeature.shared { let realTimeUpdateItemView = SettingsItemView(frame: .zero) feature.error = { [weak self] error in let message = "Error while starting real-time preview - \(error.localizedDescription)" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .error, message: message)) - self?.showToast(message) } - + feature.success = { [weak self] in let message = "Successfully started real-time preview" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message)) + self?.reloadData() guard RealtimeUpdateFeature.shared?.enabled == false else { return } - self?.showToast(message) } feature.disconnect = { [weak self] in let message = "Real-time preview disabled" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message)) + self?.reloadData() - self?.showToast(message) } - + realTimeUpdateItemView.action = { feature.enabled = !feature.enabled realTimeUpdateItemView.title = feature.enabled ? "Real-time on" : "Real-time off" @@ -88,26 +87,25 @@ extension SettingsView { realTimeUpdateItemView.statusView.isHidden = false cells.append(realTimeUpdateItemView) } - + if let feature = ScreenshotFeature.shared { let screenshotItemView = SettingsItemView(frame: .zero) screenshotItemView.action = { [weak self] in - let message = "Successfully captured screenshot" + let message = "Screenshot captured" feature.captureScreenshot(name: String(Date().timeIntervalSince1970), success: { CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message)) - self?.showToast(message) }, errorHandler: { (error) in let message = "Error while capturing screenshot - \(error?.localizedDescription ?? "Unknown")" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .error, message: message)) - self?.showToast(message) }) } + screenshotItemView.title = "Capture screenshot" screenshotItemView.statusView.isHidden = true cells.append(screenshotItemView) } } - + let logsItemView = SettingsItemView(frame: .zero) logsItemView.action = { let logsVC = CrowdinLogsVC() @@ -121,7 +119,7 @@ extension SettingsView { logsItemView.title = "Logs" logsItemView.statusView.isHidden = true cells.append(logsItemView) - + let stopItem = SettingsItemView(frame: .zero) stopItem.action = { CrowdinSDK.stop() diff --git a/Sources/CrowdinSDK/Settings/SettingsView/SettingsView.swift b/Sources/CrowdinSDK/Settings/SettingsView/SettingsView.swift index 374f6d80..f109030d 100644 --- a/Sources/CrowdinSDK/Settings/SettingsView/SettingsView.swift +++ b/Sources/CrowdinSDK/Settings/SettingsView/SettingsView.swift @@ -11,25 +11,25 @@ import UIKit final class SettingsView: UIView { static var shared: SettingsView? = SettingsView(frame: CGRect(x: 0, y: 0, width: 60, height: 60)) - + var settingsWindow = SettingsWindow() { didSet { settingsWindow.settingsView = self } } - + var cells = [SettingsItemView]() - + var blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) var settingsButton = UIButton() var closeButton = UIButton() var stackView = UIStackView() - + fileprivate let closedWidth: CGFloat = 60.0 fileprivate let openedWidth: CGFloat = 200.0 fileprivate let defaultItemHeight: CGFloat = 60.0 let enabledStatusColor = UIColor(red: 60.0 / 255.0, green: 130.0 / 255.0, blue: 130.0 / 255.0, alpha: 1.0) - + override init(frame: CGRect) { super.init(frame: frame) setupUI() @@ -40,13 +40,13 @@ final class SettingsView: UIView { super.init(coder: aDecoder) setupUI() } - + func setupUI() { addViews() layoutViews() setupViews() } - + func addViews() { translatesAutoresizingMaskIntoConstraints = false blurView.translatesAutoresizingMaskIntoConstraints = false @@ -58,7 +58,7 @@ final class SettingsView: UIView { addSubview(closeButton) addSubview(stackView) } - + func layoutViews() { addConstraints([ blurView.topAnchor.constraint(equalTo: topAnchor), @@ -82,7 +82,7 @@ final class SettingsView: UIView { ]) translatesAutoresizingMaskIntoConstraints = true } - + func setupViews() { settingsButton.setImage(UIImage(named: "settings-button", in: Bundle.module, compatibleWith: nil), for: .normal) settingsButton.addTarget(self, action: #selector(settingsButtonPressed), for: .touchUpInside) @@ -101,7 +101,7 @@ final class SettingsView: UIView { self.isUserInteractionEnabled = true gesture.delegate = self } - + var open: Bool = false { didSet { reloadData() @@ -111,20 +111,20 @@ final class SettingsView: UIView { closeButton.isHidden = !open } } - + func reloadData() { setupCells() stackView.arrangedSubviews.forEach({ stackView.removeArrangedSubview($0) }) cells.forEach({ stackView.addArrangedSubview($0) }) } - + var logsVC: UIViewController? = nil - + func dismissLogsVC() { logsVC?.cw_dismiss() logsVC = nil } - + func reloadUI() { if open == true { self.frame.size.height = CGFloat(defaultItemHeight + CGFloat(cells.count) * defaultItemHeight) @@ -134,20 +134,20 @@ final class SettingsView: UIView { self.frame.size.width = closedWidth } } - + // MARK: - IBActions - + @objc func settingsButtonPressed() { self.open = !self.open self.fixPositionIfNeeded() } - + @objc func closeButtonPressed() { open = false } - + // MARK: - Private func fixPositionIfNeeded() { let x = validateXCoordinate(value: self.center.x) @@ -157,7 +157,7 @@ final class SettingsView: UIView { self.center = CGPoint(x: x, y: y) } } - + func validateXCoordinate(value: CGFloat) -> CGFloat { guard let window = window else { return 0 } let minX = self.frame.size.width / 2.0 @@ -167,7 +167,7 @@ final class SettingsView: UIView { x = x > maxX ? maxX : x return x } - + func validateYCoordinate(value: CGFloat) -> CGFloat { guard let window = window else { return 0 } let minY = self.frame.size.height / 2.0 @@ -177,7 +177,7 @@ final class SettingsView: UIView { y = y > maxY ? maxY : y return y } - + func showConfirmationLogoutAlert() { let alert = UIAlertController(title: "CrowdinSDK", message: "Are you sure you want to log out?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self]_ in @@ -190,7 +190,7 @@ final class SettingsView: UIView { })) alert.cw_present() } - + func logout() { if let realtimeUpdateFeature = RealtimeUpdateFeature.shared, realtimeUpdateFeature.enabled { realtimeUpdateFeature.stop() @@ -199,18 +199,8 @@ final class SettingsView: UIView { $0.logout() } reloadData() - let message = "Successfully logout" - CrowdinLogsCollector.shared.add(log: .info(with: message)) - showToast(message) - } - - func showToast(_ message: String) { - DispatchQueue.main.async { [weak self] in - self?.makeToast(message) - } - - // Notify all subscribers about new log record is created - LogMessageObserver.shared.notifyAll(message) + + CrowdinLogsCollector.shared.add(log: .info(with: "Logged out")) } } From 33ddd1bb88f169d7467e8aadb10255a9e8342d89 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar Date: Fri, 3 May 2024 10:03:11 +0300 Subject: [PATCH 2/2] fix lint --- .../Settings/SettingsView/SettingsView+UITableView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift b/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift index 5b0670c5..fe08d11f 100644 --- a/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift +++ b/Sources/CrowdinSDK/Settings/SettingsView/SettingsView+UITableView.swift @@ -25,7 +25,7 @@ extension SettingsView { } let message = "Logged in" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message)) - }, error: { [weak self] error in + }, error: { error in let message = "Login error - \(error.localizedDescription)" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .error, message: message)) }) @@ -44,7 +44,7 @@ extension SettingsView { } let reloadItemView = SettingsItemView(frame: .zero) - reloadItemView.action = { [weak self] in + reloadItemView.action = { RefreshLocalizationFeature.refreshLocalization() let message = RealtimeUpdateFeature.shared?.enabled == true ? "Localization fetched from Crowdin project" : "Localization fetched from distribution" @@ -57,7 +57,7 @@ extension SettingsView { if LoginFeature.isLogined { if var feature = RealtimeUpdateFeature.shared { let realTimeUpdateItemView = SettingsItemView(frame: .zero) - feature.error = { [weak self] error in + feature.error = { error in let message = "Error while starting real-time preview - \(error.localizedDescription)" CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .error, message: message)) } @@ -90,7 +90,7 @@ extension SettingsView { if let feature = ScreenshotFeature.shared { let screenshotItemView = SettingsItemView(frame: .zero) - screenshotItemView.action = { [weak self] in + screenshotItemView.action = { let message = "Screenshot captured" feature.captureScreenshot(name: String(Date().timeIntervalSince1970), success: { CrowdinLogsCollector.shared.add(log: CrowdinLog(type: .info, message: message))