Skip to content

Commit 9e52e4e

Browse files
committed
feat + refactor: добавление возможности восстановления покупок пользователем для устройств на ios
1 parent 502be15 commit 9e52e4e

16 files changed

Lines changed: 243 additions & 104 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import 'package:flutter/material.dart';
2+
import 'package:frontend/constants/text_styles.dart';
23

34
sealed class SharedFunctions {
45
static bool isTablet(BuildContext context) {
56
final shortestSide = MediaQuery.of(context).size.shortestSide;
67
return shortestSide >= 640;
78
}
9+
10+
static void showSnackBar(BuildContext context, String text) {
11+
ScaffoldMessenger.of(context).showSnackBar(
12+
SnackBar(
13+
backgroundColor: Colors.black,
14+
content: Text(
15+
text,
16+
style: TextStyles.caption2,
17+
),
18+
duration: const Duration(seconds: 2),
19+
),
20+
);
21+
}
822
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
abstract interface class IInAppPurchaseDataSource {
22
Future<bool> buyProduct(String productId);
33
Future<bool> checkStatus(String productId);
4+
Future<bool> restorePurchases();
45
}

frontend/lib/data_sources/revenuecat_data_source.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,14 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource {
1515
final customerInfo = await Purchases.getCustomerInfo();
1616
return customerInfo.entitlements.active.containsKey(productId);
1717
}
18+
19+
@override
20+
Future<bool> restorePurchases() async {
21+
try {
22+
await Purchases.restorePurchases();
23+
return true;
24+
} catch (_) {
25+
return false;
26+
}
27+
}
1828
}

frontend/lib/l10n/en.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,7 @@
105105
"hintsModal": "Display of hints\nfor possible moves",
106106
"description": "Description",
107107
"price": "1.99 $",
108-
"purchaseError": "Could not buy the product. Try again later"
108+
"purchaseError": "Could not buy the product. Try again later",
109+
"restorePurchases": "Restore Purchases",
110+
"restorePurchasesError": "Could not restore purchases. Try again later"
109111
}

frontend/lib/l10n/ru.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,7 @@
105105
"hintsModal": "Отображение подсказок\nвозможных ходов",
106106
"description": "Описание",
107107
"price": "199 ₽",
108-
"purchaseError": "Ошибка покупки товара. Попробуйте позже"
108+
"purchaseError": "Ошибка покупки товара. Попробуйте позже",
109+
"restorePurchases": "Восстановить покупки",
110+
"restorePurchasesError": "Ошибка восстановления покупок. Попробуйте позже"
109111
}

frontend/lib/providers/pro_version_provider.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class ProVersionProvider extends ChangeNotifier {
77

88
bool _isProStatus = false;
99
bool _isPurchaseError = false;
10+
bool _isRestoreError = false;
1011

1112
final InAppPurchaseRepository _repository;
1213

@@ -21,15 +22,23 @@ class ProVersionProvider extends ChangeNotifier {
2122
}
2223

2324
void _setProStatus(bool status) {
25+
if (_isProStatus == status) return;
2426
_isProStatus = status;
2527
notifyListeners();
2628
}
2729

2830
void _setPurchaseError(bool isError) {
31+
if (_isPurchaseError == isError) return;
2932
_isPurchaseError = isError;
3033
notifyListeners();
3134
}
3235

36+
void _setRestoreError(bool isRestoreError) {
37+
if (_isRestoreError == isRestoreError) return;
38+
_isRestoreError = isRestoreError;
39+
notifyListeners();
40+
}
41+
3342
Future<void> upgradeToPro() async {
3443
try {
3544
await _repository.buyProduct(_proVersionKey);
@@ -47,6 +56,18 @@ class ProVersionProvider extends ChangeNotifier {
4756
_setProStatus(false);
4857
}
4958

59+
Future<void> restorePurchases() async {
60+
final isRestoreCompleted = await _repository.restorePurchases();
61+
if (isRestoreCompleted) {
62+
_setRestoreError(false);
63+
} else {
64+
_setRestoreError(true);
65+
await Future.delayed(const Duration(milliseconds: 100));
66+
_setRestoreError(false);
67+
}
68+
}
69+
5070
bool get isPro => _isProStatus;
5171
bool get isPurchaseError => _isPurchaseError;
72+
bool get isRestoreError => _isRestoreError;
5273
}

frontend/lib/repositories/in_app_purchase_repository.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ class InAppPurchaseRepository {
1414
Future<bool> checkStatus(String productId) {
1515
return _dataSource.checkStatus(productId);
1616
}
17+
18+
Future<bool> restorePurchases() {
19+
return _dataSource.restorePurchases();
20+
}
1721
}

frontend/lib/views/components/components.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export "custom_icon_button.dart";
44
export "loading_widget.dart";
55
export "custom_tab_bar.dart";
66
export 'pro_functions_tooltip.dart';
7+
export 'custom_button.dart';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:frontend/constants/gradient_consts.dart';
3+
4+
class CustomButton extends StatelessWidget {
5+
const CustomButton({
6+
super.key,
7+
required this.onTap,
8+
required this.child,
9+
this.borderRadius = 16,
10+
this.gradient = GradientConsts.orange,
11+
});
12+
13+
final VoidCallback onTap;
14+
final Widget child;
15+
final double borderRadius;
16+
final Gradient gradient;
17+
18+
@override
19+
Widget build(BuildContext context) {
20+
return GestureDetector(
21+
onTap: onTap,
22+
child: DecoratedBox(
23+
decoration: BoxDecoration(
24+
borderRadius: BorderRadius.circular(borderRadius),
25+
gradient: gradient,
26+
),
27+
child: Center(child: child),
28+
),
29+
);
30+
}
31+
}

frontend/lib/views/menu_view/components/menu_app_bar.dart

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:frontend/common/shared_functions.dart';
23
import 'package:go_router/go_router.dart';
34
import 'package:provider/provider.dart';
45
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -21,16 +22,8 @@ class MenuAppBar extends StatelessWidget {
2122
final isPro = provider.isPro;
2223
if (provider.isPurchaseError) {
2324
WidgetsBinding.instance.addPostFrameCallback((_) {
24-
ScaffoldMessenger.of(context).showSnackBar(
25-
SnackBar(
26-
backgroundColor: Colors.black,
27-
content: Text(
28-
AppLocalizations.of(context).purchaseError,
29-
style: TextStyles.caption2,
30-
),
31-
duration: const Duration(seconds: 2),
32-
),
33-
);
25+
SharedFunctions.showSnackBar(
26+
context, AppLocalizations.of(context).purchaseError);
3427
});
3528
}
3629
return Row(

0 commit comments

Comments
 (0)