fix(volume): use capabilities max per slider and fix 0-10 scale

This commit is contained in:
2026-04-21 17:58:20 +02:00
parent 244e5bbd03
commit 315e5b2908
4 changed files with 51 additions and 29 deletions

View File

@@ -31,13 +31,20 @@ class VolumeControlViewModel extends Notifier<VolumeControlViewState> {
if (device == null) return;
final volume = device.settings.volume;
final capVolume = device.capabilities?.volume;
final maxMedia = capVolume?.media ?? 10;
final maxRingtone = capVolume?.ringtone ?? 10;
final maxAlarm = capVolume?.alarm ?? 10;
state = state.copyWith(
isLoading: false,
device: device,
media: volume.media,
ringtone: volume.ringtone,
alarm: volume.alarm,
media: volume.media.clamp(0, maxMedia),
ringtone: volume.ringtone.clamp(0, maxRingtone),
alarm: volume.alarm.clamp(0, maxAlarm),
maxMedia: maxMedia,
maxRingtone: maxRingtone,
maxAlarm: maxAlarm,
);
} catch (e) {
if (!ref.mounted) return;

View File

@@ -9,9 +9,12 @@ abstract class VolumeControlViewState with _$VolumeControlViewState {
@Default(true) bool isLoading,
@Default(false) bool isComplete,
DeviceEntity? device,
@Default(50) int media,
@Default(50) int ringtone,
@Default(50) int alarm,
@Default(5) int media,
@Default(5) int ringtone,
@Default(5) int alarm,
@Default(10) int maxMedia,
@Default(10) int maxRingtone,
@Default(10) int maxAlarm,
@Default('') String errorMessage,
}) = _VolumeControlViewState;
}

View File

@@ -14,7 +14,7 @@ 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;
bool get isLoading; bool get isComplete; DeviceEntity? get device; int get media; int get ringtone; int get alarm; int get maxMedia; int get maxRingtone; int get maxAlarm; String get errorMessage;
/// Create a copy of VolumeControlViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $VolumeControlViewStateCopyWith<VolumeControlViewState> get copyWith => _$Volume
@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));
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.maxMedia, maxMedia) || other.maxMedia == maxMedia)&&(identical(other.maxRingtone, maxRingtone) || other.maxRingtone == maxRingtone)&&(identical(other.maxAlarm, maxAlarm) || other.maxAlarm == maxAlarm)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,device,media,ringtone,alarm,errorMessage);
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,device,media,ringtone,alarm,maxMedia,maxRingtone,maxAlarm,errorMessage);
@override
String toString() {
return 'VolumeControlViewState(isLoading: $isLoading, isComplete: $isComplete, device: $device, media: $media, ringtone: $ringtone, alarm: $alarm, errorMessage: $errorMessage)';
return 'VolumeControlViewState(isLoading: $isLoading, isComplete: $isComplete, device: $device, media: $media, ringtone: $ringtone, alarm: $alarm, maxMedia: $maxMedia, maxRingtone: $maxRingtone, maxAlarm: $maxAlarm, errorMessage: $errorMessage)';
}
@@ -45,7 +45,7 @@ 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
bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, int maxMedia, int maxRingtone, int maxAlarm, String errorMessage
});
@@ -62,7 +62,7 @@ class _$VolumeControlViewStateCopyWithImpl<$Res>
/// 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,}) {
@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? maxMedia = null,Object? maxRingtone = null,Object? maxAlarm = 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
@@ -70,6 +70,9 @@ as bool,device: freezed == device ? _self.device : device // ignore: cast_nullab
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,maxMedia: null == maxMedia ? _self.maxMedia : maxMedia // ignore: cast_nullable_to_non_nullable
as int,maxRingtone: null == maxRingtone ? _self.maxRingtone : maxRingtone // ignore: cast_nullable_to_non_nullable
as int,maxAlarm: null == maxAlarm ? _self.maxAlarm : maxAlarm // ignore: cast_nullable_to_non_nullable
as int,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
@@ -168,10 +171,10 @@ return $default(_that);case _:
/// }
/// ```
@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;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, int maxMedia, int maxRingtone, int maxAlarm, 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 $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.ringtone,_that.alarm,_that.maxMedia,_that.maxRingtone,_that.maxAlarm,_that.errorMessage);case _:
return orElse();
}
@@ -189,10 +192,10 @@ return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.
/// }
/// ```
@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;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, int maxMedia, int maxRingtone, int maxAlarm, 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 _:
return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.ringtone,_that.alarm,_that.maxMedia,_that.maxRingtone,_that.maxAlarm,_that.errorMessage);case _:
throw StateError('Unexpected subclass');
}
@@ -209,10 +212,10 @@ return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.
/// }
/// ```
@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;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, int maxMedia, int maxRingtone, int maxAlarm, 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 $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.ringtone,_that.alarm,_that.maxMedia,_that.maxRingtone,_that.maxAlarm,_that.errorMessage);case _:
return null;
}
@@ -224,7 +227,7 @@ return $default(_that.isLoading,_that.isComplete,_that.device,_that.media,_that.
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 = ''});
const _VolumeControlViewState({this.isLoading = true, this.isComplete = false, this.device, this.media = 5, this.ringtone = 5, this.alarm = 5, this.maxMedia = 10, this.maxRingtone = 10, this.maxAlarm = 10, this.errorMessage = ''});
@override@JsonKey() final bool isLoading;
@@ -233,6 +236,9 @@ class _VolumeControlViewState implements VolumeControlViewState {
@override@JsonKey() final int media;
@override@JsonKey() final int ringtone;
@override@JsonKey() final int alarm;
@override@JsonKey() final int maxMedia;
@override@JsonKey() final int maxRingtone;
@override@JsonKey() final int maxAlarm;
@override@JsonKey() final String errorMessage;
/// Create a copy of VolumeControlViewState
@@ -245,16 +251,16 @@ _$VolumeControlViewStateCopyWith<_VolumeControlViewState> get copyWith => __$Vol
@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));
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.maxMedia, maxMedia) || other.maxMedia == maxMedia)&&(identical(other.maxRingtone, maxRingtone) || other.maxRingtone == maxRingtone)&&(identical(other.maxAlarm, maxAlarm) || other.maxAlarm == maxAlarm)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,device,media,ringtone,alarm,errorMessage);
int get hashCode => Object.hash(runtimeType,isLoading,isComplete,device,media,ringtone,alarm,maxMedia,maxRingtone,maxAlarm,errorMessage);
@override
String toString() {
return 'VolumeControlViewState(isLoading: $isLoading, isComplete: $isComplete, device: $device, media: $media, ringtone: $ringtone, alarm: $alarm, errorMessage: $errorMessage)';
return 'VolumeControlViewState(isLoading: $isLoading, isComplete: $isComplete, device: $device, media: $media, ringtone: $ringtone, alarm: $alarm, maxMedia: $maxMedia, maxRingtone: $maxRingtone, maxAlarm: $maxAlarm, errorMessage: $errorMessage)';
}
@@ -265,7 +271,7 @@ abstract mixin class _$VolumeControlViewStateCopyWith<$Res> implements $VolumeCo
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
bool isLoading, bool isComplete, DeviceEntity? device, int media, int ringtone, int alarm, int maxMedia, int maxRingtone, int maxAlarm, String errorMessage
});
@@ -282,7 +288,7 @@ class __$VolumeControlViewStateCopyWithImpl<$Res>
/// 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,}) {
@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? maxMedia = null,Object? maxRingtone = null,Object? maxAlarm = 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
@@ -290,6 +296,9 @@ as bool,device: freezed == device ? _self.device : device // ignore: cast_nullab
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,maxMedia: null == maxMedia ? _self.maxMedia : maxMedia // ignore: cast_nullable_to_non_nullable
as int,maxRingtone: null == maxRingtone ? _self.maxRingtone : maxRingtone // ignore: cast_nullable_to_non_nullable
as int,maxAlarm: null == maxAlarm ? _self.maxAlarm : maxAlarm // ignore: cast_nullable_to_non_nullable
as int,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));

View File

@@ -47,6 +47,7 @@ class VolumeControlScreen extends ConsumerWidget {
_VolumeCard(
label: context.translate(I18n.volumeMedia),
value: state.media,
max: state.maxMedia,
color: primaryColor,
onChanged: vm.setMedia,
),
@@ -54,6 +55,7 @@ class VolumeControlScreen extends ConsumerWidget {
_VolumeCard(
label: context.translate(I18n.volumeRingtone),
value: state.ringtone,
max: state.maxRingtone,
color: primaryColor,
onChanged: vm.setRingtone,
),
@@ -61,6 +63,7 @@ class VolumeControlScreen extends ConsumerWidget {
_VolumeCard(
label: context.translate(I18n.volumeAlarm),
value: state.alarm,
max: state.maxAlarm,
color: primaryColor,
onChanged: vm.setAlarm,
),
@@ -96,18 +99,18 @@ class VolumeControlScreen extends ConsumerWidget {
class _VolumeCard extends StatelessWidget {
final String label;
final int value;
final int max;
final Color color;
final ValueChanged<int> onChanged;
const _VolumeCard({
required this.label,
required this.value,
required this.max,
required this.color,
required this.onChanged,
});
int get _displayValue => (value / 10).round();
@override
Widget build(BuildContext context) {
return Container(
@@ -147,8 +150,8 @@ class _VolumeCard extends StatelessWidget {
child: Slider(
value: value.toDouble(),
min: 0,
max: 100,
divisions: 10,
max: max.toDouble(),
divisions: max,
onChanged: (v) => onChanged(v.round()),
),
),
@@ -156,7 +159,7 @@ class _VolumeCard extends StatelessWidget {
SizedBox(
width: 24,
child: Text(
'$_displayValue',
'$value',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,