blob: ab1b3ca2dae6da8beff320396848fbe5a927a104 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:ermine/src/services/settings/battery_watcher_service.dart';
import 'package:ermine/src/services/settings/brightness_service.dart';
import 'package:ermine/src/services/settings/channel_service.dart';
import 'package:ermine/src/services/settings/data_sharing_consent_service.dart';
import 'package:ermine/src/services/settings/datetime_service.dart';
import 'package:ermine/src/services/settings/memory_watcher_service.dart';
import 'package:ermine/src/services/settings/network_address_service.dart';
import 'package:ermine/src/services/settings/task_service.dart';
import 'package:ermine/src/services/settings/timezone_service.dart';
import 'package:ermine/src/services/settings/volume_service.dart';
import 'package:ermine/src/services/settings/wifi_service.dart';
import 'package:ermine/src/states/settings_state.dart';
import 'package:ermine_utils/ermine_utils.dart';
import 'package:flutter/material.dart' hide Action;
import 'package:internationalization/strings.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
/// Defines the implementation of [SettingsState].
class SettingsStateImpl with Disposable implements SettingsState, TaskService {
static const kTimezonesFile = '/pkg/data/tz_ids.txt';
final settingsPage = SettingsPage.none.asObservable();
@override
bool get shortcutsPageVisible => _shortcutsPageVisible.value;
late final _shortcutsPageVisible =
(() => settingsPage.value == SettingsPage.shortcuts).asComputed();
@override
bool get allSettingsPageVisible => _allSettingsPageVisible.value;
late final _allSettingsPageVisible =
(() => settingsPage.value == SettingsPage.none).asComputed();
@override
bool get timezonesPageVisible => _timezonesPageVisible.value;
late final _timezonesPageVisible =
(() => settingsPage.value == SettingsPage.timezone).asComputed();
@override
bool get aboutPageVisible => _aboutPageVisible.value;
late final _aboutPageVisible =
(() => settingsPage.value == SettingsPage.about).asComputed();
@override
bool get channelPageVisible => _channelPageVisible.value;
late final _channelPageVisible =
(() => settingsPage.value == SettingsPage.channel).asComputed();
@override
bool get dataSharingConsentPageVisible =>
_dataSharingConsentPageVisible.value;
late final _dataSharingConsentPageVisible = (() =>
settingsPage.value == SettingsPage.dataSharingConsent).asComputed();
@override
bool get wifiPageVisible => _wifiPageVisible.value;
late final _wifiPageVisible =
(() => settingsPage.value == SettingsPage.wifi).asComputed();
@override
WiFiStrength get wifiStrength => _wifiStrength.value;
final _wifiStrength = Observable<WiFiStrength>(WiFiStrength.good);
@override
BatteryCharge get batteryCharge => _batteryCharge.value;
final _batteryCharge = Observable<BatteryCharge>(BatteryCharge.charging);
@override
final Map<String, Set<String>> shortcutBindings;
DisplayDialogCallback displayDialog;
@override
String get selectedTimezone => _selectedTimezone.value;
set selectedTimezone(String value) => _selectedTimezone.value = value;
final Observable<String> _selectedTimezone;
@override
final networkAddresses = ObservableList<String>();
@override
String get memUsed => _memUsed.value;
set memUsed(String value) => _memUsed.value = value;
final Observable<String> _memUsed = '--'.asObservable();
@override
String get memTotal => _memTotal.value;
set memTotal(String value) => _memTotal.value = value;
final Observable<String> _memTotal = '--'.asObservable();
@override
double? get memPercentUsed => _memPercentUsed.value;
set memPercentUsed(double? value) => _memPercentUsed.value = value;
final Observable<double?> _memPercentUsed = Observable<double?>(null);
@override
IconData get powerIcon => _powerIcon.value;
set powerIcon(IconData value) => _powerIcon.value = value;
final Observable<IconData> _powerIcon = Icons.battery_unknown.asObservable();
@override
double? get powerLevel => _powerLevel.value;
set powerLevel(double? value) => _powerLevel.value = value;
final Observable<double?> _powerLevel = Observable<double?>(null);
@override
double? get brightnessLevel => _brightnessLevel.value;
set brightnessLevel(double? value) => _brightnessLevel.value = value;
final Observable<double?> _brightnessLevel = Observable<double?>(null);
@override
bool? get brightnessAuto => _brightnessAuto.value;
set brightnessAuto(bool? value) => _brightnessAuto.value = value;
final Observable<bool?> _brightnessAuto = Observable<bool?>(null);
@override
IconData get brightnessIcon => _brightnessIcon.value;
set brightnessIcon(IconData value) => _brightnessIcon.value = value;
final Observable<IconData> _brightnessIcon =
Icons.brightness_auto.asObservable();
@override
bool? get optedIntoUpdates => _optedIntoUpdates.value;
set optedIntoUpdates(bool? value) => _optedIntoUpdates.value = value;
final Observable<bool?> _optedIntoUpdates = Observable<bool?>(null);
@override
String get currentChannel => _currentChannel.value;
set currentChannel(String value) => _currentChannel.value = value;
final Observable<String> _currentChannel = Observable<String>('');
@override
final List<String> availableChannels = ObservableList<String>();
@override
String get targetChannel => _targetChannel.value;
set targetChannel(String value) => _targetChannel.value = value;
final Observable<String> _targetChannel = Observable<String>('');
@override
ChannelState get channelState => _channelState.value;
set channelState(ChannelState value) => _channelState.value = value;
final _channelState = Observable<ChannelState>(ChannelState.idle);
@override
double get systemUpdateProgress => _systemUpdateProgress.value;
set systemUpdateProgress(double value) => _systemUpdateProgress.value = value;
final _systemUpdateProgress = Observable<double>(0);
@override
IconData get volumeIcon => _volumeIcon.value;
set volumeIcon(IconData value) => _volumeIcon.value = value;
final Observable<IconData> _volumeIcon = Icons.volume_up.asObservable();
@override
double? get volumeLevel => _volumeLevel.value;
set volumeLevel(double? value) => _volumeLevel.value = value;
final Observable<double?> _volumeLevel = Observable<double?>(null);
@override
bool? get volumeMuted => _volumeMuted.value;
set volumeMuted(bool? value) => _volumeMuted.value = value;
final Observable<bool?> _volumeMuted = Observable<bool?>(null);
@override
NetworkInformation get targetNetwork => _targetNetwork.value;
set targetNetwork(NetworkInformation value) => _targetNetwork.value = value;
final Observable<NetworkInformation> _targetNetwork =
Observable<NetworkInformation>(NetworkInformation());
@override
TextEditingController get networkPasswordTextController =>
_networkPasswordTextController;
final TextEditingController _networkPasswordTextController =
TextEditingController();
@override
final List<NetworkInformation> availableNetworks =
ObservableList<NetworkInformation>();
@override
final List<NetworkInformation> savedNetworks =
ObservableList<NetworkInformation>();
@override
String get currentNetwork => _currentNetwork.value;
set currentNetwork(String value) => _currentNetwork.value = value;
final Observable<String> _currentNetwork = ''.asObservable();
@override
bool get clientConnectionsEnabled => _clientConnectionsEnabled.value;
set clientConnectionsEnabled(bool value) =>
_clientConnectionsEnabled.value = value;
final Observable<bool> _clientConnectionsEnabled = Observable<bool>(true);
@override
bool get clientConnectionsMonitor => _clientConnectionsMonitor.value;
set clientConnectionsMonitor(bool value) =>
_clientConnectionsMonitor.value = value;
final Observable<bool> _clientConnectionsMonitor = Observable<bool>(true);
@override
int get wifiToggleMillisecondsPassed => _wifiToggleMillisecondsPassed.value;
set wifiToggleMillisecondsPassed(int value) =>
_wifiToggleMillisecondsPassed.value = value;
final Observable<int> _wifiToggleMillisecondsPassed = Observable<int>(0);
// TODO(fxb/88445): Get the value from dataSharingConsentService.getCurrentConsent()
@override
bool get dataSharingConsentEnabled => _dataSharingConsentEnabled.value;
set dataSharingConsentEnabled(bool value) =>
_dataSharingConsentEnabled.value = value;
final _dataSharingConsentEnabled = false.asObservable();
final List<String> _timezones;
@override
List<String> get timezones {
// Move the selected timezone to the top.
return [selectedTimezone]
..addAll(_timezones.where((zone) => zone != selectedTimezone));
}
@override
String get dateTime => _dateTime.value;
late final ObservableValue<String> _dateTime = (() =>
// Ex: Mon, Jun 7 2:25 AM
DateFormat.MMMEd().add_jm().format(dateTimeNow.value)).asComputed();
final DataSharingConsentService dataSharingConsentService;
final DateTimeService dateTimeService;
final TimezoneService timezoneService;
final NetworkAddressService networkService;
final MemoryWatcherService memoryWatcherService;
final BatteryWatcherService batteryWatcherService;
final BrightnessService brightnessService;
final ChannelService channelService;
final VolumeService volumeService;
final WiFiService wifiService;
SettingsStateImpl({
required this.shortcutBindings,
required this.displayDialog,
required this.timezoneService,
required this.dataSharingConsentService,
required this.dateTimeService,
required this.networkService,
required this.memoryWatcherService,
required this.batteryWatcherService,
required this.brightnessService,
required this.channelService,
required this.volumeService,
required this.wifiService,
}) : _timezones = _loadTimezones(),
_selectedTimezone = timezoneService.timezone.asObservable() {
dataSharingConsentService.onChanged =
(consent) => runInAction(() => dataSharingConsentEnabled = consent);
dateTimeService.onChanged = updateDateTime;
timezoneService.onChanged =
(timezone) => runInAction(() => selectedTimezone = timezone);
networkService.onChanged = () => NetworkInterface.list().then((interfaces) {
// Gather all addresses from all interfaces and sort them such that
// IPv4 addresses come before IPv6.
final addresses = interfaces
.expand((interface) => interface.addresses)
.toList(growable: false)
..sort((addr1, addr2) =>
addr1.type == InternetAddressType.IPv4 ? -1 : 0);
runInAction(() => networkAddresses
..clear()
..addAll(addresses.map((address) => address.address)));
});
memoryWatcherService.onChanged = () {
runInAction(() {
memUsed = '${memoryWatcherService.memUsed!.toStringAsPrecision(2)}GB';
memTotal = '${memoryWatcherService.memTotal!.toStringAsPrecision(2)}GB';
memPercentUsed =
memoryWatcherService.memUsed! / memoryWatcherService.memTotal!;
});
};
batteryWatcherService.onChanged = () {
runInAction(() {
powerIcon = batteryWatcherService.icon;
powerLevel = batteryWatcherService.levelPercent;
if (powerLevel != null) {
final level = powerLevel!.toInt();
if (level == 2 && batteryWatcherService.isLevelDropping) {
displayDialog(AlertDialogInfo(
title: Strings.lowBattery,
body: Strings.percentRemaining(level.toString()),
actions: [Strings.close],
));
}
}
});
};
brightnessService.onChanged = () {
runInAction(() {
brightnessLevel = brightnessService.brightness;
brightnessAuto = brightnessService.auto;
brightnessIcon = brightnessService.icon;
});
};
channelService.onChanged = () {
runInAction(() {
optedIntoUpdates = channelService.optedIntoUpdates;
currentChannel = channelService.currentChannel;
systemUpdateProgress = channelService.updateProgress;
// Ensure current channel is listed first in available channels
List<String> channels;
if (currentChannel.isNotEmpty) {
if (channelService.channels.contains(currentChannel)) {
channels = channelService.channels;
int index = channels.indexOf(currentChannel);
if (index != 0) {
channels
..removeAt(index)
..insert(0, currentChannel);
}
} else {
channels = [currentChannel]..addAll(availableChannels);
}
} else {
channels = channelService.channels;
}
availableChannels
..clear()
..addAll(channels);
targetChannel = channelService.targetChannel;
// Monitor state of update
if (channelService.checkingForUpdates) {
channelState = ChannelState.checkingForUpdates;
} else if (channelService.errorCheckingForUpdate) {
channelState = ChannelState.errorCheckingForUpdate;
} else if (channelService.noUpdateAvailable) {
channelState = ChannelState.noUpdateAvailable;
} else if (channelService.installationDeferredByPolicy) {
channelState = ChannelState.installationDeferredByPolicy;
} else if (channelService.installingUpdate) {
channelState = ChannelState.installingUpdate;
} else if (channelService.waitingForReboot) {
channelState = ChannelState.waitingForReboot;
} else if (channelService.installationError) {
channelState = ChannelState.installationError;
}
});
};
volumeService.onChanged = () {
runInAction(() {
volumeLevel = volumeService.volume;
volumeIcon = volumeService.icon;
volumeMuted = volumeService.muted;
});
};
wifiService.onChanged = () {
runInAction(() {
// TODO(fxb/79885): remove network names with non-ASCII characters
availableNetworks
..clear()
..addAll(wifiService.scannedNetworks)
..removeWhere((network) => network.name.isEmpty);
targetNetwork = wifiService.targetNetwork;
// TODO(fxb/79885): remove saved networks from available networks
// by ssid & security type
savedNetworks
..clear()
..addAll(wifiService.savedNetworks)
..removeWhere((network) => network.name.isEmpty);
currentNetwork = wifiService.currentNetwork;
clientConnectionsEnabled = wifiService.clientConnectionsEnabled;
clientConnectionsMonitor = wifiService.connectionsEnabled;
wifiToggleMillisecondsPassed = wifiService.toggleMillisecondsPassed;
});
};
}
@override
Future<void> start() async {
await Future.wait([
dataSharingConsentService.start(),
dateTimeService.start(),
timezoneService.start(),
networkService.start(),
memoryWatcherService.start(),
batteryWatcherService.start(),
brightnessService.start(),
channelService.start(),
volumeService.start(),
wifiService.start(),
]);
}
@override
Future<void> stop() async {
showAllSettings();
await dataSharingConsentService.stop();
await dateTimeService.stop();
await timezoneService.stop();
await networkService.stop();
await memoryWatcherService.stop();
await batteryWatcherService.stop();
await channelService.stop();
await wifiService.stop();
_dateTimeNow = null;
}
@override
void dispose() {
super.dispose();
dataSharingConsentService.dispose();
dateTimeService.dispose();
timezoneService.dispose();
networkService.dispose();
memoryWatcherService.dispose();
batteryWatcherService.dispose();
brightnessService.dispose();
channelService.dispose();
volumeService.dispose();
wifiService.dispose();
}
@override
void updateTimezone(String timezone) => runInAction(() {
selectedTimezone = timezone;
timezoneService.timezone = timezone;
settingsPage.value = SettingsPage.none;
});
@override
void showAllSettings() =>
runInAction(() => settingsPage.value = SettingsPage.none);
@override
void showAboutSettings() =>
runInAction(() => settingsPage.value = SettingsPage.about);
@override
void showShortcutSettings() =>
runInAction(() => settingsPage.value = SettingsPage.shortcuts);
@override
void showTimezoneSettings() =>
runInAction(() => settingsPage.value = SettingsPage.timezone);
@override
void showChannelSettings() =>
runInAction(() => settingsPage.value = SettingsPage.channel);
@override
void showDataSharingConsentSettings() =>
runInAction(() => settingsPage.value = SettingsPage.dataSharingConsent);
@override
void setTargetChannel(String value) =>
runInAction(() => channelService.targetChannel = value);
@override
void setBrightnessLevel(double value) =>
runInAction(() => brightnessService.brightness = value);
@override
void increaseBrightness() =>
runInAction(brightnessService.increaseBrightness);
@override
void decreaseBrightness() =>
runInAction(brightnessService.decreaseBrightness);
@override
void setBrightnessAuto() => runInAction(() => brightnessService.auto = true);
Observable<DateTime>? _dateTimeNow;
Observable<DateTime> get dateTimeNow =>
_dateTimeNow ??= DateTime.now().asObservable();
late final Action updateDateTime = () {
dateTimeNow.value = DateTime.now();
}.asAction();
@override
void checkForUpdates() => runInAction(channelService.checkForUpdates);
static List<String> _loadTimezones() {
return File(kTimezonesFile).readAsLinesSync();
}
@override
void setVolumeLevel(double value) =>
runInAction(() => volumeService.volume = value);
@override
void setVolumeMute({bool muted = false}) =>
runInAction(() => volumeService.muted = muted);
@override
void showWiFiSettings() =>
runInAction(() => settingsPage.value = SettingsPage.wifi);
@override
void connectToNetwork([String password = '']) =>
runInAction(() => wifiService.connectToNetwork(password));
@override
void setTargetNetwork(NetworkInformation network) =>
runInAction(() => wifiService.targetNetwork = network);
@override
void removeNetwork(NetworkInformation network) =>
runInAction(() => wifiService.remove(network));
@override
void increaseVolume() => runInAction(volumeService.increaseVolume);
@override
void decreaseVolume() => runInAction(volumeService.decreaseVolume);
@override
void toggleMute() => runInAction(volumeService.toggleMute);
@override
void setClientConnectionsEnabled({bool enabled = true}) =>
runInAction(() => wifiService.clientConnectionsEnabled = enabled);
@override
void setDataSharingConsent({bool enabled = true}) =>
runInAction(() => dataSharingConsentService.setConsent(consent: enabled));
}