Skip to content

Commit

Permalink
Enhancements and bug fixes (#1823)
Browse files Browse the repository at this point in the history
* fix: Fix UI glitch after toast is dismissed #1600 (#1816)

Increase min screen on time for toast to 1.5 seconds from 1 second.

* Add battery level check on FW upgrade from Discover (#1819)

Implements battery charge check on Firmware upgrade process on Discover

* task: Disable RSSI alert editing for local sensors #1751 (#1820)

* fix: View menu not updating after sync from cloud #1632 (#1822)

* fix: Improve activity presenter transition #1600 (#1821)

---------

Co-authored-by: Rinat Enikeev <rinat.enikeev@gmail.com>
  • Loading branch information
priyonto and rinat-enikeev authored Dec 27, 2023
1 parent 567c245 commit 58f145e
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,7 @@ extension DashboardPresenter {
using: { [weak self] _ in
guard let self = self else { return }
DispatchQueue.main.async {
self.view?.dashboardSortingType = self.dashboardSortingType()
self.viewModels = self.reorder(self.viewModels)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class TagSettingsPresenter: NSObject, TagSettingsModuleInput {
private var appDidBecomeActiveToken: NSObjectProtocol?
private var alertDidChangeToken: NSObjectProtocol?
private var backgroundToken: NSObjectProtocol?
private var cloudRequestStateToken: NSObjectProtocol?
private var mutedTillTimer: Timer?
private var exportFileUrl: URL?
private var previousAdvertisementSequence: Int?
Expand Down Expand Up @@ -108,8 +107,10 @@ class TagSettingsPresenter: NSObject, TagSettingsModuleInput {
appDidBecomeActiveToken?.invalidate()
alertDidChangeToken?.invalidate()
backgroundToken?.invalidate()
cloudRequestStateToken?.invalidate()
timer?.invalidate()
RuuviCloudRequestStateObserverManager
.shared
.stopObserving(for: ruuviTag.macId?.value)
NotificationCenter.default.removeObserver(self)
}

Expand Down Expand Up @@ -1143,27 +1144,17 @@ extension TagSettingsPresenter {
}

private func startObservingCloudRequestState() {
cloudRequestStateToken?.invalidate()
cloudRequestStateToken = nil

cloudRequestStateToken = NotificationCenter
.default
.addObserver(
forName: .RuuviCloudRequestStateDidChange,
object: nil,
queue: .main,
using: { [weak self] notification in
guard let self,
let userInfo = notification.userInfo,
let macId = userInfo[RuuviCloudRequestStateKey.macId] as? String,
macId == self.ruuviTag.macId?.value,
let state = userInfo[RuuviCloudRequestStateKey.state] as? RuuviCloudRequestStateType
else {
return
}
self.presentActivityIndicator(with: state)
}
)
guard let macId = ruuviTag.macId?.value else { return }
// Stop if already observing
RuuviCloudRequestStateObserverManager
.shared
.stopObserving(for: macId)

RuuviCloudRequestStateObserverManager
.shared
.startObserving(for: macId) { [weak self] state in
self?.presentActivityIndicator(with: state)
}
}

private func reloadMutedTill() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ extension TagSettingsAlertConfigCell {
alertLimitSliderView.disable(disable)
case .alertRSSI:
noticeView.disable(disable)
alertLimitDescriptionView.disable(disable)
alertLimitSliderView.disable(disable)
case .alertMovement, .alertConnection:
additionalTextView.disable(disable)
default: break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1243,8 +1243,17 @@ extension TagSettingsViewController {
}

rssiAlertCell.bind(viewModel.latestMeasurement) { cell, measurement in
let isClaimed = GlobalHelpers.getBool(from: viewModel.isClaimedTag.value)
cell.disableEditing(
disable: measurement == nil,
disable: measurement == nil || !isClaimed,
identifier: .alertRSSI
)
}

rssiAlertCell.bind(viewModel.isClaimedTag) { [weak self] cell, isClaimed in
let hasMeasurement = GlobalHelpers.getBool(from: self?.hasMeasurement())
cell.disableEditing(
disable: !hasMeasurement || !GlobalHelpers.getBool(from: isClaimed),
identifier: .alertRSSI
)
}
Expand Down Expand Up @@ -1603,7 +1612,8 @@ extension TagSettingsViewController {

private func rssiAlertItem() -> TagSettingsItem {
let (minRange, maxRange) = rssiMinMaxForSliders()
let disableRssi = !hasMeasurement()
let disableRssi = !hasMeasurement() ||
!GlobalHelpers.getBool(from: viewModel?.isClaimedTag.value)
let settingItem = TagSettingsItem(
createdCell: { [weak self] in
self?.rssiAlertCell?.showNoticeView()
Expand Down Expand Up @@ -3485,7 +3495,8 @@ extension TagSettingsViewController: TagSettingsExpandableSectionHeaderDelegate
selectedMaxValue: rssiUpperBound()
)
rssiAlertCell.disableEditing(
disable: GlobalHelpers.getBool(from: !hasMeasurement()),
disable: GlobalHelpers.getBool(from: !hasMeasurement()) ||
!GlobalHelpers.getBool(from: viewModel?.isClaimedTag.value),
identifier: currentSection.identifier
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,146 @@
import UIKit

public final class ActivityPresenterRuuviLogo: ActivityPresenter {
let minAnimationTime: CFTimeInterval = 1.5
var startTime: CFTimeInterval?
let window = UIWindow(frame: UIScreen.main.bounds)
let activityPresenterViewProvider: ActivityPresenterViewProvider
let activityPresenterViewController: UIViewController
let stateHolder = ActivityPresenterStateHolder()
public final class ActivityPresenterRuuviLogo {
// Minimum duration to keep the activity indicator on the screen
private let minAnimationTime: CFTimeInterval = 1.5
// Animation duration for presenting and dismissing the activity indicator.
private let animationDuration: CGFloat = 0.5
// Vertical(top/bottom) padding for activity indicator depending on position.
private let verticalPadding: CGFloat = 8

weak var appWindow: UIWindow?
private let activityPresenterViewProvider: ActivityPresenterViewProvider
private let activityPresenterViewController: UIViewController
private let stateHolder = ActivityPresenterStateHolder()

private var activityPresenterPosition: ActivityPresenterPosition = .bottom
private var startTime: CFTimeInterval?

public init() {
activityPresenterViewProvider = ActivityPresenterViewProvider(stateHolder: stateHolder)
activityPresenterViewController = activityPresenterViewProvider.makeViewController()
activityPresenterViewController.view.backgroundColor = .clear
window.windowLevel = .normal
activityPresenterViewController.view.translatesAutoresizingMaskIntoConstraints = false
window.rootViewController = activityPresenterViewController
}
}

public extension ActivityPresenterRuuviLogo {
func setPosition(_ position: ActivityPresenterPosition) {
extension ActivityPresenterRuuviLogo: ActivityPresenter {
public func show(
with state: ActivityPresenterState,
atPosition position: ActivityPresenterPosition
) {
activityPresenterPosition = position
activityPresenterViewProvider.updatePosition(position)
}

func show(with state: ActivityPresenterState) {
startTime = CFAbsoluteTimeGetCurrent()
appWindow = UIWindow.key
window.makeKeyAndVisible()
window.layoutIfNeeded()

guard let topController = RuuviPresenterHelper.topViewController(),
let toastView = activityPresenterViewController.view else {
return
}

topController.view.addSubview(toastView)
setupConstraints(for: toastView, in: topController)
animateActivityViewPresentation(toastView, atPosition: position)

activityPresenterViewProvider.updateState(state)
}

func update(with state: ActivityPresenterState) {
// Reset start time with state update since we want to show
// latest state message before dismissing the presenter.
public func update(with state: ActivityPresenterState) {
startTime = CFAbsoluteTimeGetCurrent()
activityPresenterViewProvider.updateState(state)
}

func dismiss(immediately: Bool) {
public func dismiss(immediately: Bool) {
let executionTime = CFAbsoluteTimeGetCurrent() - (startTime ?? 0)
let additionalWaitTime = immediately ? 0 :
executionTime < minAnimationTime ? (minAnimationTime - executionTime) : 0
DispatchQueue.main.asyncAfter(deadline: .now() + additionalWaitTime) { [weak self] in
self?.window.isHidden = true
self?.appWindow?.makeKeyAndVisible()
self?.appWindow = nil
let additionalWaitTime = immediately ? 0 : max(minAnimationTime - executionTime, 0)

guard let toastView = activityPresenterViewController.view else {
return
}

DispatchQueue.main.asyncAfter(
deadline: .now() + additionalWaitTime
) { [weak self] in
self?.animateActivityViewDismissal(toastView)
}
}
}

extension ActivityPresenterRuuviLogo {
private func setupConstraints(
for view: UIView,
in viewController: UIViewController
) {
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(
equalTo: viewController.view.leadingAnchor
),
view.trailingAnchor.constraint(
equalTo: viewController.view.trailingAnchor
),
view.topAnchor.constraint(
equalTo: viewController.view.topAnchor
),
view.bottomAnchor.constraint(
equalTo: viewController.view.bottomAnchor
),
])
}

private func animateActivityViewPresentation(
_ view: UIView,
atPosition position: ActivityPresenterPosition
) {
UIView.animate(
withDuration: animationDuration,
delay: 0,
options: [.curveEaseOut]
) { [weak self] in
guard let self = self else { return }
switch position {
case .top:
view.transform = CGAffineTransform(
translationX: 0,
y: self.safeAreaInsets().top + self.verticalPadding
)
case .bottom:
view.transform = CGAffineTransform(
translationX: 0,
y: -(self.safeAreaInsets().bottom + self.verticalPadding)
)
case .center:
view.transform = .identity
}
}
}

private func animateActivityViewDismissal(_ view: UIView) {
UIView.animate(
withDuration: animationDuration,
delay: 0,
options: [.curveEaseIn]
) { [weak self] in
guard let self = self else { return }
switch self.activityPresenterPosition {
case .top:
view.transform = CGAffineTransform(
translationX: 0,
y: -(view.frame.height + verticalPadding)
)
case .bottom:
view.transform = CGAffineTransform(
translationX: 0,
y: view.frame.height + verticalPadding
)
case .center:
break
}
} completion: { [weak self] _ in
view.removeFromSuperview()
self?.activityPresenterViewProvider.updateState(.dismiss)
}
}

private func safeAreaInsets() -> UIEdgeInsets {
RuuviPresenterHelper.topViewController()?.view.safeAreaInsets ?? .zero
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import Foundation

public protocol ActivityPresenter {
func setPosition(_ position: ActivityPresenterPosition)
func show(with state: ActivityPresenterState)
func show(
with state: ActivityPresenterState,
atPosition position: ActivityPresenterPosition
)
func update(with state: ActivityPresenterState)
func dismiss(immediately: Bool)
}

public extension ActivityPresenter {
func show(
with state: ActivityPresenterState,
atPosition position: ActivityPresenterPosition = .bottom
) {
show(with: state, atPosition: position)
}

func dismiss(immediately: Bool = false) {
dismiss(immediately: immediately)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class ErrorPresenterAlert: ErrorPresenter {
let feedback = UINotificationFeedbackGenerator()
feedback.notificationOccurred(.error)
feedback.prepare()
UIApplication.shared.topViewController()?.present(alert, animated: true)
RuuviPresenterHelper.topViewController()?.present(alert, animated: true)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public final class PermissionPresenterAlert: PermissionPresenter {
}

private func presentAlert(with message: String) {
guard let viewController = UIApplication.shared.topViewController() else { return }
guard let viewController = RuuviPresenterHelper.topViewController() else { return }
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let cancel = UIAlertAction(title: RuuviLocalization.cancel, style: .cancel, handler: nil)
let actionTitle = RuuviLocalization.PermissionPresenter.settings
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import UIKit

struct RuuviPresenterHelper {
public static func topViewController() -> UIViewController? {
if var topController = keyWindow()?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
return nil
}

private static func keyWindow() -> UIWindow? {
return UIApplication.shared.windows.first(where: { $0.isKeyWindow })
}
}

This file was deleted.

Loading

0 comments on commit 58f145e

Please sign in to comment.