mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-08-13 16:46:42 +00:00
Merge 82669bc5e5
into 056cf4f44b
This commit is contained in:
commit
bce487a788
30 changed files with 266 additions and 277 deletions
|
@ -10,7 +10,7 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
|
|||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/lightmeter_pro_badge/widget_badge_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
|
||||
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
||||
|
@ -77,7 +77,7 @@ void testPurchases(String description) {
|
|||
}
|
||||
|
||||
void _expectProMeteringScreen({required bool enabled}) {
|
||||
expect(find.byType(LightmeterProAnimatedDialog), !enabled ? findsOneWidget : findsNothing);
|
||||
expect(find.byType(LightmeterProBadge), !enabled ? findsOneWidget : findsNothing);
|
||||
expect(find.byType(EquipmentProfilePicker), enabled ? findsOneWidget : findsNothing);
|
||||
expect(find.byType(ExtremeExposurePairsContainer), findsOneWidget);
|
||||
expect(find.byType(FilmPicker), enabled ? findsOneWidget : findsNothing);
|
||||
|
|
|
@ -26,7 +26,7 @@ Future<void> runLightmeterApp(Environment env) async {
|
|||
runApp(
|
||||
env.buildType == BuildType.dev
|
||||
? IAPProducts(
|
||||
isPro: true,
|
||||
isPro: false,
|
||||
child: application,
|
||||
)
|
||||
: IAPProductsProvider(child: application),
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:lightmeter/res/dimens.dart';
|
|||
import 'package:lightmeter/screens/equipment_profile_edit/flow_equipment_profile_edit.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_placeholder/widget_sliver_placeholder.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||
import 'package:lightmeter/utils/guard_pro_tap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfilesScreen extends StatefulWidget {
|
||||
|
@ -41,9 +42,14 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> with
|
|||
}
|
||||
|
||||
void _addProfile() {
|
||||
Navigator.of(context).pushNamed(
|
||||
NavigationRoutes.equipmentProfileEditScreen.name,
|
||||
arguments: const EquipmentProfileEditArgs(editType: EquipmentProfileEditType.add),
|
||||
guardProTap(
|
||||
context,
|
||||
() {
|
||||
Navigator.of(context).pushNamed(
|
||||
NavigationRoutes.equipmentProfileEditScreen.name,
|
||||
arguments: const EquipmentProfileEditArgs(editType: EquipmentProfileEditType.add),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,24 +107,18 @@ class _Products extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (monthly case final monthly?)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: Dimens.paddingS),
|
||||
child: _ProductItem(
|
||||
title: S.of(context).monthly,
|
||||
price: S.of(context).pricePerMonth(monthly.price),
|
||||
isSelected: selected == monthly,
|
||||
onPressed: () => onProductSelected(monthly),
|
||||
),
|
||||
_ProductItem(
|
||||
title: S.of(context).monthly,
|
||||
price: S.of(context).pricePerMonth(monthly.price),
|
||||
isSelected: selected == monthly,
|
||||
onPressed: () => onProductSelected(monthly),
|
||||
),
|
||||
if (yearly case final yearly?)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: Dimens.paddingS),
|
||||
child: _ProductItem(
|
||||
title: S.of(context).yearly,
|
||||
price: S.of(context).pricePerYear(yearly.price),
|
||||
isSelected: selected == yearly,
|
||||
onPressed: () => onProductSelected(yearly),
|
||||
),
|
||||
_ProductItem(
|
||||
title: S.of(context).yearly,
|
||||
price: S.of(context).pricePerYear(yearly.price),
|
||||
isSelected: selected == yearly,
|
||||
onPressed: () => onProductSelected(yearly),
|
||||
),
|
||||
if (lifetime case final lifetime?)
|
||||
_ProductItem(
|
||||
|
@ -133,7 +127,7 @@ class _Products extends StatelessWidget {
|
|||
isSelected: selected == lifetime,
|
||||
onPressed: () => onProductSelected(lifetime),
|
||||
),
|
||||
],
|
||||
].intersperse(const SizedBox(height: Dimens.grid8)).toList(growable: false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -220,3 +214,16 @@ class _ProductAnimatedText extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension on List<Widget> {
|
||||
Iterable<Widget> intersperse(Widget element) sync* {
|
||||
final iterator = this.iterator;
|
||||
if (iterator.moveNext()) {
|
||||
yield iterator.current;
|
||||
while (iterator.moveNext()) {
|
||||
yield element;
|
||||
yield iterator.current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -256,10 +256,7 @@ class _FeatureHighlight extends StatelessWidget {
|
|||
).width +
|
||||
Dimens.paddingM * 2,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Dimens.paddingM,
|
||||
vertical: Dimens.paddingS,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingS),
|
||||
decoration: BoxDecoration(
|
||||
color: highlight ? Theme.of(context).colorScheme.secondaryContainer : null,
|
||||
borderRadius: roundedTop
|
||||
|
@ -274,7 +271,7 @@ class _FeatureHighlight extends StatelessWidget {
|
|||
)
|
||||
: null,
|
||||
),
|
||||
child: child,
|
||||
child: Center(child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import 'package:lightmeter/res/dimens.dart';
|
|||
import 'package:lightmeter/screens/logbook_photos/components/grid_tile/widget_grid_tile_logbook_photo.dart';
|
||||
import 'package:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:lightmeter/utils/guard_pro_tap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class LogbookPhotosScreen extends StatefulWidget {
|
||||
|
@ -30,8 +32,15 @@ class _LogbookPhotosScreenState extends State<LogbookPhotosScreen> with SingleTi
|
|||
child: SwitchListTile(
|
||||
secondary: const Icon(Icons.book_outlined),
|
||||
title: Text(S.of(context).saveNewPhotos),
|
||||
value: LogbookPhotos.isEnabledOf(context),
|
||||
onChanged: LogbookPhotosProvider.of(context).saveLogbookPhotos,
|
||||
value: LogbookPhotos.isEnabledOf(context) && context.isPro,
|
||||
onChanged: (value) {
|
||||
guardProTap(
|
||||
context,
|
||||
() {
|
||||
Navigator.of(context).pushNamed(NavigationRoutes.proFeaturesScreen.name);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/shared/animated_circular_button/widget_button_circular_animated.dart';
|
||||
import 'package:lightmeter/screens/shared/bottom_controls_bar/widget_bottom_controls_bar.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
class MeteringBottomControls extends StatelessWidget {
|
||||
final double? ev;
|
||||
|
@ -76,7 +75,7 @@ class _EvValueText extends StatelessWidget {
|
|||
}
|
||||
|
||||
String _text(BuildContext context) {
|
||||
final bool showEv100 = context.isPro && UserPreferencesProvider.showEv100Of(context);
|
||||
final bool showEv100 = UserPreferencesProvider.showEv100Of(context);
|
||||
final StringBuffer buffer = StringBuffer()
|
||||
..writeAll([
|
||||
(showEv100 ? ev100 : ev).toStringAsFixed(1),
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/models/c
|
|||
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/metering_top_bar/widget_top_bar_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/lightmeter_pro_badge/widget_badge_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/widget_container_readings.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
@ -112,20 +113,17 @@ class CameraContainer extends StatelessWidget {
|
|||
|
||||
double _meteringContainerHeight(BuildContext context) {
|
||||
double enabledFeaturesHeight = 0;
|
||||
if (!context.isPro) {
|
||||
if (RemoteConfig.isEnabled(context, Feature.showUnlockProOnMainScreen)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
} else {
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.equipmentProfiles)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (!context.isPro && RemoteConfig.isEnabled(context, Feature.showUnlockProOnMainScreen)) {
|
||||
enabledFeaturesHeight += LightmeterProBadge.height(context);
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.equipmentProfiles)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
|
||||
|
||||
class LightmeterProAnimatedDialog extends StatelessWidget {
|
||||
const LightmeterProAnimatedDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(NavigationRoutes.proFeaturesScreen.name);
|
||||
},
|
||||
child: ReadingValueContainer(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
textColor: Theme.of(context).colorScheme.onSecondary,
|
||||
values: [
|
||||
ReadingValue(
|
||||
label: S.of(context).proFeaturesTitle,
|
||||
value: S.of(context).getPro,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/utils/text_height.dart';
|
||||
|
||||
class LightmeterProBadge extends StatelessWidget {
|
||||
const LightmeterProBadge({super.key});
|
||||
|
||||
static double height(BuildContext context) {
|
||||
if (Theme.of(context).textTheme.titleMedium?.lineHeight case final lineHeight?) {
|
||||
return Dimens.paddingS * 2 + lineHeight;
|
||||
} else {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(NavigationRoutes.proFeaturesScreen.name);
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(Dimens.borderRadiusM),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Dimens.paddingM,
|
||||
vertical: Dimens.paddingS,
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).getPro,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSecondaryContainer),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
|
|||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/lightmeter_pro_badge/widget_badge_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
@ -38,10 +38,10 @@ class ReadingsContainer extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (!context.isPro && RemoteConfig.isEnabled(context, Feature.showUnlockProOnMainScreen)) ...[
|
||||
const LightmeterProAnimatedDialog(),
|
||||
const LightmeterProBadge(),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
if (context.isPro && context.meteringFeature(MeteringScreenLayoutFeature.equipmentProfiles)) ...[
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.equipmentProfiles)) ...[
|
||||
const EquipmentProfilePicker(),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
|
@ -52,7 +52,7 @@ class ReadingsContainer extends StatelessWidget {
|
|||
),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
if (context.isPro && context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) ...[
|
||||
if (context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) ...[
|
||||
FilmPicker(selectedIso: iso),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart';
|
||||
import 'package:lightmeter/screens/settings/components/about/components/restore_purchases/widget_list_tile_restore_purchases.dart';
|
||||
import 'package:lightmeter/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart';
|
||||
import 'package:lightmeter/screens/settings/components/about/components/version/widget_list_tile_version.dart';
|
||||
import 'package:lightmeter/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
class AboutSettingsSection extends StatelessWidget {
|
||||
const AboutSettingsSection({super.key});
|
||||
|
@ -13,11 +15,12 @@ class AboutSettingsSection extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
title: S.of(context).about,
|
||||
children: const [
|
||||
SourceCodeListTile(),
|
||||
ReportIssueListTile(),
|
||||
WriteEmailListTile(),
|
||||
VersionListTile(),
|
||||
children: [
|
||||
const SourceCodeListTile(),
|
||||
const ReportIssueListTile(),
|
||||
const WriteEmailListTile(),
|
||||
const VersionListTile(),
|
||||
if (context.isPro) const RestorePurchasesListTile(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,39 +4,53 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||
|
||||
class CameraFeaturesListTile extends StatelessWidget {
|
||||
const CameraFeaturesListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IAPListTile(
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.camera_alt_outlined),
|
||||
title: Text(S.of(context).cameraFeatures),
|
||||
onTap: () {
|
||||
UserPreferencesProvider.cameraConfigOf(context).entries.map(
|
||||
(entry) => DialogSwitchListItem(
|
||||
value: CameraFeature.spotMetering,
|
||||
title: S.of(context).cameraFeatureSpotMetering,
|
||||
subtitle: S.of(context).cameraFeatureSpotMeteringHint,
|
||||
initialValue: UserPreferencesProvider.cameraFeatureOf(context, CameraFeature.spotMetering),
|
||||
isProRequired: true,
|
||||
),
|
||||
);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => DialogSwitch<CameraFeature>(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
title: S.of(context).cameraFeatures,
|
||||
values: UserPreferencesProvider.cameraConfigOf(context),
|
||||
enabledAdapter: (feature) => switch (feature) {
|
||||
CameraFeature.spotMetering => true,
|
||||
CameraFeature.histogram => true,
|
||||
CameraFeature.showFocalLength =>
|
||||
ServicesProvider.of(context).userPreferencesService.cameraFocalLength != null,
|
||||
},
|
||||
titleAdapter: (context, feature) => switch (feature) {
|
||||
CameraFeature.spotMetering => S.of(context).cameraFeatureSpotMetering,
|
||||
CameraFeature.histogram => S.of(context).cameraFeatureHistogram,
|
||||
CameraFeature.showFocalLength => S.of(context).cameraFeaturesShowFocalLength,
|
||||
},
|
||||
subtitleAdapter: (context, feature) => switch (feature) {
|
||||
CameraFeature.spotMetering => S.of(context).cameraFeatureSpotMeteringHint,
|
||||
CameraFeature.histogram => S.of(context).cameraFeatureHistogramHint,
|
||||
CameraFeature.showFocalLength => S.of(context).cameraFeaturesShowFocalLengthHint,
|
||||
},
|
||||
items: [
|
||||
DialogSwitchListItem(
|
||||
value: CameraFeature.showFocalLength,
|
||||
title: S.of(context).cameraFeaturesShowFocalLength,
|
||||
subtitle: S.of(context).cameraFeaturesShowFocalLengthHint,
|
||||
initialValue: UserPreferencesProvider.cameraFeatureOf(context, CameraFeature.showFocalLength),
|
||||
isEnabled: ServicesProvider.of(context).userPreferencesService.cameraFocalLength != null,
|
||||
),
|
||||
DialogSwitchListItem(
|
||||
value: CameraFeature.spotMetering,
|
||||
title: S.of(context).cameraFeatureSpotMetering,
|
||||
subtitle: S.of(context).cameraFeatureSpotMeteringHint,
|
||||
initialValue: UserPreferencesProvider.cameraFeatureOf(context, CameraFeature.spotMetering),
|
||||
isProRequired: true,
|
||||
),
|
||||
DialogSwitchListItem(
|
||||
value: CameraFeature.histogram,
|
||||
title: S.of(context).cameraFeatureHistogram,
|
||||
subtitle: S.of(context).cameraFeatureHistogramHint,
|
||||
initialValue: UserPreferencesProvider.cameraFeatureOf(context, CameraFeature.histogram),
|
||||
isProRequired: true,
|
||||
),
|
||||
],
|
||||
onSave: UserPreferencesProvider.of(context).setCameraFeature,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||
|
||||
class LogbookListTile extends StatelessWidget {
|
||||
const LogbookListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IAPListTile(
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.book_outlined),
|
||||
title: Text(S.of(context).logbook),
|
||||
onTap: () {
|
||||
|
|
|
@ -3,24 +3,26 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/general/components/timer/bloc_list_tile_timer.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:lightmeter/utils/guard_pro_tap.dart';
|
||||
|
||||
class TimerListTile extends StatelessWidget {
|
||||
const TimerListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Disable(
|
||||
disable: !context.isPro,
|
||||
child: BlocBuilder<TimerListTileBloc, bool>(
|
||||
builder: (context, state) => SwitchListTile(
|
||||
secondary: const Icon(Icons.timer_outlined),
|
||||
title: Text(S.of(context).autostartTimer),
|
||||
value: state && context.isPro,
|
||||
onChanged: context.read<TimerListTileBloc>().onChanged,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
),
|
||||
return BlocBuilder<TimerListTileBloc, bool>(
|
||||
builder: (context, state) => SwitchListTile(
|
||||
secondary: const Icon(Icons.timer_outlined),
|
||||
title: Text(S.of(context).autostartTimer),
|
||||
value: context.isPro && state,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
onChanged: (value) {
|
||||
guardProTap(
|
||||
context,
|
||||
() => context.read<TimerListTileBloc>().onChanged(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
|
||||
class BuyProListTile extends StatelessWidget {
|
||||
const BuyProListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: implement pending handling via REvenueCat
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.bolt),
|
||||
title: Text(S.of(context).getPro),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(NavigationRoutes.proFeaturesScreen.name);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart';
|
||||
import 'package:lightmeter/screens/settings/components/lightmeter_pro/components/restore_purchases/widget_list_tile_restore_purchases.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
|
||||
|
||||
class LightmeterProSettingsSection extends StatelessWidget {
|
||||
const LightmeterProSettingsSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsSection(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Theme.of(context).colorScheme.onSecondary,
|
||||
title: S.of(context).proFeaturesTitle,
|
||||
children: const [
|
||||
BuyProListTile(),
|
||||
RestorePurchasesListTile(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||
|
||||
class EquipmentProfilesListTile extends StatelessWidget {
|
||||
const EquipmentProfilesListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IAPListTile(
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.camera_outlined),
|
||||
title: Text(S.of(context).equipmentProfiles),
|
||||
onTap: () {
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||
import 'package:lightmeter/utils/guard_pro_tap.dart';
|
||||
|
||||
class FilmsListTile extends StatelessWidget {
|
||||
const FilmsListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IAPListTile(
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.camera_roll_outlined),
|
||||
title: Text(S.of(context).films),
|
||||
onTap: () => Navigator.of(context).pushNamed(NavigationRoutes.filmsListScreen.name),
|
||||
onTap: () {
|
||||
guardProTap(
|
||||
context,
|
||||
() {
|
||||
Navigator.of(context).pushNamed(NavigationRoutes.filmsListScreen.name);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|||
import 'package:lightmeter/providers/films_provider.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class MeteringScreenLayoutListTile extends StatelessWidget {
|
||||
|
@ -23,17 +22,16 @@ class MeteringScreenLayoutListTile extends StatelessWidget {
|
|||
icon: Icons.layers_outlined,
|
||||
title: S.of(context).meteringScreenLayout,
|
||||
description: S.of(context).meteringScreenLayoutHint,
|
||||
values: UserPreferencesProvider.meteringScreenConfigOf(context),
|
||||
titleAdapter: _toStringLocalized,
|
||||
enabledAdapter: (value) {
|
||||
switch (value) {
|
||||
case MeteringScreenLayoutFeature.equipmentProfiles:
|
||||
case MeteringScreenLayoutFeature.filmPicker:
|
||||
return context.isPro;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
},
|
||||
items: UserPreferencesProvider.meteringScreenConfigOf(context)
|
||||
.entries
|
||||
.map(
|
||||
(entry) => DialogSwitchListItem(
|
||||
value: entry.key,
|
||||
title: _toStringLocalized(context, entry.key),
|
||||
initialValue: UserPreferencesProvider.meteringScreenFeatureOf(context, entry.key),
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
onSave: (value) {
|
||||
if (!value[MeteringScreenLayoutFeature.equipmentProfiles]!) {
|
||||
EquipmentProfilesProvider.of(context).selectProfile(EquipmentProfiles.of(context).first);
|
||||
|
|
|
@ -2,23 +2,18 @@ import 'package:flutter/material.dart';
|
|||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
class ShowEv100ListTile extends StatelessWidget {
|
||||
const ShowEv100ListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Disable(
|
||||
disable: !context.isPro,
|
||||
child: SwitchListTile(
|
||||
secondary: const Icon(Icons.adjust_outlined),
|
||||
title: Text(S.of(context).showEv100),
|
||||
value: context.isPro && UserPreferencesProvider.showEv100Of(context),
|
||||
onChanged: (_) => UserPreferencesProvider.of(context).toggleShowEv100(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
),
|
||||
return SwitchListTile(
|
||||
secondary: const Icon(Icons.adjust_outlined),
|
||||
title: Text(S.of(context).showEv100),
|
||||
value: UserPreferencesProvider.showEv100Of(context),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
onChanged: (_) => UserPreferencesProvider.of(context).toggleShowEv100(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,41 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:lightmeter/utils/guard_pro_tap.dart';
|
||||
|
||||
typedef StringAdapter<T> = String Function(BuildContext context, T value);
|
||||
|
||||
class DialogSwitchListItem<T> {
|
||||
final T value;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final bool initialValue;
|
||||
final bool isEnabled;
|
||||
final bool isProRequired;
|
||||
|
||||
const DialogSwitchListItem({
|
||||
required this.value,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.initialValue,
|
||||
this.isEnabled = true,
|
||||
this.isProRequired = false,
|
||||
});
|
||||
}
|
||||
|
||||
class DialogSwitch<T> extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? description;
|
||||
final Map<T, bool> values;
|
||||
final StringAdapter<T> titleAdapter;
|
||||
final StringAdapter<T>? subtitleAdapter;
|
||||
final bool Function(T value)? enabledAdapter;
|
||||
final List<DialogSwitchListItem<T>> items;
|
||||
final ValueChanged<Map<T, bool>> onSave;
|
||||
|
||||
const DialogSwitch({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.description,
|
||||
required this.values,
|
||||
required this.titleAdapter,
|
||||
this.subtitleAdapter,
|
||||
this.enabledAdapter,
|
||||
required this.items,
|
||||
required this.onSave,
|
||||
super.key,
|
||||
});
|
||||
|
@ -32,7 +45,11 @@ class DialogSwitch<T> extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _DialogSwitchState<T> extends State<DialogSwitch<T>> {
|
||||
late final Map<T, bool> _features = Map.from(widget.values);
|
||||
late final Map<T, bool> _features = Map.fromEntries(
|
||||
widget.items.map(
|
||||
(item) => MapEntry(item.value, item.initialValue),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -55,27 +72,15 @@ class _DialogSwitchState<T> extends State<DialogSwitch<T>> {
|
|||
],
|
||||
ListView(
|
||||
shrinkWrap: true,
|
||||
children: _features.entries.map(
|
||||
(entry) {
|
||||
final isEnabled = widget.enabledAdapter?.call(entry.key) ?? true;
|
||||
return Disable(
|
||||
disable: !isEnabled,
|
||||
child: SwitchListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left),
|
||||
title: Text(widget.titleAdapter(context, entry.key)),
|
||||
subtitle: widget.subtitleAdapter != null
|
||||
? Text(
|
||||
widget.subtitleAdapter!.call(context, entry.key),
|
||||
style: Theme.of(context).listTileTheme.subtitleTextStyle,
|
||||
)
|
||||
: null,
|
||||
value: isEnabled && _features[entry.key]!,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_features.update(entry.key, (_) => value);
|
||||
});
|
||||
},
|
||||
),
|
||||
children: widget.items.map(
|
||||
(item) {
|
||||
final value = _features[item.value]!;
|
||||
return SwitchListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left),
|
||||
title: Text(item.title),
|
||||
subtitle: item.subtitle != null ? Text(item.subtitle!) : null,
|
||||
value: item.isProRequired ? context.isPro && value : value,
|
||||
onChanged: item.isEnabled ? (value) => _setItem(item, value) : null,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
|
@ -99,4 +104,18 @@ class _DialogSwitchState<T> extends State<DialogSwitch<T>> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _setItem(DialogSwitchListItem<T> item, bool value) {
|
||||
void setItemState() {
|
||||
setState(() {
|
||||
_features.update(item.value, (_) => value);
|
||||
});
|
||||
}
|
||||
|
||||
if (item.isProRequired) {
|
||||
guardProTap(context, setItemState);
|
||||
} else {
|
||||
setItemState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
/// Depends on the product status and replaces [onTap] with purchase callback
|
||||
/// if the product is purchasable.
|
||||
class IAPListTile extends StatelessWidget {
|
||||
final Icon leading;
|
||||
final Text title;
|
||||
final VoidCallback onTap;
|
||||
final bool showPendingTrailing;
|
||||
|
||||
const IAPListTile({
|
||||
required this.leading,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
this.showPendingTrailing = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Disable(
|
||||
disable: !context.isPro,
|
||||
child: ListTile(
|
||||
leading: leading,
|
||||
title: title,
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,14 +4,10 @@ import 'package:lightmeter/res/dimens.dart';
|
|||
class SettingsSection extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
|
||||
const SettingsSection({
|
||||
required this.title,
|
||||
required this.children,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -25,33 +21,22 @@ class SettingsSection extends StatelessWidget {
|
|||
Dimens.paddingM,
|
||||
),
|
||||
child: Card(
|
||||
color: backgroundColor,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
listTileTheme: Theme.of(context).listTileTheme.copyWith(
|
||||
iconColor: foregroundColor,
|
||||
textColor: foregroundColor,
|
||||
),
|
||||
),
|
||||
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: foregroundColor ?? 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,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -3,12 +3,10 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart';
|
||||
import 'package:lightmeter/screens/settings/components/camera/widget_settings_section_camera.dart';
|
||||
import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart';
|
||||
import 'package:lightmeter/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart';
|
||||
import 'package:lightmeter/screens/settings/components/theme/widget_settings_section_theme.dart';
|
||||
import 'package:lightmeter/screens/settings/flow_settings.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
|
@ -41,7 +39,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
<Widget>[
|
||||
if (!context.isPro) const LightmeterProSettingsSection(),
|
||||
const MeteringSettingsSection(),
|
||||
const CameraSettingsSection(),
|
||||
const GeneralSettingsSection(),
|
||||
|
|
11
lib/utils/guard_pro_tap.dart
Normal file
11
lib/utils/guard_pro_tap.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/navigation/routes.dart';
|
||||
import 'package:lightmeter/utils/context_utils.dart';
|
||||
|
||||
void guardProTap(BuildContext context, VoidCallback callback) {
|
||||
if (context.isPro) {
|
||||
callback();
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(NavigationRoutes.proFeaturesScreen.name);
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
Before Width: | Height: | Size: 473 KiB After Width: | Height: | Size: 474 KiB |
Loading…
Reference in a new issue