mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
disable only list tiles
This commit is contained in:
parent
e1532736c2
commit
613a0f18a6
7 changed files with 60 additions and 266 deletions
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfilesListTile extends StatelessWidget {
|
||||
|
@ -8,7 +9,7 @@ class EquipmentProfilesListTile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
return IAPListTile(
|
||||
leading: const Icon(Icons.camera),
|
||||
title: Text(S.of(context).equipmentProfiles),
|
||||
onTap: () {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
|
@ -9,7 +10,7 @@ class FilmsListTile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
return IAPListTile(
|
||||
leading: const Icon(Icons.camera_roll),
|
||||
title: Text(S.of(context).filmsInUse),
|
||||
onTap: () {
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/widget_list_tile_equipment_profiles.dart';
|
||||
import 'package:lightmeter/screens/settings/components/equipment/components/films/widget_list_tile_films.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
class EquipmentSettingsSection extends StatelessWidget {
|
||||
const EquipmentSettingsSection({super.key});
|
||||
|
@ -12,7 +11,6 @@ class EquipmentSettingsSection extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: S.of(context).equipment,
|
||||
enabled: IAPProducts.isPurchased(context, IAPProductType.paidFeatures),
|
||||
children: const [
|
||||
EquipmentProfilesListTile(),
|
||||
FilmsListTile(),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
class BuyProListTile extends StatelessWidget {
|
||||
const BuyProListTile({super.key});
|
||||
|
@ -28,6 +29,7 @@ class BuyProListTile extends StatelessWidget {
|
|||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures);
|
||||
},
|
||||
child: Text(S.of(context).buy),
|
||||
),
|
||||
|
|
|
@ -1,242 +0,0 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.dart';
|
||||
import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class ExpandableSection extends StatefulWidget {
|
||||
final EquipmentProfile data;
|
||||
final ValueChanged<EquipmentProfile> onUpdate;
|
||||
final VoidCallback onDelete;
|
||||
final VoidCallback onExpand;
|
||||
|
||||
const ExpandableSection({
|
||||
required this.data,
|
||||
required this.onUpdate,
|
||||
required this.onDelete,
|
||||
required this.onExpand,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExpandableSection> createState() => ExpandableSectionState();
|
||||
}
|
||||
|
||||
class ExpandableSectionState extends State<ExpandableSection>
|
||||
with TickerProviderStateMixin {
|
||||
late EquipmentProfile _equipmentData = EquipmentProfile(
|
||||
id: widget.data.id,
|
||||
name: widget.data.name,
|
||||
apertureValues: widget.data.apertureValues,
|
||||
ndValues: widget.data.ndValues,
|
||||
shutterSpeedValues: widget.data.shutterSpeedValues,
|
||||
isoValues: widget.data.isoValues,
|
||||
);
|
||||
|
||||
late final AnimationController _controller = AnimationController(
|
||||
duration: Dimens.durationM,
|
||||
vsync: this,
|
||||
);
|
||||
bool get _expanded => _controller.isCompleted;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ExpandableSection oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_equipmentData = EquipmentProfile(
|
||||
id: widget.data.id,
|
||||
name: widget.data.name,
|
||||
apertureValues: widget.data.apertureValues,
|
||||
ndValues: widget.data.ndValues,
|
||||
shutterSpeedValues: widget.data.shutterSpeedValues,
|
||||
isoValues: widget.data.isoValues,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
title: Row(
|
||||
children: [
|
||||
_AnimatedNameLeading(controller: _controller),
|
||||
const SizedBox(width: Dimens.grid8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
_equipmentData.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_AnimatedArrowButton(
|
||||
controller: _controller,
|
||||
onPressed: () => _expanded ? collapse() : expand(),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: widget.onDelete,
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _expanded ? _showNameDialog() : expand(),
|
||||
),
|
||||
_AnimatedEquipmentListTiles(
|
||||
controller: _controller,
|
||||
equipmentData: _equipmentData,
|
||||
onApertureValuesSelected: (value) {
|
||||
_equipmentData = _equipmentData.copyWith(apertureValues: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
},
|
||||
onIsoValuesSelecred: (value) {
|
||||
_equipmentData = _equipmentData.copyWith(isoValues: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
},
|
||||
onNdValuesSelected: (value) {
|
||||
_equipmentData = _equipmentData.copyWith(ndValues: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
},
|
||||
onShutterSpeedValuesSelected: (value) {
|
||||
_equipmentData = _equipmentData.copyWith(shutterSpeedValues: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showNameDialog() {
|
||||
showDialog<String>(
|
||||
context: context,
|
||||
builder: (_) => EquipmentProfileNameDialog(initialValue: _equipmentData.name),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
_equipmentData = _equipmentData.copyWith(name: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void expand() {
|
||||
widget.onExpand();
|
||||
_controller.forward();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(_controller.duration!).then((_) {
|
||||
Scrollable.ensureVisible(
|
||||
context,
|
||||
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
|
||||
duration: _controller.duration!,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void collapse() {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedNameLeading extends AnimatedWidget {
|
||||
const _AnimatedNameLeading({required AnimationController controller})
|
||||
: super(listenable: controller);
|
||||
|
||||
Animation<double> get _progress => listenable as Animation<double>;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: _progress.value * Dimens.grid8),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
size: _progress.value * Dimens.grid24,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedArrowButton extends AnimatedWidget {
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _AnimatedArrowButton({
|
||||
required AnimationController controller,
|
||||
required this.onPressed,
|
||||
}) : super(listenable: controller);
|
||||
|
||||
Animation<double> get _progress => listenable as Animation<double>;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: onPressed,
|
||||
icon: Transform.rotate(
|
||||
angle: _progress.value * pi,
|
||||
child: const Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
||||
final EquipmentProfile equipmentData;
|
||||
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
|
||||
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
||||
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
||||
final ValueChanged<List<ShutterSpeedValue>> onShutterSpeedValuesSelected;
|
||||
|
||||
const _AnimatedEquipmentListTiles({
|
||||
required AnimationController controller,
|
||||
required this.equipmentData,
|
||||
required this.onApertureValuesSelected,
|
||||
required this.onIsoValuesSelecred,
|
||||
required this.onNdValuesSelected,
|
||||
required this.onShutterSpeedValuesSelected,
|
||||
}) : super(listenable: controller);
|
||||
|
||||
Animation<double> get _progress => listenable as Animation<double>;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedOverflowBox(
|
||||
alignment: Alignment.topCenter,
|
||||
size: Size(
|
||||
double.maxFinite,
|
||||
_progress.value * Dimens.grid56 * 4,
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: _progress.value,
|
||||
child: EquipmentListTiles(
|
||||
selectedApertureValues: equipmentData.apertureValues,
|
||||
selectedIsoValues: equipmentData.isoValues,
|
||||
selectedNdValues: equipmentData.ndValues,
|
||||
selectedShutterSpeedValues: equipmentData.shutterSpeedValues,
|
||||
onApertureValuesSelected: onApertureValuesSelected,
|
||||
onIsoValuesSelecred: onIsoValuesSelecred,
|
||||
onNdValuesSelected: onNdValuesSelected,
|
||||
onShutterSpeedValuesSelected: onShutterSpeedValuesSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
/// Depends on the product status and replaces [onTap] with purchase callback
|
||||
/// if the product is purchasable.
|
||||
class IAPListTile extends StatelessWidget {
|
||||
final IAPProductType product;
|
||||
final Icon leading;
|
||||
final Text title;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const IAPListTile({
|
||||
this.product = IAPProductType.paidFeatures,
|
||||
required this.leading,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Opacity(
|
||||
opacity: IAPProducts.isPurchased(context, product)
|
||||
? Dimens.enabledOpacity
|
||||
: Dimens.disabledOpacity,
|
||||
child: ListTile(
|
||||
leading: leading,
|
||||
title: title,
|
||||
onTap: switch (IAPProducts.productOf(context, product)?.status) {
|
||||
IAPProductStatus.purchasable => () => IAPProductsProvider.of(context).buy(product),
|
||||
IAPProductStatus.pending => null,
|
||||
IAPProductStatus.purchased => onTap,
|
||||
null => null,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,12 +4,10 @@ import 'package:lightmeter/res/dimens.dart';
|
|||
class SettingsSection extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
final bool enabled;
|
||||
|
||||
const SettingsSection({
|
||||
required this.title,
|
||||
required this.children,
|
||||
this.enabled = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -25,25 +23,22 @@ class SettingsSection extends StatelessWidget {
|
|||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Opacity(
|
||||
opacity: enabled ? Dimens.enabledOpacity : Dimens.disabledOpacity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue