reworked restoring purchases

This commit is contained in:
Vadim 2025-08-08 15:26:50 +02:00
parent b83f3193c3
commit 430d7abfb0
2 changed files with 71 additions and 52 deletions

View file

@ -8,9 +8,11 @@ import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
class LightmeterProOffering extends StatefulWidget {
const LightmeterProOffering({
super.key,
required this.isEnabled,
required this.onBuyProduct,
});
final bool isEnabled;
final ValueChanged<IAPProduct> onBuyProduct;
@override
@ -49,19 +51,26 @@ class _LightmeterProOfferingState extends State<LightmeterProOffering> {
mainAxisSize: MainAxisSize.min,
children: [
if (!_isLifetimeOnly)
Padding(
padding: const EdgeInsets.only(bottom: Dimens.paddingS),
child: _Products(
monthly: IAPProducts.of(context).monthly,
yearly: IAPProducts.of(context).yearly,
lifetime: IAPProducts.of(context).lifetime,
selected: selected,
onProductSelected: _selectProduct,
AnimatedOpacity(
duration: Dimens.durationM,
opacity: widget.isEnabled ? Dimens.enabledOpacity : Dimens.disabledOpacity,
child: IgnorePointer(
ignoring: !widget.isEnabled,
child: Padding(
padding: const EdgeInsets.only(bottom: Dimens.paddingS),
child: _Products(
monthly: IAPProducts.of(context).monthly,
yearly: IAPProducts.of(context).yearly,
lifetime: IAPProducts.of(context).lifetime,
selected: selected,
onProductSelected: _selectProduct,
),
),
),
),
FilledButtonLarge(
title: S.of(context).continuePurchase,
onPressed: selected != null ? () => widget.onBuyProduct(selected!) : null,
onPressed: widget.isEnabled && selected != null ? () => widget.onBuyProduct(selected!) : null,
),
],
),

View file

@ -9,6 +9,8 @@ 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';
typedef PurchasesState = ({bool isPurchasingProduct, bool isRestoringPurchases});
class LightmeterProScreen extends StatefulWidget {
const LightmeterProScreen({super.key});
@ -20,19 +22,34 @@ class _LightmeterProScreenState extends State<LightmeterProScreen> {
final features =
defaultTargetPlatform == TargetPlatform.android ? AppFeature.androidFeatures : AppFeature.iosFeatures;
final _isRestoringPurchases = ValueNotifier(false);
final _isPurchasingProduct = ValueNotifier(false);
final _purchasesNotifier = ValueNotifier<PurchasesState>(
(
isPurchasingProduct: false,
isRestoringPurchases: false,
),
);
@override
Widget build(BuildContext context) {
return SliverScreen(
title: Text(S.of(context).proFeaturesTitle),
appBarActions: [
/// Restoration is working, but it does not trigger pop
/// add await of didChangeDependencies
_RestorePurchasesButton(
valueListenable: _isRestoringPurchases,
onPressed: _restorePurchases,
ValueListenableBuilder(
valueListenable: _purchasesNotifier,
builder: (context, value, _) {
if (value.isRestoringPurchases) {
return const SizedBox.square(
dimension: Dimens.grid24 - Dimens.grid4,
child: CircularProgressIndicator(),
);
} else {
return IconButton(
onPressed: value.isPurchasingProduct ? null : _restorePurchases,
icon: const Icon(Icons.restore),
tooltip: S.of(context).restorePurchases,
);
}
},
),
],
slivers: [
@ -75,12 +92,26 @@ class _LightmeterProScreenState extends State<LightmeterProScreen> {
),
),
],
bottomNavigationBar: LightmeterProOffering(onBuyProduct: _buyPro),
bottomNavigationBar: ValueListenableBuilder(
valueListenable: _purchasesNotifier,
builder: (context, value, _) {
return LightmeterProOffering(
isEnabled: !value.isRestoringPurchases && !value.isPurchasingProduct,
onBuyProduct: _buyPro,
);
},
),
);
}
@override
void dispose() {
_purchasesNotifier.dispose();
super.dispose();
}
Future<void> _restorePurchases() async {
_isRestoringPurchases.value = true;
_purchasesNotifier.isRestoringPurchases = true;
try {
final isPro = await IAPProductsProvider.of(context).restorePurchases();
if (mounted && isPro) {
@ -91,12 +122,12 @@ class _LightmeterProScreenState extends State<LightmeterProScreen> {
} catch (e) {
_showSnackbar(e.toString());
} finally {
_isRestoringPurchases.value = true;
_purchasesNotifier.isRestoringPurchases = false;
}
}
Future<void> _buyPro(IAPProduct product) async {
_isPurchasingProduct.value = true;
_purchasesNotifier.isPurchasingProduct = true;
try {
final isPro = await IAPProductsProvider.of(context).buyPro(product);
if (mounted && isPro) {
@ -107,7 +138,7 @@ class _LightmeterProScreenState extends State<LightmeterProScreen> {
} catch (e) {
_showSnackbar(e.toString());
} finally {
_isPurchasingProduct.value = false;
_purchasesNotifier.isPurchasingProduct = false;
}
}
@ -123,37 +154,6 @@ class _LightmeterProScreenState extends State<LightmeterProScreen> {
}
}
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,
);
}
},
);
}
}
class _FeaturesHeader extends StatelessWidget {
const _FeaturesHeader();
@ -292,3 +292,13 @@ class _CheckBox extends StatelessWidget {
);
}
}
extension on ValueNotifier<PurchasesState> {
set isPurchasingProduct(bool isPurchasingProduct) {
value = (isPurchasingProduct: isPurchasingProduct, isRestoringPurchases: value.isRestoringPurchases);
}
set isRestoringPurchases(bool isRestoringPurchases) {
value = (isPurchasingProduct: value.isPurchasingProduct, isRestoringPurchases: isRestoringPurchases);
}
}