2024-11-11 16:20:12 +00:00
|
|
|
import 'package:collection/collection.dart';
|
2023-10-20 14:12:43 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-01-15 19:47:10 +00:00
|
|
|
import 'package:lightmeter/utils/context_utils.dart';
|
2023-10-20 14:12:43 +00:00
|
|
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
|
|
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
class EquipmentProfilesProvider extends StatefulWidget {
|
|
|
|
static const EquipmentProfile defaultProfile = EquipmentProfile(
|
|
|
|
id: '',
|
|
|
|
name: '',
|
|
|
|
apertureValues: ApertureValue.values,
|
|
|
|
ndValues: NdValue.values,
|
|
|
|
shutterSpeedValues: ShutterSpeedValue.values,
|
|
|
|
isoValues: IsoValue.values,
|
|
|
|
);
|
|
|
|
|
2025-07-29 10:38:48 +00:00
|
|
|
final IapStorageService storageService;
|
2024-11-11 16:20:12 +00:00
|
|
|
final VoidCallback? onInitialized;
|
2023-10-20 14:12:43 +00:00
|
|
|
final Widget child;
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
const EquipmentProfilesProvider({
|
2023-10-20 14:12:43 +00:00
|
|
|
required this.storageService,
|
2024-11-11 16:20:12 +00:00
|
|
|
this.onInitialized,
|
2023-10-20 14:12:43 +00:00
|
|
|
required this.child,
|
|
|
|
super.key,
|
|
|
|
});
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
static EquipmentProfilesProviderState of(BuildContext context) {
|
|
|
|
return context.findAncestorStateOfType<EquipmentProfilesProviderState>()!;
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2024-11-11 16:20:12 +00:00
|
|
|
State<EquipmentProfilesProvider> createState() => EquipmentProfilesProviderState();
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
class EquipmentProfilesProviderState extends State<EquipmentProfilesProvider> {
|
2025-09-04 18:32:11 +00:00
|
|
|
final TogglableMap<IEquipmentProfile> _profiles = {};
|
2023-10-20 14:12:43 +00:00
|
|
|
String _selectedId = '';
|
|
|
|
|
2025-09-04 18:32:11 +00:00
|
|
|
IEquipmentProfile get _selectedProfile => _profiles[_selectedId]?.value ?? EquipmentProfilesProvider.defaultProfile;
|
2023-10-20 14:12:43 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2024-11-11 16:20:12 +00:00
|
|
|
_init();
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return EquipmentProfiles(
|
2025-09-04 18:32:11 +00:00
|
|
|
profiles: context.isPro ? _profiles : {},
|
2024-11-11 16:20:12 +00:00
|
|
|
selected: context.isPro ? _selectedProfile : EquipmentProfilesProvider.defaultProfile,
|
2023-10-20 14:12:43 +00:00
|
|
|
child: widget.child,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
Future<void> _init() async {
|
|
|
|
_selectedId = widget.storageService.selectedEquipmentProfileId;
|
2025-09-04 18:32:11 +00:00
|
|
|
_profiles
|
|
|
|
..addAll(await widget.storageService.getEquipmentProfiles())
|
|
|
|
..addAll(await widget.storageService.getPinholeEquipmentProfiles());
|
|
|
|
_sortProfiles();
|
2024-11-11 16:20:12 +00:00
|
|
|
_discardSelectedIfNotIncluded();
|
|
|
|
if (mounted) setState(() {});
|
|
|
|
widget.onInitialized?.call();
|
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
Future<void> addProfile(IEquipmentProfile profile) async {
|
|
|
|
switch (profile) {
|
|
|
|
case final PinholeEquipmentProfile profile:
|
|
|
|
await widget.storageService.addPinholeEquipmentProfile(profile);
|
|
|
|
case final EquipmentProfile profile:
|
|
|
|
await widget.storageService.addEquipmentProfile(profile);
|
|
|
|
}
|
2025-09-04 18:32:11 +00:00
|
|
|
_profiles[profile.id] = (value: profile, isUsed: true);
|
|
|
|
_sortProfiles();
|
2024-11-11 16:20:12 +00:00
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
Future<void> updateProfile(IEquipmentProfile profile) async {
|
|
|
|
switch (profile) {
|
|
|
|
case final PinholeEquipmentProfile profile:
|
2025-09-04 18:32:11 +00:00
|
|
|
final oldProfile = _profiles[profile.id]!.value as PinholeEquipmentProfile;
|
2025-09-03 19:11:04 +00:00
|
|
|
await widget.storageService.updatePinholeEquipmentProfile(
|
|
|
|
id: profile.id,
|
|
|
|
name: profile.name,
|
|
|
|
aperture: oldProfile.aperture.changedOrNull(profile.aperture),
|
|
|
|
isoValues: oldProfile.isoValues.changedOrNull(profile.isoValues),
|
2025-09-04 17:50:38 +00:00
|
|
|
ndValues: oldProfile.ndValues.changedOrNull(profile.ndValues),
|
2025-09-03 19:11:04 +00:00
|
|
|
lensZoom: oldProfile.lensZoom.changedOrNull(profile.lensZoom),
|
|
|
|
exposureOffset: oldProfile.exposureOffset.changedOrNull(profile.exposureOffset),
|
|
|
|
);
|
|
|
|
case final EquipmentProfile profile:
|
2025-09-04 18:32:11 +00:00
|
|
|
final oldProfile = _profiles[profile.id]!.value as EquipmentProfile;
|
2025-09-03 19:11:04 +00:00
|
|
|
await widget.storageService.updateEquipmentProfile(
|
|
|
|
id: profile.id,
|
|
|
|
name: oldProfile.name.changedOrNull(profile.name),
|
|
|
|
apertureValues: oldProfile.apertureValues.changedOrNull(profile.apertureValues),
|
|
|
|
shutterSpeedValues: oldProfile.shutterSpeedValues.changedOrNull(profile.shutterSpeedValues),
|
|
|
|
isoValues: oldProfile.isoValues.changedOrNull(profile.isoValues),
|
|
|
|
ndValues: oldProfile.ndValues.changedOrNull(profile.ndValues),
|
|
|
|
lensZoom: oldProfile.lensZoom.changedOrNull(profile.lensZoom),
|
|
|
|
exposureOffset: oldProfile.exposureOffset.changedOrNull(profile.exposureOffset),
|
|
|
|
);
|
|
|
|
}
|
2025-09-04 18:32:11 +00:00
|
|
|
final bool shouldSort = _profiles[profile.id]!.value.name != profile.name;
|
|
|
|
_profiles[profile.id] = (value: profile, isUsed: _profiles[profile.id]!.isUsed);
|
|
|
|
if (shouldSort) _sortProfiles();
|
2024-11-11 16:20:12 +00:00
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
Future<void> deleteProfile(IEquipmentProfile profile) async {
|
2024-11-11 16:20:12 +00:00
|
|
|
if (profile.id == _selectedId) {
|
|
|
|
_selectedId = EquipmentProfilesProvider.defaultProfile.id;
|
|
|
|
widget.storageService.selectedEquipmentProfileId = EquipmentProfilesProvider.defaultProfile.id;
|
|
|
|
}
|
2025-09-03 19:11:04 +00:00
|
|
|
switch (profile) {
|
|
|
|
case final PinholeEquipmentProfile profile:
|
|
|
|
await widget.storageService.deletePinholeEquipmentProfile(profile.id);
|
|
|
|
case final EquipmentProfile profile:
|
|
|
|
await widget.storageService.deleteEquipmentProfile(profile.id);
|
|
|
|
}
|
2025-09-04 18:32:11 +00:00
|
|
|
_profiles.remove(profile.id);
|
2024-11-11 16:20:12 +00:00
|
|
|
_discardSelectedIfNotIncluded();
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
void selectProfile(String id) {
|
|
|
|
if (_selectedId != id) {
|
2023-10-20 14:12:43 +00:00
|
|
|
setState(() {
|
2025-09-03 19:11:04 +00:00
|
|
|
_selectedId = id;
|
2023-10-20 14:12:43 +00:00
|
|
|
});
|
|
|
|
widget.storageService.selectedEquipmentProfileId = _selectedProfile.id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
Future<void> toggleProfile(String id, bool enabled) async {
|
2025-09-04 18:32:11 +00:00
|
|
|
if (_profiles.containsKey(id)) {
|
|
|
|
_profiles[id] = (value: _profiles[id]!.value, isUsed: enabled);
|
2025-09-03 19:11:04 +00:00
|
|
|
await widget.storageService.updateEquipmentProfile(id: id, isUsed: enabled);
|
2024-11-11 16:20:12 +00:00
|
|
|
} else {
|
|
|
|
return;
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
2024-11-11 16:20:12 +00:00
|
|
|
_discardSelectedIfNotIncluded();
|
|
|
|
setState(() {});
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
void _discardSelectedIfNotIncluded() {
|
|
|
|
if (_selectedId == EquipmentProfilesProvider.defaultProfile.id) {
|
|
|
|
return;
|
|
|
|
}
|
2025-09-04 18:32:11 +00:00
|
|
|
final isSelectedUsed = _profiles[_selectedId]?.isUsed ?? false;
|
2024-11-11 16:20:12 +00:00
|
|
|
if (!isSelectedUsed) {
|
|
|
|
_selectedId = EquipmentProfilesProvider.defaultProfile.id;
|
|
|
|
widget.storageService.selectedEquipmentProfileId = _selectedId;
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
}
|
2025-09-04 18:32:11 +00:00
|
|
|
|
|
|
|
void _sortProfiles() {
|
|
|
|
final sortedByName = _profiles.values.toList(growable: false)
|
|
|
|
..sort((a, b) => a.value.name.toLowerCase().compareTo(b.value.name.toLowerCase()));
|
|
|
|
_profiles.clear();
|
|
|
|
_profiles.addEntries(sortedByName.map((e) => MapEntry(e.value.id, e)));
|
|
|
|
}
|
2024-11-11 16:20:12 +00:00
|
|
|
}
|
2023-10-20 14:12:43 +00:00
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
enum _EquipmentProfilesModelAspect {
|
|
|
|
profiles,
|
|
|
|
profilesInUse,
|
|
|
|
selected,
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
class EquipmentProfiles extends InheritedModel<_EquipmentProfilesModelAspect> {
|
2025-09-04 18:32:11 +00:00
|
|
|
final TogglableMap<IEquipmentProfile> profiles;
|
2025-09-03 19:11:04 +00:00
|
|
|
final IEquipmentProfile selected;
|
2024-11-11 16:20:12 +00:00
|
|
|
|
2023-10-20 14:12:43 +00:00
|
|
|
const EquipmentProfiles({
|
2024-11-11 16:20:12 +00:00
|
|
|
required this.profiles,
|
|
|
|
required this.selected,
|
2023-10-20 14:12:43 +00:00
|
|
|
required super.child,
|
|
|
|
});
|
|
|
|
|
2024-11-11 16:20:12 +00:00
|
|
|
/// _default + profiles create by the user
|
2025-09-03 19:11:04 +00:00
|
|
|
static List<IEquipmentProfile> of(BuildContext context) {
|
2024-11-11 16:20:12 +00:00
|
|
|
final model =
|
|
|
|
InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: _EquipmentProfilesModelAspect.profiles)!;
|
2025-09-03 19:11:04 +00:00
|
|
|
return List<IEquipmentProfile>.from(
|
2024-11-11 16:20:12 +00:00
|
|
|
[
|
|
|
|
EquipmentProfilesProvider.defaultProfile,
|
|
|
|
...model.profiles.values.map((p) => p.value),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
static List<IEquipmentProfile> inUseOf(BuildContext context) {
|
2024-11-11 16:20:12 +00:00
|
|
|
final model =
|
|
|
|
InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: _EquipmentProfilesModelAspect.profilesInUse)!;
|
2025-09-03 19:11:04 +00:00
|
|
|
return List<IEquipmentProfile>.from(
|
2024-11-11 16:20:12 +00:00
|
|
|
[
|
|
|
|
EquipmentProfilesProvider.defaultProfile,
|
|
|
|
...model.profiles.values.where((p) => p.isUsed).map((p) => p.value),
|
|
|
|
],
|
|
|
|
);
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
|
|
|
|
2025-09-03 19:11:04 +00:00
|
|
|
static IEquipmentProfile selectedOf(BuildContext context) {
|
2024-11-11 16:20:12 +00:00
|
|
|
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: _EquipmentProfilesModelAspect.selected)!
|
2023-10-20 14:12:43 +00:00
|
|
|
.selected;
|
|
|
|
}
|
2024-11-11 16:20:12 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
bool updateShouldNotify(EquipmentProfiles _) => true;
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool updateShouldNotifyDependent(EquipmentProfiles oldWidget, Set<_EquipmentProfilesModelAspect> dependencies) {
|
|
|
|
return (dependencies.contains(_EquipmentProfilesModelAspect.selected) && oldWidget.selected != selected) ||
|
|
|
|
((dependencies.contains(_EquipmentProfilesModelAspect.profiles) ||
|
|
|
|
dependencies.contains(_EquipmentProfilesModelAspect.profilesInUse)) &&
|
2025-09-04 18:32:11 +00:00
|
|
|
const DeepCollectionEquality().equals(oldWidget.profiles, profiles));
|
2024-11-11 16:20:12 +00:00
|
|
|
}
|
2023-10-20 14:12:43 +00:00
|
|
|
}
|
2025-07-09 17:27:46 +00:00
|
|
|
|
|
|
|
extension on Object {
|
|
|
|
T? changedOrNull<T>(T newValue) {
|
|
|
|
return this != newValue ? newValue : null;
|
|
|
|
}
|
|
|
|
}
|