diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e016d93..c7d81a1 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -93,8 +93,10 @@ } } }, - "lightmeterPro": "Lightmeter PRO", - "buyLightmeterPro": "Buy Lightmeter PRO", - "lightmeterProDescription": "TBD", + "buyLightmeterPro": "Buy Lightmeter Pro", + "lightmeterPro": "Lightmeter Pro", + "lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing aperture, shutter speed, ISO and ND values filters and films.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.", + "equipmentProfilesFeatureDescription": "Each equipment profile allows you to select:\n- Aperture values and shutter speeds, that your lens and camera have\n- ND filters, that fit the chosen lens\n- ISO values, that your camera supports\nCreating multiple profiles for different cameras and lenses allows you to easily switch between them and always have the relevant readings!", + "filmsInUseFeatureDescription": "Select the films that you usually use. Selecting one will apply a correction to shutter speeds greater than 1\" to compensate for the reciprocity failure.", "buy": "Buy" -} +} \ No newline at end of file diff --git a/lib/screens/settings/components/buy_pro/components/equipment_profiles/components/equipment_profile_screen/screen_buy_pro.dart b/lib/screens/settings/components/buy_pro/components/equipment_profiles/components/equipment_profile_screen/screen_buy_pro.dart deleted file mode 100644 index 6b6fa10..0000000 --- a/lib/screens/settings/components/buy_pro/components/equipment_profiles/components/equipment_profile_screen/screen_buy_pro.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/generated/l10n.dart'; - -import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; - -class BuyProScreen extends StatelessWidget { - const BuyProScreen({super.key}); - - @override - Widget build(BuildContext context) { - return SliverScreen( - title: S.of(context).lightmeterPro, - appBarActions: [ - IconButton( - onPressed: Navigator.of(context).pop, - icon: const Icon(Icons.close), - ), - ], - slivers: [ - SliverList( - delegate: SliverChildListDelegate( - [ - Text(S.of(context).lightmeterProDescription), - Row( - children: [ - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text(S.of(context).buy), - ), - ], - ) - ], - ), - ), - SliverToBoxAdapter(child: SizedBox(height: MediaQuery.paddingOf(context).bottom)), - ], - ); - } -} diff --git a/lib/screens/settings/components/buy_pro/components/equipment_profiles/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/buy_pro/components/equipment_profiles/widget_list_tile_buy_pro.dart deleted file mode 100644 index 59a1029..0000000 --- a/lib/screens/settings/components/buy_pro/components/equipment_profiles/widget_list_tile_buy_pro.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/screens/settings/components/buy_pro/components/equipment_profiles/components/equipment_profile_screen/screen_buy_pro.dart'; - -class BuyProListTile extends StatelessWidget { - const BuyProListTile({super.key}); - - @override - Widget build(BuildContext context) { - return ListTile( - leading: const Icon(Icons.camera), - title: Text(S.of(context).buyLightmeterPro), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (_) => const BuyProScreen()), - ); - }, - ); - } -} diff --git a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart new file mode 100644 index 0000000..20ea137 --- /dev/null +++ b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; + +class BuyProListTile extends StatelessWidget { + const BuyProListTile({super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: const Icon(Icons.star), + title: Text(S.of(context).buyLightmeterPro), + onTap: () { + showDialog( + context: context, + builder: (_) => AlertDialog( + icon: const Icon(Icons.star), + titlePadding: Dimens.dialogIconTitlePadding, + title: Text(S.of(context).lightmeterPro), + contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), + content: SingleChildScrollView(child: Text(S.of(context).lightmeterProDescription)), + actionsPadding: Dimens.dialogActionsPadding, + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(S.of(context).cancel), + ), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(S.of(context).buy), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/screens/settings/components/buy_pro/widget_settings_section_pro.dart b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart similarity index 61% rename from lib/screens/settings/components/buy_pro/widget_settings_section_pro.dart rename to lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart index c918d74..c060dc1 100644 --- a/lib/screens/settings/components/buy_pro/widget_settings_section_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/screens/settings/components/buy_pro/components/equipment_profiles/widget_list_tile_buy_pro.dart'; +import 'package:lightmeter/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; -class BuyProSettingsSection extends StatelessWidget { - const BuyProSettingsSection({super.key}); +class LightmeterProSettingsSection extends StatelessWidget { + const LightmeterProSettingsSection({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/components/shared/expandable_section/widget_section_expandable.dart b/lib/screens/settings/components/shared/expandable_section/widget_section_expandable.dart new file mode 100644 index 0000000..d201987 --- /dev/null +++ b/lib/screens/settings/components/shared/expandable_section/widget_section_expandable.dart @@ -0,0 +1,242 @@ +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 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 createState() => ExpandableSectionState(); +} + +class ExpandableSectionState extends State + 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( + 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 get _progress => listenable as Animation; + + @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 get _progress => listenable as Animation; + + @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> onApertureValuesSelected; + final ValueChanged> onIsoValuesSelecred; + final ValueChanged> onNdValuesSelected; + final ValueChanged> 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 get _progress => listenable as Animation; + + @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, + ), + ), + ); + } +} diff --git a/lib/screens/settings/screen_settings.dart b/lib/screens/settings/screen_settings.dart index 5c61e8a..85da4a1 100644 --- a/lib/screens/settings/screen_settings.dart +++ b/lib/screens/settings/screen_settings.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart'; -import 'package:lightmeter/screens/settings/components/buy_pro/widget_settings_section_pro.dart'; +import 'package:lightmeter/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart'; import 'package:lightmeter/screens/settings/components/equipment/widget_settings_section_equipment.dart'; import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart'; import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart'; @@ -44,7 +44,7 @@ class _SettingsScreenState extends State { SliverList( delegate: SliverChildListDelegate( [ - const BuyProSettingsSection(), + const LightmeterProSettingsSection(), const MeteringSettingsSection(), const EquipmentSettingsSection(), const GeneralSettingsSection(),