sub
This commit is contained in:
parent
5a37539554
commit
35725aeaa1
25 changed files with 800 additions and 280 deletions
|
|
@ -11,6 +11,7 @@ import 'package:mnemo_cards_frontend_common/mnemo_cards_frontend_common.dart';
|
|||
import '../utils/string_helper.dart';
|
||||
|
||||
import '../main.dart';
|
||||
import 'text_param.dart';
|
||||
|
||||
class AddPackage with Api {
|
||||
EditCardPackDto cardPackDto;
|
||||
|
|
@ -96,26 +97,26 @@ class AddPackage with Api {
|
|||
],
|
||||
),
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Title',
|
||||
cardPackDto.title,
|
||||
(v) {
|
||||
cardPackDto = cardPackDto.copyWith.title(v);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Subtitle',
|
||||
cardPackDto.subtitle,
|
||||
(v) {
|
||||
cardPackDto = cardPackDto.copyWith.subtitle(v);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Description',
|
||||
cardPackDto.description,
|
||||
(v) => cardPackDto = cardPackDto.copyWith.description(v),
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Size',
|
||||
cardPackDto.size?.toString(),
|
||||
(v) {
|
||||
|
|
@ -124,28 +125,28 @@ class AddPackage with Api {
|
|||
);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Google play id',
|
||||
cardPackDto.googlePlayId,
|
||||
(v) {
|
||||
cardPackDto = cardPackDto.copyWith.googlePlayId(v);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Rustore id',
|
||||
cardPackDto.rustoreId,
|
||||
(v) {
|
||||
cardPackDto = cardPackDto.copyWith.rustoreId(v);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Appstore id',
|
||||
cardPackDto.appStoreId,
|
||||
(v) {
|
||||
cardPackDto = cardPackDto.copyWith.appStoreId(v);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Price',
|
||||
cardPackDto.price,
|
||||
(v) {
|
||||
|
|
@ -254,7 +255,7 @@ class AddPackage with Api {
|
|||
),
|
||||
);
|
||||
}),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Preview cards',
|
||||
cardPackDto.previewCards?.toString(),
|
||||
(v) {
|
||||
|
|
@ -265,7 +266,7 @@ class AddPackage with Api {
|
|||
cardPackDto = cardPackDto.copyWith.previewCards(cards);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Order (lower is higher)',
|
||||
cardPackDto.order?.toString(),
|
||||
(v) {
|
||||
|
|
@ -274,7 +275,7 @@ class AddPackage with Api {
|
|||
);
|
||||
},
|
||||
),
|
||||
_TextParam(
|
||||
TextParam(
|
||||
'Cards order (ids)',
|
||||
cardPackDto.cardsOrder?.join(','),
|
||||
(v) {
|
||||
|
|
@ -295,28 +296,3 @@ class AddPackage with Api {
|
|||
}
|
||||
}
|
||||
|
||||
class _TextParam extends StatelessWidget {
|
||||
final String title;
|
||||
final String? initial;
|
||||
final Function(String v) onChanged;
|
||||
|
||||
_TextParam(this.title, this.initial, this.onChanged);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(title),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: TextEditingController(text: initial),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
335
lib/admin/add_plan.dart
Normal file
335
lib/admin/add_plan.dart
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
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';
|
||||
|
||||
import '../di/injector.dart';
|
||||
import '../main.dart';
|
||||
import 'text_param.dart';
|
||||
|
||||
class AddPlan {
|
||||
List<SubscriptionPlanAdminDto> allPlans = [];
|
||||
String? planId = null;
|
||||
List<SubscriptionFeatureEnum> selectedFeatures = [];
|
||||
late SubscriptionPlanAdminDto planDto;
|
||||
late AdminApi adminApi;
|
||||
|
||||
final availableFeatures = [
|
||||
SubscriptionFeatureEnum.ads,
|
||||
SubscriptionFeatureEnum.packs,
|
||||
];
|
||||
|
||||
AddPlan() {
|
||||
this.adminApi = getIt.get<AdminApi>();
|
||||
_init(null);
|
||||
}
|
||||
|
||||
void _init(SubscriptionPlanAdminDto? dto) {
|
||||
planId = dto?.id;
|
||||
planDto = dto ??
|
||||
const SubscriptionPlanAdminDto(
|
||||
ui: SubscriptionPlanUIDto(
|
||||
currency: 'руб',
|
||||
),
|
||||
price: '123',
|
||||
currency: 'руб',
|
||||
durationDays: 0,
|
||||
features: [
|
||||
SubscriptionFeatureEnum.ads,
|
||||
SubscriptionFeatureEnum.packs,
|
||||
],
|
||||
);
|
||||
selectedFeatures = planDto.features.toList();
|
||||
}
|
||||
|
||||
Future<bool> _loadPlans() async {
|
||||
final result = await adminApi.getSubscriptionPlans();
|
||||
allPlans = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
void _saveSubscriptionPlan() async {
|
||||
final result = await adminApi.addSubscriptionPlan(
|
||||
planDto.copyWith(
|
||||
features: selectedFeatures,
|
||||
),
|
||||
);
|
||||
if (!result) {
|
||||
ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(SnackBar(
|
||||
content: Text('ERROR'),
|
||||
));
|
||||
} else {
|
||||
ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(SnackBar(
|
||||
content: Text('Success!!!'),
|
||||
));
|
||||
appRouter.maybePop();
|
||||
}
|
||||
}
|
||||
|
||||
void _deletePLan() async {
|
||||
if (planDto.id == null) return;
|
||||
final result = await adminApi.deleteSubscriptionPlan(planDto.id.toString());
|
||||
if (!result) {
|
||||
ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(SnackBar(
|
||||
content: Text('ERROR'),
|
||||
));
|
||||
} else {
|
||||
ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(SnackBar(
|
||||
content: Text('Success!!!'),
|
||||
));
|
||||
appRouter.maybePop();
|
||||
}
|
||||
}
|
||||
|
||||
void addPlan(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (c) {
|
||||
return FutureBuilder(
|
||||
future: _loadPlans(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data != true) {
|
||||
return Center(
|
||||
child: Material(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Center(
|
||||
child: Scaffold(
|
||||
body: Material(
|
||||
child: StatefulBuilder(builder: (context, setPlan) {
|
||||
return ListView(
|
||||
children: [
|
||||
Header(
|
||||
'${planDto.id}',
|
||||
hasPopButton: true,
|
||||
trail: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await _loadPlans();
|
||||
setPlan(() {});
|
||||
},
|
||||
icon: Icon(Icons.sync),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _saveSubscriptionPlan,
|
||||
icon: Icon(Icons.save),
|
||||
),
|
||||
if (planDto.id != null)
|
||||
GestureDetector(
|
||||
onLongPress: _deletePLan,
|
||||
child: Icon(Icons.delete),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenu<String?>(
|
||||
initialSelection: null,
|
||||
onSelected: (String? value) {
|
||||
setPlan(() {
|
||||
_init(
|
||||
allPlans
|
||||
.firstWhereOrNull((v) => v.id == value),
|
||||
);
|
||||
});
|
||||
},
|
||||
dropdownMenuEntries: [
|
||||
...allPlans,
|
||||
null,
|
||||
].map((plan) {
|
||||
return DropdownMenuEntry(
|
||||
value: plan?.id,
|
||||
label: plan == null
|
||||
? 'New'
|
||||
: '${plan.ui.titleLead ?? ''} ${plan.ui.title ?? ''}',
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
StatefulBuilder(builder: (context, setState) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
TextParam(
|
||||
'Currency',
|
||||
planDto.currency,
|
||||
(v) {
|
||||
planDto = planDto.copyWith(
|
||||
currency: v);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'Price',
|
||||
planDto.price,
|
||||
(v) {
|
||||
planDto =
|
||||
planDto.copyWith(price: v);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'Days',
|
||||
planDto.durationDays.toString(),
|
||||
(v) {
|
||||
final days = int.parse(v);
|
||||
planDto = planDto.copyWith(
|
||||
durationDays: days,
|
||||
);
|
||||
},
|
||||
),
|
||||
Text('Features:'),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount:
|
||||
availableFeatures.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item =
|
||||
availableFeatures[index];
|
||||
return ListTile(
|
||||
title: Text(item.name),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selectedFeatures
|
||||
.contains(item)) {
|
||||
selectedFeatures
|
||||
.remove(item);
|
||||
} else {
|
||||
selectedFeatures
|
||||
.add(item);
|
||||
}
|
||||
});
|
||||
},
|
||||
selected: selectedFeatures
|
||||
.contains(item),
|
||||
selectedTileColor: Colors.blue
|
||||
.withOpacity(0.5),
|
||||
);
|
||||
},
|
||||
),
|
||||
Text('UI:'),
|
||||
TextParam(
|
||||
'UI Currency',
|
||||
planDto.ui.currency,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
currency: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Title lead',
|
||||
planDto.ui.titleLead,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
titleLead: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Title',
|
||||
planDto.ui.title,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
title: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Price per month',
|
||||
planDto.ui.pricePerMonth,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
pricePerMonth: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Subtitle crossed',
|
||||
planDto.ui.subtitleCrossed,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
subtitleCrossed: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Subtitle',
|
||||
planDto.ui.subtitle,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
subtitle: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Tip',
|
||||
planDto.ui.tip,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
tip: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
TextParam(
|
||||
'UI Tip color',
|
||||
planDto.ui.tipColor,
|
||||
(v) {
|
||||
final ui = planDto.ui.copyWith(
|
||||
tipColor: v,
|
||||
);
|
||||
planDto =
|
||||
planDto.copyWith.ui(ui);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget uiEditor() {
|
||||
return Column(
|
||||
children: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -111,4 +111,29 @@ class AdminApi with Api {
|
|||
|
||||
Future<List<CardPackPreviewDto>> packs() =>
|
||||
getIt.get<PacksApi>().getPacksPreviews(null);
|
||||
|
||||
Future<List<SubscriptionPlanAdminDto>> getSubscriptionPlans() async {
|
||||
final r = await _dio.get<String>(
|
||||
'$path/subscriptions',
|
||||
);
|
||||
return (jsonDecode(r.data!)['plans'] as List)
|
||||
.cast<Map<String, Object?>>()
|
||||
.map(SubscriptionPlanAdminDto.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<bool> addSubscriptionPlan(SubscriptionPlanAdminDto dto) async {
|
||||
final r = await _dio.post<String>(
|
||||
'$path/subscription/add',
|
||||
data: jsonEncode(dto.toJson()),
|
||||
);
|
||||
return r.statusCode == 200;
|
||||
}
|
||||
|
||||
Future<bool> deleteSubscriptionPlan(String id) async {
|
||||
final r = await _dio.post<String>(
|
||||
'$path/subscription/delete/$id',
|
||||
);
|
||||
return r.statusCode == 200;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
lib/admin/text_param.dart
Normal file
28
lib/admin/text_param.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class TextParam extends StatelessWidget {
|
||||
final String title;
|
||||
final String? initial;
|
||||
final Function(String v) onChanged;
|
||||
|
||||
TextParam(this.title, this.initial, this.onChanged);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(title),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: TextEditingController(text: initial),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart';
|
|||
import 'package:mnemo_cards/admin/admin_api.dart';
|
||||
import 'package:mnemo_cards/features/packs/pack_cache_manager.dart';
|
||||
import 'package:mnemo_cards/features/packs/packs_api.dart';
|
||||
import 'package:mnemo_cards/features/subscription/subscription_api.dart';
|
||||
import 'package:mnemo_cards/flags.dart';
|
||||
import 'package:mnemo_cards/managers/favorite_cards.dart';
|
||||
import 'package:mnemo_cards/managers/repository/dio_provider.dart';
|
||||
|
|
@ -35,6 +36,11 @@ Future<void> setInjections() async {
|
|||
);
|
||||
print('set pack');
|
||||
|
||||
getIt.registerLazySingleton<SubscriptionApi>(
|
||||
() => SubscriptionApi(getIt.get<DioProvider>().dio),
|
||||
);
|
||||
print('set subscription');
|
||||
|
||||
if (ADMIN_BUILD) {
|
||||
getIt.registerLazySingleton<AdminApi>(
|
||||
() => AdminApi(getIt.get<DioProvider>().dio));
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import '../features/packs/pack_cache_manager.dart';
|
|||
import '../features/packs/pack_updater.dart';
|
||||
import '../features/purchase/in_app_purchase.dart';
|
||||
import '../features/packs/pack_manager.dart';
|
||||
import '../features/subscription/subscription_api.dart';
|
||||
import '../features/tests/test_manager.dart';
|
||||
import '../managers/user_manager.dart';
|
||||
import 'injector.dart';
|
||||
|
|
@ -26,6 +27,8 @@ class Locator {
|
|||
|
||||
UserManager get userManager => _getIt.get<UserManager>();
|
||||
|
||||
SubscriptionApi get subscriptionApi => _getIt.get<SubscriptionApi>();
|
||||
|
||||
FavoriteCardsController get favoriteCardsController =>
|
||||
_getIt.get<FavoriteCardsController>();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +1,73 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/theme/themes.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
|
||||
showErrorDialog(String message) {
|
||||
return showDialog(
|
||||
context: scaffoldKey.currentContext!,
|
||||
builder: (context) {
|
||||
return Center(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(4.0.h),
|
||||
padding: EdgeInsets.all(4.0.h),
|
||||
height: 200.h,
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
decoration: BoxDecoration(
|
||||
color: white,
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
Future<void> showErrorDialog(String message, {Completer? completer}) {
|
||||
final context = scaffoldKey.currentContext!;
|
||||
completer ??= Completer();
|
||||
final result = showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (context) {
|
||||
return Center(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(4.0.h),
|
||||
padding: EdgeInsets.all(4.0.h),
|
||||
height: 200.h,
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
decoration: BoxDecoration(
|
||||
color: white,
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
width: 1,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: Container(
|
||||
height: 40.h,
|
||||
decoration: BoxDecoration(
|
||||
color: testBlue,
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: Container(
|
||||
height: 40.h,
|
||||
decoration: BoxDecoration(
|
||||
color: testBlue,
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
width: 1,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text('Оки'),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text('Оки'),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
bool dialogClosed = false;
|
||||
completer.future.then((_) {
|
||||
if (!dialogClosed) {
|
||||
return Navigator.of(context).pop();
|
||||
}
|
||||
});
|
||||
return result.then((_) => dialogClosed = true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class PackCacheManager {
|
|||
await file.writeAsString(jsonEncode(json));
|
||||
}
|
||||
} catch (e, s) {
|
||||
log('Cant marck pack for update', error: e, stackTrace: s);
|
||||
log('Cant mark pack for update', error: e, stackTrace: s);
|
||||
Analytics.crashlyticsError(
|
||||
e: e,
|
||||
s: s,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import 'dart:async';
|
|||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive_io.dart' as a;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:mnemo_cards/features/packs/packs_api.dart';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import '../../di/locator.dart';
|
||||
import '../../features/packs/pack_cache_manager.dart';
|
||||
import '../../features/packs/pack_holder.dart';
|
||||
import '../analytics/analytics.dart';
|
||||
import 'pack_cache_manager.dart';
|
||||
import 'pack_holder.dart';
|
||||
import 'packs_api.dart';
|
||||
import 'preview_pack_holder.dart';
|
||||
|
||||
/// Updates cache and state holders for available packs
|
||||
|
|
@ -33,15 +33,19 @@ class PackUpdater {
|
|||
final pollStream = Stream.periodic(const Duration(seconds: 600));
|
||||
pollStream.listen((event) async {
|
||||
await updateAvailablePacks();
|
||||
try {
|
||||
final r = await _packsApi.getPacksPreviews(null);
|
||||
_previewPackHolder.setPreviewPacks(r, save: true);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
await _updatePreviewPacks();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _updatePreviewPacks() async {
|
||||
try {
|
||||
final r = await _packsApi.getPacksPreviews(null);
|
||||
_previewPackHolder.setPreviewPacks(r, save: true);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateAvailablePacks() async {
|
||||
if (_updateCompleter?.isCompleted ?? true) {
|
||||
_updateCompleter = Completer();
|
||||
|
|
@ -106,8 +110,10 @@ class PackUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
Future<CardPackDto> updatePackAndSaveImages(String packId,
|
||||
{bool shouldUpdateHolder = false}) async {
|
||||
Future<CardPackDto> updatePackAndSaveImages(
|
||||
String packId, {
|
||||
bool shouldUpdateHolder = false,
|
||||
}) async {
|
||||
try {
|
||||
log('Updating with cards ${packId}', name: 'updatePackWithCards');
|
||||
final s = Stopwatch()..start();
|
||||
|
|
@ -118,14 +124,13 @@ class PackUpdater {
|
|||
}
|
||||
final imagesData = await _packsApi.packImagesArchive(packId);
|
||||
print('get cards from api ${s.elapsedMilliseconds} mills');
|
||||
final userId = locator.userManager.userId!;
|
||||
final appVersion = (await PackageInfo.fromPlatform()).version;
|
||||
final password = TokenGenerator.generateArchivePassword(
|
||||
appVersion: appVersion,
|
||||
packId: packId,
|
||||
);
|
||||
print('generate token ${s.elapsedMilliseconds} mills');
|
||||
final imagesArchive = a.ZipDecoder().decodeBytes(
|
||||
final imagesArchive = ZipDecoder().decodeBytes(
|
||||
imagesData,
|
||||
password: password,
|
||||
);
|
||||
|
|
@ -133,7 +138,7 @@ class PackUpdater {
|
|||
Map<String, MemoryImage> images = {};
|
||||
|
||||
// todo optimize, make really async
|
||||
Future<void> setImage(a.ArchiveFile file) async {
|
||||
Future<void> setImage(ArchiveFile file) async {
|
||||
final fileData = file.content as List<int>;
|
||||
final data = Uint8List.fromList(fileData);
|
||||
images[file.name] = MemoryImage(data);
|
||||
|
|
@ -143,7 +148,6 @@ class PackUpdater {
|
|||
for (final file in imagesArchive) setImage(file),
|
||||
]);
|
||||
|
||||
|
||||
print('loaded images ${s.elapsedMilliseconds} mills');
|
||||
_cacheManager.savePack(packDto, images: images);
|
||||
print('saved pack ${s.elapsedMilliseconds} mills');
|
||||
|
|
@ -156,8 +160,12 @@ class PackUpdater {
|
|||
}
|
||||
return packDto;
|
||||
} catch (e, s) {
|
||||
log('Cant update pack', name: 'updatePackWithCards');
|
||||
log(e.toString(), stackTrace: s);
|
||||
log('Cant update pack', error: e, stackTrace: s);
|
||||
Analytics.crashlyticsError(
|
||||
message: 'Cant update pack',
|
||||
e: e,
|
||||
s: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,9 +185,9 @@ class PurchaseDetailsStreamSubscription {
|
|||
},
|
||||
);
|
||||
final result = await locator.repository.checkPayment(
|
||||
purchaseDetails.productID,
|
||||
purchaseDetails.verificationData.serverVerificationData,
|
||||
androidPaymentSystem,
|
||||
itemId: purchaseDetails.productID,
|
||||
key: purchaseDetails.verificationData.serverVerificationData,
|
||||
paymentSystem: androidPaymentSystem,
|
||||
);
|
||||
if (result) {
|
||||
Analytics.buyComplete(
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ class RustorePurchaseService {
|
|||
// creating payment to check it later
|
||||
// in case there will be an error after confirmation
|
||||
await locator.repository.checkPayment(
|
||||
result.successPurchase!.productId,
|
||||
result.successPurchase!.subscriptionToken!,
|
||||
androidPaymentSystem,
|
||||
itemId: result.successPurchase!.productId,
|
||||
key: result.successPurchase!.subscriptionToken!,
|
||||
paymentSystem: androidPaymentSystem,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Analytics.buyError(
|
||||
|
|
@ -70,13 +70,13 @@ class RustorePurchaseService {
|
|||
return false;
|
||||
}
|
||||
final p = await RustoreBillingClient.confirm(
|
||||
result.successPurchase!.purchaseId,
|
||||
result.successPurchase!.subscriptionToken!,
|
||||
);
|
||||
if (p.success) {
|
||||
final r = await locator.repository.checkPayment(
|
||||
result.successPurchase!.productId,
|
||||
result.successPurchase!.subscriptionToken!,
|
||||
androidPaymentSystem,
|
||||
itemId: result.successPurchase!.productId,
|
||||
key: result.successPurchase!.subscriptionToken!,
|
||||
paymentSystem: androidPaymentSystem,
|
||||
);
|
||||
if (r) {
|
||||
return r;
|
||||
|
|
|
|||
|
|
@ -10,16 +10,15 @@ class SubscriptionApi with Api {
|
|||
|
||||
SubscriptionApi(this._dio);
|
||||
|
||||
// buy page dto if not available
|
||||
Future<CardPackBuyDto?> getPackBuy(String id) async {
|
||||
Future<SubscriptionDto?> getBuySubscription() async {
|
||||
final r = await _dio.get<String>(
|
||||
'$path/pack/buy/$id',
|
||||
'$path/subscription/page',
|
||||
options: Options(
|
||||
sendTimeout: Duration(seconds: 5),
|
||||
receiveTimeout: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
return (r.data as String).decode(CardPackBuyDto.fromJson);
|
||||
return (r.data as String).decode(SubscriptionDto.fromJson);
|
||||
}
|
||||
|
||||
/// cards zip archive for available pack
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mnemo_cards/domain/router/dialogs.dart';
|
||||
import 'package:yandex_mobileads/mobile_ads.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
|
||||
class YandexAds {
|
||||
static BannerAd createBanner(
|
||||
BoxConstraints constraints, {
|
||||
|
|
@ -42,6 +46,7 @@ class YandexAds {
|
|||
}
|
||||
|
||||
static RewardedAd? _preloadedAd;
|
||||
static Completer? _showingAdCompleter;
|
||||
|
||||
static Future<RewardedAd?> loadRewardedAd() async {
|
||||
final completer = Completer<RewardedAd?>();
|
||||
|
|
@ -66,30 +71,58 @@ class YandexAds {
|
|||
Function(Reward reward) onRewarded, {
|
||||
maxAttempts = 2,
|
||||
}) async {
|
||||
if (_preloadedAd == null) {
|
||||
final _loadedAd = await loadRewardedAd();
|
||||
if (_loadedAd == null) {
|
||||
return false;
|
||||
}
|
||||
if (_showingAdCompleter?.isCompleted == false) {
|
||||
return false;
|
||||
}
|
||||
final _ad = _preloadedAd;
|
||||
_preloadedAd = null;
|
||||
_ad?.setAdEventListener(
|
||||
eventListener: RewardedAdEventListener(onAdDismissed: () {
|
||||
_ad.destroy();
|
||||
}, onAdFailedToShow: (e) {
|
||||
_ad.destroy();
|
||||
if (maxAttempts-- > 0) {
|
||||
showRewardedAd(onRewarded, maxAttempts: maxAttempts);
|
||||
try {
|
||||
_showingAdCompleter = Completer();
|
||||
if (_preloadedAd == null) {
|
||||
// final dialogCompleter = Completer();
|
||||
// final dialogFuture =
|
||||
// showErrorDialog('Загрузка...', completer: dialogCompleter);
|
||||
try {
|
||||
final _loadedAd = await loadRewardedAd().timeout(
|
||||
Duration(seconds: 5),
|
||||
);
|
||||
if (_loadedAd == null) {
|
||||
return false;
|
||||
}
|
||||
} finally {}
|
||||
// dialogCompleter.complete();
|
||||
// await dialogFuture;
|
||||
// final frameCompleter = Completer();
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// frameCompleter.complete();
|
||||
// });
|
||||
// await frameCompleter;
|
||||
}
|
||||
}, onAdShown: () {
|
||||
// _ad.destroy();
|
||||
}, onRewarded: (r) {
|
||||
onRewarded(r);
|
||||
_ad.destroy();
|
||||
}));
|
||||
await _ad?.show();
|
||||
final reward = await _ad?.waitForDismiss();
|
||||
return reward != null;
|
||||
final _ad = _preloadedAd;
|
||||
_preloadedAd = null;
|
||||
await _ad?.setAdEventListener(
|
||||
eventListener: RewardedAdEventListener(
|
||||
onAdDismissed: () {
|
||||
_ad.destroy();
|
||||
},
|
||||
onAdFailedToShow: (e) {
|
||||
_ad.destroy();
|
||||
if (maxAttempts-- > 0) {
|
||||
showRewardedAd(onRewarded, maxAttempts: maxAttempts);
|
||||
}
|
||||
},
|
||||
onAdShown: () {
|
||||
// _ad.destroy();
|
||||
},
|
||||
onRewarded: (r) {
|
||||
onRewarded(r);
|
||||
_ad.destroy();
|
||||
},
|
||||
),
|
||||
);
|
||||
await _ad?.show();
|
||||
final reward = await _ad?.waitForDismiss();
|
||||
return reward != null;
|
||||
} finally {
|
||||
_showingAdCompleter?.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ mixin Api {
|
|||
? 'https://89.19.210.178:8080' //FirebaseRepository.appConfig.path
|
||||
: androidDeviceInfo!.isPhysicalDevice
|
||||
//device to localhost
|
||||
? 'https://192.168.31.158:8080'
|
||||
? 'https://192.168.31.159:8080'
|
||||
: //emulator to localhost
|
||||
'https://10.0.2.2:8080';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,11 +119,11 @@ class HttpRepository extends Repository with Api {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool> checkPayment(
|
||||
String itemId,
|
||||
String key,
|
||||
PaymentSystem paymentSystem,
|
||||
) async {
|
||||
Future<bool> checkPayment({
|
||||
required String itemId,
|
||||
required String key,
|
||||
required PaymentSystem paymentSystem,
|
||||
}) async {
|
||||
final r = await _dio.get<String>(
|
||||
'$path/check_payment/',
|
||||
queryParameters: {
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ abstract class Repository {
|
|||
|
||||
Future<TestDto?> getTest(String id);
|
||||
|
||||
Future<bool> checkPayment(
|
||||
String itemId,
|
||||
String key,
|
||||
PaymentSystem PaymentSystem,
|
||||
);
|
||||
Future<bool> checkPayment({
|
||||
required String itemId,
|
||||
required String key,
|
||||
required PaymentSystem paymentSystem,
|
||||
});
|
||||
|
||||
Future<bool> checkUserPayment();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class UserManager {
|
|||
|
||||
UserManager(this._repository) : userStateHolder = UserStateHolder();
|
||||
|
||||
bool get hasUser => userStateHolder.user != null;
|
||||
bool get hasUser => user != null;
|
||||
|
||||
UserDto? get user => userStateHolder.user;
|
||||
|
||||
int? get userId => userStateHolder.user?.id;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/admin/add_plan.dart';
|
||||
import 'package:mnemo_cards/domain/router/app_router.dart';
|
||||
import 'package:mnemo_cards/domain/router/app_router.gr.dart';
|
||||
import 'package:mnemo_cards/features/yandex_ads/yandex_ads.dart';
|
||||
|
|
@ -18,6 +19,7 @@ import '../di/locator.dart';
|
|||
import '../domain/router/dialogs.dart';
|
||||
import '../features/analytics/analytics.dart';
|
||||
import '../theme/themes.dart';
|
||||
import '../widgets/buy_subscription_widget.dart';
|
||||
import '../widgets/enter_promo_code_widget.dart';
|
||||
import '../widgets/shared_pref_button.dart';
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ class ProfilePage extends StatelessWidget {
|
|||
context: context,
|
||||
builder: (c) => const AllCards());
|
||||
},
|
||||
child: Text('ALL CARDS'),
|
||||
child: Text('Cards'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
|
@ -85,7 +87,15 @@ class ProfilePage extends StatelessWidget {
|
|||
context: context,
|
||||
builder: (c) => const AllUsers());
|
||||
},
|
||||
child: Text('ALL USERS'),
|
||||
child: Text('Users'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: MaterialButton(
|
||||
onPressed: () {
|
||||
AddPlan().addPlan(context);
|
||||
},
|
||||
child: Text('Plans'),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -146,6 +156,11 @@ 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) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/main.dart';
|
||||
import 'package:mnemo_cards/utils/string_helper.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:mnemo_cards_frontend_common/mnemo_cards_frontend_common.dart';
|
||||
|
||||
import '../di/injector.dart';
|
||||
import '../di/locator.dart';
|
||||
import '../domain/router/app_router.dart';
|
||||
import '../features/packs/packs_api.dart';
|
||||
import '../theme/themes.dart';
|
||||
|
||||
class MobileBuySubscriptionWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BuySubscriptionWidget(onBuySubscription: onBuySubscription);
|
||||
|
|
@ -20,12 +26,188 @@ class MobileBuySubscriptionWidget extends StatelessWidget {
|
|||
AppRouter.openAuthOrProfile();
|
||||
return;
|
||||
}
|
||||
if (user.subscription) {
|
||||
return;
|
||||
final api = locator.subscriptionApi;
|
||||
final buyDto = await api.getBuySubscription().catchError((_) => null);
|
||||
if (buyDto != null) {
|
||||
showDialog(
|
||||
context: scaffoldKey.currentContext!,
|
||||
builder: (_) => SubscriptionPage(buyDto.page!),
|
||||
);
|
||||
}
|
||||
final packsApi = getIt.get<PacksApi>();
|
||||
// final buyDto = await packsApi.getPackBuy(id).catchError((_) => null);
|
||||
}
|
||||
|
||||
const MobileBuySubscriptionWidget();
|
||||
}
|
||||
|
||||
class SubscriptionPage extends StatelessWidget {
|
||||
final SubscriptionPageDto dto;
|
||||
|
||||
SubscriptionPage(this.dto);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Header(dto.title, hasPopButton: true, center: true),
|
||||
...dto.items.build(),
|
||||
...dto.plans.map(_SubscriptionPlanWidget.new),
|
||||
BigBackButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SubscriptionPlanWidget extends StatelessWidget {
|
||||
final SubscriptionPlanDto plan;
|
||||
|
||||
_SubscriptionPlanWidget(this.plan);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 10.0, right: 10.0),
|
||||
height: (90 + (plan.tip == null ? 0 : 13)).h,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: plan.tip == null ? 0 : 13.h),
|
||||
child: Container(
|
||||
height: 80.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
left: 10.0.w,
|
||||
right: 20.w,
|
||||
top: 10.h,
|
||||
bottom: 10.h,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: _lead(plan)),
|
||||
SizedBox(
|
||||
width: 85.w,
|
||||
child: _trail(plan),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (plan.tip != null)
|
||||
Container(
|
||||
height: 26.h,
|
||||
width: 110.w,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: plan.tipColor?.asColor ?? progressBlue,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
padding: EdgeInsets.all(2.0.w),
|
||||
child: Text(
|
||||
plan.tip!,
|
||||
style: TextStyle(color: white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _lead(SubscriptionPlanDto plan) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
if (plan.titleLead != null)
|
||||
TextSpan(
|
||||
text: plan.titleLead,
|
||||
style: TextStyle(fontSize: 32),
|
||||
),
|
||||
if (plan.title != null)
|
||||
TextSpan(
|
||||
text: ' ${plan.title}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(
|
||||
height: 0.85,
|
||||
)),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
if (plan.subtitleCrossed != null)
|
||||
TextSpan(
|
||||
text: plan.subtitleCrossed,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: borderGray,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
if (plan.subtitleCrossed != null && plan.subtitle != null)
|
||||
TextSpan(text: ' '),
|
||||
if (plan.subtitle != null)
|
||||
TextSpan(
|
||||
text: plan.subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 20.h,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _trail(SubscriptionPlanDto plan) {
|
||||
if (plan.pricePerMonth != null)
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(
|
||||
text: plan.pricePerMonth!,
|
||||
style: TextStyle(fontSize: 24),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${plan.currency}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
], style: TextStyle(height: 0.85)),
|
||||
),
|
||||
Text(
|
||||
'в месяц',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class AvailablePack extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}),
|
||||
_AdTile(),
|
||||
if (locator.userManager.user?.subscription != true) _AdTile(),
|
||||
_TestsSection(cardPack.id.toString()),
|
||||
],
|
||||
),
|
||||
|
|
@ -257,9 +257,8 @@ class _AdTile extends StatelessWidget {
|
|||
if (kIsWeb) return SizedBox.shrink();
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final banner = YandexAds.createBanner(
|
||||
BoxConstraints.tight(Size(constraints.maxWidth - 16, 80 - 8)),
|
||||
id: 'R-M-3761037-2'
|
||||
);
|
||||
BoxConstraints.tight(Size(constraints.maxWidth - 16, 80 - 8)),
|
||||
id: 'R-M-3761037-2');
|
||||
return FadeInWidget(
|
||||
future: banner.loadAd(),
|
||||
animationDuration: Duration(milliseconds: 1000),
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import 'package:mnemo_cards/utils/string_helper.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:mnemo_cards_frontend_common/mnemo_cards_frontend_common.dart';
|
||||
import '../../features/analytics/analytics.dart';
|
||||
import '../../main.dart';
|
||||
import '../items/builder.dart';
|
||||
|
||||
import '../../di/locator.dart';
|
||||
import '../../domain/router/app_router.dart';
|
||||
|
|
|
|||
|
|
@ -61,10 +61,8 @@ class GameCardDecoration extends StatelessWidget {
|
|||
),
|
||||
decoration: BoxDecoration(
|
||||
// color: Theme.of(context).scaffoldBackgroundColor,
|
||||
color: Theme.of(context)
|
||||
.scaffoldBackgroundColor
|
||||
.lighten(0.05),
|
||||
// .withOpacity(alpha),
|
||||
color: Theme.of(context).scaffoldBackgroundColor.lighten(0.05),
|
||||
// .withOpacity(alpha),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context)
|
||||
|
|
@ -390,45 +388,3 @@ class FrontCard extends StatelessWidget {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _BannerWidget extends StatelessWidget {
|
||||
final BoxConstraints constraints;
|
||||
final String adId;
|
||||
final String adKey;
|
||||
late final BannerAd bannerAd;
|
||||
|
||||
bool bannerLoaded = false;
|
||||
|
||||
_BannerWidget(this.constraints, this.adId, this.adKey)
|
||||
: super(key: ValueKey(adKey));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
BannerAd? bannerAd;
|
||||
bool bannerLoaded = false;
|
||||
return StatefulBuilder(builder: (context, setState) {
|
||||
bannerAd ??= YandexAds.createBanner(
|
||||
BoxConstraints(
|
||||
maxHeight: constraints.maxHeight,
|
||||
maxWidth: constraints.maxWidth,
|
||||
),
|
||||
id: adId,
|
||||
key: adKey,
|
||||
onLoaded: () {
|
||||
bannerLoaded = true;
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
return (bannerLoaded || true)
|
||||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: AdWidget(
|
||||
bannerAd: bannerAd!,
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mnemo_cards/theme/themes.dart';
|
||||
import 'package:mnemo_cards/utils/string_helper.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
|
||||
class ItemBuilder {
|
||||
static Widget build(Item item) {
|
||||
switch (item.type) {
|
||||
case ItemType.text:
|
||||
return TextItemBuilder(item as TextItem);
|
||||
default:
|
||||
log('Undefined item type ${item.type} ${item.id}');
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemExt on Item {
|
||||
Widget build() => ItemBuilder.build(this);
|
||||
}
|
||||
|
||||
extension ItemIterableExt on Iterable<Item> {
|
||||
Iterable<Widget> build() => map((e) => e.build());
|
||||
}
|
||||
|
||||
class TextItemBuilder extends StatelessWidget {
|
||||
final TextItem item;
|
||||
|
||||
TextItemBuilder(this.item);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
border: item.hasBorder
|
||||
? Border.all(
|
||||
color: item.color?.asColor ?? borderGray,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (item.title != null)
|
||||
Text(
|
||||
item.title!,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 24,
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (item.subtitle != null)
|
||||
Text(
|
||||
item.subtitle!,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 16,
|
||||
color: Colors.black,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -576,6 +576,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_widget_from_html_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_widget_from_html_core
|
||||
sha256: df7c7c9e5ea144f7ab0adfbad733b4d4f7d408ab733c94e6e9fdcb327af92aa1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.1"
|
||||
freezed:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ dependencies:
|
|||
flutter_colorpicker: ^1.1.0
|
||||
jailbreak_root_detection: ^1.1.1
|
||||
no_screenshot: ^0.0.1+6
|
||||
flutter_widget_from_html_core: ^0.15.1
|
||||
|
||||
file_picker:
|
||||
url_launcher: ^6.2.6
|
||||
|
|
|
|||
Loading…
Reference in a new issue