init refilc-plus
This commit is contained in:
commit
6edc2029bd
37 changed files with 6988 additions and 0 deletions
201
lib/ui/mobile/plus/activation_view/activation_dashboard.dart
Normal file
201
lib/ui/mobile/plus/activation_view/activation_dashboard.dart
Normal file
|
@ -0,0 +1,201 @@
|
|||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_plus/providers/plus_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ActivationDashboard extends StatefulWidget {
|
||||
const ActivationDashboard({super.key});
|
||||
|
||||
@override
|
||||
State<ActivationDashboard> createState() => _ActivationDashboardState();
|
||||
}
|
||||
|
||||
class _ActivationDashboardState extends State<ActivationDashboard> {
|
||||
bool manualActivationLoading = false;
|
||||
|
||||
Future<void> onManualActivation() async {
|
||||
final data = await Clipboard.getData("text/plain");
|
||||
if (data == null || data.text == null || data.text == "") {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
manualActivationLoading = true;
|
||||
});
|
||||
final result =
|
||||
// ignore: use_build_context_synchronously
|
||||
await context.read<PlusProvider>().auth.finishAuth(data.text!);
|
||||
setState(() {
|
||||
manualActivationLoading = false;
|
||||
});
|
||||
|
||||
if (!result && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text(
|
||||
"Sikertelen aktiválás. Kérlek próbáld újra később!",
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
backgroundColor: Colors.red,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Center(
|
||||
child: Image.asset(
|
||||
"assets/icons/ic_rounded.png",
|
||||
height: 64.0,
|
||||
),
|
||||
// child: SvgPicture.asset(
|
||||
// "assets/images/github.svg",
|
||||
// height: 64.0,
|
||||
// ),
|
||||
),
|
||||
const SizedBox(height: 32.0),
|
||||
const Text(
|
||||
"Válassz fizetési módot, majd folytasd a fizetést a Stripe felületén, hogy aktiváld az előfizetésed.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0),
|
||||
),
|
||||
// const SizedBox(height: 12.0),
|
||||
// Card(
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(14.0)),
|
||||
// child: const Padding(
|
||||
// padding: EdgeInsets.all(16.0),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// Icon(FeatherIcons.alertTriangle,
|
||||
// size: 20.0, color: Colors.orange),
|
||||
// SizedBox(width: 12.0),
|
||||
// Text(
|
||||
// "Figyelem!",
|
||||
// style: TextStyle(
|
||||
// fontSize: 18.0, fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// SizedBox(height: 6.0),
|
||||
// Text(
|
||||
// "Az automatikus visszairányítás az alkalmazásba nem mindig működik. Ebben az esetben kérjük nyomd meg lent a \"Manuális aktiválás\" gombot!",
|
||||
// style: TextStyle(fontSize: 16.0),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(height: 12.0),
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14.0)),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(FeatherIcons.alertTriangle,
|
||||
size: 20.0, color: Colors.orange),
|
||||
SizedBox(width: 12.0),
|
||||
Text(
|
||||
"Figyelem!",
|
||||
style: TextStyle(
|
||||
fontSize: 18.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 6.0),
|
||||
Text(
|
||||
"Az aktiválás azonnal történik, amint kifizetted a szolgáltatás díját. A szolgáltatás automatikusan megújul, lemondásra a beállításokban lesz lehetőséget.",
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14.0)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Ha fizetés után a Stripe nem irányít vissza az alkalmazásba automatikusan, aktiválhatod a támogatásod a munkamenet azonosítóval, melyet kimásolhatsz a hibás URL \"session_id\" paraméteréből.",
|
||||
style:
|
||||
TextStyle(fontSize: 15.0, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 6.0),
|
||||
Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: onManualActivation,
|
||||
style: ButtonStyle(
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).colorScheme.secondary),
|
||||
overlayColor: WidgetStatePropertyAll(Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary
|
||||
.withOpacity(.1)),
|
||||
),
|
||||
icon: manualActivationLoading
|
||||
? const SizedBox(
|
||||
height: 16.0,
|
||||
width: 16.0,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const Icon(FeatherIcons.key, size: 20.0),
|
||||
label: const Padding(
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
"Aktiválás azonosítóval",
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
WidgetStatePropertyAll(AppColors.of(context).text),
|
||||
overlayColor: WidgetStatePropertyAll(
|
||||
AppColors.of(context).text.withOpacity(.1)),
|
||||
),
|
||||
icon: const Icon(FeatherIcons.arrowLeft, size: 20.0),
|
||||
label: const Text(
|
||||
"Vissza",
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
97
lib/ui/mobile/plus/activation_view/activation_view.dart
Normal file
97
lib/ui/mobile/plus/activation_view/activation_view.dart
Normal file
|
@ -0,0 +1,97 @@
|
|||
import 'package:animations/animations.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_plus/providers/plus_provider.dart';
|
||||
import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_dashboard.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_plus/ui/mobile/plus/plus_things.i18n.dart';
|
||||
|
||||
class PremiumActivationView extends StatefulWidget {
|
||||
const PremiumActivationView({super.key, required this.product});
|
||||
|
||||
final String product;
|
||||
|
||||
@override
|
||||
State<PremiumActivationView> createState() => _PremiumActivationViewState();
|
||||
}
|
||||
|
||||
class _PremiumActivationViewState extends State<PremiumActivationView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController animation;
|
||||
bool activated = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
context.read<PlusProvider>().auth.initAuth(product: widget.product);
|
||||
|
||||
animation =
|
||||
AnimationController(vsync: this, duration: const Duration(seconds: 2));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
animation.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final premium = context.watch<PlusProvider>();
|
||||
|
||||
if (premium.hasPremium && !activated) {
|
||||
activated = true;
|
||||
animation.forward();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
Future.delayed(const Duration(seconds: 5)).then((value) {
|
||||
if (mounted) {
|
||||
// pop the anim
|
||||
Navigator.of(context).pop();
|
||||
// pop the plus view
|
||||
Navigator.of(context).pop();
|
||||
// show alert to save code
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"copy_code_asap".i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
backgroundColor: AppColors.of(context).background,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: PageTransitionSwitcher(
|
||||
transitionBuilder: (child, primaryAnimation, secondaryAnimation) =>
|
||||
SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.horizontal,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
),
|
||||
child: premium.hasPremium
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
width: 400,
|
||||
child: Lottie.network(
|
||||
"https://assets2.lottiefiles.com/packages/lf20_wkebwzpz.json",
|
||||
controller: animation,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SafeArea(child: ActivationDashboard()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
138
lib/ui/mobile/plus/plus_things.i18n.dart
Normal file
138
lib/ui/mobile/plus/plus_things.i18n.dart
Normal file
|
@ -0,0 +1,138 @@
|
|||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
// upsell titles
|
||||
"u_title_1": "Use more accounts?",
|
||||
"u_title_2": "Want to try the updates in advance?",
|
||||
"u_title_3": "\"Hi kitty, do you have an owner?\"",
|
||||
"u_title_4": "Would you write down your tasks?",
|
||||
"u_title_5": "Lazy to do maths?",
|
||||
"u_title_6": "I know, the plain grey is not very nice :P",
|
||||
"u_title_7":
|
||||
"\"What were we doing in class? Was there English homework??\"",
|
||||
"u_title_8": "Now that's something special!",
|
||||
"u_title_9": "Woah! What beautiful letters!",
|
||||
"u_title_10": "Need more suggestions?",
|
||||
"u_title_11": "Not epic, but ultra-super?",
|
||||
"u_title_12": "Do you even need it in your calendar?!",
|
||||
"u_title_13": "Wanna see your past years' grades?",
|
||||
// upsell descriptions
|
||||
"u_desc_1": "The limit increases with each tier.",
|
||||
"u_desc_2":
|
||||
"Subscribe to reFilc+ to receive beta updates in advance.",
|
||||
"u_desc_3": "For a unique greeting, just the lowest tier is enough!",
|
||||
"u_desc_4":
|
||||
"Support us and make a note of all your important things.",
|
||||
"u_desc_5":
|
||||
"reFilc+ makes it easier to calculate your projected average.",
|
||||
"u_desc_6": "With Gold tier, you can recolour to anything.",
|
||||
"u_desc_7": "No more questions in Gold.",
|
||||
"u_desc_8": "Upgrade to Gold to change the app icon.",
|
||||
"u_desc_9": "You can also change the font with Gold tier.",
|
||||
"u_desc_10":
|
||||
"Support us on Gold tier and use all the features of goal setting!",
|
||||
"u_desc_11": "With reFilc+ lowest tier it's also available!",
|
||||
"u_desc_12": "Sync your time-table with reFilc+ Gold!",
|
||||
"u_desc_13": "You can export every year's grades with reFilc+!",
|
||||
// button
|
||||
"subscribe": "Subscribe",
|
||||
"subscriber": "Subscribed",
|
||||
// other
|
||||
"copy_code_asap":
|
||||
"Copy your reFilc+ ID, in case you loose your device!",
|
||||
},
|
||||
"hu_hu": {
|
||||
// upsell titles
|
||||
"u_title_1": "Több fiókot használnál?",
|
||||
"u_title_2": "Előre kipróbálnád a frissítéseket?",
|
||||
"u_title_3": "\"Szia cica, van gazdád?\"",
|
||||
"u_title_4": "Felírnád a feladataid?",
|
||||
"u_title_5": "Lusta vagy matekozni?",
|
||||
"u_title_6": "Tudom, nem túl szép a sima szürke :P",
|
||||
"u_title_7": "\"Mit is csináltunk órán? Volt angol házi??\"",
|
||||
"u_title_8": "Ez aztán különleges!",
|
||||
"u_title_9": "Woah! Micsoda gyönyörű betűk!",
|
||||
"u_title_10": "Még több javaslat kell?",
|
||||
"u_title_11": "Nem epikus, hanem ultraszuper?",
|
||||
"u_title_12": "Még a naptáradba is kell?!",
|
||||
"u_title_13": "Szeretnéd látni az előző évi jegyeid?",
|
||||
// upsell descriptions
|
||||
"u_desc_1": "Minden támogatási szinttel egyre magasabb a limit.",
|
||||
"u_desc_2":
|
||||
"Fizess elő reFilc+-ra, hogy előre megkapd a béta frissítéseket.",
|
||||
"u_desc_3": "Az egyedi üdvözléshez elég csupán a legalsó szint!",
|
||||
"u_desc_4": "Támogass minket, és jegyezd fel minden fontos dolgod.",
|
||||
"u_desc_5": "reFilc+-al egyszerűbb kiszámolnod a tervezett átlagod.",
|
||||
"u_desc_6": "Gold szintű támogatással átszínezhetsz bármilyenre.",
|
||||
"u_desc_7": "Nincs több ilyen kérdés, ha Gold szinten támogatsz.",
|
||||
"u_desc_8":
|
||||
"Fizess elő Gold szintre az alkalmazás ikonjának megváltoztatásához.",
|
||||
"u_desc_9":
|
||||
"Gold szintű támogatással megváltoztathatod a betűtípust is.",
|
||||
"u_desc_10":
|
||||
"Támogass Gold szinten és használd ki a cél kitűzés minden funkcióját!",
|
||||
"u_desc_11": "A reFilc+ alap szintjével ez is elérhető!",
|
||||
"u_desc_12": "Szinkronizáld az órarended reFilc+ Gold-al!",
|
||||
"u_desc_13": "Minden évi jegyedet exportálhatod reFilc+-al!",
|
||||
// button
|
||||
"subscribe": "Előfizetés",
|
||||
"subscriber": "Előfizetve",
|
||||
// other
|
||||
"copy_code_asap":
|
||||
"Másold ki a reFilc+ ID-t, mielőtt elveszítenéd a telefonod!",
|
||||
},
|
||||
"de_de": {
|
||||
// upsell titles
|
||||
"u_title_1": "Mehr Accounts nutzen?",
|
||||
"u_title_2": "Willst du die Updates im vorraus testen?",
|
||||
"u_title_3": "\"Hallo mein Kätzchen, hast du einen besitzer?\"",
|
||||
"u_title_4": "Würdest du deine Aufgaben aufschreiben?",
|
||||
"u_title_5": "Faul um Mathe zu machen?",
|
||||
"u_title_6": "Ich weiß, das schlichte Grau ist nicht so toll :P",
|
||||
"u_title_7":
|
||||
"\"Was haben wir im Unterricht gemacht? Gab es Englisch Hausaufgaben??\"",
|
||||
"u_title_8": "Na das ist mal was besonderes!",
|
||||
"u_title_9": "Wow! Was für schöne Texte!",
|
||||
"u_title_10": "Brauchst du mehr Vorschläge?",
|
||||
"u_title_11":
|
||||
"Willst du vielleicht statt episch etwas anderes wie super?",
|
||||
"u_title_12": "Brauchst du das wirklich in deinem Kalender?!",
|
||||
"u_title_13": "Möchtest du deine Noten der vergangenen Jahre sehen?",
|
||||
// upsell descriptions
|
||||
"u_desc_1": "Das limit erhöht sich mit jedem Abo-plan.",
|
||||
"u_desc_2":
|
||||
"Abonniere reFilc+ um Beta Updates im vorraus zu erhalten.",
|
||||
"u_desc_3":
|
||||
"Für eine eigene Begrüßung ist der niedrigste Abo-Plan schon genug!",
|
||||
"u_desc_4":
|
||||
"Unterstütze uns und schreib alles wichtige für dich auf.",
|
||||
"u_desc_5":
|
||||
"reFilc+ macht es einfacher deinen Durchschnitt zu berechnen.",
|
||||
"u_desc_6": "Mit dem Gold-Plan, kannst du alles anders Färben.",
|
||||
"u_desc_7": "Keine weiteren Fragen mit Gold.",
|
||||
"u_desc_8": "Upgrade auf Gold um das App Icon zu ändern.",
|
||||
"u_desc_9":
|
||||
"Du kannst mit dem Gold-Plan auch die Schriftart verändern.",
|
||||
"u_desc_10":
|
||||
"Unterstütze uns mit einem Gold-Plan und benutze alle features des Ziel-setzens!",
|
||||
"u_desc_11":
|
||||
"Mit reFilc+ niedrigstem Abo-Plan ist es auch Verfügbar!",
|
||||
"u_desc_12": "Synchronisiere deinen Stundenplan mit reFilc+ Gold!",
|
||||
"u_desc_13": "Du kannst jede Jahresnote mit reFilc+ exportieren!",
|
||||
// button
|
||||
"subscribe": "Abonnieren",
|
||||
"subscriber": "im Abonnement",
|
||||
// other
|
||||
"copy_code_asap":
|
||||
"Kopieren Sie Ihre reFilc+ ID, bevor Sie Ihr Handy verlieren!",
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
74
lib/ui/mobile/plus/premium_inline.dart
Normal file
74
lib/ui/mobile/plus/premium_inline.dart
Normal file
|
@ -0,0 +1,74 @@
|
|||
// ignore_for_file: unused_element
|
||||
|
||||
import 'package:refilc_plus/ui/mobile/plus/upsell.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum PremiumInlineFeature { nickname, theme, widget, goal, stats }
|
||||
|
||||
const Map<PremiumInlineFeature, String> _featureAssets = {
|
||||
PremiumInlineFeature.nickname:
|
||||
"assets/images/premium_nickname_inline_showcase.png",
|
||||
PremiumInlineFeature.theme: "assets/images/premium_theme_inline_showcase.png",
|
||||
PremiumInlineFeature.widget:
|
||||
"assets/images/premium_widget_inline_showcase.png",
|
||||
PremiumInlineFeature.goal: "assets/images/premium_goal_inline_showcase.png",
|
||||
PremiumInlineFeature.stats: "assets/images/premium_stats_inline_showcase.png",
|
||||
};
|
||||
|
||||
const Map<PremiumInlineFeature, PremiumFeature> _featuresInline = {
|
||||
PremiumInlineFeature.nickname: PremiumFeature.profile,
|
||||
PremiumInlineFeature.theme: PremiumFeature.customcolors,
|
||||
PremiumInlineFeature.widget: PremiumFeature.widget,
|
||||
// PremiumInlineFeature.goal: PremiumFeature.goalplanner,
|
||||
PremiumInlineFeature.stats: PremiumFeature.gradestats,
|
||||
};
|
||||
|
||||
class PremiumInline extends StatelessWidget {
|
||||
const PremiumInline({super.key, required this.features});
|
||||
|
||||
final List<PremiumInlineFeature> features;
|
||||
|
||||
String _getAsset() {
|
||||
for (int i = 0; i < features.length; i++) {
|
||||
if (DateTime.now().day % features.length == i) {
|
||||
return _featureAssets[features[i]]!;
|
||||
}
|
||||
}
|
||||
|
||||
return _featureAssets[features[0]]!;
|
||||
}
|
||||
|
||||
PremiumFeature _getFeature() {
|
||||
for (int i = 0; i < features.length; i++) {
|
||||
if (DateTime.now().day % features.length == i) {
|
||||
return _featuresInline[features[i]]!;
|
||||
}
|
||||
}
|
||||
|
||||
return _featuresInline[features[0]]!;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(
|
||||
height: 0,
|
||||
);
|
||||
// return Stack(
|
||||
// children: [
|
||||
// Image.asset(_getAsset()),
|
||||
// Positioned.fill(
|
||||
// child: Material(
|
||||
// type: MaterialType.transparency,
|
||||
// child: InkWell(
|
||||
// borderRadius: BorderRadius.circular(16.0),
|
||||
// onTap: () {
|
||||
// PlusLockedFeaturePopup.show(
|
||||
// context: context, feature: _getFeature());
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
}
|
||||
}
|
91
lib/ui/mobile/plus/settings_inline.dart
Normal file
91
lib/ui/mobile/plus/settings_inline.dart
Normal file
|
@ -0,0 +1,91 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/plus/plus_screen.dart';
|
||||
import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
import 'package:refilc_plus/providers/plus_provider.dart';
|
||||
import 'plus_things.i18n.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
|
||||
|
||||
class PlusSettingsInline extends StatelessWidget {
|
||||
const PlusSettingsInline({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String plusTier = Provider.of<PlusProvider>(context)
|
||||
.hasScope(PremiumScopes.tierGold)
|
||||
? 'gold'
|
||||
: (Provider.of<PlusProvider>(context).hasScope(PremiumScopes.tierBasic)
|
||||
? 'basic'
|
||||
: 'none');
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (plusTier == 'none') {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return const PlusScreen();
|
||||
}));
|
||||
} else {
|
||||
SettingsHelper.plusOptions(context);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: plusTier == 'gold'
|
||||
? const AssetImage('assets/images/btn_plus_gold.png')
|
||||
: const AssetImage('assets/images/btn_plus_standard.png'),
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 2.0,
|
||||
),
|
||||
Image.asset(
|
||||
plusTier == 'gold'
|
||||
? 'assets/images/plus_tier_ink.png'
|
||||
: 'assets/images/plus_tier_cap.png',
|
||||
width: 23.0,
|
||||
height: 23.0,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14.0,
|
||||
),
|
||||
Text(
|
||||
'reFilc+',
|
||||
style: TextStyle(
|
||||
color: plusTier == 'gold'
|
||||
? const Color(0xFF341C01)
|
||||
: const Color(0xFF150D4E),
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
plusTier == 'none' ? '0.99 €' : 'subscriber'.i18n,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF150D4E),
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
486
lib/ui/mobile/plus/upsell.dart
Normal file
486
lib/ui/mobile/plus/upsell.dart
Normal file
|
@ -0,0 +1,486 @@
|
|||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:refilc_mobile_ui/plus/plus_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'plus_things.i18n.dart';
|
||||
|
||||
enum PremiumFeature {
|
||||
// old things
|
||||
gradestats,
|
||||
customcolors,
|
||||
profile,
|
||||
iconpack,
|
||||
subjectrename,
|
||||
weeklytimetable,
|
||||
widget,
|
||||
// new things
|
||||
moreAccounts, // cap, (ink, sponge)
|
||||
betaReleases, // cap
|
||||
welcomeMessage, // cap
|
||||
selfNotes, // cap
|
||||
gradeCalculation, // ink
|
||||
liveActivity, // ink
|
||||
timetableNotes, // ink
|
||||
iconChange, // sponge
|
||||
fontChange, // sponge
|
||||
goalPlanner, // sponge
|
||||
gradeRarities,
|
||||
calendarSync,
|
||||
gradeExporting, // basic
|
||||
}
|
||||
|
||||
enum PremiumFeatureLevel {
|
||||
old,
|
||||
cap,
|
||||
ink,
|
||||
sponge,
|
||||
// new new new
|
||||
basic,
|
||||
gold,
|
||||
}
|
||||
|
||||
const Map<PremiumFeature, PremiumFeatureLevel> _featureLevels = {
|
||||
// old things
|
||||
PremiumFeature.gradestats: PremiumFeatureLevel.old,
|
||||
PremiumFeature.customcolors: PremiumFeatureLevel.old,
|
||||
PremiumFeature.profile: PremiumFeatureLevel.old,
|
||||
PremiumFeature.iconpack: PremiumFeatureLevel.old,
|
||||
PremiumFeature.subjectrename: PremiumFeatureLevel.old,
|
||||
PremiumFeature.weeklytimetable: PremiumFeatureLevel.old,
|
||||
PremiumFeature.widget: PremiumFeatureLevel.old,
|
||||
// new things
|
||||
PremiumFeature.moreAccounts: PremiumFeatureLevel.basic,
|
||||
PremiumFeature.betaReleases: PremiumFeatureLevel.basic,
|
||||
PremiumFeature.welcomeMessage: PremiumFeatureLevel.basic,
|
||||
PremiumFeature.selfNotes: PremiumFeatureLevel.basic,
|
||||
PremiumFeature.gradeCalculation: PremiumFeatureLevel.basic,
|
||||
PremiumFeature.liveActivity: PremiumFeatureLevel.gold,
|
||||
PremiumFeature.timetableNotes: PremiumFeatureLevel.gold,
|
||||
PremiumFeature.iconChange: PremiumFeatureLevel.gold,
|
||||
PremiumFeature.fontChange: PremiumFeatureLevel.gold,
|
||||
PremiumFeature.goalPlanner: PremiumFeatureLevel.gold,
|
||||
PremiumFeature.gradeRarities: PremiumFeatureLevel.basic,
|
||||
PremiumFeature.calendarSync: PremiumFeatureLevel.gold,
|
||||
PremiumFeature.gradeExporting: PremiumFeatureLevel.basic,
|
||||
};
|
||||
|
||||
// const Map<PremiumFeature, String> _featureAssets = {
|
||||
// // old
|
||||
// PremiumFeature.gradestats: "assets/images/premium_stats_showcase.png",
|
||||
// PremiumFeature.customcolors: "assets/images/premium_theme_showcase.png",
|
||||
// PremiumFeature.profile: "assets/images/premium_nickname_showcase.png",
|
||||
// PremiumFeature.weeklytimetable:
|
||||
// "assets/images/premium_timetable_showcase.png",
|
||||
// // PremiumFeature.goalplanner: "assets/images/premium_goal_showcase.png",
|
||||
// PremiumFeature.widget: "assets/images/premium_widget_showcase.png",
|
||||
// // new
|
||||
// PremiumFeature.moreAccounts: "assets/images/premium_banner/more_accounts.png",
|
||||
// PremiumFeature.betaReleases: "assets/images/premium_banner/beta_releases.png",
|
||||
// PremiumFeature.welcomeMessage:
|
||||
// "assets/images/premium_banner/welcome_message.png",
|
||||
// PremiumFeature.selfNotes: "assets/images/premium_banner/self_notes.png",
|
||||
// PremiumFeature.gradeCalculation:
|
||||
// "assets/images/premium_banner/grade_calc.png",
|
||||
// PremiumFeature.liveActivity: "assets/images/premium_banner/live_activity.png",
|
||||
// PremiumFeature.timetableNotes:
|
||||
// "assets/images/premium_banner/timetable_notes.png",
|
||||
// PremiumFeature.iconChange: "assets/images/premium_banner/app_icon.png",
|
||||
// PremiumFeature.fontChange: "assets/images/premium_banner/font.png",
|
||||
// PremiumFeature.goalPlanner: "assets/images/premium_banner/goal_planner.png",
|
||||
|
||||
// PremiumFeature.gradeRarities:
|
||||
// "assets/images/premium_banner/grade_rarities.png",
|
||||
// PremiumFeature.calendarSync: "assets/images/premium_banner/calendar_sync.png",
|
||||
// };
|
||||
|
||||
const Map<PremiumFeature, String> _featureTitles = {
|
||||
// old shit
|
||||
PremiumFeature.gradestats: "Találtál egy prémium funkciót.",
|
||||
PremiumFeature.customcolors: "Több személyre szabás kell?",
|
||||
PremiumFeature.profile: "Nem tetszik a neved?",
|
||||
PremiumFeature.iconpack: "Jobban tetszettek a régi ikonok?",
|
||||
PremiumFeature.subjectrename:
|
||||
"Sokáig tart elolvasni, hogy \"Földrajz természettudomány\"?",
|
||||
PremiumFeature.weeklytimetable: "Szeretnéd egyszerre az egész hetet látni?",
|
||||
// PremiumFeature.goalplanner: "Kövesd a céljaidat, sok-sok statisztikával.",
|
||||
PremiumFeature.widget: "Órák a kezdőképernyőd kényelméből.",
|
||||
// new shit
|
||||
PremiumFeature.moreAccounts: "u_title_1",
|
||||
PremiumFeature.betaReleases: "u_title_2",
|
||||
PremiumFeature.welcomeMessage: "u_title_3",
|
||||
PremiumFeature.selfNotes: "u_title_4",
|
||||
PremiumFeature.gradeCalculation: "u_title_5",
|
||||
PremiumFeature.liveActivity: "u_title_6",
|
||||
PremiumFeature.timetableNotes: "u_title_7",
|
||||
PremiumFeature.iconChange: "u_title_8",
|
||||
PremiumFeature.fontChange: "u_title_9",
|
||||
PremiumFeature.goalPlanner: "u_title_10",
|
||||
PremiumFeature.gradeRarities: "u_title_11",
|
||||
PremiumFeature.calendarSync: "u_title_12",
|
||||
PremiumFeature.gradeExporting: "u_title_13",
|
||||
};
|
||||
|
||||
const Map<PremiumFeature, String> _featureDescriptions = {
|
||||
// old
|
||||
PremiumFeature.gradestats:
|
||||
"Támogass Kupak szinten, hogy több statisztikát láthass. ",
|
||||
PremiumFeature.customcolors:
|
||||
"Támogass Kupak szinten, és szabd személyre az elemek, a háttér, és a panelek színeit.",
|
||||
PremiumFeature.profile:
|
||||
"Kupak szinten változtathatod a nevedet, sőt, akár a profilképedet is.",
|
||||
PremiumFeature.iconpack:
|
||||
"Támogass Kupak szinten, hogy ikon témát választhass.",
|
||||
PremiumFeature.subjectrename:
|
||||
"Támogass Kupak szinten, hogy átnevezhesd Föcire.",
|
||||
PremiumFeature.weeklytimetable:
|
||||
"Támogass Tinta szinten a heti órarend funkcióért.",
|
||||
// PremiumFeature.goalplanner: "A célkövetéshez támogass Tinta szinten.",
|
||||
PremiumFeature.widget:
|
||||
"Támogass Tinta szinten, és helyezz egy widgetet a kezdőképernyődre.",
|
||||
// new
|
||||
PremiumFeature.moreAccounts: "u_desc_1",
|
||||
PremiumFeature.betaReleases: "u_desc_2",
|
||||
PremiumFeature.welcomeMessage: "u_desc_3",
|
||||
PremiumFeature.selfNotes: "u_desc_4",
|
||||
PremiumFeature.gradeCalculation: "u_desc_5",
|
||||
PremiumFeature.liveActivity: "u_desc_6",
|
||||
PremiumFeature.timetableNotes: "u_desc_7",
|
||||
PremiumFeature.iconChange: "u_desc_8",
|
||||
PremiumFeature.fontChange: "u_desc_9",
|
||||
PremiumFeature.goalPlanner: "u_desc_10",
|
||||
PremiumFeature.gradeRarities: "u_desc_11",
|
||||
PremiumFeature.calendarSync: "u_desc_12",
|
||||
PremiumFeature.gradeExporting: "u_desc_13",
|
||||
};
|
||||
|
||||
// class PremiumLockedFeatureUpsell extends StatelessWidget {
|
||||
// const PremiumLockedFeatureUpsell({super.key, required this.feature});
|
||||
|
||||
// static void show(
|
||||
// {required BuildContext context, required PremiumFeature feature}) =>
|
||||
// showRoundedModalBottomSheet(context,
|
||||
// child: PremiumLockedFeatureUpsell(feature: feature));
|
||||
|
||||
// final PremiumFeature feature;
|
||||
|
||||
// IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap
|
||||
// ? FilcIcons.kupak
|
||||
// : _featureLevels[feature] == PremiumFeatureLevel.ink
|
||||
// ? FilcIcons.tinta
|
||||
// : FilcIcons.tinta;
|
||||
// Color _getColor(BuildContext context) =>
|
||||
// _featureLevels[feature] == PremiumFeatureLevel.gold
|
||||
// ? const Color(0xFFC89B08)
|
||||
// : Theme.of(context).brightness == Brightness.light
|
||||
// ? const Color(0xff691A9B)
|
||||
// : const Color(0xffA66FC8);
|
||||
// String? _getAsset() => _featureAssets[feature];
|
||||
// String _getTitle() => _featureTitles[feature]!;
|
||||
// String _getDescription() => _featureDescriptions[feature]!;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final Color color = _getColor(context);
|
||||
|
||||
// return Dialog(
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0),
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// // Title Bar
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(left: 8.0),
|
||||
// child: Icon(_getIcon()),
|
||||
// ),
|
||||
// IconButton(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// icon: const Icon(Icons.close),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
|
||||
// // Image showcase
|
||||
// if (_getAsset() != null)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top: 8.0),
|
||||
// child: Image.asset(_getAsset()!),
|
||||
// ),
|
||||
|
||||
// // Dialog title
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top: 12.0),
|
||||
// child: Text(
|
||||
// _getTitle(),
|
||||
// style: const TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// fontSize: 20.0,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // Dialog description
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top: 8.0),
|
||||
// child: Text(
|
||||
// _getDescription(),
|
||||
// style: const TextStyle(
|
||||
// fontSize: 16.0,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // CTA button
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top: 8.0),
|
||||
// child: SizedBox(
|
||||
// width: double.infinity,
|
||||
// child: TextButton(
|
||||
// style: ButtonStyle(
|
||||
// backgroundColor:
|
||||
// WidgetStatePropertyAll(color.withOpacity(.25)),
|
||||
// foregroundColor: WidgetStatePropertyAll(color),
|
||||
// overlayColor:
|
||||
// WidgetStatePropertyAll(color.withOpacity(.1))),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context, rootNavigator: true)
|
||||
// .push(MaterialPageRoute(builder: (context) {
|
||||
// return const PlusScreen();
|
||||
// }));
|
||||
// },
|
||||
// child: const Text(
|
||||
// "Vigyél oda!",
|
||||
// style: TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// fontSize: 18.0,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class PlusLockedFeaturePopup extends StatelessWidget {
|
||||
const PlusLockedFeaturePopup({super.key, required this.feature});
|
||||
|
||||
static void show({
|
||||
required BuildContext context,
|
||||
required PremiumFeature feature,
|
||||
}) =>
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: PlusLockedFeaturePopup(
|
||||
feature: feature,
|
||||
),
|
||||
showHandle: false,
|
||||
);
|
||||
|
||||
final PremiumFeature feature;
|
||||
|
||||
PremiumFeatureLevel? _getFeatureLevel() => _featureLevels[feature];
|
||||
|
||||
// IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap
|
||||
// ? FilcIcons.kupak
|
||||
// : _featureLevels[feature] == PremiumFeatureLevel.ink
|
||||
// ? FilcIcons.tinta
|
||||
// : FilcIcons.tinta;
|
||||
// Color _getColor(BuildContext context) =>
|
||||
// _featureLevels[feature] == PremiumFeatureLevel.gold
|
||||
// ? const Color(0xFFC89B08)
|
||||
// : Theme.of(context).brightness == Brightness.light
|
||||
// ? const Color(0xff691A9B)
|
||||
// : const Color(0xffA66FC8);
|
||||
// String? _getAsset() => _featureAssets[feature];
|
||||
String _getTitle() => _featureTitles[feature]!.i18n;
|
||||
String _getDescription() => _featureDescriptions[feature]!.i18n;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isGold = _getFeatureLevel() == PremiumFeatureLevel.gold;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isGold ? const Color(0xFFF7EDD9) : const Color(0xFFDCDAF7),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
// "assets/svg/mesh_bg.svg",
|
||||
"assets/svg/cover_arts/grid.svg",
|
||||
// ignore: deprecated_member_use
|
||||
color: isGold ? const Color(0xFFf0dcb6) : const Color(0xFFbcb8f0),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: isGold
|
||||
? const Color(0xFF341C01)
|
||||
: const Color(0xFF130667),
|
||||
borderRadius: BorderRadius.circular(
|
||||
2.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 38.0,
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/plus_${isGold ? 'gold' : 'standard'}.png',
|
||||
width: 66,
|
||||
height: 66,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 55.0,
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(
|
||||
0xFFF7F9FC,
|
||||
).withOpacity(0.7),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(6.0),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(14.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isGold ? 'reFilc+ Gold' : 'reFilc+',
|
||||
style: TextStyle(
|
||||
color: isGold
|
||||
? const Color(0xFFAD7637)
|
||||
: const Color(0xFF7463E2),
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Text(
|
||||
_getTitle(),
|
||||
style: TextStyle(
|
||||
color: isGold
|
||||
? const Color(0xFF341C01)
|
||||
: const Color(0xFF150D4E),
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Text(
|
||||
_getDescription(),
|
||||
style: TextStyle(
|
||||
color: isGold
|
||||
? const Color(0xFF341C01)
|
||||
: const Color(0xFF150D4E),
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6.0,
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(
|
||||
0xFFF7F9FC,
|
||||
).withOpacity(0.7),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(6.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(14.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'A reFilc+ 0.99 €, a reFilc+ Gold 2.99 €',
|
||||
style: TextStyle(
|
||||
color: isGold
|
||||
? const Color(0xFF341C01)
|
||||
: const Color(0xFF150D4E),
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24.0,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return const PlusScreen();
|
||||
}));
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/images/btn_plus_${isGold ? 'gold' : 'standard'}.png'),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'subscribe'.i18n,
|
||||
style: TextStyle(
|
||||
color: isGold
|
||||
? const Color(0xFF341C01)
|
||||
: const Color(0xFF150D4E),
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue