some fixes

This commit is contained in:
Dmitry 2024-08-07 00:33:11 +03:00
parent 67cfa49e2d
commit d5052aff77
13 changed files with 239 additions and 62 deletions

View file

@ -1,21 +1,23 @@
<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" />
<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
@ -23,12 +25,20 @@
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:resource="@style/NormalTheme" />
<intent-filter>
<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.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

View file

@ -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,
);
},
),
],
),
],

View file

@ -15,6 +15,7 @@ class TextParam extends StatelessWidget {
child: Row(
children: [
Text(title),
SizedBox(width: 2,),
Expanded(
child: TextField(
controller: TextEditingController(text: initial),

View file

@ -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(),
);

View file

@ -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, {

View file

@ -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,12 +104,9 @@ 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);

View file

@ -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);

View file

@ -153,14 +153,8 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver {
themeMode: themeMode,
routerConfig: appRouter.config(
deepLinkBuilder: (link) {
// if(link.path.contains('/ru/reset-password')) {
// return const DeepLink(
// [ResetPassRoute()]
// );
// } else {
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();

View file

@ -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),
),
if (SHOW_ADMIN) ...[
const MobileBuySubscriptionWidget(),
Divider(
height: 5,
color: borderGray.withOpacity(0.2),
),
if (SHOW_ADMIN)
SimpleTile(title: Text('Посмотреть рекламу'), onTap: () {
SimpleTile(
title: Text('Посмотреть рекламу'),
onTap: () {
YandexAds.showRewardedAd((r) {
showErrorDialog('Вы получили\n${r.amount} ${r.type}');
showErrorDialog(
'Вы получили\n${r.amount} ${r.type}');
});
},),
if (SHOW_ADMIN)
},
),
Divider(
height: 5,
color: borderGray.withOpacity(0.2),
),
],
SimpleTile.text(
text: 'Написать в телеграм',
onTap: () => launchUrl(

View file

@ -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;
},
),
],
),

View file

@ -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,

View file

@ -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

View file

@ -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