mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-08-26 23:16:42 +00:00
don't fetch products
This commit is contained in:
parent
e24bd8b2ec
commit
b83f3193c3
2 changed files with 109 additions and 89 deletions
|
@ -1,7 +1,4 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/res/theme.dart';
|
import 'package:lightmeter/res/theme.dart';
|
||||||
|
@ -9,7 +6,12 @@ import 'package:lightmeter/screens/shared/button/widget_button_filled_large.dart
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
|
||||||
class LightmeterProOffering extends StatefulWidget {
|
class LightmeterProOffering extends StatefulWidget {
|
||||||
const LightmeterProOffering({super.key});
|
const LightmeterProOffering({
|
||||||
|
super.key,
|
||||||
|
required this.onBuyProduct,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ValueChanged<IAPProduct> onBuyProduct;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LightmeterProOffering> createState() => _LightmeterProOfferingState();
|
State<LightmeterProOffering> createState() => _LightmeterProOfferingState();
|
||||||
|
@ -17,29 +19,12 @@ class LightmeterProOffering extends StatefulWidget {
|
||||||
|
|
||||||
class _LightmeterProOfferingState extends State<LightmeterProOffering> {
|
class _LightmeterProOfferingState extends State<LightmeterProOffering> {
|
||||||
late final Future<List<IAPProduct>> productsFuture;
|
late final Future<List<IAPProduct>> productsFuture;
|
||||||
bool _isLoading = true;
|
|
||||||
IAPProduct? monthly;
|
|
||||||
IAPProduct? yearly;
|
|
||||||
IAPProduct? lifetime;
|
|
||||||
IAPProduct? selected;
|
IAPProduct? selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void didChangeDependencies() {
|
||||||
super.initState();
|
super.didChangeDependencies();
|
||||||
productsFuture = IAPProductsProvider.of(context).fetchProducts();
|
selected = IAPProducts.of(context).monthly ?? IAPProducts.of(context).lifetime;
|
||||||
productsFuture.then((products) async {
|
|
||||||
monthly = products.firstWhereOrNull((p) => p.type == PurchaseType.monthly);
|
|
||||||
yearly = products.firstWhereOrNull((p) => p.type == PurchaseType.yearly);
|
|
||||||
lifetime = products.firstWhereOrNull((p) => p.type == PurchaseType.lifetime);
|
|
||||||
selected = monthly ?? lifetime;
|
|
||||||
}).onError((e, __) {
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_showSnackbar(e.toString());
|
|
||||||
});
|
|
||||||
}).whenComplete(() {
|
|
||||||
_isLoading = false;
|
|
||||||
if (mounted) setState(() {});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -66,78 +51,29 @@ class _LightmeterProOfferingState extends State<LightmeterProOffering> {
|
||||||
if (!_isLifetimeOnly)
|
if (!_isLifetimeOnly)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: Dimens.paddingS),
|
padding: const EdgeInsets.only(bottom: Dimens.paddingS),
|
||||||
child: Stack(
|
child: _Products(
|
||||||
alignment: Alignment.center,
|
monthly: IAPProducts.of(context).monthly,
|
||||||
children: [
|
yearly: IAPProducts.of(context).yearly,
|
||||||
AnimatedOpacity(
|
lifetime: IAPProducts.of(context).lifetime,
|
||||||
duration: Dimens.durationM,
|
selected: selected,
|
||||||
opacity: _isLoading ? Dimens.disabledOpacity : Dimens.enabledOpacity,
|
onProductSelected: _selectProduct,
|
||||||
child: _Products(
|
|
||||||
monthly: monthly,
|
|
||||||
yearly: yearly,
|
|
||||||
lifetime: lifetime,
|
|
||||||
selected: selected,
|
|
||||||
onProductSelected: _selectProduct,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_isLoading)
|
|
||||||
const SizedBox(
|
|
||||||
height: 120,
|
|
||||||
child: Center(child: CircularProgressIndicator()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FilledButtonLarge(
|
FilledButtonLarge(
|
||||||
title: S.of(context).continuePurchase,
|
title: S.of(context).continuePurchase,
|
||||||
onPressed: _isLoading || selected != null ? _buyPro : null,
|
onPressed: selected != null ? () => widget.onBuyProduct(selected!) : null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isLifetimeOnly => lifetime != null && yearly == null && monthly == null;
|
bool get _isLifetimeOnly => !IAPProducts.of(context).hasSubscriptions;
|
||||||
|
|
||||||
void _selectProduct(IAPProduct product) {
|
void _selectProduct(IAPProduct product) {
|
||||||
if (!_isLoading) {
|
|
||||||
setState(() {
|
|
||||||
selected = product;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _buyPro() async {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
selected = product;
|
||||||
});
|
});
|
||||||
try {
|
|
||||||
final isPro = await IAPProductsProvider.of(context).buyPro(selected!);
|
|
||||||
if (mounted && isPro) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
_showSnackbar(e.message ?? '');
|
|
||||||
} catch (e) {
|
|
||||||
_showSnackbar(e.toString());
|
|
||||||
} finally {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showSnackbar(String message) {
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(message),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:lightmeter/data/models/app_feature.dart';
|
import 'package:lightmeter/data/models/app_feature.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
@ -8,21 +9,30 @@ import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||||
import 'package:lightmeter/utils/text_height.dart';
|
import 'package:lightmeter/utils/text_height.dart';
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
|
||||||
class LightmeterProScreen extends StatelessWidget {
|
class LightmeterProScreen extends StatefulWidget {
|
||||||
|
const LightmeterProScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LightmeterProScreen> createState() => _LightmeterProScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LightmeterProScreenState extends State<LightmeterProScreen> {
|
||||||
final features =
|
final features =
|
||||||
defaultTargetPlatform == TargetPlatform.android ? AppFeature.androidFeatures : AppFeature.iosFeatures;
|
defaultTargetPlatform == TargetPlatform.android ? AppFeature.androidFeatures : AppFeature.iosFeatures;
|
||||||
|
|
||||||
LightmeterProScreen({super.key});
|
final _isRestoringPurchases = ValueNotifier(false);
|
||||||
|
final _isPurchasingProduct = ValueNotifier(false);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverScreen(
|
return SliverScreen(
|
||||||
title: Text(S.of(context).proFeaturesTitle),
|
title: Text(S.of(context).proFeaturesTitle),
|
||||||
appBarActions: [
|
appBarActions: [
|
||||||
IconButton(
|
/// Restoration is working, but it does not trigger pop
|
||||||
onPressed: IAPProductsProvider.of(context).restorePurchases,
|
/// add await of didChangeDependencies
|
||||||
icon: const Icon(Icons.restore),
|
_RestorePurchasesButton(
|
||||||
tooltip: S.of(context).restorePurchases,
|
valueListenable: _isRestoringPurchases,
|
||||||
|
onPressed: _restorePurchases,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
slivers: [
|
slivers: [
|
||||||
|
@ -65,7 +75,81 @@ class LightmeterProScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottomNavigationBar: const LightmeterProOffering(),
|
bottomNavigationBar: LightmeterProOffering(onBuyProduct: _buyPro),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restorePurchases() async {
|
||||||
|
_isRestoringPurchases.value = true;
|
||||||
|
try {
|
||||||
|
final isPro = await IAPProductsProvider.of(context).restorePurchases();
|
||||||
|
if (mounted && isPro) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
_showSnackbar(e.message ?? '');
|
||||||
|
} catch (e) {
|
||||||
|
_showSnackbar(e.toString());
|
||||||
|
} finally {
|
||||||
|
_isRestoringPurchases.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _buyPro(IAPProduct product) async {
|
||||||
|
_isPurchasingProduct.value = true;
|
||||||
|
try {
|
||||||
|
final isPro = await IAPProductsProvider.of(context).buyPro(product);
|
||||||
|
if (mounted && isPro) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
_showSnackbar(e.message ?? '');
|
||||||
|
} catch (e) {
|
||||||
|
_showSnackbar(e.toString());
|
||||||
|
} finally {
|
||||||
|
_isPurchasingProduct.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSnackbar(String message) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestorePurchasesButton extends StatelessWidget {
|
||||||
|
const _RestorePurchasesButton({
|
||||||
|
required this.valueListenable,
|
||||||
|
required this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ValueNotifier<bool> valueListenable;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: valueListenable,
|
||||||
|
builder: (context, value, _) {
|
||||||
|
if (value) {
|
||||||
|
return const SizedBox.square(
|
||||||
|
dimension: Dimens.grid24 - Dimens.grid4,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: const Icon(Icons.restore),
|
||||||
|
tooltip: S.of(context).restorePurchases,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue