some fixes
This commit is contained in:
parent
67cfa49e2d
commit
d5052aff77
13 changed files with 239 additions and 62 deletions
|
|
@ -1,33 +1,43 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:label="Mnemo cards"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:label="Mnemo cards">
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||
android:value="ca-app-pub-3940256099942544~3347511713"/>
|
||||
android:value="ca-app-pub-3940256099942544~3347511713" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="mnemocards" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
|
|
@ -43,8 +53,8 @@
|
|||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:mnemo_cards/admin/admin_api.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:mnemo_cards_frontend_common/mnemo_cards_frontend_common.dart';
|
||||
|
|
@ -14,7 +10,6 @@ import 'text_param.dart';
|
|||
|
||||
class AddPlan {
|
||||
List<SubscriptionPlanAdminDto> allPlans = [];
|
||||
String? planId = null;
|
||||
List<SubscriptionFeatureEnum> selectedFeatures = [];
|
||||
late SubscriptionPlanAdminDto planDto;
|
||||
late AdminApi adminApi;
|
||||
|
|
@ -30,7 +25,6 @@ class AddPlan {
|
|||
}
|
||||
|
||||
void _init(SubscriptionPlanAdminDto? dto) {
|
||||
planId = dto?.id;
|
||||
planDto = dto ??
|
||||
const SubscriptionPlanAdminDto(
|
||||
ui: SubscriptionPlanUIDto(
|
||||
|
|
@ -43,6 +37,8 @@ class AddPlan {
|
|||
SubscriptionFeatureEnum.ads,
|
||||
SubscriptionFeatureEnum.packs,
|
||||
],
|
||||
paymentId: null,
|
||||
paymentSystem: PaymentSystem.unknown,
|
||||
);
|
||||
selectedFeatures = planDto.features.toList();
|
||||
}
|
||||
|
|
@ -122,6 +118,14 @@ class AddPlan {
|
|||
onPressed: _saveSubscriptionPlan,
|
||||
icon: Icon(Icons.save),
|
||||
),
|
||||
if (planDto.id != null)
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
planDto = planDto.copyWith(id: null);
|
||||
_saveSubscriptionPlan();
|
||||
},
|
||||
icon: Icon(Icons.copy),
|
||||
),
|
||||
if (planDto.id != null)
|
||||
GestureDetector(
|
||||
onLongPress: _deletePLan,
|
||||
|
|
@ -309,6 +313,56 @@ class AddPlan {
|
|||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Payment type '),
|
||||
Expanded(
|
||||
child: StatefulBuilder(
|
||||
builder: (context,
|
||||
setState) {
|
||||
return DropdownButton<
|
||||
PaymentSystem>(
|
||||
value: planDto
|
||||
.paymentSystem,
|
||||
items:
|
||||
PaymentSystem.values
|
||||
.map(
|
||||
(v) =>
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
v.name),
|
||||
value: v,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
planDto = planDto
|
||||
.copyWith(
|
||||
paymentSystem: v ??
|
||||
PaymentSystem
|
||||
.unknown,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextParam(
|
||||
'Payment id',
|
||||
planDto.paymentId,
|
||||
(v) {
|
||||
planDto = planDto.copyWith(
|
||||
paymentId: v,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class TextParam extends StatelessWidget {
|
|||
child: Row(
|
||||
children: [
|
||||
Text(title),
|
||||
SizedBox(width: 2,),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: TextEditingController(text: initial),
|
||||
|
|
|
|||
|
|
@ -41,12 +41,6 @@ Future<void> setInjections() async {
|
|||
);
|
||||
print('set subscription');
|
||||
|
||||
if (ADMIN_BUILD) {
|
||||
getIt.registerLazySingleton<AdminApi>(
|
||||
() => AdminApi(getIt.get<DioProvider>().dio));
|
||||
print('set admin');
|
||||
}
|
||||
|
||||
getIt.registerLazySingleton<PackHolder>(
|
||||
() => PackHolder(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -96,6 +96,25 @@ class Analytics {
|
|||
}.asFirebaseMap);
|
||||
}
|
||||
|
||||
static Future<void> buySubscriptionError(
|
||||
SubscriptionPlanDto? dto,
|
||||
PaymentSystem system, {
|
||||
Map<String, String?>? data,
|
||||
String? message,
|
||||
}) {
|
||||
return _analytics.logEvent(
|
||||
name: 'buy_sub_error',
|
||||
parameters: {
|
||||
'id': dto?.id.toString(),
|
||||
'system': system.name,
|
||||
'currency': dto?.currency,
|
||||
'price': dto?.price.toString(),
|
||||
if (message != null) 'message': message,
|
||||
if (data != null) ...data,
|
||||
}.asFirebaseMap,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> buyEvent(
|
||||
CardPackBuyDto? dto,
|
||||
PaymentSystem system, {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
|
@ -27,6 +28,36 @@ class InAppPurchaseService {
|
|||
|
||||
InAppPurchase get instance => _inAppPurchase;
|
||||
|
||||
Future<bool> buySubscription(SubscriptionPlanDto dto) async {
|
||||
if (dto.paymentSystem == PaymentSystem.yookassa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final subscriptionProducts = await _getSubscriptionStoreProduct(dto);
|
||||
ProductDetails? product;
|
||||
if (Platform.isAndroid && !RUSTORE_BUILD) {
|
||||
// final googlePlayProducts = subscriptionProducts.whereType<GooglePlayProductDetails>();
|
||||
product = subscriptionProducts.firstWhereOrNull(
|
||||
(p) => p.rawPrice == double.parse(dto.price),
|
||||
);
|
||||
} else {
|
||||
product = subscriptionProducts.first;
|
||||
}
|
||||
if (product == null) {
|
||||
Analytics.buySubscriptionError(
|
||||
dto,
|
||||
androidPaymentSystem,
|
||||
message: 'cant find plan with price ${dto.price}',
|
||||
data: {
|
||||
'plans': subscriptionProducts.join(';'),
|
||||
},
|
||||
);
|
||||
return false;
|
||||
}
|
||||
final result = await _buySubscriptionInStore(product);
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<bool> buyPack(CardPackBuyDto dto) async {
|
||||
try {
|
||||
if (RUSTORE_BUILD) {
|
||||
|
|
@ -73,11 +104,8 @@ class InAppPurchaseService {
|
|||
if (url.startsWith('http')) {
|
||||
return NavigationDecision.navigate;
|
||||
} else {
|
||||
if (await canLaunchUrlString(url)) {
|
||||
launchUrlString(url);
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
return NavigationDecision.navigate;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
@ -127,7 +155,7 @@ class InAppPurchaseService {
|
|||
return [];
|
||||
}
|
||||
final ids = {
|
||||
if (Platform.isAndroid) dto.googlePlayId,
|
||||
if (Platform.isAndroid) RUSTORE_BUILD ? dto.rustoreId : dto.googlePlayId,
|
||||
if (Platform.isIOS) dto.appStoreId,
|
||||
}.whereNotNull().toSet();
|
||||
if (ids.isEmpty) {
|
||||
|
|
@ -158,7 +186,60 @@ class InAppPurchaseService {
|
|||
return productDetailResponse.productDetails;
|
||||
}
|
||||
|
||||
Future<List<ProductDetails>> _getSubscriptionStoreProduct(
|
||||
SubscriptionPlanDto dto,
|
||||
) async {
|
||||
final bool isAvailable = await _inAppPurchase.isAvailable();
|
||||
if (!isAvailable) {
|
||||
Analytics.buySubscriptionError(
|
||||
dto,
|
||||
androidPaymentSystem,
|
||||
message: 'in app purchase not available',
|
||||
);
|
||||
return [];
|
||||
}
|
||||
final ids = {
|
||||
if (Platform.isAndroid)
|
||||
if (RUSTORE_BUILD && dto.paymentSystem == PaymentSystem.rustore)
|
||||
dto.paymentId,
|
||||
if (!RUSTORE_BUILD && dto.paymentSystem == PaymentSystem.google)
|
||||
dto.googlePlaySubscriptionId,
|
||||
if (dto.paymentSystem == PaymentSystem.yookassa) dto.paymentId,
|
||||
if (Platform.isIOS && dto.paymentSystem == PaymentSystem.apple)
|
||||
dto.paymentId,
|
||||
}.whereNotNull().toSet();
|
||||
if (ids.isEmpty) {
|
||||
Analytics.buySubscriptionError(
|
||||
dto,
|
||||
androidPaymentSystem,
|
||||
message: 'product id not set',
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
final productDetailResponse = await _inAppPurchase.queryProductDetails(ids);
|
||||
if (productDetailResponse.error != null ||
|
||||
productDetailResponse.productDetails.isEmpty) {
|
||||
Analytics.buySubscriptionError(
|
||||
dto,
|
||||
androidPaymentSystem,
|
||||
data: {
|
||||
'msg': 'cant get product details ${productDetailResponse.error}',
|
||||
'not_found': productDetailResponse.notFoundIDs.join(','),
|
||||
},
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return productDetailResponse.productDetails;
|
||||
}
|
||||
|
||||
Future<bool> _buyItemInStore(ProductDetails product) async {
|
||||
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
|
||||
return InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
|
||||
}
|
||||
|
||||
Future<bool> _buySubscriptionInStore(ProductDetails product) async {
|
||||
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
|
||||
return InAppPurchase.instance
|
||||
.buyNonConsumable(purchaseParam: purchaseParam);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
import 'package:mnemo_cards/admin/admin_api.dart';
|
||||
import 'package:mnemo_cards/di/injector.dart';
|
||||
import 'package:mnemo_cards/main.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:mnemo_cards_frontend_common/mnemo_cards_frontend_common.dart';
|
||||
|
||||
bool get ADMIN_BUILD => CommonPrefs.ADMIN_BUILD;
|
||||
import 'di/locator.dart';
|
||||
import 'managers/user_manager.dart';
|
||||
|
||||
bool get ADMIN_BUILD =>
|
||||
getIt.isRegistered<UserManager>() &&
|
||||
locator.userManager.user?.admin == true;
|
||||
const RUSTORE_BUILD = CommonPrefs.RUSTORE_BUILD;
|
||||
|
||||
final androidPaymentSystem = CommonPrefs.androidPaymentSystem;
|
||||
|
||||
bool get SHOW_ADMIN =>
|
||||
ADMIN_BUILD && (globalSharedPreferences.getBool('show_admin') ?? true);
|
||||
getIt.isRegistered<AdminApi>() &&
|
||||
ADMIN_BUILD &&
|
||||
(globalSharedPreferences.getBool('show_admin') ?? true);
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ void main() async {
|
|||
await locator.previewPackPoller.init();
|
||||
await locator.packUpdater.init();
|
||||
print('initing complete ${stopwatch.elapsedMilliseconds}');
|
||||
if (!kIsWeb) unawaited(MobileAds.initialize());
|
||||
if (!kIsWeb) unawaited(MobileAds.initialize());
|
||||
print('mobile ads ${stopwatch.elapsedMilliseconds}');
|
||||
print('runApp ${stopwatch.elapsedMilliseconds}');
|
||||
runApp(const MyApp());
|
||||
|
|
@ -152,15 +152,9 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
|||
darkTheme: darkTheme2,
|
||||
themeMode: themeMode,
|
||||
routerConfig: appRouter.config(
|
||||
deepLinkBuilder: (link) {
|
||||
// if(link.path.contains('/ru/reset-password')) {
|
||||
// return const DeepLink(
|
||||
// [ResetPassRoute()]
|
||||
// );
|
||||
// } else {
|
||||
return DeepLink.defaultPath;
|
||||
}
|
||||
// }
|
||||
deepLinkBuilder: (link) {
|
||||
return DeepLink.defaultPath;
|
||||
},
|
||||
),
|
||||
scaffoldMessengerKey: scaffoldMessengerKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
|
|
@ -209,7 +203,6 @@ class _MainTabsPageState extends State<MainTabsPage> {
|
|||
|
||||
_Tabs _currentTab = _Tabs.home;
|
||||
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class ProfilePage extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (SHOW_ADMIN)
|
||||
if (SHOW_ADMIN) ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
|
@ -100,7 +100,6 @@ class ProfilePage extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (SHOW_ADMIN)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
|
@ -126,6 +125,7 @@ class ProfilePage extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: StreamBuilder(
|
||||
stream: locator.userManager.userStateHolder.asStream,
|
||||
|
|
@ -156,22 +156,26 @@ class ProfilePage extends StatelessWidget {
|
|||
height: 5,
|
||||
color: borderGray.withOpacity(0.2),
|
||||
),
|
||||
const MobileBuySubscriptionWidget(),
|
||||
Divider(
|
||||
height: 5,
|
||||
color: borderGray.withOpacity(0.2),
|
||||
),
|
||||
if (SHOW_ADMIN)
|
||||
SimpleTile(title: Text('Посмотреть рекламу'), onTap: () {
|
||||
YandexAds.showRewardedAd((r) {
|
||||
showErrorDialog('Вы получили\n${r.amount} ${r.type}');
|
||||
});
|
||||
},),
|
||||
if (SHOW_ADMIN)
|
||||
Divider(
|
||||
height: 5,
|
||||
color: borderGray.withOpacity(0.2),
|
||||
),
|
||||
if (SHOW_ADMIN) ...[
|
||||
const MobileBuySubscriptionWidget(),
|
||||
Divider(
|
||||
height: 5,
|
||||
color: borderGray.withOpacity(0.2),
|
||||
),
|
||||
SimpleTile(
|
||||
title: Text('Посмотреть рекламу'),
|
||||
onTap: () {
|
||||
YandexAds.showRewardedAd((r) {
|
||||
showErrorDialog(
|
||||
'Вы получили\n${r.amount} ${r.type}');
|
||||
});
|
||||
},
|
||||
),
|
||||
Divider(
|
||||
height: 5,
|
||||
color: borderGray.withOpacity(0.2),
|
||||
),
|
||||
],
|
||||
SimpleTile.text(
|
||||
text: 'Написать в телеграм',
|
||||
onTap: () => launchUrl(
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@ import 'package:mnemo_cards_frontend_common/mnemo_cards_frontend_common.dart';
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../admin/admin_api.dart';
|
||||
import '../di/injector.dart';
|
||||
import '../di/locator.dart';
|
||||
import '../features/analytics/analytics.dart';
|
||||
import '../managers/repository/dio_provider.dart';
|
||||
import '../theme/themes.dart';
|
||||
import '../widgets/shared_pref_button.dart';
|
||||
|
||||
|
|
@ -125,7 +128,15 @@ class SettingsPage extends StatelessWidget {
|
|||
value: enabled ?? false,
|
||||
),
|
||||
),
|
||||
setOnTap: (v) => !(v ?? false),
|
||||
setOnTap: (v) {
|
||||
final val = !(v ?? false);
|
||||
if (val && !getIt.isRegistered<AdminApi>()) {
|
||||
getIt.registerLazySingleton<AdminApi>(
|
||||
() => AdminApi(getIt.get<DioProvider>().dio));
|
||||
print('set admin');
|
||||
}
|
||||
return val;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class _SubscriptionPlanWidget extends StatelessWidget {
|
|||
height: (90 + (plan.tip == null ? 0 : 13)).h,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
|
||||
locator.purchaseService.buySubscription(plan);
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
|
|
|
|||
|
|
@ -729,7 +729,7 @@ packages:
|
|||
source: hosted
|
||||
version: "3.2.0"
|
||||
in_app_purchase_android:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: in_app_purchase_android
|
||||
sha256: f3f3ded26d08d13383b8c9952d1cf1422f0a3440b04d27f9838adee86f2ce899
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ dependencies:
|
|||
firebase_crashlytics: ^3.5.7
|
||||
firebase_analytics: ^10.10.7
|
||||
in_app_purchase: ^3.2.0
|
||||
in_app_purchase_android:
|
||||
dot_navigation_bar: ^1.0.2
|
||||
reorderables: ^0.6.0
|
||||
story: ^1.1.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue