import 'dart:ui'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:design/design.dart'; import 'package:guru_app/financial/iap/iap_model.dart'; import 'package:guru_app/guru_app.dart'; import 'package:guru_ui/guru_widget.dart'; import 'package:guru_ui/localizations/l10n/generated/app_localizations.dart'; import 'package:guru_ui/localizations/ui_strings.dart'; import 'package:guru_ui/pages/subscription/subscription_card.dart'; import 'package:guru_ui/pages/subscription/subscription_controller.dart'; import 'package:guru_widgets/button/single_tap_widget.dart'; import 'package:guru_widgets/pages/webview/guru_webview_page.dart'; import 'package:guru_widgets/theme/guru_theme.dart'; import 'package:guru_popup/guru_popup.dart'; import 'package:guru_utils/router/router.dart'; part 'subscription_page.g.dart'; @DesignSpec(width: 750, height: 1624) abstract class SubscriptionDesignSpec implements BasicDesignSpec { @SpecEdgeInsets.only( start: SpecWidth(40), end: SpecWidth(40), top: SpecStatusBarHeight(50)) EdgeInsetsDirectional get appBarMargin; @CombinedSpec(SpecHeight(88), SpecStatusBarHeight(50)) double get appBarHeight; @SpecHeight(48) double get closeIconSize; @SpecAbsoluteFontSize(24, consistent: true) double get restoreFontSize; @SpecEdgeInsets.only( top: SpecHeight(24, consistent: true), bottom: SpecNavigationBarHeight(30), start: SpecWidth(32), end: SpecWidth(32)) EdgeInsetsDirectional get bottomCardsPadding; @SpecHeight(48, consistent: true) double get premiumCardsBottomSpacing; @SpecSize(SpecWidth(630), SpecHeight(104)) Size get buttonSize; @SpecEdgeInsets.only(top: SpecHeight(28), bottom: SpecHeight(32)) EdgeInsetsDirectional get policyPadding; @SpecAbsoluteFontSize(20) double get policyFontSize; @NestedSpec(242, 320) SubscriptionMainCardDesignSpec get mainCardSpec; @NestedSpec(206, 268) SubscriptionEdgeCardDesignSpec get edgeCardSpec; @NestedSpec(650, 124) SubscriptionListCardDesignSpec get listCardSpec; @SpecHeight(22) double get listCardBottomSpacing; static SubscriptionDesignSpec get() => _SubscriptionDesignSpec.get(); } enum SubscriptionCardType { edge, main, list } class SubscriptionCardStyle { final Decoration? decoration; final Color? cardTitleColor; final Color? cardSubTitleColor; final Color? cardThenColor; final Color? cardPriceColor; SubscriptionCardStyle( {this.decoration, this.cardTitleColor, this.cardSubTitleColor, this.cardThenColor, this.cardPriceColor}); } class SubscriptionTheme { final Color? backgroundColor; final Decoration? bottomDecoration; final SubscriptionCardStyle? cardStyle; final SubscriptionCardStyle? activeCardStyle; final GuruButtonStyle buttonStyle; static SubscriptionTheme defaultTheme = SubscriptionTheme(buttonStyle: GuruButtonStyle.neutral); SubscriptionTheme( {this.backgroundColor, this.bottomDecoration, this.cardStyle, this.activeCardStyle, required this.buttonStyle}); } class SubscriptionCardItem { // final IapProduct productId; final String title; final String? subTitle; final String? thenTexg; final String price; final String? labelImage; final String? labelText; final String? buttonText; final SubscriptionCardType type; SubscriptionCardItem({ // required this.productId, required this.title, this.subTitle, this.thenTexg, required this.price, this.labelText, this.labelImage, this.buttonText, this.type = SubscriptionCardType.edge, }); } class _UIHolder { ThemeData? _theme; GuruThemeData? _guruTheme; SubscriptionTheme? _subscriptionTheme; late SubscriptionDesignSpec _designSpec; late AppLocalizations appStrings; _UIHolder(); void attach(BuildContext context) { _theme ??= Theme.of(context); _guruTheme ??= GuruTheme.of(context); _subscriptionTheme ??= _guruTheme?.getCustomTheme( SubscriptionTheme.defaultTheme.runtimeType) ?? SubscriptionTheme.defaultTheme; _designSpec = SubscriptionDesignSpec.get(); appStrings = UIStrings.get(); } } mixin _UIData { final _UIHolder _uiHolder = _UIHolder(); ThemeData get theme => _uiHolder._theme ?? ThemeData(); GuruThemeData get guruTheme => _uiHolder._guruTheme ?? GuruThemeData(); GuruColorScheme get colorScheme => guruTheme.colorScheme; SubscriptionTheme get subscriptionTheme => _uiHolder._subscriptionTheme ?? SubscriptionTheme.defaultTheme; SubscriptionDesignSpec get designSpec => _uiHolder._designSpec; AppLocalizations get appStrings => _uiHolder.appStrings; } class SubscriptionPage extends GetWidget with _UIData { final List items; final Widget Function(BuildContext) contentBuilder; SubscriptionPage( {super.key, required this.items, required this.contentBuilder}); Widget _buildPremiumCards(BuildContext context, int index) { bool hasListItem = false; for (var item in items) { if (item.type == SubscriptionCardType.list) { hasListItem = true; } } final List widgets = []; for (var i = 0; i < items.length; i++) { final selected = i == index; if (hasListItem) { widgets.add(SubscriptionListCard( model: SubscriptionListCardModel( cardItem: items[i], designSpec: designSpec.listCardSpec, subscriptionTheme: subscriptionTheme, onTap: () { controller.updateSelectedProductIndex(i); }, selected: selected, product: null), )); if (i != items.length - 1) { widgets.add(SizedBox(height: designSpec.listCardBottomSpacing)); } } else { if (items[i].type == SubscriptionCardType.edge) { widgets.add(SubscriptionEdgeCard( model: SubscriptionEdgeCardModel( cardItem: items[i], designSpec: designSpec.edgeCardSpec, subscriptionTheme: subscriptionTheme, onTap: () { controller.updateSelectedProductIndex(i); }, selected: selected, product: null), )); } else { widgets.add(SubscriptionMainCard( model: SubscriptionMainCardModel( cardItem: items[i], designSpec: designSpec.mainCardSpec, subscriptionTheme: subscriptionTheme, onTap: () { controller.updateSelectedProductIndex(i); }, selected: selected, product: null), )); } } } return Padding( padding: EdgeInsets.only(bottom: designSpec.premiumCardsBottomSpacing), child: hasListItem ? Column( children: widgets, ) : Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: widgets, )); } Widget _buildBottom(BuildContext context) { return Container( decoration: subscriptionTheme.bottomDecoration ?? const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0x00121212), Color(0xFF121212), Color(0xFF121212)], stops: [0.0, 55 / 584, 1.0], ), ), padding: designSpec.bottomCardsPadding, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ StreamBuilder( stream: controller.observableSelectProductIndex, builder: (context, snapshot) { final int index = snapshot.data ?? 1; return _buildPremiumCards(context, index); }, ), StreamBuilder( stream: controller.observableSelectProductIndex, builder: (context, snapshot) { final int index = snapshot.data ?? 1; return GuruButton( size: designSpec.buttonSize, sizeSpec: GuruButtonSizeSpec.s1, action: items[index].buttonText ?? 'Buy', onPressed: () { // final selectedProduct = items[index].productId; // controller.purchase(selectedProduct, selectedProduct.offerId); }, ); }, ), Padding( padding: designSpec.policyPadding, child: _buildPolicy(), ), ], ), ); } Widget _buildContent(BuildContext context) { return Stack(fit: StackFit.expand, children: [ SingleChildScrollView( child: contentBuilder(context), ), Align( alignment: Alignment.bottomCenter, child: _buildBottom(context), ), Align( alignment: Alignment.topCenter, child: Container( width: designSpec.measuredSize.width, height: designSpec.appBarHeight, padding: designSpec.appBarMargin, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ TapWidget( onTap: () { RouteCenter.instance.back(); }, child: guruTheme.iconScheme.closeIcon != null ? Image.asset( guruTheme.iconScheme.closeIcon!, width: designSpec.closeIconSize, height: designSpec.closeIconSize, fit: BoxFit.fill, ) : Icon( Icons.close, size: designSpec.closeIconSize, color: colorScheme.primaryContentColor ?? Colors.white, ), ), TapWidget( onTap: () {}, child: Text( 'restore', style: TextStyle( color: colorScheme.primaryContentColor ?? Colors.white, fontSize: designSpec.restoreFontSize, fontWeight: GuruTheme.fwMedium, ), ), ), ], ), )) ]); } @override Widget build(BuildContext context) { _uiHolder.attach(context); return MediaQuery.removePadding( context: context, removeTop: true, child: Scaffold( backgroundColor: subscriptionTheme.backgroundColor ?? colorScheme.backgroundColor ?? const Color(0xFF121212), body: _buildContent(context), ), ); } Widget _buildPolicy() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _textLink( text: 'privacyPolicy', // text: appStrings.privacyPolicy, url: GuruApp.instance.details.policyUrl, onAfterTap: () { GuruAnalytics.instance .logEventEx("settings", itemName: "privacy_clk"); }, ), Text( " & ", style: TextStyle( fontSize: designSpec.policyFontSize, color: const Color(0x66FFFFFF), fontWeight: GuruTheme.fwRegular, ), ), _textLink( text: 'termsOfService', // text: appStrings.termsOfService, url: GuruApp.instance.details.termsUrl, onAfterTap: () { GuruAnalytics.instance.logEventEx("settings", itemName: "tos_clk"); }, ) ], ); } Widget _textLink( {required String text, required String url, VoidCallback? onAfterTap}) { return TapWidget( child: AutoSizeText( text, stepGranularity: 0.1, minFontSize: 5, maxLines: 1, style: TextStyle( fontSize: designSpec.policyFontSize, color: const Color(0x66FFFFFF), decoration: TextDecoration.underline, fontWeight: GuruTheme.fwRegular, ), ), onTap: () { GuruPopup.instance.showDialog( widget: GuruWebviewPage(url: Uri.dataFromString(url), title: text), useSafeArea: false); onAfterTap?.call(); }, ); } }