feat: add volume control and merge sound mode feature
- Add volume control screen with sliders for media, ringtone, and alarm - Update device settings via PUT /devices with CSV (same as language) - Extract DeviceCsvBuilder to legacy_shared (shared between language and volume) - Create Riverpod provider for DeviceUpdateDatasource - Extract VolumeThumbShape to separate widget file - Merge sound mode feature (SET_SOUND_MODE command, pending backend whitelist) - Fix sound screen overflow with SingleChildScrollView
This commit is contained in:
@@ -141,6 +141,11 @@ void configureAppRouter() {
|
|||||||
name: 'apps_use',
|
name: 'apps_use',
|
||||||
pageBuilder: const AppsUseBuilder().buildPage,
|
pageBuilder: const AppsUseBuilder().buildPage,
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'volume_control',
|
||||||
|
name: 'volume_control',
|
||||||
|
pageBuilder: const VolumeControlBuilder().buildPage,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ export 'src/features/locate_device/locate_device_builder.dart';
|
|||||||
export 'src/features/health/health_builder.dart';
|
export 'src/features/health/health_builder.dart';
|
||||||
export 'src/features/rewards/rewards_builder.dart';
|
export 'src/features/rewards/rewards_builder.dart';
|
||||||
export 'src/features/activity_meter/activity_meter_builder.dart';
|
export 'src/features/activity_meter/activity_meter_builder.dart';
|
||||||
export 'src/features/apps_use/apps_use_builder.dart';
|
export 'src/features/apps_use/apps_use_builder.dart';
|
||||||
|
export 'src/features/volume_control/volume_control_builder.dart';
|
||||||
@@ -68,6 +68,15 @@ class DeviceManagementScreen extends ConsumerWidget {
|
|||||||
// text: context.translate(I18n.videoCall),
|
// text: context.translate(I18n.videoCall),
|
||||||
// ),
|
// ),
|
||||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
|
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
|
||||||
|
AppMenuButton(
|
||||||
|
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||||
|
onPressed: () =>
|
||||||
|
navigationContract.pushTo(AppRoutes.volumeControl),
|
||||||
|
icon: Icons.volume_up_outlined,
|
||||||
|
iconSize: SizeUtils.getByScreen(small: 42, big: 40),
|
||||||
|
text: context.translate(I18n.volumeControl),
|
||||||
|
),
|
||||||
|
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
|
||||||
AppMenuButton(
|
AppMenuButton(
|
||||||
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
color: theme.getColorFor(ThemeCode.legacyPrimary),
|
||||||
onPressed: () => navigationContract.pushTo(AppRoutes.health),
|
onPressed: () => navigationContract.pushTo(AppRoutes.health),
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:legacy_shared/legacy_shared.dart';
|
||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
class DeviceUpdateDatasource {
|
||||||
|
DeviceUpdateDatasource(this._repository);
|
||||||
|
|
||||||
|
final QuestiaRepository _repository;
|
||||||
|
|
||||||
|
Future<void> updateDeviceSettings({
|
||||||
|
required DeviceEntity device,
|
||||||
|
required Map<String, dynamic> updatedSettings,
|
||||||
|
}) async {
|
||||||
|
final csvBase64 = DeviceCsvBuilder.buildBase64Csv(
|
||||||
|
device: device,
|
||||||
|
settings: updatedSettings,
|
||||||
|
);
|
||||||
|
|
||||||
|
await safeCall(
|
||||||
|
() => _repository.put<dynamic>(
|
||||||
|
'/devices',
|
||||||
|
body: {'csv': csvBase64},
|
||||||
|
),
|
||||||
|
'Error updating device settings',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
|
|
||||||
|
import 'device_update_datasource.dart';
|
||||||
|
|
||||||
|
final deviceUpdateDatasourceProvider = Provider<DeviceUpdateDatasource>((ref) {
|
||||||
|
return DeviceUpdateDatasource(GetIt.I<QuestiaRepository>());
|
||||||
|
});
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:legacy_shared/legacy_shared.dart';
|
||||||
|
|
||||||
|
import '../../data/device_update_datasource.dart';
|
||||||
|
import '../../data/device_update_datasource_provider.dart';
|
||||||
|
import 'volume_control_view_state.dart';
|
||||||
|
|
||||||
|
final volumeControlViewModelProvider =
|
||||||
|
NotifierProvider.autoDispose<VolumeControlViewModel, VolumeControlViewState>(
|
||||||
|
VolumeControlViewModel.new,
|
||||||
|
);
|
||||||
|
|
||||||
|
class VolumeControlViewModel extends Notifier<VolumeControlViewState> {
|
||||||
|
late final DeviceUpdateDatasource _datasource;
|
||||||
|
|
||||||
|
@override
|
||||||
|
VolumeControlViewState build() {
|
||||||
|
_datasource = ref.read(deviceUpdateDatasourceProvider);
|
||||||
|
Future.microtask(() => _load());
|
||||||
|
return const VolumeControlViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final device = ref.read(selectedDeviceProvider);
|
||||||
|
if (device == null) return;
|
||||||
|
|
||||||
|
final volume = device.settings['volume'] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
device: device,
|
||||||
|
media: (volume['media'] as num?)?.toInt() ?? 50,
|
||||||
|
ringtone: (volume['ringtone'] as num?)?.toInt() ?? 50,
|
||||||
|
alarm: (volume['alarm'] as num?)?.toInt() ?? 50,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!ref.mounted) return;
|
||||||
|
state = state.copyWith(isLoading: false, errorMessage: e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMedia(int value) => state = state.copyWith(media: value);
|
||||||
|
void setRingtone(int value) => state = state.copyWith(ringtone: value);
|
||||||
|
void setAlarm(int value) => state = state.copyWith(alarm: value);
|
||||||
|
|
||||||
|
Future<void> submit() async {
|
||||||
|
final device = state.device;
|
||||||
|
if (device == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
state = state.copyWith(isLoading: true, isComplete: false, errorMessage: '');
|
||||||
|
|
||||||
|
final settings = Map<String, dynamic>.from(device.settings);
|
||||||
|
settings['volume'] = {
|
||||||
|
'media': state.media,
|
||||||
|
'ringtone': state.ringtone,
|
||||||
|
'alarm': state.alarm,
|
||||||
|
};
|
||||||
|
|
||||||
|
await _datasource.updateDeviceSettings(
|
||||||
|
device: device,
|
||||||
|
updatedSettings: settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ref.mounted) return;
|
||||||
|
state = state.copyWith(isLoading: false, isComplete: true);
|
||||||
|
} catch (e) {
|
||||||
|
if (!ref.mounted) return;
|
||||||
|
state = state.copyWith(isLoading: false, errorMessage: e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
part 'volume_control_view_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class VolumeControlViewState with _$VolumeControlViewState {
|
||||||
|
const factory VolumeControlViewState({
|
||||||
|
@Default(true) bool isLoading,
|
||||||
|
@Default(false) bool isComplete,
|
||||||
|
DeviceEntity? device,
|
||||||
|
@Default(50) int media,
|
||||||
|
@Default(50) int ringtone,
|
||||||
|
@Default(50) int alarm,
|
||||||
|
@Default('') String errorMessage,
|
||||||
|
}) = _VolumeControlViewState;
|
||||||
|
}
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'volume_control_view_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$VolumeControlViewState {
|
||||||
|
|
||||||
|
bool get isLoading; bool get isComplete; DeviceEntity? get device; int get media; int get ringtone; int get alarm; String get errorMessage;
|
||||||
|
/// Create a copy of VolumeControlViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$VolumeControlViewStateCopyWith<VolumeControlViewState> get copyWith => _$VolumeControlViewStateCopyWithImpl<VolumeControlViewState>(this as VolumeControlViewState, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is VolumeControlViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.device, device) || other.device == device)&&(identical(other.media, media) || other.media == media)&&(identical(other.ringtone, ringtone) || other.ringtone == ringtone)&&(identical(other.alarm, alarm) || other.alarm == alarm)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,device,media,ringtone,alarm,errorMessage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'VolumeControlViewState(isLoading: $isLoading, isComplete: $isComplete, device: $device, media: $media, ringtone: $ringtone, alarm: $alarm, errorMessage: $errorMessage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $VolumeControlViewStateCopyWith<$Res> {
|
||||||
|
factory $VolumeControlViewStateCopyWith(VolumeControlViewState value, $Res Function(VolumeControlViewState) _then) = _$VolumeControlViewStateCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, String errorMessage
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$DeviceEntityCopyWith<$Res>? get device;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$VolumeControlViewStateCopyWithImpl<$Res>
|
||||||
|
implements $VolumeControlViewStateCopyWith<$Res> {
|
||||||
|
_$VolumeControlViewStateCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final VolumeControlViewState _self;
|
||||||
|
final $Res Function(VolumeControlViewState) _then;
|
||||||
|
|
||||||
|
/// Create a copy of VolumeControlViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? isComplete = null,Object? device = freezed,Object? media = null,Object? ringtone = null,Object? alarm = null,Object? errorMessage = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DeviceEntity?,media: null == media ? _self.media : media // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,ringtone: null == ringtone ? _self.ringtone : ringtone // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,alarm: null == alarm ? _self.alarm : alarm // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of VolumeControlViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DeviceEntityCopyWith<$Res>? get device {
|
||||||
|
if (_self.device == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DeviceEntityCopyWith<$Res>(_self.device!, (value) {
|
||||||
|
return _then(_self.copyWith(device: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [VolumeControlViewState].
|
||||||
|
extension VolumeControlViewStatePatterns on VolumeControlViewState {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _VolumeControlViewState value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _VolumeControlViewState() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _VolumeControlViewState value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _VolumeControlViewState():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _VolumeControlViewState value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _VolumeControlViewState() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _VolumeControlViewState() when $default != null:
|
||||||
|
return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.ringtone,_that.alarm,_that.errorMessage);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, String errorMessage) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _VolumeControlViewState():
|
||||||
|
return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.ringtone,_that.alarm,_that.errorMessage);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, String errorMessage)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _VolumeControlViewState() when $default != null:
|
||||||
|
return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.ringtone,_that.alarm,_that.errorMessage);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _VolumeControlViewState implements VolumeControlViewState {
|
||||||
|
const _VolumeControlViewState({this.isLoading = true, this.isComplete = false, this.device, this.media = 50, this.ringtone = 50, this.alarm = 50, this.errorMessage = ''});
|
||||||
|
|
||||||
|
|
||||||
|
@override@JsonKey() final bool isLoading;
|
||||||
|
@override@JsonKey() final bool isComplete;
|
||||||
|
@override final DeviceEntity? device;
|
||||||
|
@override@JsonKey() final int media;
|
||||||
|
@override@JsonKey() final int ringtone;
|
||||||
|
@override@JsonKey() final int alarm;
|
||||||
|
@override@JsonKey() final String errorMessage;
|
||||||
|
|
||||||
|
/// Create a copy of VolumeControlViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$VolumeControlViewStateCopyWith<_VolumeControlViewState> get copyWith => __$VolumeControlViewStateCopyWithImpl<_VolumeControlViewState>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VolumeControlViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.device, device) || other.device == device)&&(identical(other.media, media) || other.media == media)&&(identical(other.ringtone, ringtone) || other.ringtone == ringtone)&&(identical(other.alarm, alarm) || other.alarm == alarm)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,device,media,ringtone,alarm,errorMessage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'VolumeControlViewState(isLoading: $isLoading, isComplete: $isComplete, device: $device, media: $media, ringtone: $ringtone, alarm: $alarm, errorMessage: $errorMessage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$VolumeControlViewStateCopyWith<$Res> implements $VolumeControlViewStateCopyWith<$Res> {
|
||||||
|
factory _$VolumeControlViewStateCopyWith(_VolumeControlViewState value, $Res Function(_VolumeControlViewState) _then) = __$VolumeControlViewStateCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, String errorMessage
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $DeviceEntityCopyWith<$Res>? get device;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$VolumeControlViewStateCopyWithImpl<$Res>
|
||||||
|
implements _$VolumeControlViewStateCopyWith<$Res> {
|
||||||
|
__$VolumeControlViewStateCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _VolumeControlViewState _self;
|
||||||
|
final $Res Function(_VolumeControlViewState) _then;
|
||||||
|
|
||||||
|
/// Create a copy of VolumeControlViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? isComplete = null,Object? device = freezed,Object? media = null,Object? ringtone = null,Object? alarm = null,Object? errorMessage = null,}) {
|
||||||
|
return _then(_VolumeControlViewState(
|
||||||
|
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DeviceEntity?,media: null == media ? _self.media : media // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,ringtone: null == ringtone ? _self.ringtone : ringtone // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,alarm: null == alarm ? _self.alarm : alarm // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of VolumeControlViewState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DeviceEntityCopyWith<$Res>? get device {
|
||||||
|
if (_self.device == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DeviceEntityCopyWith<$Res>(_self.device!, (value) {
|
||||||
|
return _then(_self.copyWith(device: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:legacy_shared/legacy_shared.dart';
|
||||||
|
import 'package:sf_localizations/sf_localizations.dart';
|
||||||
|
|
||||||
|
import 'state/volume_control_view_model.dart';
|
||||||
|
import 'widgets/volume_thumb_shape.dart';
|
||||||
|
|
||||||
|
class VolumeControlScreen extends ConsumerWidget {
|
||||||
|
const VolumeControlScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final theme = ref.watch(themePortProvider);
|
||||||
|
final vm = ref.read(volumeControlViewModelProvider.notifier);
|
||||||
|
final state = ref.watch(volumeControlViewModelProvider);
|
||||||
|
|
||||||
|
ref.listen(volumeControlViewModelProvider.select((s) => s.errorMessage),
|
||||||
|
(_, msg) {
|
||||||
|
if (msg.isNotEmpty) {
|
||||||
|
showTopSnackbar(context, message: msg, type: MessageType.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.listen(volumeControlViewModelProvider.select((s) => s.isComplete),
|
||||||
|
(_, done) {
|
||||||
|
if (done) Navigator.pop(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
|
||||||
|
|
||||||
|
return LegacyPageLayout(
|
||||||
|
theme: theme,
|
||||||
|
title: context.translate(I18n.volumeControl),
|
||||||
|
body: state.isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_VolumeCard(
|
||||||
|
label: context.translate(I18n.volumeMedia),
|
||||||
|
value: state.media,
|
||||||
|
color: primaryColor,
|
||||||
|
onChanged: vm.setMedia,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_VolumeCard(
|
||||||
|
label: context.translate(I18n.volumeRingtone),
|
||||||
|
value: state.ringtone,
|
||||||
|
color: primaryColor,
|
||||||
|
onChanged: vm.setRingtone,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_VolumeCard(
|
||||||
|
label: context.translate(I18n.volumeAlarm),
|
||||||
|
value: state.alarm,
|
||||||
|
color: primaryColor,
|
||||||
|
onChanged: vm.setAlarm,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
context.translate(I18n.volumeHint),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
footer: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||||
|
child: state.isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: PrimaryButton(
|
||||||
|
onPressed: vm.submit,
|
||||||
|
text: context.translate(I18n.volumeSend),
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VolumeCard extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final int value;
|
||||||
|
final Color color;
|
||||||
|
final ValueChanged<int> onChanged;
|
||||||
|
|
||||||
|
const _VolumeCard({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.color,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
int get _displayValue => (value / 10).round();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 14, 16, 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.05),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.volume_up, color: Colors.grey.shade400, size: 22),
|
||||||
|
Expanded(
|
||||||
|
child: SliderTheme(
|
||||||
|
data: SliderThemeData(
|
||||||
|
activeTrackColor: color,
|
||||||
|
thumbColor: Colors.white,
|
||||||
|
thumbShape: VolumeThumbShape(color: color),
|
||||||
|
inactiveTrackColor: Colors.grey.shade200,
|
||||||
|
overlayColor: color.withValues(alpha: 0.1),
|
||||||
|
trackHeight: 6,
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
value: value.toDouble(),
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
divisions: 10,
|
||||||
|
onChanged: (v) => onChanged(v.round()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 24,
|
||||||
|
child: Text(
|
||||||
|
'$_displayValue',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class VolumeThumbShape extends SliderComponentShape {
|
||||||
|
final Color color;
|
||||||
|
static const _radius = 11.0;
|
||||||
|
static const _strokeWidth = 2.5;
|
||||||
|
static const _lineSpacing = 2.5;
|
||||||
|
static const _lineLength = 4.0;
|
||||||
|
|
||||||
|
const VolumeThumbShape({required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size getPreferredSize(bool isEnabled, bool isDiscrete) =>
|
||||||
|
const Size(_radius * 2, _radius * 2);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(
|
||||||
|
PaintingContext context,
|
||||||
|
Offset center, {
|
||||||
|
required Animation<double> activationAnimation,
|
||||||
|
required Animation<double> enableAnimation,
|
||||||
|
required bool isDiscrete,
|
||||||
|
required TextPainter labelPainter,
|
||||||
|
required RenderBox parentBox,
|
||||||
|
required SliderThemeData sliderTheme,
|
||||||
|
required TextDirection textDirection,
|
||||||
|
required double value,
|
||||||
|
required double textScaleFactor,
|
||||||
|
required Size sizeWithOverflow,
|
||||||
|
}) {
|
||||||
|
final canvas = context.canvas;
|
||||||
|
|
||||||
|
canvas.drawCircle(
|
||||||
|
center,
|
||||||
|
_radius,
|
||||||
|
Paint()
|
||||||
|
..color = Colors.white
|
||||||
|
..style = PaintingStyle.fill,
|
||||||
|
);
|
||||||
|
canvas.drawCircle(
|
||||||
|
center,
|
||||||
|
_radius,
|
||||||
|
Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = _strokeWidth,
|
||||||
|
);
|
||||||
|
|
||||||
|
final linePaint = Paint()
|
||||||
|
..color = color
|
||||||
|
..strokeWidth = 2
|
||||||
|
..strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(center.dx - _lineSpacing, center.dy - _lineLength),
|
||||||
|
Offset(center.dx - _lineSpacing, center.dy + _lineLength),
|
||||||
|
linePaint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(center.dx + _lineSpacing, center.dy - _lineLength),
|
||||||
|
Offset(center.dx + _lineSpacing, center.dy + _lineLength),
|
||||||
|
linePaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import 'presentation/volume_control_screen.dart';
|
||||||
|
|
||||||
|
class VolumeControlBuilder {
|
||||||
|
const VolumeControlBuilder();
|
||||||
|
|
||||||
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
|
return MaterialPage<void>(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: const VolumeControlScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:legacy_shared/legacy_shared.dart';
|
import 'package:legacy_shared/legacy_shared.dart';
|
||||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
@@ -19,38 +17,10 @@ class LanguageRemoteDatasourceImpl implements LanguageRemoteDatasource {
|
|||||||
final settings = Map<String, dynamic>.from(device.settings);
|
final settings = Map<String, dynamic>.from(device.settings);
|
||||||
settings['language'] = newLanguage;
|
settings['language'] = newLanguage;
|
||||||
|
|
||||||
String csvEscape(dynamic value) {
|
final csvBase64 = DeviceCsvBuilder.buildBase64Csv(
|
||||||
if (value == null) return '';
|
device: device,
|
||||||
final json = jsonEncode(value);
|
settings: settings,
|
||||||
return '"${json.replaceAll('"', '""')}"';
|
);
|
||||||
}
|
|
||||||
|
|
||||||
final csvHeader =
|
|
||||||
'id,carrierName,flags,settings,battery,carrierBirthday,'
|
|
||||||
'carrierWeight,carrierStepLength,carrierGenre,comment,'
|
|
||||||
'groupId,lastConnection,paymentOptions,phone,simId,tags';
|
|
||||||
|
|
||||||
final csvRow = [
|
|
||||||
device.id,
|
|
||||||
device.carrierName ?? '',
|
|
||||||
csvEscape(device.flags),
|
|
||||||
csvEscape(settings),
|
|
||||||
device.battery ?? '',
|
|
||||||
device.carrierBirthday ?? '',
|
|
||||||
device.carrierWeight ?? '',
|
|
||||||
device.carrierStepLength ?? '',
|
|
||||||
device.carrierGenre ?? '',
|
|
||||||
device.comment ?? '',
|
|
||||||
device.groupId ?? '',
|
|
||||||
device.lastConnection ?? '',
|
|
||||||
device.paymentOptions != null ? csvEscape(device.paymentOptions) : '',
|
|
||||||
device.phone ?? '',
|
|
||||||
device.simId ?? '',
|
|
||||||
csvEscape(device.tags ?? []),
|
|
||||||
].join(',');
|
|
||||||
|
|
||||||
final csv = '$csvHeader\n$csvRow';
|
|
||||||
final csvBase64 = base64Encode(utf8.encode(csv));
|
|
||||||
|
|
||||||
await safeCall(
|
await safeCall(
|
||||||
() => _repository.put<dynamic>(
|
() => _repository.put<dynamic>(
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
text: I18n.sosContacts,
|
text: I18n.sosContacts,
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
// _item(
|
_item(
|
||||||
// context,
|
context,
|
||||||
// onPressed: () => navigationContract.pushTo(AppRoutes.sound),
|
onPressed: () => navigationContract.pushTo(AppRoutes.sound),
|
||||||
// icon: Icons.volume_up_outlined,
|
icon: Icons.volume_up_outlined,
|
||||||
// text: I18n.sound,
|
text: I18n.sound,
|
||||||
// color: color,
|
color: color,
|
||||||
// ),
|
),
|
||||||
// _item(
|
// _item(
|
||||||
// context,
|
// context,
|
||||||
// onPressed: () =>
|
// onPressed: () =>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
abstract class SetSoundUseCase {
|
|
||||||
Future<void> setSound({required String deviceId});
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import 'package:settings/src/core/domain/repositories/settings_repository.dart';
|
|
||||||
|
|
||||||
import 'set_sound_use_case.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class SetSoundUseCaseImpl implements SetSoundUseCase {
|
|
||||||
SetSoundUseCaseImpl(this._repository);
|
|
||||||
|
|
||||||
final SettingsRepository _repository;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> setSound({required String deviceId}) async {
|
|
||||||
return;
|
|
||||||
// return _repository.setSound(deviceId: deviceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:settings/src/core/providers/settings_repository_provider.dart';
|
|
||||||
|
|
||||||
import '../../domain/set_sound_use_case.dart';
|
|
||||||
import '../../domain/set_sound_use_case_impl.dart';
|
|
||||||
|
|
||||||
final setSoundUseCaseProvider = Provider.autoDispose<SetSoundUseCase>((ref) {
|
|
||||||
final settingsRepository = ref.read(settingsRepositoryProvider);
|
|
||||||
return SetSoundUseCaseImpl(settingsRepository);
|
|
||||||
});
|
|
||||||
@@ -20,11 +20,31 @@ class SoundScreen extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.read(themePortProvider);
|
final theme = ref.read(themePortProvider);
|
||||||
|
|
||||||
|
ref.listen(soundViewModelProvider.select((s) => s.errorMessage), (
|
||||||
|
_,
|
||||||
|
errorMessage,
|
||||||
|
) {
|
||||||
|
if (errorMessage.isNotEmpty) {
|
||||||
|
showTopSnackbar(
|
||||||
|
context,
|
||||||
|
message: errorMessage,
|
||||||
|
type: MessageType.error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.listen(soundViewModelProvider.select((s) => s.isComplete), (
|
||||||
|
_,
|
||||||
|
isComplete,
|
||||||
|
) {
|
||||||
|
if (isComplete) Navigator.pop(context);
|
||||||
|
});
|
||||||
|
|
||||||
return LegacyPageLayout(
|
return LegacyPageLayout(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
title: context.translate(I18n.sound),
|
title: context.translate(I18n.sound),
|
||||||
body: Padding(
|
body: SingleChildScrollView(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Center(child:
|
Center(child:
|
||||||
@@ -33,7 +53,7 @@ class SoundScreen extends ConsumerWidget {
|
|||||||
size: 180,
|
size: 180,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
SizedBox(height: 36),
|
const SizedBox(height: 36),
|
||||||
const _OptionsSection()
|
const _OptionsSection()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -61,15 +81,15 @@ class _OptionsSection extends ConsumerWidget {
|
|||||||
_SectionButton(
|
_SectionButton(
|
||||||
title: context.translate(I18n.soundAndVibration),
|
title: context.translate(I18n.soundAndVibration),
|
||||||
icon: Icons.volume_up_outlined,
|
icon: Icons.volume_up_outlined,
|
||||||
active: soundOption == 'SOUND_AND_VIBRATION',
|
active: soundOption == 'VIBRATION_AND_RINGING',
|
||||||
onPressed: () {vm.setSoundOption('SOUND_AND_VIBRATION');},
|
onPressed: () {vm.setSoundOption('VIBRATION_AND_RINGING');},
|
||||||
),
|
),
|
||||||
SizedBox(height: 12),
|
SizedBox(height: 12),
|
||||||
_SectionButton(
|
_SectionButton(
|
||||||
title: context.translate(I18n.soundOnly),
|
title: context.translate(I18n.soundOnly),
|
||||||
icon: Icons.volume_up_outlined,
|
icon: Icons.volume_up_outlined,
|
||||||
active: soundOption == 'SOUND',
|
active: soundOption == 'RINGING',
|
||||||
onPressed: () {vm.setSoundOption('SOUND');},
|
onPressed: () {vm.setSoundOption('RINGING');},
|
||||||
),
|
),
|
||||||
SizedBox(height: 12),
|
SizedBox(height: 12),
|
||||||
_SectionButton(
|
_SectionButton(
|
||||||
@@ -82,8 +102,8 @@ class _OptionsSection extends ConsumerWidget {
|
|||||||
_SectionButton(
|
_SectionButton(
|
||||||
title: context.translate(I18n.silent),
|
title: context.translate(I18n.silent),
|
||||||
icon: Icons.volume_mute_outlined,
|
icon: Icons.volume_mute_outlined,
|
||||||
active: soundOption == 'SILENT',
|
active: soundOption == 'SILENCE',
|
||||||
onPressed: () {vm.setSoundOption('SILENT');},
|
onPressed: () {vm.setSoundOption('SILENCE');},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@@ -152,7 +172,7 @@ class _SaveSection extends ConsumerWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
onPressed: vm.submit,
|
onPressed: () {vm.submit();},
|
||||||
text: context.translate(I18n.save),
|
text: context.translate(I18n.save),
|
||||||
color: theme.getColorFor(ThemeCode.legacyPrimary)
|
color: theme.getColorFor(ThemeCode.legacyPrimary)
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:legacy_shared/legacy_shared.dart';
|
import 'package:legacy_shared/legacy_shared.dart';
|
||||||
import 'package:sf_shared/sf_shared.dart';
|
|
||||||
|
|
||||||
import '../../domain/set_sound_use_case.dart';
|
|
||||||
import '../providers/set_sound_use_case_provider.dart';
|
|
||||||
import 'sound_view_state.dart';
|
import 'sound_view_state.dart';
|
||||||
|
|
||||||
final soundViewModelProvider =
|
final soundViewModelProvider =
|
||||||
@@ -12,11 +9,11 @@ NotifierProvider.autoDispose<SoundViewModel, SoundViewState>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
class SoundViewModel extends Notifier<SoundViewState> {
|
class SoundViewModel extends Notifier<SoundViewState> {
|
||||||
late final SetSoundUseCase _setSoundUseCase;
|
late final CommandsRepository _commandsRepository;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SoundViewState build() {
|
SoundViewState build() {
|
||||||
_setSoundUseCase = ref.read(setSoundUseCaseProvider);
|
_commandsRepository = ref.read(commandsRepositoryProvider);
|
||||||
|
|
||||||
Future.microtask(() => load());
|
Future.microtask(() => load());
|
||||||
|
|
||||||
@@ -25,16 +22,12 @@ class SoundViewModel extends Notifier<SoundViewState> {
|
|||||||
|
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
final device = ref.read(selectedDeviceProvider);
|
final device = ref.read(selectedDeviceProvider);
|
||||||
setDevice(device!);
|
if (device == null) return;
|
||||||
state = state.copyWith(
|
|
||||||
soundOption: 'SOUND_AND_VIBRATION',
|
|
||||||
isLoading: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDevice(DeviceEntity device) {
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
deviceId: device.identificator,
|
deviceId: device.identificator,
|
||||||
|
soundOption: device.settings['soundMode'] ?? 'VIBRATION',
|
||||||
|
isLoading: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +43,20 @@ class SoundViewModel extends Notifier<SoundViewState> {
|
|||||||
try {
|
try {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
isComplete: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final request = SendCommandRequestModel(
|
||||||
|
device: state.deviceId,
|
||||||
|
command: DeviceCommand.setSoundMode,
|
||||||
|
data: {'soundMode': state.soundOption}
|
||||||
|
);
|
||||||
|
await _commandsRepository.send(request: request);
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
isComplete: true,
|
||||||
);
|
);
|
||||||
_setSoundUseCase.setSound(deviceId: state.deviceId);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ abstract class SoundViewState with _$SoundViewState {
|
|||||||
@Default('') String deviceId,
|
@Default('') String deviceId,
|
||||||
String? soundOption,
|
String? soundOption,
|
||||||
@Default(true) bool isLoading,
|
@Default(true) bool isLoading,
|
||||||
|
@Default(false) bool isComplete,
|
||||||
@Default('') String errorMessage,
|
@Default('') String errorMessage,
|
||||||
}) = _SoundViewState;
|
}) = _SoundViewState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SoundViewState {
|
mixin _$SoundViewState {
|
||||||
|
|
||||||
String get deviceId; String? get soundOption; bool get isLoading; String get errorMessage;
|
String get deviceId; String? get soundOption; bool get isLoading; bool get isComplete; String get errorMessage;
|
||||||
/// Create a copy of SoundViewState
|
/// Create a copy of SoundViewState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -25,16 +25,16 @@ $SoundViewStateCopyWith<SoundViewState> get copyWith => _$SoundViewStateCopyWith
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SoundViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.soundOption, soundOption) || other.soundOption == soundOption)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SoundViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.soundOption, soundOption) || other.soundOption == soundOption)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,deviceId,soundOption,isLoading,errorMessage);
|
int get hashCode => Object.hash(runtimeType,deviceId,soundOption,isLoading,isComplete,errorMessage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SoundViewState(deviceId: $deviceId, soundOption: $soundOption, isLoading: $isLoading, errorMessage: $errorMessage)';
|
return 'SoundViewState(deviceId: $deviceId, soundOption: $soundOption, isLoading: $isLoading, isComplete: $isComplete, errorMessage: $errorMessage)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ abstract mixin class $SoundViewStateCopyWith<$Res> {
|
|||||||
factory $SoundViewStateCopyWith(SoundViewState value, $Res Function(SoundViewState) _then) = _$SoundViewStateCopyWithImpl;
|
factory $SoundViewStateCopyWith(SoundViewState value, $Res Function(SoundViewState) _then) = _$SoundViewStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String deviceId, String? soundOption, bool isLoading, String errorMessage
|
String deviceId, String? soundOption, bool isLoading, bool isComplete, String errorMessage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -62,11 +62,12 @@ class _$SoundViewStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SoundViewState
|
/// Create a copy of SoundViewState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? soundOption = freezed,Object? isLoading = null,Object? errorMessage = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? soundOption = freezed,Object? isLoading = null,Object? isComplete = null,Object? errorMessage = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,soundOption: freezed == soundOption ? _self.soundOption : soundOption // ignore: cast_nullable_to_non_nullable
|
as String,soundOption: freezed == soundOption ? _self.soundOption : soundOption // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
));
|
));
|
||||||
@@ -153,10 +154,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String? soundOption, bool isLoading, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String? soundOption, bool isLoading, bool isComplete, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SoundViewState() when $default != null:
|
case _SoundViewState() when $default != null:
|
||||||
return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMessage);case _:
|
return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.isComplete,_that.errorMessage);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -174,10 +175,10 @@ return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMess
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String? soundOption, bool isLoading, String errorMessage) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String? soundOption, bool isLoading, bool isComplete, String errorMessage) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SoundViewState():
|
case _SoundViewState():
|
||||||
return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMessage);case _:
|
return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.isComplete,_that.errorMessage);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -194,10 +195,10 @@ return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMess
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String? soundOption, bool isLoading, String errorMessage)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String? soundOption, bool isLoading, bool isComplete, String errorMessage)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SoundViewState() when $default != null:
|
case _SoundViewState() when $default != null:
|
||||||
return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMessage);case _:
|
return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.isComplete,_that.errorMessage);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -209,12 +210,13 @@ return $default(_that.deviceId,_that.soundOption,_that.isLoading,_that.errorMess
|
|||||||
|
|
||||||
|
|
||||||
class _SoundViewState implements SoundViewState {
|
class _SoundViewState implements SoundViewState {
|
||||||
const _SoundViewState({this.deviceId = '', this.soundOption, this.isLoading = true, this.errorMessage = ''});
|
const _SoundViewState({this.deviceId = '', this.soundOption, this.isLoading = true, this.isComplete = false, this.errorMessage = ''});
|
||||||
|
|
||||||
|
|
||||||
@override@JsonKey() final String deviceId;
|
@override@JsonKey() final String deviceId;
|
||||||
@override final String? soundOption;
|
@override final String? soundOption;
|
||||||
@override@JsonKey() final bool isLoading;
|
@override@JsonKey() final bool isLoading;
|
||||||
|
@override@JsonKey() final bool isComplete;
|
||||||
@override@JsonKey() final String errorMessage;
|
@override@JsonKey() final String errorMessage;
|
||||||
|
|
||||||
/// Create a copy of SoundViewState
|
/// Create a copy of SoundViewState
|
||||||
@@ -227,16 +229,16 @@ _$SoundViewStateCopyWith<_SoundViewState> get copyWith => __$SoundViewStateCopyW
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SoundViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.soundOption, soundOption) || other.soundOption == soundOption)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SoundViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.soundOption, soundOption) || other.soundOption == soundOption)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,deviceId,soundOption,isLoading,errorMessage);
|
int get hashCode => Object.hash(runtimeType,deviceId,soundOption,isLoading,isComplete,errorMessage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SoundViewState(deviceId: $deviceId, soundOption: $soundOption, isLoading: $isLoading, errorMessage: $errorMessage)';
|
return 'SoundViewState(deviceId: $deviceId, soundOption: $soundOption, isLoading: $isLoading, isComplete: $isComplete, errorMessage: $errorMessage)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -247,7 +249,7 @@ abstract mixin class _$SoundViewStateCopyWith<$Res> implements $SoundViewStateCo
|
|||||||
factory _$SoundViewStateCopyWith(_SoundViewState value, $Res Function(_SoundViewState) _then) = __$SoundViewStateCopyWithImpl;
|
factory _$SoundViewStateCopyWith(_SoundViewState value, $Res Function(_SoundViewState) _then) = __$SoundViewStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String deviceId, String? soundOption, bool isLoading, String errorMessage
|
String deviceId, String? soundOption, bool isLoading, bool isComplete, String errorMessage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -264,11 +266,12 @@ class __$SoundViewStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SoundViewState
|
/// Create a copy of SoundViewState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? soundOption = freezed,Object? isLoading = null,Object? errorMessage = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? soundOption = freezed,Object? isLoading = null,Object? isComplete = null,Object? errorMessage = null,}) {
|
||||||
return _then(_SoundViewState(
|
return _then(_SoundViewState(
|
||||||
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,soundOption: freezed == soundOption ? _self.soundOption : soundOption // ignore: cast_nullable_to_non_nullable
|
as String,soundOption: freezed == soundOption ? _self.soundOption : soundOption // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
as String?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export 'src/data/models/device_response_model.dart';
|
|||||||
export 'src/data/models/get_devices_response_model.dart';
|
export 'src/data/models/get_devices_response_model.dart';
|
||||||
export 'src/data/models/send_command_request_model.dart';
|
export 'src/data/models/send_command_request_model.dart';
|
||||||
export 'src/utils/dio_error_mapper.dart';
|
export 'src/utils/dio_error_mapper.dart';
|
||||||
|
export 'src/utils/device_csv_builder.dart';
|
||||||
export 'src/domain/repositories/command_repository.dart';
|
export 'src/domain/repositories/command_repository.dart';
|
||||||
export 'src/providers/commands_repository_provider.dart';
|
export 'src/providers/commands_repository_provider.dart';
|
||||||
export 'src/domain/repositories/devices_repository.dart';
|
export 'src/domain/repositories/devices_repository.dart';
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ enum DeviceCommand {
|
|||||||
setLanguage,
|
setLanguage,
|
||||||
@JsonValue('SHUTDOWN')
|
@JsonValue('SHUTDOWN')
|
||||||
shutdown,
|
shutdown,
|
||||||
@JsonValue('SOUND')
|
@JsonValue('SET_SOUND_MODE')
|
||||||
sound,
|
setSoundMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ const _$DeviceCommandEnumMap = {
|
|||||||
DeviceCommand.rewards: 'REWARDS',
|
DeviceCommand.rewards: 'REWARDS',
|
||||||
DeviceCommand.setLanguage: 'SET_LANGUAGE',
|
DeviceCommand.setLanguage: 'SET_LANGUAGE',
|
||||||
DeviceCommand.shutdown: 'SHUTDOWN',
|
DeviceCommand.shutdown: 'SHUTDOWN',
|
||||||
DeviceCommand.sound: 'SOUND',
|
DeviceCommand.setSoundMode: 'SET_SOUND_MODE',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:sf_shared/sf_shared.dart';
|
||||||
|
|
||||||
|
class DeviceCsvBuilder {
|
||||||
|
const DeviceCsvBuilder._();
|
||||||
|
|
||||||
|
static String buildBase64Csv({
|
||||||
|
required DeviceEntity device,
|
||||||
|
required Map<String, dynamic> settings,
|
||||||
|
}) {
|
||||||
|
final csvHeader =
|
||||||
|
'id,carrierName,flags,settings,battery,carrierBirthday,'
|
||||||
|
'carrierWeight,carrierStepLength,carrierGenre,comment,'
|
||||||
|
'groupId,lastConnection,paymentOptions,phone,simId,tags';
|
||||||
|
|
||||||
|
final csvRow = [
|
||||||
|
device.id,
|
||||||
|
device.carrierName ?? '',
|
||||||
|
_csvEscape(device.flags),
|
||||||
|
_csvEscape(settings),
|
||||||
|
device.battery ?? '',
|
||||||
|
device.carrierBirthday ?? '',
|
||||||
|
device.carrierWeight ?? '',
|
||||||
|
device.carrierStepLength ?? '',
|
||||||
|
device.carrierGenre ?? '',
|
||||||
|
device.comment ?? '',
|
||||||
|
device.groupId ?? '',
|
||||||
|
device.lastConnection ?? '',
|
||||||
|
device.paymentOptions != null ? _csvEscape(device.paymentOptions) : '',
|
||||||
|
device.phone ?? '',
|
||||||
|
device.simId ?? '',
|
||||||
|
_csvEscape(device.tags ?? []),
|
||||||
|
].join(',');
|
||||||
|
|
||||||
|
final csv = '$csvHeader\n$csvRow';
|
||||||
|
return base64Encode(utf8.encode(csv));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _csvEscape(dynamic value) {
|
||||||
|
if (value == null) return '';
|
||||||
|
final json = jsonEncode(value);
|
||||||
|
return '"${json.replaceAll('"', '""')}"';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,7 @@ class AppRoutes {
|
|||||||
static const rewards = '$deviceManagement/rewards';
|
static const rewards = '$deviceManagement/rewards';
|
||||||
static const activityMeter = '$deviceManagement/activity_meter';
|
static const activityMeter = '$deviceManagement/activity_meter';
|
||||||
static const appsUse = '$deviceManagement/apps_use';
|
static const appsUse = '$deviceManagement/apps_use';
|
||||||
|
static const volumeControl = '$deviceManagement/volume_control';
|
||||||
|
|
||||||
static const legacyLogin = '$legacy/login';
|
static const legacyLogin = '$legacy/login';
|
||||||
static const legacySignup = '$legacy/signup';
|
static const legacySignup = '$legacy/signup';
|
||||||
|
|||||||
@@ -617,5 +617,11 @@
|
|||||||
"vibrationOnly": "Nur Vibration",
|
"vibrationOnly": "Nur Vibration",
|
||||||
"silent": "Lautlos",
|
"silent": "Lautlos",
|
||||||
"syncClockMessage": "Synchronisieren Sie die Geräteuhr mit der aktuellen Uhrzeit",
|
"syncClockMessage": "Synchronisieren Sie die Geräteuhr mit der aktuellen Uhrzeit",
|
||||||
"locationWifiNetworksOptional": "WLAN-Netzwerke (optional)"
|
"locationWifiNetworksOptional": "WLAN-Netzwerke (optional)",
|
||||||
|
"volumeControl": "Lautstärkeregelung",
|
||||||
|
"volumeMedia": "Medienlautstärke",
|
||||||
|
"volumeRingtone": "Klingeltonlautstärke",
|
||||||
|
"volumeAlarm": "Alarmlautstärke",
|
||||||
|
"volumeHint": "Sie können den Schieberegler ziehen, um die Gerätelautstärke anzupassen. Die App speichert nur die zuletzt erfolgreich eingestellte Lautstärke. Die tatsächliche Lautstärke hängt vom Gerät ab.",
|
||||||
|
"volumeSend": "Senden"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -605,6 +605,12 @@
|
|||||||
"smsAlert": "SMS Alerts",
|
"smsAlert": "SMS Alerts",
|
||||||
"sosContacts": "SOS Contacts",
|
"sosContacts": "SOS Contacts",
|
||||||
"sound": "Sounds",
|
"sound": "Sounds",
|
||||||
|
"volumeControl": "Volume control",
|
||||||
|
"volumeMedia": "Media volume",
|
||||||
|
"volumeRingtone": "Ringtone volume",
|
||||||
|
"volumeAlarm": "Alarm volume",
|
||||||
|
"volumeHint": "You can drag the slider to adjust the device volume. The app only saves the most recently successfully adjusted volume level in cache. The actual volume will depend on the device.",
|
||||||
|
"volumeSend": "Send",
|
||||||
"syncClock": "Time Sync",
|
"syncClock": "Time Sync",
|
||||||
"timezone": "Timezone",
|
"timezone": "Timezone",
|
||||||
"wifiSettings": "WiFi Settings",
|
"wifiSettings": "WiFi Settings",
|
||||||
|
|||||||
@@ -603,6 +603,12 @@
|
|||||||
"smsAlert": "Alertas SMS",
|
"smsAlert": "Alertas SMS",
|
||||||
"sosContacts": "Agenda SOS",
|
"sosContacts": "Agenda SOS",
|
||||||
"sound": "Sonidos",
|
"sound": "Sonidos",
|
||||||
|
"volumeControl": "Control de volumen",
|
||||||
|
"volumeMedia": "Volumen de medios",
|
||||||
|
"volumeRingtone": "Volumen de tono de llamada",
|
||||||
|
"volumeAlarm": "Volumen de alarma",
|
||||||
|
"volumeHint": "Puede arrastrar el control deslizante para ajustar el volumen del dispositivo. La aplicación solo guarda el nivel de volumen más recientemente ajustado con éxito en la memoria caché. El volumen real dependerá del dispositivo.",
|
||||||
|
"volumeSend": "Enviar",
|
||||||
"syncClock": "Sincronización de tiempo",
|
"syncClock": "Sincronización de tiempo",
|
||||||
"timezone": "Cambio de horario y zona",
|
"timezone": "Cambio de horario y zona",
|
||||||
"wifiSettings": "Configuración WiFi",
|
"wifiSettings": "Configuración WiFi",
|
||||||
|
|||||||
@@ -617,5 +617,11 @@
|
|||||||
"vibrationOnly": "Vibration uniquement",
|
"vibrationOnly": "Vibration uniquement",
|
||||||
"silent": "Silencieux",
|
"silent": "Silencieux",
|
||||||
"syncClockMessage": "Synchroniser l'horloge de l'appareil avec l'heure actuelle",
|
"syncClockMessage": "Synchroniser l'horloge de l'appareil avec l'heure actuelle",
|
||||||
"locationWifiNetworksOptional": "Réseaux WiFi (facultatif)"
|
"locationWifiNetworksOptional": "Réseaux WiFi (facultatif)",
|
||||||
|
"volumeControl": "Contrôle du volume",
|
||||||
|
"volumeMedia": "Volume des médias",
|
||||||
|
"volumeRingtone": "Volume de la sonnerie",
|
||||||
|
"volumeAlarm": "Volume de l'alarme",
|
||||||
|
"volumeHint": "Vous pouvez faire glisser le curseur pour régler le volume de l'appareil. L'application ne sauvegarde que le dernier niveau de volume ajusté avec succès. Le volume réel dépendra de l'appareil.",
|
||||||
|
"volumeSend": "Envoyer"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -617,5 +617,11 @@
|
|||||||
"vibrationOnly": "Solo vibrazione",
|
"vibrationOnly": "Solo vibrazione",
|
||||||
"silent": "Silenzioso",
|
"silent": "Silenzioso",
|
||||||
"syncClockMessage": "Sincronizza l'orologio del dispositivo con l'ora attuale",
|
"syncClockMessage": "Sincronizza l'orologio del dispositivo con l'ora attuale",
|
||||||
"locationWifiNetworksOptional": "Reti WiFi (facoltativo)"
|
"locationWifiNetworksOptional": "Reti WiFi (facoltativo)",
|
||||||
|
"volumeControl": "Controllo volume",
|
||||||
|
"volumeMedia": "Volume multimediale",
|
||||||
|
"volumeRingtone": "Volume suoneria",
|
||||||
|
"volumeAlarm": "Volume sveglia",
|
||||||
|
"volumeHint": "Puoi trascinare il cursore per regolare il volume del dispositivo. L'app salva solo l'ultimo livello di volume regolato con successo. Il volume effettivo dipenderà dal dispositivo.",
|
||||||
|
"volumeSend": "Invia"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -617,5 +617,11 @@
|
|||||||
"vibrationOnly": "Apenas vibração",
|
"vibrationOnly": "Apenas vibração",
|
||||||
"silent": "Silencioso",
|
"silent": "Silencioso",
|
||||||
"syncClockMessage": "Sincronize o relógio do dispositivo com a hora atual",
|
"syncClockMessage": "Sincronize o relógio do dispositivo com a hora atual",
|
||||||
"locationWifiNetworksOptional": "Redes WiFi (opcional)"
|
"locationWifiNetworksOptional": "Redes WiFi (opcional)",
|
||||||
|
"volumeControl": "Controle de volume",
|
||||||
|
"volumeMedia": "Volume de mídia",
|
||||||
|
"volumeRingtone": "Volume do toque",
|
||||||
|
"volumeAlarm": "Volume do alarme",
|
||||||
|
"volumeHint": "Você pode arrastar o controle deslizante para ajustar o volume do dispositivo. O aplicativo salva apenas o nível de volume ajustado com sucesso mais recentemente. O volume real dependerá do dispositivo.",
|
||||||
|
"volumeSend": "Enviar"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -646,6 +646,12 @@ class I18n {
|
|||||||
static const String sound = 'sound';
|
static const String sound = 'sound';
|
||||||
static const String soundAndVibration = 'soundAndVibration';
|
static const String soundAndVibration = 'soundAndVibration';
|
||||||
static const String soundOnly = 'soundOnly';
|
static const String soundOnly = 'soundOnly';
|
||||||
|
static const String volumeControl = 'volumeControl';
|
||||||
|
static const String volumeMedia = 'volumeMedia';
|
||||||
|
static const String volumeRingtone = 'volumeRingtone';
|
||||||
|
static const String volumeAlarm = 'volumeAlarm';
|
||||||
|
static const String volumeHint = 'volumeHint';
|
||||||
|
static const String volumeSend = 'volumeSend';
|
||||||
static const String spo2 = 'spo2';
|
static const String spo2 = 'spo2';
|
||||||
static const String start = 'start';
|
static const String start = 'start';
|
||||||
static const String stateHint = 'stateHint';
|
static const String stateHint = 'stateHint';
|
||||||
|
|||||||
Reference in New Issue
Block a user