414 lines
12 KiB
Dart
414 lines
12 KiB
Dart
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>(
|
|
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<SubscriptionController> with _UIData {
|
|
final List<SubscriptionCardItem> 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<Widget> 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<int>(
|
|
stream: controller.observableSelectProductIndex,
|
|
builder: (context, snapshot) {
|
|
final int index = snapshot.data ?? 1;
|
|
return _buildPremiumCards(context, index);
|
|
},
|
|
),
|
|
StreamBuilder<int>(
|
|
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();
|
|
},
|
|
);
|
|
}
|
|
}
|