// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_mediator.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/notreached.h"
#import "ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_constants.h"
#import "ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_consumer.h"
#import "ios/chrome/browser/unit_conversion/ui_bundled/unit_conversion_mutator.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/unit_conversion/unit_conversion_api.h"
@implementation UnitConversionMediator {
// A boolean to track if the unit type has changed.
BOOL _unitTypeChanged;
// A boolean to track if the source unit value has changed.
BOOL _sourceUnitValueChanged;
// A boolean to track if the target unit value has changed.
BOOL _targetUnitValueChanged;
// An item to track the source unit change before the unit type is changed,
// it's initialised with the value `kUnchanged` and store the first change
// only.
UnitConversionActionTypes _sourceUnitChangedBeforeUnitType;
// An item to track the source unit change after the unit type is changed,
// it's initialised with the value `kUnchanged` and store the first change
// only.
UnitConversionActionTypes _sourceUnitChangedAfterUnitType;
// An item to track the target unit change, it is initialised with the value
// `kUnchanged` and store the first change only.
UnitConversionActionTypes _targetUnitChanged;
// The unit conversion keyed service to keep track of the changes of the
// target unit based on a source unit and store them as the new default
// conversion.
raw_ptr<UnitConversionService> _service;
}
- (instancetype)initWithService:(UnitConversionService*)service {
self = [super init];
if (self) {
_unitTypeChanged = NO;
_sourceUnitValueChanged = NO;
_targetUnitValueChanged = NO;
_sourceUnitChangedBeforeUnitType = UnitConversionActionTypes::kUnchanged;
_sourceUnitChangedAfterUnitType = UnitConversionActionTypes::kUnchanged;
_targetUnitChanged = UnitConversionActionTypes::kUnchanged;
_service = service;
}
return self;
}
- (void)reportMetrics {
base::UmaHistogramEnumeration(kSourceUnitChangeAfterUnitTypeChangeHistogram,
_sourceUnitChangedAfterUnitType);
base::UmaHistogramEnumeration(kSourceUnitChangeBeforeUnitTypeChangeHistogram,
_sourceUnitChangedBeforeUnitType);
base::UmaHistogramEnumeration(kTargetUnitChangeHistogram, _targetUnitChanged);
}
- (void)shutdown {
_service = nullptr;
}
#pragma mark - UnitConversionMutator
- (void)unitTypeDidChange:(ios::provider::UnitType)unitType
unitValue:(double)unitValue {
NSUnit* sourceUnit = ios::provider::GetDefaultUnitForType(unitType);
NSUnit* targetUnit = ios::provider::GetDefaultTargetUnit(sourceUnit);
if (sourceUnit && targetUnit) {
NSMeasurement* sourceUnitMeasurement =
[[NSMeasurement alloc] initWithDoubleValue:unitValue unit:sourceUnit];
if ([sourceUnitMeasurement canBeConvertedToUnit:targetUnit]) {
if (!_unitTypeChanged) {
base::RecordAction(
base::UserMetricsAction("IOS.UnitConversion.UnitTypeChange"));
_unitTypeChanged = YES;
}
NSMeasurement* targetUnitMeasurement =
[sourceUnitMeasurement measurementByConvertingToUnit:targetUnit];
[self.consumer updateSourceUnit:sourceUnit reload:NO];
[self.consumer updateTargetUnit:targetUnit reload:NO];
[self.consumer updateSourceUnitValue:sourceUnitMeasurement.doubleValue
reload:NO];
[self.consumer updateTargetUnitValue:targetUnitMeasurement.doubleValue
reload:NO];
[self.consumer updateUnitTypeTitle:unitType];
}
}
}
- (void)sourceUnitDidChange:(NSUnit*)sourceUnit
targetUnit:(NSUnit*)targetUnit
unitValue:(double)unitValue
unitType:(ios::provider::UnitType)unitType {
NSMeasurement* sourceUnitMeasurement =
[[NSMeasurement alloc] initWithDoubleValue:unitValue unit:sourceUnit];
if ([sourceUnitMeasurement canBeConvertedToUnit:targetUnit]) {
NSMeasurement* targetUnitMeasurement =
[sourceUnitMeasurement measurementByConvertingToUnit:targetUnit];
[self.consumer updateSourceUnit:sourceUnit reload:YES];
[self.consumer updateTargetUnitValue:targetUnitMeasurement.doubleValue
reload:YES];
if (_unitTypeChanged) {
// Update _sourceUnitChangedAfterUnitType only if the unit type has
// changed and its previous value is `kUnchanged`.
if (_sourceUnitChangedAfterUnitType ==
UnitConversionActionTypes::kUnchanged) {
_sourceUnitChangedAfterUnitType =
[self unitConversionActionFromUnitType:unitType
isMetric:NSLocale.currentLocale
.usesMetricSystem];
}
} else {
// Update `_sourceUnitChangedBeforeUnitType` only if the unit type hasn't
// changed and its previous value is `kUnchanged`.
if (_sourceUnitChangedBeforeUnitType ==
UnitConversionActionTypes::kUnchanged) {
_sourceUnitChangedBeforeUnitType =
[self unitConversionActionFromUnitType:unitType
isMetric:NSLocale.currentLocale
.usesMetricSystem];
}
}
}
}
- (void)targetUnitDidChange:(NSUnit*)targetUnit
sourceUnit:(NSUnit*)sourceUnit
unitValue:(double)unitValue
unitType:(ios::provider::UnitType)unitType {
NSMeasurement* sourceUnitMeasurement =
[[NSMeasurement alloc] initWithDoubleValue:unitValue unit:sourceUnit];
if ([sourceUnitMeasurement canBeConvertedToUnit:targetUnit]) {
_service->UpdateDefaultConversionCache(sourceUnit, targetUnit);
NSMeasurement* targetUnitMeasurement =
[sourceUnitMeasurement measurementByConvertingToUnit:targetUnit];
[self.consumer updateTargetUnit:targetUnit reload:YES];
[self.consumer updateTargetUnitValue:targetUnitMeasurement.doubleValue
reload:YES];
// update `_targetUnitChanged` only if its value is `kUnchanged`.
if (_targetUnitChanged == UnitConversionActionTypes::kUnchanged) {
_targetUnitChanged =
[self unitConversionActionFromUnitType:unitType
isMetric:NSLocale.currentLocale
.usesMetricSystem];
}
}
}
- (void)sourceUnitValueFieldDidChange:(NSString*)sourceUnitValueField
sourceUnit:(NSUnit*)sourceUnit
targetUnit:(NSUnit*)targetUnit {
NSNumber* unitValueNumber = [self numberFromString:sourceUnitValueField];
if (!unitValueNumber) {
// Update the target field with 0 as a default value when the source field's
// content is not valid (empty and non numerical values).
[self.consumer updateTargetUnitValue:0 reload:YES];
return;
}
double unitValue = unitValueNumber.doubleValue;
NSMeasurement* sourceUnitMeasurement =
[[NSMeasurement alloc] initWithDoubleValue:unitValue unit:sourceUnit];
if ([sourceUnitMeasurement canBeConvertedToUnit:targetUnit]) {
NSMeasurement* targetUnitMeasurement =
[sourceUnitMeasurement measurementByConvertingToUnit:targetUnit];
[self.consumer updateTargetUnitValue:targetUnitMeasurement.doubleValue
reload:YES];
// Record only the first sourceUnitValueChange before any change of the unit
// type.
if (!_unitTypeChanged && !_sourceUnitValueChanged) {
_sourceUnitValueChanged = YES;
base::RecordAction(
base::UserMetricsAction("IOS.UnitConversion.SourceUnitValueChange"));
}
}
}
- (void)targetUnitValueFieldDidChange:(NSString*)targetUnitValueField
sourceUnit:(NSUnit*)sourceUnit
targetUnit:(NSUnit*)targetUnit {
NSNumber* unitValueNumber = [self numberFromString:targetUnitValueField];
if (!unitValueNumber) {
// Update the source field with 0 as a default value when the target field's
// content is not valid (empty and non numerical values).
[self.consumer updateSourceUnitValue:0 reload:YES];
return;
}
double unitValue = unitValueNumber.doubleValue;
NSMeasurement* targetUnitMeasurement =
[[NSMeasurement alloc] initWithDoubleValue:unitValue unit:targetUnit];
if ([targetUnitMeasurement canBeConvertedToUnit:sourceUnit]) {
NSMeasurement* sourceUnitMeasurement =
[targetUnitMeasurement measurementByConvertingToUnit:sourceUnit];
[self.consumer updateSourceUnitValue:sourceUnitMeasurement.doubleValue
reload:YES];
// Record only the first targetUnitValueChange before any change of the unit
// type.
if (!_targetUnitValueChanged) {
_targetUnitValueChanged = YES;
base::RecordAction(
base::UserMetricsAction("IOS.UnitConversion.TargetUnitValueChange"));
}
}
}
#pragma mark - Private
// Returns a `UnitConversionActionTypes` based on the `UnitType` and the
// current locale system (metric/imperial).
- (UnitConversionActionTypes)unitConversionActionFromUnitType:
(ios::provider::UnitType)unitType
isMetric:(BOOL)isMetric {
switch (unitType) {
case ios::provider::kUnitTypeArea:
return isMetric ? UnitConversionActionTypes::kAreaMetric
: UnitConversionActionTypes::kAreaImperial;
case ios::provider::kUnitTypeInformationStorage:
return isMetric ? UnitConversionActionTypes::kInformationStorageMetric
: UnitConversionActionTypes::kInformationStorageImperial;
case ios::provider::kUnitTypeLength:
return isMetric ? UnitConversionActionTypes::kLengthMetric
: UnitConversionActionTypes::kLengthImperial;
case ios::provider::kUnitTypeMass:
return isMetric ? UnitConversionActionTypes::kMassMetric
: UnitConversionActionTypes::kMassImperial;
case ios::provider::kUnitTypeSpeed:
return isMetric ? UnitConversionActionTypes::kSpeedMetric
: UnitConversionActionTypes::kSpeedImperial;
case ios::provider::kUnitTypeTemperature:
return isMetric ? UnitConversionActionTypes::kTemperatureMetric
: UnitConversionActionTypes::kTemperatureImperial;
case ios::provider::kUnitTypeVolume:
return isMetric ? UnitConversionActionTypes::kVolumeMetric
: UnitConversionActionTypes::kVolumeImperial;
case ios::provider::kUnitTypeUnknown:
NOTREACHED();
}
}
// Converts a string to a NSNumber*, returns nil if not valid.
- (NSNumber*)numberFromString:(NSString*)string {
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.locale = [NSLocale currentLocale];
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
return [numberFormatter numberFromString:string];
}
@end