refactor(device_management): migrate call_history to Riverpod

This commit is contained in:
2026-04-22 21:11:32 +02:00
parent 9f5ec3f1da
commit 4e50384dd9
9 changed files with 370 additions and 496 deletions

View File

@@ -1,104 +1,140 @@
import 'package:device_management/src/features/call_history/data/call_history_entity.dart';
import 'package:device_management/src/features/call_history/presentation/providers/call_history_filter_provider.dart';
import 'package:device_management/src/features/call_history/presentation/providers/call_history_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:legacy_ui/legacy_ui.dart';
import 'package:sf_localizations/sf_localizations.dart';
import '../data/call_history_entity.dart';
import 'state/call_history_view_model.dart';
import 'state/call_history_view_state.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:sf_shared/sf_shared.dart';
class CallHistoryScreen extends ConsumerWidget {
const CallHistoryScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(callHistoryViewModelProvider);
final vm = ref.read(callHistoryViewModelProvider.notifier);
final filtered = state.filteredCalls;
final device = ref.watch(selectedDeviceProvider).value;
final filter = ref.watch(callHistoryFilterProvider);
if (device == null) {
return LegacyPageLayout(
title: context.translate(I18n.callHistory),
body: const SizedBox.shrink(),
);
}
final callsAsync = ref.watch(callHistoryProvider(device.identificator));
return LegacyPageLayout(
title: context.translate(I18n.callHistory),
body: state.isLoading
? const Center(child: CircularProgressIndicator())
: state.errorMessage.isNotEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Theme.of(context).colorScheme.outline,
),
const SizedBox(height: 12),
Text(
state.errorMessage,
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 14),
textAlign: TextAlign.center,
),
],
body: callsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Theme.of(context).colorScheme.outline,
),
)
: Column(
children: [
_FilterBar(selected: state.filter, onChanged: vm.setFilter),
Expanded(
child: filtered.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.phone_missed_outlined,
size: 64,
color: Theme.of(context).colorScheme.outline,
const SizedBox(height: 12),
Text(
err.toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
),
data: (calls) {
final filtered = _applyFilter(calls, filter);
return Column(
children: [
_FilterBar(
selected: filter,
onChanged:
ref.read(callHistoryFilterProvider.notifier).select,
),
Expanded(
child: filtered.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.phone_missed_outlined,
size: 64,
color: Theme.of(context).colorScheme.outline,
),
const SizedBox(height: 12),
Text(
context.translate(I18n.callHistoryEmpty),
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
fontSize: 14,
),
const SizedBox(height: 12),
Text(
context.translate(I18n.callHistoryEmpty),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 14,
),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
itemCount: filtered.length,
itemBuilder: (context, index) {
final call = filtered[index];
final showDateHeader = index == 0 ||
!_isSameDay(
filtered[index - 1].occurredAt,
call.occurredAt,
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showDateHeader)
_DateHeader(timestamp: call.occurredAt),
_CallTile(
call: call,
primaryColor:
context.sfColors.legacyPrimary,
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
itemCount: filtered.length,
itemBuilder: (context, index) {
final call = filtered[index];
final showDateHeader =
index == 0 ||
!_isSameDay(
filtered[index - 1].occurredAt,
call.occurredAt,
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showDateHeader)
_DateHeader(timestamp: call.occurredAt),
_CallTile(
call: call,
primaryColor: context.sfColors.legacyPrimary,
),
],
);
},
),
),
],
),
);
},
),
),
],
);
},
),
);
}
List<CallHistoryEntity> _applyFilter(
List<CallHistoryEntity> calls,
CallFilter filter,
) {
switch (filter) {
case CallFilter.all:
return calls;
case CallFilter.incoming:
return calls.where((c) => c.isIncoming).toList();
case CallFilter.outgoing:
return calls.where((c) => !c.isIncoming).toList();
case CallFilter.missed:
return calls.where((c) => !c.isAccepted).toList();
}
}
static bool _isSameDay(int ts1, int ts2) {
final d1 = DateTime.fromMillisecondsSinceEpoch(ts1);
final d2 = DateTime.fromMillisecondsSinceEpoch(ts2);
@@ -156,9 +192,12 @@ class _FilterBar extends StatelessWidget {
children: filters.map((filter) {
final isSelected = filter == selected;
final label = switch (filter) {
CallFilter.all => context.translate(I18n.locationListAll),
CallFilter.incoming => context.translate(I18n.callIncoming),
CallFilter.outgoing => context.translate(I18n.callOutgoing),
CallFilter.all =>
context.translate(I18n.locationListAll),
CallFilter.incoming =>
context.translate(I18n.callIncoming),
CallFilter.outgoing =>
context.translate(I18n.callOutgoing),
CallFilter.missed => context.translate(I18n.callMissed),
};
@@ -176,7 +215,9 @@ class _FilterBar extends StatelessWidget {
: FontWeight.w500,
color: isSelected
? Colors.black87
: Theme.of(context).colorScheme.onSurfaceVariant,
: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
),
@@ -281,7 +322,9 @@ class _CallTile extends StatelessWidget {
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: isAccepted ? Colors.black87 : Theme.of(context).colorScheme.error,
color: isAccepted
? Colors.black87
: Theme.of(context).colorScheme.error,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -297,20 +340,29 @@ class _CallTile extends StatelessWidget {
if (subtitle != null) ...[
Text(
subtitle,
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 8),
],
if (isAccepted)
Text(
durationStr,
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
trailing: Text(
timeStr,
style: TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.onSurfaceVariant),
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
);

View File

@@ -0,0 +1,24 @@
import 'dart:async';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sf_tracking/sf_tracking.dart';
part 'call_history_filter_provider.g.dart';
enum CallFilter { all, incoming, outgoing, missed }
@riverpod
class CallHistoryFilter extends _$CallHistoryFilter {
@override
CallFilter build() => CallFilter.all;
void select(CallFilter filter) {
if (filter == state) return;
unawaited(
ref
.read(sfTrackingProvider)
.legacyDeviceCallHistoryFilterChanged(filter.name),
);
state = filter;
}
}

View File

@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'call_history_filter_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(CallHistoryFilter)
const callHistoryFilterProvider = CallHistoryFilterProvider._();
final class CallHistoryFilterProvider
extends $NotifierProvider<CallHistoryFilter, CallFilter> {
const CallHistoryFilterProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'callHistoryFilterProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$callHistoryFilterHash();
@$internal
@override
CallHistoryFilter create() => CallHistoryFilter();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(CallFilter value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<CallFilter>(value),
);
}
}
String _$callHistoryFilterHash() => r'f0a91c1ced288a1cb51d741963b8f230fcdadef3';
abstract class _$CallHistoryFilter extends $Notifier<CallFilter> {
CallFilter build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<CallFilter, CallFilter>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<CallFilter, CallFilter>,
CallFilter,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:device_management/src/features/call_history/data/call_history_datasource_provider.dart';
import 'package:device_management/src/features/call_history/data/call_history_entity.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'call_history_provider.g.dart';
@riverpod
Future<List<CallHistoryEntity>> callHistory(
Ref ref,
String deviceIdentificator,
) async {
return ref
.read(callHistoryDatasourceProvider)
.getCallHistory(deviceIdentificator: deviceIdentificator);
}

View File

@@ -0,0 +1,87 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'call_history_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(callHistory)
const callHistoryProvider = CallHistoryFamily._();
final class CallHistoryProvider
extends
$FunctionalProvider<
AsyncValue<List<CallHistoryEntity>>,
List<CallHistoryEntity>,
FutureOr<List<CallHistoryEntity>>
>
with
$FutureModifier<List<CallHistoryEntity>>,
$FutureProvider<List<CallHistoryEntity>> {
const CallHistoryProvider._({
required CallHistoryFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'callHistoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$callHistoryHash();
@override
String toString() {
return r'callHistoryProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<List<CallHistoryEntity>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<CallHistoryEntity>> create(Ref ref) {
final argument = this.argument as String;
return callHistory(ref, argument);
}
@override
bool operator ==(Object other) {
return other is CallHistoryProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$callHistoryHash() => r'f5b99a06bc69f62660d1cf7b66648a279724f216';
final class CallHistoryFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<CallHistoryEntity>>, String> {
const CallHistoryFamily._()
: super(
retry: null,
name: r'callHistoryProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
CallHistoryProvider call(String deviceIdentificator) =>
CallHistoryProvider._(argument: deviceIdentificator, from: this);
@override
String toString() => r'callHistoryProvider';
}

View File

@@ -1,94 +0,0 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
import '../../data/call_history_datasource.dart';
import '../../data/call_history_datasource_provider.dart';
import '../../data/call_history_entity.dart';
import 'call_history_view_state.dart';
final callHistoryViewModelProvider =
NotifierProvider.autoDispose<CallHistoryViewModel, CallHistoryViewState>(
CallHistoryViewModel.new,
);
class CallHistoryViewModel extends Notifier<CallHistoryViewState> {
late final CallHistoryDatasource _datasource;
late final SfTrackingRepository _tracking;
@override
CallHistoryViewState build() {
_datasource = ref.read(callHistoryDatasourceProvider);
_tracking = ref.read(sfTrackingProvider);
Future.microtask(() => _load());
return const CallHistoryViewState();
}
Future<void> _load() async {
final device = ref.read(selectedDeviceProvider).value;
if (device == null) {
state = state.copyWith(isLoading: false);
return;
}
try {
final calls = await _datasource.getCallHistory(
deviceIdentificator: device.identificator,
);
if (!ref.mounted) return;
state = state.copyWith(
calls: calls,
filteredCalls: calls,
isLoading: false,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(isLoading: false, errorMessage: e.toString());
}
}
void setFilter(CallFilter filter) {
if (filter == state.filter) return;
unawaited(
_tracking.legacyDeviceCallHistoryFilterChanged(_filterName(filter)),
);
state = state.copyWith(
filter: filter,
filteredCalls: _applyFilter(state.calls, filter),
);
}
List<CallHistoryEntity> _applyFilter(
List<CallHistoryEntity> calls,
CallFilter filter,
) {
switch (filter) {
case CallFilter.all:
return calls;
case CallFilter.incoming:
return calls.where((c) => c.isIncoming).toList();
case CallFilter.outgoing:
return calls.where((c) => !c.isIncoming).toList();
case CallFilter.missed:
return calls.where((c) => !c.isAccepted).toList();
}
}
String _filterName(CallFilter filter) {
switch (filter) {
case CallFilter.all:
return 'all';
case CallFilter.incoming:
return 'incoming';
case CallFilter.outgoing:
return 'outgoing';
case CallFilter.missed:
return 'missed';
}
}
}

View File

@@ -1,18 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../data/call_history_entity.dart';
part 'call_history_view_state.freezed.dart';
enum CallFilter { all, incoming, outgoing, missed }
@freezed
abstract class CallHistoryViewState with _$CallHistoryViewState {
const factory CallHistoryViewState({
@Default(true) bool isLoading,
@Default([]) List<CallHistoryEntity> calls,
@Default([]) List<CallHistoryEntity> filteredCalls,
@Default(CallFilter.all) CallFilter filter,
@Default('') String errorMessage,
}) = _CallHistoryViewState;
}

View File

@@ -1,295 +0,0 @@
// 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 'call_history_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$CallHistoryViewState {
bool get isLoading; List<CallHistoryEntity> get calls; List<CallHistoryEntity> get filteredCalls; CallFilter get filter; String get errorMessage;
/// Create a copy of CallHistoryViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$CallHistoryViewStateCopyWith<CallHistoryViewState> get copyWith => _$CallHistoryViewStateCopyWithImpl<CallHistoryViewState>(this as CallHistoryViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallHistoryViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other.calls, calls)&&const DeepCollectionEquality().equals(other.filteredCalls, filteredCalls)&&(identical(other.filter, filter) || other.filter == filter)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,const DeepCollectionEquality().hash(calls),const DeepCollectionEquality().hash(filteredCalls),filter,errorMessage);
@override
String toString() {
return 'CallHistoryViewState(isLoading: $isLoading, calls: $calls, filteredCalls: $filteredCalls, filter: $filter, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class $CallHistoryViewStateCopyWith<$Res> {
factory $CallHistoryViewStateCopyWith(CallHistoryViewState value, $Res Function(CallHistoryViewState) _then) = _$CallHistoryViewStateCopyWithImpl;
@useResult
$Res call({
bool isLoading, List<CallHistoryEntity> calls, List<CallHistoryEntity> filteredCalls, CallFilter filter, String errorMessage
});
}
/// @nodoc
class _$CallHistoryViewStateCopyWithImpl<$Res>
implements $CallHistoryViewStateCopyWith<$Res> {
_$CallHistoryViewStateCopyWithImpl(this._self, this._then);
final CallHistoryViewState _self;
final $Res Function(CallHistoryViewState) _then;
/// Create a copy of CallHistoryViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? calls = null,Object? filteredCalls = null,Object? filter = null,Object? errorMessage = null,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,calls: null == calls ? _self.calls : calls // ignore: cast_nullable_to_non_nullable
as List<CallHistoryEntity>,filteredCalls: null == filteredCalls ? _self.filteredCalls : filteredCalls // ignore: cast_nullable_to_non_nullable
as List<CallHistoryEntity>,filter: null == filter ? _self.filter : filter // ignore: cast_nullable_to_non_nullable
as CallFilter,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [CallHistoryViewState].
extension CallHistoryViewStatePatterns on CallHistoryViewState {
/// 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( _CallHistoryViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _CallHistoryViewState() 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( _CallHistoryViewState value) $default,){
final _that = this;
switch (_that) {
case _CallHistoryViewState():
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( _CallHistoryViewState value)? $default,){
final _that = this;
switch (_that) {
case _CallHistoryViewState() 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, List<CallHistoryEntity> calls, List<CallHistoryEntity> filteredCalls, CallFilter filter, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CallHistoryViewState() when $default != null:
return $default(_that.isLoading,_that.calls,_that.filteredCalls,_that.filter,_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, List<CallHistoryEntity> calls, List<CallHistoryEntity> filteredCalls, CallFilter filter, String errorMessage) $default,) {final _that = this;
switch (_that) {
case _CallHistoryViewState():
return $default(_that.isLoading,_that.calls,_that.filteredCalls,_that.filter,_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, List<CallHistoryEntity> calls, List<CallHistoryEntity> filteredCalls, CallFilter filter, String errorMessage)? $default,) {final _that = this;
switch (_that) {
case _CallHistoryViewState() when $default != null:
return $default(_that.isLoading,_that.calls,_that.filteredCalls,_that.filter,_that.errorMessage);case _:
return null;
}
}
}
/// @nodoc
class _CallHistoryViewState implements CallHistoryViewState {
const _CallHistoryViewState({this.isLoading = true, final List<CallHistoryEntity> calls = const [], final List<CallHistoryEntity> filteredCalls = const [], this.filter = CallFilter.all, this.errorMessage = ''}): _calls = calls,_filteredCalls = filteredCalls;
@override@JsonKey() final bool isLoading;
final List<CallHistoryEntity> _calls;
@override@JsonKey() List<CallHistoryEntity> get calls {
if (_calls is EqualUnmodifiableListView) return _calls;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_calls);
}
final List<CallHistoryEntity> _filteredCalls;
@override@JsonKey() List<CallHistoryEntity> get filteredCalls {
if (_filteredCalls is EqualUnmodifiableListView) return _filteredCalls;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_filteredCalls);
}
@override@JsonKey() final CallFilter filter;
@override@JsonKey() final String errorMessage;
/// Create a copy of CallHistoryViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$CallHistoryViewStateCopyWith<_CallHistoryViewState> get copyWith => __$CallHistoryViewStateCopyWithImpl<_CallHistoryViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallHistoryViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&const DeepCollectionEquality().equals(other._calls, _calls)&&const DeepCollectionEquality().equals(other._filteredCalls, _filteredCalls)&&(identical(other.filter, filter) || other.filter == filter)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,const DeepCollectionEquality().hash(_calls),const DeepCollectionEquality().hash(_filteredCalls),filter,errorMessage);
@override
String toString() {
return 'CallHistoryViewState(isLoading: $isLoading, calls: $calls, filteredCalls: $filteredCalls, filter: $filter, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class _$CallHistoryViewStateCopyWith<$Res> implements $CallHistoryViewStateCopyWith<$Res> {
factory _$CallHistoryViewStateCopyWith(_CallHistoryViewState value, $Res Function(_CallHistoryViewState) _then) = __$CallHistoryViewStateCopyWithImpl;
@override @useResult
$Res call({
bool isLoading, List<CallHistoryEntity> calls, List<CallHistoryEntity> filteredCalls, CallFilter filter, String errorMessage
});
}
/// @nodoc
class __$CallHistoryViewStateCopyWithImpl<$Res>
implements _$CallHistoryViewStateCopyWith<$Res> {
__$CallHistoryViewStateCopyWithImpl(this._self, this._then);
final _CallHistoryViewState _self;
final $Res Function(_CallHistoryViewState) _then;
/// Create a copy of CallHistoryViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? calls = null,Object? filteredCalls = null,Object? filter = null,Object? errorMessage = null,}) {
return _then(_CallHistoryViewState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,calls: null == calls ? _self._calls : calls // ignore: cast_nullable_to_non_nullable
as List<CallHistoryEntity>,filteredCalls: null == filteredCalls ? _self._filteredCalls : filteredCalls // ignore: cast_nullable_to_non_nullable
as List<CallHistoryEntity>,filter: null == filter ? _self.filter : filter // ignore: cast_nullable_to_non_nullable
as CallFilter,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View File

@@ -0,0 +1,40 @@
import 'package:device_management/src/features/call_history/presentation/providers/call_history_filter_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sf_shared/testing.dart';
import 'package:sf_tracking/sf_tracking.dart';
void main() {
ProviderContainer buildContainer() {
return makeContainer(
overrides: [
sfTrackingProvider.overrideWithValue(
SfTrackingRepository(clients: const []),
),
],
);
}
group('CallHistoryFilter', () {
test('starts with all', () {
final container = buildContainer();
addTearDown(container.dispose);
expect(container.read(callHistoryFilterProvider), CallFilter.all);
});
test('select updates state and is no-op when same', () {
final container = buildContainer();
addTearDown(container.dispose);
final notifier = container.read(callHistoryFilterProvider.notifier);
notifier.select(CallFilter.missed);
expect(container.read(callHistoryFilterProvider), CallFilter.missed);
notifier.select(CallFilter.missed);
expect(container.read(callHistoryFilterProvider), CallFilter.missed);
notifier.select(CallFilter.incoming);
expect(container.read(callHistoryFilterProvider), CallFilter.incoming);
});
});
}