mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-21 23:10:40 +00:00
c66381f813
* sync with resources * separated `ExpandableSectionList` as widget * fixed generic type * implemented `FilmsScreen` (wip) * made `SliverScreen` title a widget * [`FilmEditScreen`] wip * [`FilmEditScreen`] added validation * fixed title overflow for `SliverScreen` * [`FilmEditScreen`] separated add and edit blocs * [`FilmEditScreen`] split into separate components * added bottom widget to `SliverScreen` * implemented films list tabs fo `FilmsScreen` * added films screen to navigation * replaced explicit routes names with enum values * implemented CRUD for custom films * added placeholder for empty custom films list * added `FilmsStorageService` * fixed unit tests * fixed integration tests * lint * fixed golden tests * added iap stub methods * added custom films to features list * use 2.0.0 resouces * fixed film picket tests * migrated to iap 1.0.1 * autofocus film name field * wait for the film to edited * migrated to iap 1.1.0 * typo * wait for storage initialization * migrated to iap 1.1.1 * fixed films initialization * added conditions to films model `updateShouldNotifyDependent` * typo * fixed select film discard notify * covered films model `updateShouldNotifyDependent`
229 lines
7.2 KiB
Dart
229 lines
7.2 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:lightmeter/data/models/app_feature.dart';
|
|
import 'package:lightmeter/generated/l10n.dart';
|
|
import 'package:lightmeter/providers/services_provider.dart';
|
|
import 'package:lightmeter/res/dimens.dart';
|
|
import 'package:lightmeter/res/theme.dart';
|
|
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
|
import 'package:lightmeter/utils/text_height.dart';
|
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
|
|
|
class LightmeterProScreen extends StatelessWidget {
|
|
final features =
|
|
defaultTargetPlatform == TargetPlatform.android ? AppFeature.androidFeatures : AppFeature.iosFeatures;
|
|
|
|
LightmeterProScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: SliverScreen(
|
|
title: Text(S.of(context).proFeaturesTitle),
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(Dimens.paddingM),
|
|
child: Text(
|
|
S.of(context).proFeaturesPromoText,
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(
|
|
Dimens.paddingM,
|
|
0,
|
|
Dimens.paddingM,
|
|
Dimens.paddingS,
|
|
),
|
|
child: Text(
|
|
S.of(context).proFeaturesWhatsIncluded,
|
|
style: Theme.of(context).textTheme.headlineSmall,
|
|
),
|
|
),
|
|
),
|
|
const SliverToBoxAdapter(child: _FeaturesHeader()),
|
|
SliverList.separated(
|
|
itemCount: features.length,
|
|
itemBuilder: (_, index) => _FeatureItem(feature: features[index]),
|
|
separatorBuilder: (_, __) => const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
|
child: Divider(),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(Dimens.paddingM),
|
|
child: Text(S.of(context).proFeaturesSupportText),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
color: Theme.of(context).colorScheme.surfaceElevated1,
|
|
width: MediaQuery.sizeOf(context).width,
|
|
padding: EdgeInsets.fromLTRB(
|
|
Dimens.paddingM,
|
|
Dimens.paddingM,
|
|
Dimens.paddingM,
|
|
Dimens.paddingM + MediaQuery.paddingOf(context).bottom,
|
|
),
|
|
child: FilledButton(
|
|
onPressed: () {
|
|
ServicesProvider.maybeOf(context)
|
|
?.analytics
|
|
.setCustomKey('iap_product_type', IAPProductType.paidFeatures.storeId);
|
|
IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures);
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text(S.of(context).getNowFor(IAPProducts.productOf(context, IAPProductType.paidFeatures)!.price)),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FeaturesHeader extends StatelessWidget {
|
|
const _FeaturesHeader();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
|
child: Row(
|
|
children: [
|
|
const Spacer(),
|
|
_FeatureHighlight(child: Text(S.of(context).featuresFree)),
|
|
_FeatureHighlight(
|
|
roundedTop: true,
|
|
highlight: true,
|
|
child: Text(
|
|
S.of(context).featuresPro,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium!
|
|
.copyWith(color: Theme.of(context).colorScheme.onSecondaryContainer),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FeatureItem extends StatelessWidget {
|
|
final AppFeature feature;
|
|
|
|
const _FeatureItem({
|
|
required this.feature,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: Dimens.grid48),
|
|
child: IntrinsicHeight(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Expanded(
|
|
child: Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: Dimens.paddingM,
|
|
vertical: Dimens.paddingS,
|
|
),
|
|
child: Text(
|
|
feature.name(context),
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Opacity(
|
|
opacity: feature.isFree ? 1 : 0,
|
|
child: const _FeatureHighlight(
|
|
child: _CheckBox(highlight: false),
|
|
),
|
|
),
|
|
_FeatureHighlight(
|
|
highlight: true,
|
|
roundedBottom: feature == AppFeature.values.last,
|
|
child: const _CheckBox(highlight: true),
|
|
),
|
|
const SizedBox(width: Dimens.grid16),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FeatureHighlight extends StatelessWidget {
|
|
final bool highlight;
|
|
final bool roundedTop;
|
|
final bool roundedBottom;
|
|
final Widget child;
|
|
|
|
const _FeatureHighlight({
|
|
this.highlight = false,
|
|
this.roundedTop = false,
|
|
this.roundedBottom = false,
|
|
required this.child,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
constraints: BoxConstraints(
|
|
minWidth: textSize(
|
|
highlight ? S.of(context).featuresPro : S.of(context).featuresFree,
|
|
Theme.of(context).textTheme.bodyMedium,
|
|
MediaQuery.sizeOf(context).width,
|
|
).width +
|
|
Dimens.paddingM * 2,
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: Dimens.paddingM,
|
|
vertical: Dimens.paddingS,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: highlight ? Theme.of(context).colorScheme.secondaryContainer : null,
|
|
borderRadius: roundedTop
|
|
? const BorderRadius.only(
|
|
topLeft: Radius.circular(Dimens.borderRadiusM),
|
|
topRight: Radius.circular(Dimens.borderRadiusM),
|
|
)
|
|
: roundedBottom
|
|
? const BorderRadius.only(
|
|
bottomLeft: Radius.circular(Dimens.borderRadiusM),
|
|
bottomRight: Radius.circular(Dimens.borderRadiusM),
|
|
)
|
|
: null,
|
|
),
|
|
child: child,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CheckBox extends StatelessWidget {
|
|
final bool highlight;
|
|
|
|
const _CheckBox({required this.highlight});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Icon(
|
|
Icons.check_outlined,
|
|
color: highlight ? Theme.of(context).colorScheme.onSecondaryContainer : null,
|
|
);
|
|
}
|
|
}
|