From 3500e7680aa292ac3b9c4c6c07ba02269eac24b2 Mon Sep 17 00:00:00 2001 From: Priyonto M Rahman Date: Sun, 3 Dec 2023 18:40:45 +0100 Subject: [PATCH] task: Implement toast based activity indicator #1600 - Replaces old implementation of indicator --- .../RuuviLogo/ActivityPresenterPosition.swift | 7 ++ .../ActivityPresenterRuuviLogo.swift | 77 +++++-------- .../RuuviLogo/ActivityPresenterState.swift | 26 +++++ .../View/ActivityPresenterView.swift | 107 ++++++++++++++++++ .../View/ActivityPresenterViewProvider.swift | 29 +++++ .../ActivityRuuviLogoViewController.swift | 76 ------------- .../RuuviLogo/View/ActivitySpinnerView.swift | 15 ++- .../RuuviPresenters/ActivityPresenter.swift | 13 ++- .../Error/Alert/ErrorPresenterAlert.swift | 7 +- .../Contents.json | 2 +- .../ruuvi_activity_presenter_logo.png} | Bin .../de.lproj/RuuviPresenters.strings | 3 + .../en.lproj/RuuviPresenters.strings | 3 + .../fi.lproj/RuuviPresenters.strings | 3 + .../fr.lproj/RuuviPresenters.strings | 3 + .../ru.lproj/RuuviPresenters.strings | 3 + .../sv.lproj/RuuviPresenters.strings | 3 + station.localization | 2 +- .../TagChartsViewInteractorOutput.swift | 1 - .../Presenter/TagChartsViewPresenter.swift | 17 +-- .../Home/Presenter/DashboardPresenter.swift | 4 +- .../Presenter/MyRuuviAccountPresenter.swift | 11 +- .../Share/Presenter/SharePresenter.swift | 8 +- .../SignIn/Presenter/SignInPresenter.swift | 20 ++-- .../Presenter/TagSettingsPresenter.swift | 10 +- .../Submodules/DFU/View/DFUViewModel.swift | 2 +- .../Presenter/SensorForceClaimPresenter.swift | 21 +--- .../Owner/Presenter/OwnerPresenter.swift | 23 ++-- .../Alert/Impl/AlertPresenterImpl.swift | 6 +- .../Strings/de.lproj/Localizable.strings | 6 + .../Strings/en.lproj/Localizable.strings | 6 + .../Strings/fi.lproj/Localizable.strings | 6 + .../Strings/fr.lproj/Localizable.strings | 6 + .../Strings/ru.lproj/Localizable.strings | 6 + .../Strings/sv.lproj/Localizable.strings | 6 + 35 files changed, 321 insertions(+), 217 deletions(-) create mode 100644 Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterPosition.swift create mode 100644 Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterState.swift create mode 100644 Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterView.swift create mode 100644 Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterViewProvider.swift delete mode 100644 Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityRuuviLogoViewController.swift rename Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/{ruuvi_logo_for_activity_presenter.imageset => ruuvi_activity_presenter_logo.imageset}/Contents.json (68%) rename Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/{ruuvi_logo_for_activity_presenter.imageset/web-ruuvi-eye-nega.png => ruuvi_activity_presenter_logo.imageset/ruuvi_activity_presenter_logo.png} (100%) diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterPosition.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterPosition.swift new file mode 100644 index 000000000..7b8d675c1 --- /dev/null +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterPosition.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum ActivityPresenterPosition { + case top + case bottom + case center +} diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterRuuviLogo.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterRuuviLogo.swift index 340e07d42..1dc2b30a2 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterRuuviLogo.swift +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterRuuviLogo.swift @@ -1,74 +1,51 @@ import UIKit public final class ActivityPresenterRuuviLogo: ActivityPresenter { - var counter = 0 { - didSet { - switch counter { - case 0: - hide() - case 1: - show() - default: - return - } - } - } - let minAnimationTime: CFTimeInterval = 0.75 + let minAnimationTime: CFTimeInterval = 1.0 var startTime: CFTimeInterval? let window = UIWindow(frame: UIScreen.main.bounds) - let hudViewController: ActivityRuuviLogoViewController + let activityPresenterViewProvider: ActivityPresenterViewProvider + let activityPresenterViewController: UIViewController + let stateHolder = ActivityPresenterStateHolder() + weak var appWindow: UIWindow? public init() { - hudViewController = ActivityRuuviLogoViewController() + activityPresenterViewProvider = ActivityPresenterViewProvider(stateHolder: stateHolder) + activityPresenterViewController = activityPresenterViewProvider.makeViewController() + activityPresenterViewController.view.backgroundColor = .clear window.windowLevel = .normal - hudViewController.view.translatesAutoresizingMaskIntoConstraints = false - window.rootViewController = hudViewController - } - - public func increment() { - counter += 1 - hideMessageLabel() - } - - public func increment(with message: String) { - counter += 1 - showMessageLabel(with: message) + activityPresenterViewController.view.translatesAutoresizingMaskIntoConstraints = false + window.rootViewController = activityPresenterViewController } +} - public func decrement() { - guard counter > 0 else { - return - } - counter -= 1 +extension ActivityPresenterRuuviLogo { + public func setPosition(_ position: ActivityPresenterPosition) { + activityPresenterViewProvider.updatePosition(position) } - private func show() { + public func show(with state: ActivityPresenterState) { startTime = CFAbsoluteTimeGetCurrent() appWindow = UIWindow.key window.makeKeyAndVisible() - hudViewController.spinnerView.animate() + window.layoutIfNeeded() + activityPresenterViewProvider.updateState(state) } - private func showMessageLabel(with message: String) { - hudViewController.messageLabel.alpha = 1 - hudViewController.messageLabel.text = message + public func update(with state: ActivityPresenterState) { + activityPresenterViewProvider.updateState(state) } - private func hide() { + public func dismiss(immediately: Bool) { let executionTime = CFAbsoluteTimeGetCurrent() - (startTime ?? 0) - let additionalWaitTime = executionTime < minAnimationTime ? (minAnimationTime - executionTime) : 0 - DispatchQueue.main.asyncAfter(deadline: .now() + additionalWaitTime) { - self.appWindow?.makeKeyAndVisible() - self.appWindow = nil - self.window.isHidden = true - self.hudViewController.spinnerView.stopAnimating() - self.hideMessageLabel() + let additionalWaitTime = immediately ? 0 : + executionTime < minAnimationTime ? (minAnimationTime - executionTime) : 0 + DispatchQueue.main.asyncAfter(deadline: .now() + additionalWaitTime) { [weak self] in + self?.activityPresenterViewProvider.updateState(.dismiss) + self?.appWindow?.makeKeyAndVisible() + self?.appWindow = nil + self?.window.isHidden = true } } - - private func hideMessageLabel() { - hudViewController.messageLabel.alpha = 0 - hudViewController.messageLabel.text = nil - } } diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterState.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterState.swift new file mode 100644 index 000000000..4534bbf82 --- /dev/null +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/ActivityPresenterState.swift @@ -0,0 +1,26 @@ +import Foundation + +public enum ActivityPresenterState: Equatable { + case loading(message: String?) + case success(message: String?) + case failed(message: String?) + case dismiss + + public static func == ( + lhs: ActivityPresenterState, + rhs: ActivityPresenterState + ) -> Bool { + switch (lhs, rhs) { + case (.loading(let lhsMessage), .loading(let rhsMessage)): + return lhsMessage == rhsMessage + case (.success(let lhsMessage), .success(let rhsMessage)): + return lhsMessage == rhsMessage + case (.failed(let lhsMessage), .failed(let rhsMessage)): + return lhsMessage == rhsMessage + case (.dismiss, .dismiss): + return true + default: + return false + } + } +} diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterView.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterView.swift new file mode 100644 index 000000000..bd4f999b3 --- /dev/null +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterView.swift @@ -0,0 +1,107 @@ +import UIKit +import SwiftUI + +private struct ActivityPresenterAssets { + static let activityOngoingDefault = "activity_ongoing_generic" + static let activitySuccessDefault = "activity_success_generic" + static let activityFailedDefault = "activity_failed_generic" + + static let activityLogoRuuvi = "ruuvi_activity_presenter_logo" +} + +public struct ActivityPresenterView: View { + @EnvironmentObject var stateHolder: ActivityPresenterStateHolder + + public var body: some View { + VStack { + if stateHolder.position == .bottom || stateHolder.position == .center { + Spacer() + } + + ActivityPresenterContentView(state: stateHolder.state) + .padding([.leading, .trailing], + stateHolder.state == .dismiss ? 0 : 12) + .padding([.top, .bottom], + stateHolder.state == .dismiss ? 0 : 12) + .background(Color.black.opacity(0.8)) + .cornerRadius(8) + .foregroundColor(.white) + .transition(.scale.combined(with: .opacity)) + .opacity(stateHolder.state == .dismiss ? 0 : 1) + + if stateHolder.position == .top || stateHolder.position == .center { + Spacer() + } + } + } +} + +struct ActivityPresenterContentView: View { + let state: ActivityPresenterState + + var body: some View { + HStack(spacing: 8) { + if case .loading = state { + ZStack { + contentImage? + .resizable() + .frame(width: 24, height: 24) + ActivitySpinnerViewRepresentable() + .frame(width: 30, height: 30) + } + } else { + contentImage? + .resizable() + .frame(width: 12, height: 12) + } + Text(message) + } + } + + private var contentImage: Image? { + switch state { + case .loading: + return Image( + ActivityPresenterAssets.activityLogoRuuvi, + bundle: .pod(ActivityPresenterViewProvider.self) + ) + case .success: + return Image(systemName: "checkmark") + case .failed: + return Image(systemName: "xmark") + default: + return nil + } + } + + private var message: String { + switch state { + case .loading(let message): + if let message = message { + return message + } else { + return ActivityPresenterAssets + .activityOngoingDefault + .localized(for: ActivityPresenterViewProvider.self) + } + case .success(let message): + if let message = message { + return message + } else { + return ActivityPresenterAssets + .activitySuccessDefault + .localized(for: ActivityPresenterViewProvider.self) + } + case .failed(let message): + if let message = message { + return message + } else { + return ActivityPresenterAssets + .activityFailedDefault + .localized(for: ActivityPresenterViewProvider.self) + } + case .dismiss: + return "" // Placeholder for dismiss state + } + } +} diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterViewProvider.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterViewProvider.swift new file mode 100644 index 000000000..8feb10916 --- /dev/null +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityPresenterViewProvider.swift @@ -0,0 +1,29 @@ +import UIKit +import SwiftUI + +public class ActivityPresenterStateHolder: ObservableObject { + @Published var state: ActivityPresenterState = .dismiss + @Published var position: ActivityPresenterPosition = .bottom +} + +public class ActivityPresenterViewProvider: NSObject { + private var stateHolder: ActivityPresenterStateHolder + + public init(stateHolder: ActivityPresenterStateHolder) { + self.stateHolder = stateHolder + } + + public func makeViewController() -> UIViewController { + return UIHostingController( + rootView: ActivityPresenterView().environmentObject(stateHolder) + ) + } + + func updateState(_ newState: ActivityPresenterState) { + self.stateHolder.state = newState + } + + func updatePosition(_ position: ActivityPresenterPosition) { + self.stateHolder.position = position + } +} diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityRuuviLogoViewController.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityRuuviLogoViewController.swift deleted file mode 100644 index c50eab597..000000000 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivityRuuviLogoViewController.swift +++ /dev/null @@ -1,76 +0,0 @@ -import UIKit - -public final class ActivityRuuviLogoViewController: UIViewController { - var statusBarStyle = UIStatusBarStyle.default - var statusBarHidden = false - - private var logoImageView = UIImageView() - var spinnerView = ActivitySpinnerView() - var messageLabel = UILabel() - - public init() { - super.init(nibName: nil, bundle: nil) - setupView() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public override func viewDidLoad() { - super.viewDidLoad() - logoImageView.tintColor = UIColor.white - messageLabel.textColor = .white - } - - public override var preferredStatusBarStyle: UIStatusBarStyle { - guard let topVC = UIApplication.shared.topViewController() else { return statusBarStyle } - if !topVC.isKind(of: ActivityRuuviLogoViewController.self) { - statusBarStyle = topVC.preferredStatusBarStyle - } - return statusBarStyle - } - - public override var prefersStatusBarHidden: Bool { - guard let topVC = UIApplication.shared.topViewController() else { return statusBarHidden } - if !topVC.isKind(of: ActivityRuuviLogoViewController.self) { - statusBarHidden = topVC.prefersStatusBarHidden - } - return statusBarHidden - } - - private func setupView() { - view.backgroundColor = UIColor(white: 0, alpha: 0.8) - - logoImageView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(logoImageView) - NSLayoutConstraint.activate([ - logoImageView.widthAnchor.constraint(equalToConstant: 64), - logoImageView.heightAnchor.constraint(equalToConstant: 64), - logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), - logoImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor), - ]) - - logoImageView.image = UIImage.named("ruuvi_logo_for_activity_presenter", for: Self.self) - - spinnerView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(spinnerView) - NSLayoutConstraint.activate([ - spinnerView.widthAnchor.constraint(equalToConstant: 80), - spinnerView.heightAnchor.constraint(equalToConstant: 80), - spinnerView.centerXAnchor.constraint(equalTo: logoImageView.centerXAnchor), - spinnerView.centerYAnchor.constraint(equalTo: logoImageView.centerYAnchor), - ]) - - messageLabel.numberOfLines = 0 - messageLabel.textAlignment = .center - messageLabel.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(messageLabel) - NSLayoutConstraint.activate([ - messageLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), - view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16), - messageLabel.topAnchor.constraint(equalTo: spinnerView.bottomAnchor, constant: 16), - ]) - } -} diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivitySpinnerView.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivitySpinnerView.swift index 42d5978a1..2fc57ef06 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivitySpinnerView.swift +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Activity/RuuviLogo/View/ActivitySpinnerView.swift @@ -1,6 +1,19 @@ import UIKit +import SwiftUI + +// UIViewRepresentable wrapper for ActivitySpinnerView +struct ActivitySpinnerViewRepresentable: UIViewRepresentable { + func makeUIView(context: Context) -> ActivitySpinnerView { + let spinnerView = ActivitySpinnerView() + spinnerView.animate() + return spinnerView + } + + func updateUIView(_ uiView: ActivitySpinnerView, context: Context) { + // No op + } +} -@IBDesignable class ActivitySpinnerView: UIView { private var strokeColor = UIColor(red: 0.21, green: 0.68, blue: 0.62, alpha: 1.00) diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/ActivityPresenter.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/ActivityPresenter.swift index 6850e281c..084b591bd 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/ActivityPresenter.swift +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/ActivityPresenter.swift @@ -1,7 +1,14 @@ import Foundation public protocol ActivityPresenter { - func increment() - func increment(with message: String) - func decrement() + func setPosition(_ position: ActivityPresenterPosition) + func show(with state: ActivityPresenterState) + func update(with state: ActivityPresenterState) + func dismiss(immediately: Bool) +} + +public extension ActivityPresenter { + func dismiss(immediately: Bool = false) { + dismiss(immediately: immediately) + } } diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Error/Alert/ErrorPresenterAlert.swift b/Common/RuuviPresenters/Sources/RuuviPresenters/Error/Alert/ErrorPresenterAlert.swift index 3c6f5bd9c..2c217c1d2 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Error/Alert/ErrorPresenterAlert.swift +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Error/Alert/ErrorPresenterAlert.swift @@ -22,14 +22,9 @@ public final class ErrorPresenterAlert: ErrorPresenter { let group = DispatchGroup() DispatchQueue.main.async { group.enter() - let topViewController = UIApplication.shared.topViewController() - var fireAfter: DispatchTimeInterval = .milliseconds(0) - if topViewController is ActivityRuuviLogoViewController { - fireAfter = .milliseconds(750) - } group.leave() group.notify(queue: .main) { - DispatchQueue.main.asyncAfter(deadline: .now() + fireAfter) { + DispatchQueue.main.async { let feedback = UINotificationFeedbackGenerator() feedback.notificationOccurred(.error) feedback.prepare() diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_logo_for_activity_presenter.imageset/Contents.json b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_activity_presenter_logo.imageset/Contents.json similarity index 68% rename from Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_logo_for_activity_presenter.imageset/Contents.json rename to Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_activity_presenter_logo.imageset/Contents.json index 265ff6156..7164fe16b 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_logo_for_activity_presenter.imageset/Contents.json +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_activity_presenter_logo.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "web-ruuvi-eye-nega.png", + "filename" : "ruuvi_activity_presenter_logo.png", "idiom" : "universal" } ], diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_logo_for_activity_presenter.imageset/web-ruuvi-eye-nega.png b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_activity_presenter_logo.imageset/ruuvi_activity_presenter_logo.png similarity index 100% rename from Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_logo_for_activity_presenter.imageset/web-ruuvi-eye-nega.png rename to Common/RuuviPresenters/Sources/RuuviPresenters/Resources/RuuviPresenters.xcassets/ruuvi_activity_presenter_logo.imageset/ruuvi_activity_presenter_logo.png diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/de.lproj/RuuviPresenters.strings b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/de.lproj/RuuviPresenters.strings index 04fc50a21..8c6a6e367 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/de.lproj/RuuviPresenters.strings +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/de.lproj/RuuviPresenters.strings @@ -6,3 +6,6 @@ "PermissionPresenter.settings" = "Einstellungen"; "PermissionPresenter.NoPushNotificationsPermission.message" = "Ruuvi Station benötigt die Berechtigung für Push-Benachrichtigungen, um diese Funktion zu aktivieren"; "Cancel" = "Abbrechen"; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/en.lproj/RuuviPresenters.strings b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/en.lproj/RuuviPresenters.strings index 388ecf8fc..f06d8f267 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/en.lproj/RuuviPresenters.strings +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/en.lproj/RuuviPresenters.strings @@ -6,3 +6,6 @@ "PermissionPresenter.settings" = "Settings"; "PermissionPresenter.NoPushNotificationsPermission.message" = "Ruuvi Station needs Push Notifications permission to enable this feature"; "Cancel" = "Cancel"; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fi.lproj/RuuviPresenters.strings b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fi.lproj/RuuviPresenters.strings index 540220e1b..97a76334a 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fi.lproj/RuuviPresenters.strings +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fi.lproj/RuuviPresenters.strings @@ -6,3 +6,6 @@ "PermissionPresenter.settings" = "Asetukset"; "PermissionPresenter.NoPushNotificationsPermission.message" = "Tämä ominaisuus vaatii oikeuden palveluilmoitusten näyttämiseen."; "Cancel" = "Peruuta"; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fr.lproj/RuuviPresenters.strings b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fr.lproj/RuuviPresenters.strings index 8af2b7633..af478a56a 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fr.lproj/RuuviPresenters.strings +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/fr.lproj/RuuviPresenters.strings @@ -6,3 +6,6 @@ "PermissionPresenter.settings" = "Réglages"; "PermissionPresenter.NoPushNotificationsPermission.message" = "Veuillez autoriser les notifications dans les réglages de l'appareil."; "Cancel" = "Annuler"; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/ru.lproj/RuuviPresenters.strings b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/ru.lproj/RuuviPresenters.strings index 5642f7623..f5c97052c 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/ru.lproj/RuuviPresenters.strings +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/ru.lproj/RuuviPresenters.strings @@ -6,3 +6,6 @@ "PermissionPresenter.settings" = "Настройки"; "PermissionPresenter.NoPushNotificationsPermission.message" = "Ruuvi Station необходим доступ к уведомлениям для того, чтобы использовать эту функцию"; "Cancel" = "Отмена"; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/sv.lproj/RuuviPresenters.strings b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/sv.lproj/RuuviPresenters.strings index 27d00de4c..1fb12d7d6 100644 --- a/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/sv.lproj/RuuviPresenters.strings +++ b/Common/RuuviPresenters/Sources/RuuviPresenters/Resources/sv.lproj/RuuviPresenters.strings @@ -6,3 +6,6 @@ "PermissionPresenter.settings" = "Inställningar"; "PermissionPresenter.NoPushNotificationsPermission.message" = "Ruuvi Station behöver behörighett ill Push-meddelanden för att aktivera den här funktionen."; "Cancel" = "Avbryt"; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/station.localization b/station.localization index 45c2e8f00..abd81752b 160000 --- a/station.localization +++ b/station.localization @@ -1 +1 @@ -Subproject commit 45c2e8f008e6d9c94a259b120f7ea190087aa94e +Subproject commit abd81752bbe8a799ff315786287c614b893f6816 diff --git a/station/Classes/Presentation/Modules/Dashboard/Charts/Interactor/TagChartsViewInteractorOutput.swift b/station/Classes/Presentation/Modules/Dashboard/Charts/Interactor/TagChartsViewInteractorOutput.swift index e68d3c750..89299bb21 100644 --- a/station/Classes/Presentation/Modules/Dashboard/Charts/Interactor/TagChartsViewInteractorOutput.swift +++ b/station/Classes/Presentation/Modules/Dashboard/Charts/Interactor/TagChartsViewInteractorOutput.swift @@ -2,7 +2,6 @@ import Foundation import RuuviOntology protocol TagChartsViewInteractorOutput: AnyObject { - var isLoading: Bool { get set } func insertMeasurements(_ newValues: [RuuviMeasurement]) func updateLatestRecord(_ record: RuuviTagSensorRecord) func interactorDidError(_ error: RUError) diff --git a/station/Classes/Presentation/Modules/Dashboard/Charts/Presenter/TagChartsViewPresenter.swift b/station/Classes/Presentation/Modules/Dashboard/Charts/Presenter/TagChartsViewPresenter.swift index b6f9cd155..6592c1f46 100644 --- a/station/Classes/Presentation/Modules/Dashboard/Charts/Presenter/TagChartsViewPresenter.swift +++ b/station/Classes/Presentation/Modules/Dashboard/Charts/Presenter/TagChartsViewPresenter.swift @@ -52,15 +52,6 @@ class TagChartsViewPresenter: NSObject, TagChartsViewModuleInput { var infoProvider: InfoProvider! private var isSyncing: Bool = false - var isLoading: Bool = false { - didSet { - if isLoading { - activityPresenter.increment() - } else { - activityPresenter.decrement() - } - } - } private var output: TagChartsViewModuleOutput? private var advertisementToken: ObservationToken? @@ -235,12 +226,12 @@ extension TagChartsViewPresenter: TagChartsViewOutput { } func viewDidConfirmToClear(for viewModel: TagChartsViewModel) { - isLoading = true + activityPresenter.show(with: .loading(message: nil)) interactor.deleteAllRecords(for: ruuviTag) .on(failure: {[weak self] (error) in self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.isLoading = false + self?.activityPresenter.dismiss(immediately: true) }) } @@ -254,14 +245,14 @@ extension TagChartsViewPresenter: TagChartsViewOutput { } func viewDidTapOnExport() { - isLoading = true + activityPresenter.show(with: .loading(message: nil)) exportService.csvLog(for: ruuviTag.id, settings: sensorSettings) .on(success: { [weak self] url in self?.view?.showExportSheet(with: url) }, failure: { [weak self] (error) in self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.isLoading = false + self?.activityPresenter.dismiss(immediately: true) }) } diff --git a/station/Classes/Presentation/Modules/Dashboard/Home/Presenter/DashboardPresenter.swift b/station/Classes/Presentation/Modules/Dashboard/Home/Presenter/DashboardPresenter.swift index 57d041a42..e203939e5 100644 --- a/station/Classes/Presentation/Modules/Dashboard/Home/Presenter/DashboardPresenter.swift +++ b/station/Classes/Presentation/Modules/Dashboard/Home/Presenter/DashboardPresenter.swift @@ -1682,7 +1682,7 @@ extension DashboardPresenter { /// Log out user if the auth token is expired. private func forceLogoutUser() { guard ruuviUser.isAuthorized else { return } - activityPresenter.increment() + activityPresenter.show(with: .loading(message: nil)) cloudNotificationService.unregister( token: pnManager.fcmToken, tokenId: nil @@ -1703,7 +1703,7 @@ extension DashboardPresenter { self?.reloadWidgets() self?.handleCloudModeState() }, completion: { [weak self] in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss() }) } diff --git a/station/Classes/Presentation/Modules/My Ruuvi/Presenter/MyRuuviAccountPresenter.swift b/station/Classes/Presentation/Modules/My Ruuvi/Presenter/MyRuuviAccountPresenter.swift index 791619ffe..690d0ad12 100644 --- a/station/Classes/Presentation/Modules/My Ruuvi/Presenter/MyRuuviAccountPresenter.swift +++ b/station/Classes/Presentation/Modules/My Ruuvi/Presenter/MyRuuviAccountPresenter.swift @@ -34,14 +34,16 @@ extension MyRuuviAccountPresenter: MyRuuviAccountViewOutput { func viewDidTapDeleteButton() { guard let email = ruuviUser.email else { return } - activityPresenter.increment() + activityPresenter.show(with: .loading(message: nil)) ruuviCloud.deleteAccount(email: email).on(success: { [weak self] _ in + self?.activityPresenter.update(with: .success(message: nil)) self?.view.viewDidShowAccountDeletionConfirmation() }, failure: { [weak self] error in + self?.activityPresenter.update(with: .failed(message: nil)) self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss(immediately: false) }) } @@ -75,13 +77,14 @@ extension MyRuuviAccountPresenter { let confirmAction = UIAlertAction(title: confirmActionTitle, style: .default) { [weak self] (_) in guard let sSelf = self else { return } - sSelf.activityPresenter.increment() + sSelf.activityPresenter.show(with: .loading(message: nil)) sSelf.cloudNotificationService.unregister( token: sSelf.pnManager.fcmToken, tokenId: nil ).on(success: { _ in sSelf.pnManager.fcmToken = nil sSelf.pnManager.fcmTokenLastRefreshed = nil + sSelf.activityPresenter.update(with: .success(message: nil)) }) sSelf.authService.logout() @@ -91,7 +94,7 @@ extension MyRuuviAccountPresenter { sSelf.syncViewModel() sSelf.reloadWidgets() }, completion: { [weak self] in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss() }) } let cancleAction = UIAlertAction(title: cancelActionTitle, diff --git a/station/Classes/Presentation/Modules/Share/Presenter/SharePresenter.swift b/station/Classes/Presentation/Modules/Share/Presenter/SharePresenter.swift index b4b7c5ab3..ebf527cfe 100644 --- a/station/Classes/Presentation/Modules/Share/Presenter/SharePresenter.swift +++ b/station/Classes/Presentation/Modules/Share/Presenter/SharePresenter.swift @@ -48,7 +48,7 @@ extension SharePresenter: ShareViewOutput { return } - activityPresenter.increment() + activityPresenter.show(with: .loading(message: nil)) ruuviOwnershipService .share(macId: sensor.id.mac, with: email) .on(success: { [weak self] result in @@ -63,12 +63,12 @@ extension SharePresenter: ShareViewOutput { }, failure: { [weak self] error in self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss() }) } private func unshareTag(_ email: String) { - activityPresenter.increment() + activityPresenter.show(with: .loading(message: nil)) ruuviOwnershipService .unshare(macId: sensor.id.mac, with: email) .on(success: { [weak self] _ in @@ -76,7 +76,7 @@ extension SharePresenter: ShareViewOutput { }, failure: { [weak self] error in self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss() }) } diff --git a/station/Classes/Presentation/Modules/SignIn/Presenter/SignInPresenter.swift b/station/Classes/Presentation/Modules/SignIn/Presenter/SignInPresenter.swift index 94d96a99e..653039973 100644 --- a/station/Classes/Presentation/Modules/SignIn/Presenter/SignInPresenter.swift +++ b/station/Classes/Presentation/Modules/SignIn/Presenter/SignInPresenter.swift @@ -126,7 +126,7 @@ extension SignInPresenter { } private func sendVerificationCode(for email: String) { - activityPresenter.increment() + activityPresenter.show(with: .loading(message: nil)) ruuviCloud.requestCode(email: email) .on(success: { [weak self] email in guard let sSelf = self else { return } @@ -136,12 +136,12 @@ extension SignInPresenter { }, failure: { [weak self] (error) in self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss() }) } private func verify(_ code: String) { - activityPresenter.increment(with: "SignIn.Sync.message".localized()) + activityPresenter.show(with: .loading(message: "SignIn.Sync.message".localized())) ruuviCloud.validateCode(code: code) .on(success: { [weak self] result in guard let sSelf = self else { return } @@ -156,26 +156,26 @@ extension SignInPresenter { sSelf.registerFCMToken() sSelf.cloudSyncService.syncAllRecords().on(success: { [weak sSelf] _ in guard let ssSelf = sSelf else { return } - ssSelf.activityPresenter.decrement() ssSelf.cloudSyncDaemon.start() ssSelf.output?.signIn(module: ssSelf, didSuccessfulyLogin: nil) sSelf?.settings.isSyncing = false }, failure: { [weak self] error in - self?.activityPresenter.decrement() self?.errorPresenter.present(error: error) + }, completion: { [weak self] in + self?.activityPresenter.dismiss() }) } else if let requestedEmail = sSelf.ruuviUser.email { - sSelf.activityPresenter.decrement() + sSelf.activityPresenter.dismiss() sSelf.view.showEmailsAreDifferent( requestedEmail: requestedEmail, validatedEmail: result.email ) } else { sSelf.view.showFailedToGetRequestedEmail() - sSelf.activityPresenter.decrement() + sSelf.activityPresenter.dismiss() } }, failure: { [weak self] (error) in - self?.activityPresenter.decrement() + self?.activityPresenter.dismiss() self?.view.showInvalidTokenEntered() self?.errorPresenter.present(error: error) }) @@ -214,7 +214,7 @@ extension SignInPresenter { @objc private func handleAppEnterForgroundState() { switch state { case .isSyncing: - activityPresenter.increment(with: "SignIn.Sync.message".localized()) + activityPresenter.show(with: .loading(message: "SignIn.Sync.message".localized())) default: return } @@ -223,7 +223,7 @@ extension SignInPresenter { @objc private func handleAppEnterBackgroundState() { switch state { case .isSyncing: - activityPresenter.decrement() + activityPresenter.dismiss() default: return } diff --git a/station/Classes/Presentation/Modules/TagSettings/Presenter/TagSettingsPresenter.swift b/station/Classes/Presentation/Modules/TagSettings/Presenter/TagSettingsPresenter.swift index 788205a2f..824e6c84a 100644 --- a/station/Classes/Presentation/Modules/TagSettings/Presenter/TagSettingsPresenter.swift +++ b/station/Classes/Presentation/Modules/TagSettings/Presenter/TagSettingsPresenter.swift @@ -86,15 +86,7 @@ class TagSettingsPresenter: NSObject, TagSettingsModuleInput { syncOffsetCorrection() } } - private var isLoading: Bool = false { - didSet { - if isLoading { - activityPresenter.increment() - } else { - activityPresenter.decrement() - } - } - } + private var firmwareUpdateDialogShown: Bool = false private var timer: Timer? diff --git a/station/Classes/Presentation/Modules/TagSettings/Submodules/DFU/View/DFUViewModel.swift b/station/Classes/Presentation/Modules/TagSettings/Submodules/DFU/View/DFUViewModel.swift index dab7709c2..e3ecbf1a1 100644 --- a/station/Classes/Presentation/Modules/TagSettings/Submodules/DFU/View/DFUViewModel.swift +++ b/station/Classes/Presentation/Modules/TagSettings/Submodules/DFU/View/DFUViewModel.swift @@ -35,7 +35,7 @@ final class DFUViewModel: ObservableObject { var isLoading: Bool = false { didSet { - isLoading ? activityPresenter.increment() : activityPresenter.decrement() + isLoading ? activityPresenter.show(with: .loading(message: nil)) : activityPresenter.dismiss() } } diff --git a/station/Classes/Presentation/Modules/TagSettings/Submodules/Force Claim/Presenter/SensorForceClaimPresenter.swift b/station/Classes/Presentation/Modules/TagSettings/Submodules/Force Claim/Presenter/SensorForceClaimPresenter.swift index bbd9d27d0..dad1a507e 100644 --- a/station/Classes/Presentation/Modules/TagSettings/Submodules/Force Claim/Presenter/SensorForceClaimPresenter.swift +++ b/station/Classes/Presentation/Modules/TagSettings/Submodules/Force Claim/Presenter/SensorForceClaimPresenter.swift @@ -21,15 +21,7 @@ final class SensorForceClaimPresenter: SensorForceClaimModuleInput { private var ruuviTag: RuuviTagSensor? private var secret: String? - private var isLoading: Bool = false { - didSet { - if isLoading { - activityPresenter.increment() - } else { - activityPresenter.decrement() - } - } - } + private var timer: Timer? private var gattTimeoutSeconds: Double = 15 @@ -90,7 +82,7 @@ extension SensorForceClaimPresenter { withTimeInterval: gattTimeoutSeconds, repeats: true, block: { [weak self] (_) in - self?.isLoading = false + self?.activityPresenter.dismiss() self?.invalidateTimer() self?.view?.showGATTConnectionTimeoutDialog() }) @@ -106,8 +98,7 @@ extension SensorForceClaimPresenter { guard let luid = ruuviTag?.luid else { return } - isLoading = true - // TODO: Check the timeout issue. Timeout not trigerred now. + activityPresenter.show(with: .loading(message: nil)) background.services.gatt.serialRevision( for: self, uuid: luid.value, @@ -117,8 +108,8 @@ extension SensorForceClaimPresenter { case .success(let secret): self?.contestSensor(with: secret) case .failure(let error): + self?.activityPresenter.dismiss() self?.errorPresenter.present(error: error) - self?.isLoading = false } } } @@ -128,7 +119,7 @@ extension SensorForceClaimPresenter { guard let ruuviTag = ruuviTag, let secret = secret else { return } - isLoading = true + activityPresenter.show(with: .loading(message: nil)) ruuviOwnershipService .contest(sensor: ruuviTag, secret: secret) .on(success: { [weak self] _ in @@ -136,7 +127,7 @@ extension SensorForceClaimPresenter { }, failure: { [weak self] error in self?.errorPresenter.present(error: error) }, completion: { [weak self] in - self?.isLoading = false + self?.activityPresenter.dismiss() }) } diff --git a/station/Classes/Presentation/Modules/TagSettings/Submodules/Owner/Presenter/OwnerPresenter.swift b/station/Classes/Presentation/Modules/TagSettings/Submodules/Owner/Presenter/OwnerPresenter.swift index 853ff5759..5d3f66d7e 100644 --- a/station/Classes/Presentation/Modules/TagSettings/Submodules/Owner/Presenter/OwnerPresenter.swift +++ b/station/Classes/Presentation/Modules/TagSettings/Submodules/Owner/Presenter/OwnerPresenter.swift @@ -29,15 +29,6 @@ final class OwnerPresenter: OwnerModuleInput { view.mode = ownershipMode } } - private var isLoading: Bool = false { - didSet { - if isLoading { - activityPresenter.increment() - } else { - activityPresenter.decrement() - } - } - } func configure(ruuviTag: RuuviTagSensor, mode: OwnershipMode) { self.ruuviTag = ruuviTag @@ -102,12 +93,13 @@ extension OwnerPresenter { } private func claimSensor() { - isLoading = true + activityPresenter.show(with: .loading(message: nil)) ruuviOwnershipService .claim(sensor: ruuviTag) .on(success: { [weak self] _ in self?.router.dismiss() self?.removeConnection() + self?.activityPresenter.show(with: .success(message: nil)) }, failure: { [weak self] error in switch error { case .ruuviCloud(.api(.api(.erSensorAlreadyClaimed))): @@ -116,15 +108,15 @@ extension OwnerPresenter { } self?.view.showSensorAlreadyClaimedDialog() default: - self?.errorPresenter.present(error: error) + self?.activityPresenter.show(with: .failed(message: error.localizedDescription)) } }, completion: { [weak self] in - self?.isLoading = false + self?.activityPresenter.dismiss() }) } private func unclaimSensor(removeCloudHistory: Bool) { - isLoading = true + activityPresenter.show(with: .loading(message: nil)) ruuviOwnershipService .unclaim( sensor: ruuviTag, @@ -132,10 +124,11 @@ extension OwnerPresenter { ) .on(success: { [weak self] _ in self?.router.dismiss() + self?.activityPresenter.update(with: .success(message: nil)) }, failure: { [weak self] error in - self?.errorPresenter.present(error: error) + self?.activityPresenter.show(with: .failed(message: error.localizedDescription)) }, completion: { [weak self] in - self?.isLoading = false + self?.activityPresenter.dismiss() }) } } diff --git a/station/Classes/Presentation/Presenters/Alert/Impl/AlertPresenterImpl.swift b/station/Classes/Presentation/Presenters/Alert/Impl/AlertPresenterImpl.swift index b489f0b85..cf5934113 100644 --- a/station/Classes/Presentation/Presenters/Alert/Impl/AlertPresenterImpl.swift +++ b/station/Classes/Presentation/Presenters/Alert/Impl/AlertPresenterImpl.swift @@ -11,13 +11,9 @@ class AlertPresenterImpl: AlertPresenter { DispatchQueue.main.async { group.enter() let topViewController = UIApplication.shared.topViewController() - var fireAfter: DispatchTimeInterval = .milliseconds(0) - if topViewController is ActivityRuuviLogoViewController { - fireAfter = .milliseconds(750) - } group.leave() group.notify(queue: .main) { - DispatchQueue.main.asyncAfter(deadline: .now() + fireAfter) { + DispatchQueue.main.async { let feedback = UINotificationFeedbackGenerator() feedback.notificationOccurred(.error) feedback.prepare() diff --git a/station/Resources/Strings/de.lproj/Localizable.strings b/station/Resources/Strings/de.lproj/Localizable.strings index b6072e237..0779fa1a1 100644 --- a/station/Resources/Strings/de.lproj/Localizable.strings +++ b/station/Resources/Strings/de.lproj/Localizable.strings @@ -751,3 +751,9 @@ Wenn die Beanspruchung nicht erfolgreich war oder NFC auf Ihrem Gerät nicht ver "remove_claimed_sensor_description" = "Durch das Entfernen des Sensors wird Ihr Sensor-Eigentumsstatus aufgehoben, und Sensor-Einstellungen wie Name, Hintergrundbild, Kalibrierungseinstellungen und Alarmeinstellungen werden entfernt. Nach der Entfernung kann jemand anderes das Eigentum am Sensor beanspruchen. Jeder Ruuvi-Sensor kann nur einen Besitzer haben."; "remove_shared_sensor_description" = "Wenn Sie diesen freigegebenen Sensor entfernen, wird der Eigentümer des Sensors benachrichtigt und Sie können nicht mehr auf den Sensor zugreifen.\n\nSie verlieren außerdem alle zugehörigen Sensoreinstellungen wie Name, Hintergrundbild und Alarmkonfigurationen ."; "remove_local_sensor_description" = "Wenn Sie diesen Sensor entfernen, führt dies zur Löschung Ihres lokal gespeicherten Messverlaufs sowie zur Entfernung aller zugehörigen Sensoreinstellungen wie Name, Hintergrundbild, Kalibrierung und Alarmkonfigurationen.\n\nSie können hinzufügen diesen Sensor später bei Bedarf erneut."; +"activity_saving_to_cloud" = "Speichern in der Cloud...bitte warten."; +"activity_saving_success" = "Erfolgreich gespeichert."; +"activity_saving_fail" = "Die Änderungen konnten nicht in der Cloud gespeichert werden."; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/station/Resources/Strings/en.lproj/Localizable.strings b/station/Resources/Strings/en.lproj/Localizable.strings index 7e28af6cd..87102a26d 100644 --- a/station/Resources/Strings/en.lproj/Localizable.strings +++ b/station/Resources/Strings/en.lproj/Localizable.strings @@ -746,3 +746,9 @@ Your RuuviTag sensor is ready for use!"; "remove_claimed_sensor_description" = "By removing the sensor, your sensor ownership status will be revoked and sensor settings, such as name, background image, calibration settings and alert settings will be removed. After removal, someone else can claim ownership of the sensor. Each Ruuvi sensor can have only one owner."; "remove_shared_sensor_description" = "If you choose to remove this shared sensor, the owner of the sensor will be notified and you will not be able to access the sensor anymore.\n\nYou will also lose any related sensor settings like name, background image and alert configurations."; "remove_local_sensor_description" = "If you choose to remove this sensor, it will result in the deletion of your locally stored measurement history, along with the removal of any related sensor settings like name, background image, calibration, and alert configurations.\n\nYou can add this sensor later again, if needed."; +"activity_saving_to_cloud" = "Saving to cloud...please wait."; +"activity_saving_success" = "Saved successfully."; +"activity_saving_fail" = "Couldn't save changes to cloud."; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/station/Resources/Strings/fi.lproj/Localizable.strings b/station/Resources/Strings/fi.lproj/Localizable.strings index fcc3332bb..ee58e036e 100644 --- a/station/Resources/Strings/fi.lproj/Localizable.strings +++ b/station/Resources/Strings/fi.lproj/Localizable.strings @@ -746,3 +746,9 @@ RuuviTag on valmis käyttöön!"; "remove_claimed_sensor_description" = "Poistamalla anturin omistusoikeutesi peruutetaan, ja anturin asetukset, kuten nimi, taustakuva, kalibrointiasetukset ja hälytysasetukset poistetaan. Poiston jälkeen joku toinen voi ottaa anturin omistukseensa. Jokaisella Ruuvi-anturilla voi olla vain yksi omistaja."; "remove_shared_sensor_description" = "Jos poistat tämän jaetun anturin, anturin omistajalle lähetetään ilmoitus poistosta, etkä voi enää käyttää anturia.\n\nMenetät myös kaikki tähän anturiin liittyvät anturin asetukset, kuten nimen, taustakuvan ja asetetut hälytykset."; "remove_local_sensor_description" = "Jos poistat tämän anturin, paikallisesti tallennettu mittaushistoria ja kaikki tähän anturiin liittyvät asetukset, kuten nimi, taustakuva, kalibrointiasetukset ja hälytysasetukset poistetaan.\n\nVoit lisätä tämän anturin myöhemmin uudelleen tarvittaessa."; +"activity_saving_to_cloud" = "Tallennetaan pilveen...odota hetki."; +"activity_saving_success" = "Tallennus onnistui."; +"activity_saving_fail" = "Muutoksia ei voitu tallentaa pilveen."; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/station/Resources/Strings/fr.lproj/Localizable.strings b/station/Resources/Strings/fr.lproj/Localizable.strings index d74b054d2..dbd59e762 100644 --- a/station/Resources/Strings/fr.lproj/Localizable.strings +++ b/station/Resources/Strings/fr.lproj/Localizable.strings @@ -747,4 +747,10 @@ RuuviTag est prêt à être utilisé !"; "remove_claimed_sensor_description" = "En supprimant le capteur, votre statut de propriétaire du capteur sera révoqué, et les paramètres du capteur, tels que le nom, l'image d'arrière-plan, les paramètres de calibration et les paramètres d'alerte, seront supprimés. Après la suppression, quelqu'un d'autre peut revendiquer la propriété du capteur. Chaque capteur Ruuvi ne peut avoir qu'un seul propriétaire."; "remove_shared_sensor_description" = "Si vous choisissez de supprimer ce capteur partagé, le propriétaire du capteur sera averti et vous ne pourrez plus accéder au capteur.\n\nVous perdrez également tous les paramètres du capteur associés, tels que le nom, l'image d'arrière-plan et les configurations d'alerte. ."; "remove_local_sensor_description" = "Si vous choisissez de supprimer ce capteur, cela entraînera la suppression de votre historique de mesures stocké localement, ainsi que la suppression de tous les paramètres du capteur associés tels que le nom, l'image d'arrière-plan, l'étalonnage et les configurations d'alerte.\n\nVous pouvez ajouter ce capteur plus tard, si nécessaire."; +"activity_saving_to_cloud" = "Enregistrement dans le cloud... veuillez patienter."; +"activity_saving_success" = "Enregistré avec succès."; +"activity_saving_fail" = "Impossible d'enregistrer les modifications dans le cloud."; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/station/Resources/Strings/ru.lproj/Localizable.strings b/station/Resources/Strings/ru.lproj/Localizable.strings index ce889a66e..1087d203b 100644 --- a/station/Resources/Strings/ru.lproj/Localizable.strings +++ b/station/Resources/Strings/ru.lproj/Localizable.strings @@ -746,3 +746,9 @@ If you cannot see the Language option in the settings, make sure that you have a "remove_claimed_sensor_description" = "By removing the sensor, your sensor ownership status will be revoked and sensor settings, such as name, background image, calibration settings and alert settings will be removed. After removal, someone else can claim ownership of the sensor. Each Ruuvi sensor can have only one owner."; "remove_shared_sensor_description" = "If you choose to remove this shared sensor, the owner of the sensor will be notified and you will not be able to access the sensor anymore.\n\nYou will also lose any related sensor settings like name, background image and alert configurations."; "remove_local_sensor_description" = "If you choose to remove this sensor, it will result in the deletion of your locally stored measurement history, along with the removal of any related sensor settings like name, background image, calibration, and alert configurations.\n\nYou can add this sensor later again, if needed."; +"activity_saving_to_cloud" = "Saving to cloud...please wait."; +"activity_saving_success" = "Saved successfully."; +"activity_saving_fail" = "Couldn't save changes to cloud."; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed."; diff --git a/station/Resources/Strings/sv.lproj/Localizable.strings b/station/Resources/Strings/sv.lproj/Localizable.strings index bbe7a036c..c7e23633d 100644 --- a/station/Resources/Strings/sv.lproj/Localizable.strings +++ b/station/Resources/Strings/sv.lproj/Localizable.strings @@ -746,3 +746,9 @@ RuuviTag-sensorn är redo att användas!"; "remove_claimed_sensor_description" = "Genom att ta bort sensorn kommer den inte längre synas i ditt konto och sensorinställningar som namn, bakgrundsbild, kalibreringsinställningar och larminställningar kommer att tas bort. Efter borttagning kan någon annan ta ägande om sensorn. Varje Ruuvi-sensor kan bara ha en ägare."; "remove_shared_sensor_description" = "Om du tar bort den här delade sensorn kommer sensorns ägare att meddelas om borttagningen och du kommer inte längre att kunna använda sensorn.\n\nDu kommer också att förlora alla sensorinställningar som är kopplade till denna sensor, t.ex. namn, bakgrundsbild och ställ in alarm."; "remove_local_sensor_description" = "Om du väljer att ta bort den här sensorn kommer det att resultera i radering av din lokalt lagrade mäthistorik, tillsammans med att eventuella relaterade sensorinställningar som namn, bakgrundsbild, kalibrering och varningskonfigurationer tas bort.\n\nDu kan lägga till denna sensor senare igen, om det behövs."; +"activity_saving_to_cloud" = "Sparar till molnet... vänligen vänta."; +"activity_saving_success" = "Sparad."; +"activity_saving_fail" = "Det gick inte att spara ändringar i molnet."; +"activity_ongoing_generic" = "Please wait..."; +"activity_success_generic" = "Operation successful."; +"activity_failed_generic" = "Operation failed.";