mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
Cw 973 in app gift card redemption flow plus UI (#2285)
* refactor: cleanup cake_pay folder * refactor: add My Cards tab on the main screen * ui: add user card item * refactor: cakePay purchase flow * feat: add user card info * refactor: auth flow * refactor: theme data * revert * Update qr_widget.dart * fix: update with new themes * minor fixes * feat: put redemption flow under a feature flag * feat: implement simulated purchasing flow in debug mode * feat: support LTC and MWEB payments * Update savings_page.dart * fix: UI fixes * add Cake Pay transaction sent bottom sheet * localization * Update cake_pay_buy_card_page.dart * addressing review comments * fix marge conflicts
This commit is contained in:
@@ -3,28 +3,31 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AlertBackground extends StatelessWidget {
|
||||
AlertBackground({required this.child});
|
||||
const AlertBackground({Key? key, required this.child, this.dismissible = false});
|
||||
|
||||
final Widget child;
|
||||
final bool dismissible;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Container(
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
color: Colors.transparent,
|
||||
body: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: dismissible ? () => Navigator.of(context).pop() : null,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
||||
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.8)),
|
||||
decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface.withAlpha(200)),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
|
||||
child: child,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -43,7 +43,9 @@ class BaseTextFormField extends StatelessWidget {
|
||||
this.onFieldSubmitted,
|
||||
this.hasUnderlineBorder = false,
|
||||
this.borderWidth = 1.0,
|
||||
super.key,
|
||||
this.prefixIconConstraints,
|
||||
this.suffixIconConstraints,
|
||||
super.key, this.suffixText,
|
||||
});
|
||||
|
||||
final TextEditingController? controller;
|
||||
@@ -70,6 +72,9 @@ class BaseTextFormField extends StatelessWidget {
|
||||
final bool readOnly;
|
||||
final bool? enableInteractiveSelection;
|
||||
final String? initialValue;
|
||||
final BoxConstraints? prefixIconConstraints;
|
||||
final BoxConstraints? suffixIconConstraints;
|
||||
final String? suffixText;
|
||||
final void Function(String)? onSubmit;
|
||||
final bool obscureText;
|
||||
final bool? autofocus;
|
||||
@@ -120,8 +125,12 @@ class BaseTextFormField extends StatelessWidget {
|
||||
alignLabelWithHint: alignLabelWithHint,
|
||||
contentPadding: contentPadding,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
prefixIconConstraints: prefixIconConstraints ??
|
||||
const BoxConstraints(minWidth: 0, minHeight: 0),
|
||||
prefix: prefix,
|
||||
prefixIcon: prefixIcon,
|
||||
suffixIconConstraints: suffixIconConstraints ??
|
||||
const BoxConstraints(minWidth: 0, minHeight: 0),
|
||||
suffix: suffix,
|
||||
suffixIcon: suffixIcon,
|
||||
filled: !hasUnderlineBorder,
|
||||
|
||||
@@ -1,62 +1,53 @@
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_slide_button_widget.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum FooterType { none, slideActionButton, singleActionButton, doubleActionButton }
|
||||
|
||||
abstract class BaseBottomSheet extends StatelessWidget {
|
||||
const BaseBottomSheet({
|
||||
super.key,
|
||||
required this.titleText,
|
||||
this.titleIconPath,
|
||||
required this.footerType,
|
||||
this.currentTheme,
|
||||
this.slideActionButtonText,
|
||||
this.onSlideActionComplete,
|
||||
this.singleActionButtonText,
|
||||
this.accessibleNavigationModeSlideActionButtonText,
|
||||
this.onSingleActionButtonPressed,
|
||||
this.singleActionButtonKey,
|
||||
this.doubleActionLeftButtonText,
|
||||
this.doubleActionRightButtonText,
|
||||
this.onLeftActionButtonPressed,
|
||||
this.onRightActionButtonPressed,
|
||||
this.leftActionButtonKey,
|
||||
this.rightActionButtonKey,
|
||||
required this.maxHeight,
|
||||
}) : assert(footerType == FooterType.none || currentTheme != null,
|
||||
'currentTheme is required unless footerType is none');
|
||||
|
||||
final String titleText;
|
||||
final String? titleIconPath;
|
||||
final MaterialThemeBase? currentTheme;
|
||||
final FooterType footerType;
|
||||
final String? slideActionButtonText;
|
||||
final VoidCallback? onSlideActionComplete;
|
||||
final String? singleActionButtonText;
|
||||
final String? accessibleNavigationModeSlideActionButtonText;
|
||||
final VoidCallback? onSingleActionButtonPressed;
|
||||
final Key? singleActionButtonKey;
|
||||
final String? doubleActionLeftButtonText;
|
||||
final String? doubleActionRightButtonText;
|
||||
final VoidCallback? onLeftActionButtonPressed;
|
||||
final VoidCallback? onRightActionButtonPressed;
|
||||
final Key? leftActionButtonKey;
|
||||
final Key? rightActionButtonKey;
|
||||
final double maxHeight;
|
||||
|
||||
const BaseBottomSheet({required this.titleText, this.titleIconPath, this.maxHeight = 900});
|
||||
|
||||
Widget headerWidget(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(flex: 4),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (titleIconPath != null)
|
||||
Image.asset(titleIconPath!, height: 24, width: 24, excludeFromSemantics: true)
|
||||
else
|
||||
Container(),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
titleText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget contentWidget(BuildContext context);
|
||||
|
||||
Widget footerWidget(BuildContext context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
@@ -69,13 +60,106 @@ abstract class BaseBottomSheet extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
headerWidget(context),
|
||||
_buildHeader(context),
|
||||
contentWidget(context),
|
||||
footerWidget(context),
|
||||
_buildFooter(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context) => Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: 64,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (titleIconPath != null) ...[
|
||||
Image.asset(titleIconPath!, height: 24, width: 24),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
titleText,
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 13),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildFooter(BuildContext context) {
|
||||
switch (footerType) {
|
||||
case FooterType.none:
|
||||
return const SizedBox.shrink();
|
||||
|
||||
case FooterType.slideActionButton:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(40, 12, 40, 34),
|
||||
child: StandardSlideButton(
|
||||
buttonText: slideActionButtonText ?? '',
|
||||
onSlideComplete: onSlideActionComplete ?? () {},
|
||||
currentTheme: currentTheme!,
|
||||
accessibleNavigationModeButtonText: accessibleNavigationModeSlideActionButtonText ?? '',
|
||||
),
|
||||
);
|
||||
|
||||
case FooterType.singleActionButton:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
|
||||
child: LoadingPrimaryButton(
|
||||
key: singleActionButtonKey,
|
||||
text: singleActionButtonText ?? '',
|
||||
onPressed: onSingleActionButtonPressed ?? () {},
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
),
|
||||
);
|
||||
|
||||
case FooterType.doubleActionButton:
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 34),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
key: leftActionButtonKey,
|
||||
text: doubleActionLeftButtonText ?? '',
|
||||
onPressed: onLeftActionButtonPressed,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.onSecondaryContainer
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
key: rightActionButtonKey,
|
||||
text: doubleActionRightButtonText ?? '',
|
||||
onPressed: onRightActionButtonPressed,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
import 'package:cake_wallet/cake_pay/src/widgets/cake_pay_alert_modal.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/widgets/flip_card_widget.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/widgets/link_extractor.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cake_wallet/utils/image_utill.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'base_bottom_sheet_widget.dart';
|
||||
|
||||
class CakePayCardInfoBottomSheet extends BaseBottomSheet {
|
||||
CakePayCardInfoBottomSheet({
|
||||
required String titleText,
|
||||
required MaterialThemeBase currentTheme,
|
||||
required FooterType footerType,
|
||||
String? titleIconPath,
|
||||
String? singleActionButtonText,
|
||||
VoidCallback? onSingleActionButtonPressed,
|
||||
Key? singleActionButtonKey,
|
||||
String? doubleActionLeftButtonText,
|
||||
String? doubleActionRightButtonText,
|
||||
VoidCallback? onLeftActionButtonPressed,
|
||||
VoidCallback? onRightActionButtonPressed,
|
||||
Key? rightActionButtonKey,
|
||||
Key? leftActionButtonKey,
|
||||
required this.onUpdateBalancePressed,
|
||||
required this.isReloadable,
|
||||
required this.balance,
|
||||
this.contentImage,
|
||||
this.howToUse,
|
||||
this.applyBoxShadow = false,
|
||||
Key? key,
|
||||
}) : _currentTheme = currentTheme,
|
||||
super(
|
||||
titleText: titleText,
|
||||
maxHeight: 900,
|
||||
titleIconPath: titleIconPath,
|
||||
currentTheme: currentTheme,
|
||||
footerType: footerType,
|
||||
singleActionButtonText: singleActionButtonText,
|
||||
onSingleActionButtonPressed: onSingleActionButtonPressed,
|
||||
singleActionButtonKey: singleActionButtonKey,
|
||||
doubleActionLeftButtonText: doubleActionLeftButtonText,
|
||||
doubleActionRightButtonText: doubleActionRightButtonText,
|
||||
onLeftActionButtonPressed: onLeftActionButtonPressed,
|
||||
onRightActionButtonPressed: onRightActionButtonPressed,
|
||||
leftActionButtonKey: leftActionButtonKey,
|
||||
rightActionButtonKey: rightActionButtonKey,
|
||||
key: key);
|
||||
|
||||
final VoidCallback onUpdateBalancePressed;
|
||||
final MaterialThemeBase _currentTheme;
|
||||
final String? contentImage;
|
||||
final String? howToUse;
|
||||
final bool isReloadable;
|
||||
final String balance;
|
||||
final bool applyBoxShadow;
|
||||
|
||||
final _cardKey = GlobalKey<FlipCardState>();
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
final itemTitleTextStyle = Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
final itemSubTitleTextStyle = Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
|
||||
final tileBackgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (contentImage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 16),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.6,
|
||||
child: FlipCard(
|
||||
key: _cardKey,
|
||||
flipOnTouch: true,
|
||||
front: _buildCardImage(context, contentImage!, applyBoxShadow),
|
||||
back: _buildBarcodeSide(context,
|
||||
cardNumber: '6006491979836784204', pin: '4782'),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () => _cardKey.currentState?.toggleCard(),
|
||||
child: Container(
|
||||
height: 43,
|
||||
width: 43,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 1),
|
||||
color: Colors.white.withAlpha(75),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Transform.scale(
|
||||
scale: .8,
|
||||
child: const ImageIcon(
|
||||
AssetImage('assets/images/transfer.png'),
|
||||
color: Colors.white,
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(),
|
||||
Text(
|
||||
'Tap card to show details',
|
||||
style: itemTitleTextStyle.copyWith(fontSize: 12, fontWeight: FontWeight.w700),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 34),
|
||||
CakePayInfoTile(
|
||||
isReloadable: isReloadable,
|
||||
itemValue: balance,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
tileBackgroundColor: tileBackgroundColor),
|
||||
const SizedBox(height: 8),
|
||||
_HowToUseTile(howToUse: howToUse ?? '', tileBackgroundColor: tileBackgroundColor),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HowToUseTile extends StatelessWidget {
|
||||
const _HowToUseTile({required this.howToUse, required this.tileBackgroundColor});
|
||||
|
||||
final String howToUse;
|
||||
final Color tileBackgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () => _showHowToUseCard(context: context, howToUse: howToUse),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).how_to_use_card,
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color!,
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CakePayInfoTile extends StatelessWidget {
|
||||
const CakePayInfoTile({
|
||||
super.key,
|
||||
required this.isReloadable,
|
||||
required this.itemValue,
|
||||
required this.itemTitleTextStyle,
|
||||
this.itemSubTitle,
|
||||
required this.itemSubTitleTextStyle,
|
||||
required this.tileBackgroundColor,
|
||||
});
|
||||
|
||||
final bool isReloadable;
|
||||
final String itemValue;
|
||||
final TextStyle itemTitleTextStyle;
|
||||
final String? itemSubTitle;
|
||||
final TextStyle itemSubTitleTextStyle;
|
||||
final Color tileBackgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
container: true,
|
||||
label: isReloadable ? 'Balance' : 'Total Value',
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(isReloadable ? 'Balance' : 'Total Value', style: itemTitleTextStyle),
|
||||
Text(itemValue,
|
||||
style: itemTitleTextStyle.copyWith(fontSize: 18, fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
child: Text(isReloadable ? 'Top Up Balance' : 'Manually update Balance',
|
||||
style: itemTitleTextStyle.copyWith(fontSize: 12, fontWeight: FontWeight.w600)),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: Theme.of(context).dialogBackgroundColor)),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showHowToUseCard({required BuildContext context, String? howToUse}) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CakePayAlertModal(
|
||||
title: S.of(context).how_to_use_card,
|
||||
content: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
ClickableLinksText(
|
||||
text: howToUse ?? '',
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium!,
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color!,
|
||||
fontSize: 18,
|
||||
fontStyle: FontStyle.italic,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
]),
|
||||
actionTitle: S.current.got_it,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCardImage(BuildContext ctx, String path, bool addShadow) {
|
||||
final border = BorderRadius.circular(10);
|
||||
|
||||
return Container(
|
||||
decoration: addShadow
|
||||
? BoxDecoration(
|
||||
borderRadius: border,
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withAlpha(150), blurRadius: 5)
|
||||
],
|
||||
)
|
||||
: null,
|
||||
child: ClipRRect(
|
||||
borderRadius: border,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Container(color: Theme.of(ctx).cardColor.withAlpha(200)),
|
||||
ImageUtil.getImageFromPath(
|
||||
imagePath: path,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBarcodeSide(BuildContext context, {required String cardNumber, required String pin}) =>
|
||||
SizedBox.expand(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 34),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor.withAlpha(200),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withAlpha(150), blurRadius: 5)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color!.withOpacity(.1),
|
||||
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Gift Card Number',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color.fromRGBO(207, 207, 207, 1))),
|
||||
const SizedBox(height: 4),
|
||||
Text(cardNumber,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color.fromRGBO(146, 146, 146, 1))),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('PIN Number',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color.fromRGBO(207, 207, 207, 1))),
|
||||
const SizedBox(height: 4),
|
||||
Text(pin,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Lato',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color.fromRGBO(146, 146, 146, 1))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,239 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/utils/image_utill.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CakePayTransactionSentBottomSheet extends StatelessWidget {
|
||||
const CakePayTransactionSentBottomSheet({
|
||||
super.key,
|
||||
required this.titleText,
|
||||
required this.currency,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
required this.output,
|
||||
required this.fee,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
this.titleIconWidget,
|
||||
required this.quantity,
|
||||
required this.onClose,
|
||||
required this.paymentId,
|
||||
required this.paymentIdValue,
|
||||
});
|
||||
|
||||
final String titleText;
|
||||
final Widget? titleIconWidget;
|
||||
final CryptoCurrency currency;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final Output output;
|
||||
final String fee;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final String quantity;
|
||||
final VoidCallback onClose;
|
||||
final String paymentId;
|
||||
final String paymentIdValue;
|
||||
|
||||
TextStyle _titleStyle(BuildContext ctx) => Theme.of(ctx).textTheme.bodyLarge!;
|
||||
|
||||
TextStyle _valueStyle(BuildContext ctx) => Theme.of(ctx).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
TextStyle _subStyle(BuildContext ctx) => Theme.of(ctx).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(ctx).colorScheme.onSurfaceVariant,
|
||||
);
|
||||
|
||||
Widget _buildHeader(BuildContext ctx) => Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: 64,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(ctx).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (titleIconWidget != null) titleIconWidget!,
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
titleText,
|
||||
style: Theme.of(ctx).textTheme.titleLarge!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildBody(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
_StandardTile(
|
||||
itemTitle: amount,
|
||||
titleStyle: _titleStyle(context),
|
||||
itemValue: '$amountValue ${currency.title}',
|
||||
itemValueStyle: _valueStyle(context),
|
||||
itemSubTitle: fiatAmountValue,
|
||||
itemSubTitleStyle: _subStyle(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_StandardTile(
|
||||
itemTitle: fee,
|
||||
titleStyle: _titleStyle(context),
|
||||
itemValue: feeValue,
|
||||
itemValueStyle: _valueStyle(context),
|
||||
itemSubTitle: feeFiatAmount,
|
||||
itemSubTitleStyle: _subStyle(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_StandardTile(
|
||||
itemTitle: output.parsedAddress.profileName,
|
||||
titleStyle: _titleStyle(context),
|
||||
itemValue: output.fiatAmount,
|
||||
itemValueStyle: _valueStyle(context),
|
||||
itemSubTitle: quantity,
|
||||
itemSubTitleStyle: _subStyle(context),
|
||||
imagePath: output.parsedAddress.profileImageUrl,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(paymentId + ': ' + paymentIdValue,
|
||||
style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 18),
|
||||
Image.asset('assets/images/envelope.png'),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
S.of(context).cake_pay_card_email_delivered_message,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
PrimaryButton(
|
||||
text: S.of(context).close,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
onPressed: onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.9;
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildHeader(context),
|
||||
_buildBody(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StandardTile extends StatelessWidget {
|
||||
const _StandardTile({
|
||||
required this.itemTitle,
|
||||
required this.titleStyle,
|
||||
required this.itemValue,
|
||||
required this.itemValueStyle,
|
||||
this.itemSubTitle,
|
||||
this.itemSubTitleStyle,
|
||||
this.imagePath,
|
||||
});
|
||||
|
||||
final String itemTitle;
|
||||
final TextStyle titleStyle;
|
||||
final String itemValue;
|
||||
final TextStyle itemValueStyle;
|
||||
final String? itemSubTitle;
|
||||
final TextStyle? itemSubTitleStyle;
|
||||
final String? imagePath;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
container: true,
|
||||
label: itemTitle,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerLowest.withAlpha(80)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
if (imagePath != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: ImageUtil.getImageFromPath(
|
||||
imagePath: imagePath!, height: 40, width: 40),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
itemTitle,
|
||||
style: titleStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(itemValue, style: itemValueStyle.copyWith(height: 1.0)),
|
||||
if (itemSubTitle != null) Text(itemSubTitle!, style: itemSubTitleStyle),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,29 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_slide_button_widget.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
|
||||
import 'package:cake_wallet/utils/address_formatter.dart';
|
||||
import 'package:cake_wallet/utils/image_utill.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
import 'base_bottom_sheet_widget.dart';
|
||||
|
||||
class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
final CryptoCurrency currency;
|
||||
final MaterialThemeBase currentTheme;
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final String fee;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final String? explanation;
|
||||
final List<Output> outputs;
|
||||
final VoidCallback onSlideComplete;
|
||||
final WalletType walletType;
|
||||
final PendingChange? change;
|
||||
final bool isOpenCryptoPay;
|
||||
|
||||
ConfirmSendingBottomSheet({
|
||||
required String titleText,
|
||||
required MaterialThemeBase currentTheme,
|
||||
required FooterType footerType,
|
||||
String? titleIconPath,
|
||||
String? slideActionButtonText,
|
||||
VoidCallback? onSlideActionComplete,
|
||||
String? accessibleNavigationModeSlideActionButtonText,
|
||||
required this.currency,
|
||||
required this.currentTheme,
|
||||
this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
@@ -44,14 +34,45 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs,
|
||||
required this.onSlideComplete,
|
||||
required this.walletType,
|
||||
this.change,
|
||||
this.explanation,
|
||||
this.isOpenCryptoPay = false,
|
||||
this.cakePayBuyCardViewModel,
|
||||
this.quantity,
|
||||
Key? key,
|
||||
}) : showScrollbar = outputs.length > 3,
|
||||
super(titleText: titleText, titleIconPath: titleIconPath);
|
||||
_currentTheme = currentTheme,
|
||||
super(
|
||||
titleText: titleText,
|
||||
maxHeight: 900,
|
||||
titleIconPath: titleIconPath,
|
||||
currentTheme: currentTheme,
|
||||
footerType: footerType,
|
||||
slideActionButtonText: slideActionButtonText ?? 'Swipe to send',
|
||||
onSlideActionComplete: onSlideActionComplete,
|
||||
accessibleNavigationModeSlideActionButtonText:
|
||||
accessibleNavigationModeSlideActionButtonText,
|
||||
key: key);
|
||||
|
||||
final CryptoCurrency currency;
|
||||
final MaterialThemeBase _currentTheme;
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final String fee;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final WalletType walletType;
|
||||
final PendingChange? change;
|
||||
final bool isOpenCryptoPay;
|
||||
final CakePayBuyCardViewModel? cakePayBuyCardViewModel;
|
||||
final String? quantity;
|
||||
final String? explanation;
|
||||
|
||||
final bool showScrollbar;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
@@ -69,25 +90,32 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
|
||||
final tileBackgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||
final tileBackgroundColor = _currentTheme.isDark
|
||||
? CustomThemeColors.backgroundGradientColorDark.withAlpha(140)
|
||||
: CustomThemeColors.cardGradientColorPrimaryLight;
|
||||
|
||||
Widget content = Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 0, showScrollbar ? 16 : 8, 8),
|
||||
child: Column(
|
||||
children: [
|
||||
if (paymentId != null && paymentIdValue != null)
|
||||
if (paymentId != null && paymentIdValue != null && cakePayBuyCardViewModel != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: AddressTile(
|
||||
itemTitle: paymentId!,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
walletType: walletType,
|
||||
isBatchSending: false,
|
||||
amount: '',
|
||||
address: paymentIdValue!,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
tileBackgroundColor: tileBackgroundColor,
|
||||
),
|
||||
child: Observer(
|
||||
builder: (_) => AddressTile(
|
||||
itemTitle: paymentId!,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
amountTextStyle: itemSubTitleTextStyle,
|
||||
walletType: walletType,
|
||||
amount: expirationTime != null
|
||||
? S.current.offer_expires_in +
|
||||
' ${cakePayBuyCardViewModel!.formattedRemainingTime}'
|
||||
: null,
|
||||
address: paymentIdValue!,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
tileBackgroundColor: tileBackgroundColor,
|
||||
applyAddressFormatting: false,
|
||||
)),
|
||||
),
|
||||
if (explanation != null)
|
||||
Padding(
|
||||
@@ -128,12 +156,13 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
final bool isBatchSending = outputs.length > 1;
|
||||
final item = outputs[index];
|
||||
final contactName = item.parsedAddress.name;
|
||||
final isCakePayName = contactName == 'Cake Pay';
|
||||
final batchContactTitle =
|
||||
'${index + 1}/${outputs.length} - ${contactName.isEmpty ? 'Address' : contactName}';
|
||||
final _address = item.isParsedAddress ? item.extractedAddress : item.address;
|
||||
final _amount = item.cryptoAmount.replaceAll(',', '.') + ' ${currency.title}';
|
||||
return isBatchSending || contactName.isNotEmpty
|
||||
? AddressExpansionTile(
|
||||
return isBatchSending || (contactName.isNotEmpty && !isCakePayName)
|
||||
? ExpansionAddressTile(
|
||||
contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact,
|
||||
name: isBatchSending ? batchContactTitle : contactName,
|
||||
address: _address,
|
||||
@@ -145,12 +174,15 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
tileBackgroundColor: tileBackgroundColor,
|
||||
)
|
||||
: AddressTile(
|
||||
itemTitle: S.of(context).address,
|
||||
itemTitle: isCakePayName
|
||||
? item.parsedAddress.profileName
|
||||
: S.of(context).address,
|
||||
imagePath: isCakePayName ? item.parsedAddress.profileImageUrl : null,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
isBatchSending: isBatchSending,
|
||||
walletType: walletType,
|
||||
amount: _amount,
|
||||
amount: isCakePayName ? item.fiatAmount : _amount,
|
||||
address: _address,
|
||||
itemSubTitle: isCakePayName ? quantity : null,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
tileBackgroundColor: tileBackgroundColor,
|
||||
);
|
||||
@@ -159,7 +191,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
if (change != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: AddressExpansionTile(
|
||||
child: ExpansionAddressTile(
|
||||
contactType: 'Change',
|
||||
name: S.of(context).send_change_to_you,
|
||||
address: change!.address,
|
||||
@@ -195,31 +227,6 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget footerWidget(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(40, 12, 40, 34),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
boxShadow: [
|
||||
if (showScrollbar)
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
spreadRadius: 2,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: StandardSlideButton(
|
||||
onSlideComplete: onSlideComplete,
|
||||
buttonText: 'Swipe to send',
|
||||
currentTheme: currentTheme,
|
||||
accessibleNavigationModeButtonText: S.of(context).send,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardTile extends StatelessWidget {
|
||||
@@ -304,22 +311,28 @@ class AddressTile extends StatelessWidget {
|
||||
super.key,
|
||||
required this.itemTitle,
|
||||
required this.itemTitleTextStyle,
|
||||
required this.isBatchSending,
|
||||
required this.amount,
|
||||
required this.address,
|
||||
required this.itemSubTitleTextStyle,
|
||||
required this.tileBackgroundColor,
|
||||
required this.walletType,
|
||||
this.amountTextStyle,
|
||||
this.applyAddressFormatting = true,
|
||||
this.imagePath,
|
||||
this.amount,
|
||||
this.itemSubTitle,
|
||||
});
|
||||
|
||||
final String itemTitle;
|
||||
final TextStyle itemTitleTextStyle;
|
||||
final bool isBatchSending;
|
||||
final String amount;
|
||||
final String? amount;
|
||||
final String address;
|
||||
final TextStyle itemSubTitleTextStyle;
|
||||
final TextStyle? amountTextStyle;
|
||||
final Color tileBackgroundColor;
|
||||
final WalletType walletType;
|
||||
final bool applyAddressFormatting;
|
||||
final String? imagePath;
|
||||
final String? itemSubTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -335,26 +348,65 @@ class AddressTile extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(itemTitle, style: itemTitleTextStyle),
|
||||
if (isBatchSending) Text(amount, style: itemTitleTextStyle),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
if (imagePath != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: ImageUtil.getImageFromPath(
|
||||
imagePath: imagePath!, height: 40, width: 40),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
itemTitle,
|
||||
style: itemTitleTextStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (amount != null) Text(amount!, style: amountTextStyle ?? itemTitleTextStyle),
|
||||
],
|
||||
),
|
||||
AddressFormatter.buildSegmentedAddress(
|
||||
address: address,
|
||||
walletType: walletType,
|
||||
evenTextStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
address.isEmpty
|
||||
? Container()
|
||||
: applyAddressFormatting
|
||||
? AddressFormatter.buildSegmentedAddress(
|
||||
address: address,
|
||||
walletType: walletType,
|
||||
evenTextStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
))
|
||||
: Text(
|
||||
address,
|
||||
style: itemTitleTextStyle,
|
||||
),
|
||||
itemSubTitle == null
|
||||
? Container()
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(itemSubTitle!, style: itemSubTitleTextStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressExpansionTile extends StatelessWidget {
|
||||
const AddressExpansionTile({
|
||||
class ExpansionAddressTile extends StatelessWidget {
|
||||
const ExpansionAddressTile({
|
||||
super.key,
|
||||
required this.contactType,
|
||||
required this.name,
|
||||
@@ -425,7 +477,6 @@ class AddressExpansionTile extends StatelessWidget {
|
||||
walletType: walletType,
|
||||
evenTextStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,74 +1,96 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/src/widgets/simple_checkbox.dart';
|
||||
|
||||
|
||||
|
||||
import 'package:cake_wallet/utils/image_utill.dart';
|
||||
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
|
||||
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import 'base_bottom_sheet_widget.dart';
|
||||
|
||||
class LoadingBottomSheet extends BaseBottomSheet {
|
||||
LoadingBottomSheet({required String titleText, String? titleIconPath})
|
||||
: super(titleText: titleText, titleIconPath: titleIconPath);
|
||||
LoadingBottomSheet(
|
||||
{required String titleText, String? titleIconPath})
|
||||
: super(titleText: titleText, titleIconPath: titleIconPath, footerType: FooterType.none, maxHeight: 900);
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
height: 300,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget footerWidget(BuildContext context) => const SizedBox(height: 94);
|
||||
}
|
||||
|
||||
class InfoBottomSheet extends BaseBottomSheet {
|
||||
final MaterialThemeBase currentTheme;
|
||||
final String? contentImage;
|
||||
final Color? contentImageColor;
|
||||
final String? content;
|
||||
final bool isTwoAction;
|
||||
final bool showDontAskMeCheckbox;
|
||||
final bool showDisclaimerText;
|
||||
final Function(bool)? onCheckboxChanged;
|
||||
final String? actionButtonText;
|
||||
final VoidCallback? actionButton;
|
||||
final Key? actionButtonKey;
|
||||
final String? leftButtonText;
|
||||
final String? rightButtonText;
|
||||
final VoidCallback? actionLeftButton;
|
||||
final VoidCallback? actionRightButton;
|
||||
final Key? rightActionButtonKey;
|
||||
final Key? leftActionButtonKey;
|
||||
final double height;
|
||||
final double? contentImageSize;
|
||||
|
||||
InfoBottomSheet({
|
||||
required String titleText,
|
||||
String? titleIconPath,
|
||||
required this.currentTheme,
|
||||
required this.footerType,
|
||||
this.contentImage,
|
||||
this.contentImageColor,
|
||||
this.contentImageSize,
|
||||
this.content,
|
||||
this.isTwoAction = false,
|
||||
this.showDontAskMeCheckbox = false,
|
||||
this.showDisclaimerText = false,
|
||||
this.height = 200,
|
||||
this.onCheckboxChanged,
|
||||
this.actionButtonText,
|
||||
this.actionButton,
|
||||
this.actionButtonKey,
|
||||
this.leftButtonText,
|
||||
this.rightButtonText,
|
||||
this.actionLeftButton,
|
||||
this.actionRightButton,
|
||||
this.rightActionButtonKey,
|
||||
this.content,
|
||||
this.bottomActionPanel,
|
||||
this.singleActionButtonText,
|
||||
this.onSingleActionButtonPressed,
|
||||
this.singleActionButtonKey,
|
||||
this.doubleActionLeftButtonText,
|
||||
this.doubleActionRightButtonText,
|
||||
this.onLeftActionButtonPressed,
|
||||
this.onRightActionButtonPressed,
|
||||
this.leftActionButtonKey,
|
||||
double maxHeight = 900,
|
||||
}) : super(titleText: titleText, titleIconPath: titleIconPath, maxHeight: maxHeight);
|
||||
this.rightActionButtonKey,
|
||||
this.showDisclaimerText = true,
|
||||
Key? key,
|
||||
}) : super(
|
||||
titleText: titleText,
|
||||
titleIconPath: titleIconPath,
|
||||
maxHeight: 900,
|
||||
currentTheme: currentTheme,
|
||||
footerType: footerType,
|
||||
singleActionButtonText: singleActionButtonText,
|
||||
onSingleActionButtonPressed: onSingleActionButtonPressed,
|
||||
singleActionButtonKey: singleActionButtonKey,
|
||||
doubleActionLeftButtonText: doubleActionLeftButtonText,
|
||||
doubleActionRightButtonText: doubleActionRightButtonText,
|
||||
onLeftActionButtonPressed: onLeftActionButtonPressed,
|
||||
onRightActionButtonPressed: onRightActionButtonPressed,
|
||||
leftActionButtonKey: leftActionButtonKey,
|
||||
rightActionButtonKey: rightActionButtonKey,
|
||||
key: key);
|
||||
|
||||
final MaterialThemeBase currentTheme;
|
||||
final FooterType footerType;
|
||||
final String? contentImage;
|
||||
final Color? contentImageColor;
|
||||
final String? content;
|
||||
final Widget? bottomActionPanel;
|
||||
final String? singleActionButtonText;
|
||||
final VoidCallback? onSingleActionButtonPressed;
|
||||
final Key? singleActionButtonKey;
|
||||
final String? doubleActionLeftButtonText;
|
||||
final String? doubleActionRightButtonText;
|
||||
final VoidCallback? onLeftActionButtonPressed;
|
||||
final VoidCallback? onRightActionButtonPressed;
|
||||
final Key? rightActionButtonKey;
|
||||
final Key? leftActionButtonKey;
|
||||
final double height;
|
||||
final double? contentImageSize;
|
||||
final bool showDisclaimerText;
|
||||
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
@@ -79,9 +101,11 @@ class InfoBottomSheet extends BaseBottomSheet {
|
||||
if (contentImage != null)
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: SizedBox(
|
||||
width: contentImageSize,
|
||||
child: getImage(contentImage!, imageColor: contentImageColor),
|
||||
child: ImageUtil.getImageFromPath(
|
||||
imagePath: contentImage!,
|
||||
svgImageColor: contentImageColor,
|
||||
fit: BoxFit.contain,
|
||||
borderRadius: 10,
|
||||
),
|
||||
)
|
||||
else
|
||||
@@ -110,6 +134,7 @@ class InfoBottomSheet extends BaseBottomSheet {
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomActionPanel ?? const SizedBox(),
|
||||
if (showDisclaimerText)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 10),
|
||||
@@ -141,123 +166,8 @@ class InfoBottomSheet extends BaseBottomSheet {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDontAskMeCheckbox)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 34),
|
||||
child: Row(
|
||||
children: [
|
||||
SimpleCheckbox(onChanged: onCheckboxChanged),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Don’t ask me next time',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget footerWidget(BuildContext context) {
|
||||
if (isTwoAction) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 34),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(right: 8.0, top: 8.0),
|
||||
child: PrimaryButton(
|
||||
key: leftActionButtonKey,
|
||||
onPressed: actionLeftButton,
|
||||
text: leftButtonText ?? '',
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 8.0, top: 8.0),
|
||||
child: PrimaryButton(
|
||||
key: rightActionButtonKey,
|
||||
onPressed: actionRightButton,
|
||||
text: rightButtonText ?? '',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
|
||||
child: LoadingPrimaryButton(
|
||||
key: actionButtonKey,
|
||||
onPressed: actionButton ?? () {},
|
||||
text: actionButtonText ?? '',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget getImage(String imagePath, {Color? imageColor}) {
|
||||
final bool isSvg = imagePath.endsWith('.svg');
|
||||
if (isSvg) {
|
||||
return SvgPicture.asset(
|
||||
imagePath,
|
||||
colorFilter: imageColor != null ? ColorFilter.mode(imageColor, BlendMode.srcIn) : null,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(imagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleCheckbox extends StatefulWidget {
|
||||
SimpleCheckbox({this.onChanged});
|
||||
|
||||
final Function(bool)? onChanged;
|
||||
|
||||
@override
|
||||
State<SimpleCheckbox> createState() => _SimpleCheckboxState();
|
||||
}
|
||||
|
||||
class _SimpleCheckboxState extends State<SimpleCheckbox> {
|
||||
bool initialValue = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
child: Checkbox(
|
||||
value: initialValue,
|
||||
onChanged: (value) => setState(() {
|
||||
initialValue = value!;
|
||||
widget.onChanged?.call(value);
|
||||
}),
|
||||
checkColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
activeColor: Colors.transparent,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: WidgetStateBorderSide.resolveWith((states) =>
|
||||
BorderSide(color: Theme.of(context).colorScheme.onSurfaceVariant, width: 1.0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class InfoStepsBottomSheet extends BaseBottomSheet {
|
||||
required this.steps,
|
||||
String? titleIconPath,
|
||||
required this.currentTheme,
|
||||
}) : super(titleText: titleText, titleIconPath: titleIconPath);
|
||||
}) : super(titleText: titleText, titleIconPath: titleIconPath, footerType: FooterType.none, maxHeight: 900);
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) => SizedBox(
|
||||
|
||||
@@ -56,67 +56,47 @@ class _NumberTextFieldState extends State<NumberTextField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TextField(
|
||||
style: textMediumSemiBold(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
enableInteractiveSelection: false,
|
||||
textAlign: TextAlign.center,
|
||||
textAlignVertical: TextAlignVertical.bottom,
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
|
||||
decoration: InputDecoration(
|
||||
style: Theme.of(context).textTheme.titleMedium!,
|
||||
enableInteractiveSelection: false,
|
||||
textAlign: TextAlign.center,
|
||||
textAlignVertical: TextAlignVertical.bottom,
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
fillColor: Colors.transparent,
|
||||
counterText: '',
|
||||
isDense: true,
|
||||
filled: true,
|
||||
suffixIconConstraints: BoxConstraints(
|
||||
maxHeight: widget.arrowsHeight,
|
||||
maxWidth: widget.arrowsWidth + widget.contentPadding.right,
|
||||
),
|
||||
prefixIconConstraints: BoxConstraints(
|
||||
maxHeight: widget.arrowsHeight,
|
||||
maxWidth: widget.arrowsWidth + widget.contentPadding.left,
|
||||
),
|
||||
suffixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
|
||||
prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
|
||||
prefixIcon: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: widget.arrowsWidth,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Icon(
|
||||
Icons.arrow_left_outlined,
|
||||
size: widget.arrowsWidth,
|
||||
),
|
||||
),
|
||||
onTap: _canGoDown ? () => _update(false) : null,
|
||||
),
|
||||
),
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: widget.arrowsWidth,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Icon(Icons.keyboard_arrow_left_outlined ,size: widget.arrowsWidth)),
|
||||
onTap: _canGoDown ? () => _update(false) : null)),
|
||||
suffixIcon: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: widget.arrowsWidth,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Icon(
|
||||
Icons.arrow_right_outlined,
|
||||
size: widget.arrowsWidth,
|
||||
),
|
||||
),
|
||||
onTap: _canGoUp ? () => _update(true) : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
maxLines: 1,
|
||||
onChanged: (value) {
|
||||
final intValue = int.tryParse(value);
|
||||
widget.onChanged?.call(intValue);
|
||||
_updateArrows(intValue);
|
||||
},
|
||||
inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)],
|
||||
);
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: widget.arrowsWidth,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Icon(Icons.keyboard_arrow_right_outlined, size: widget.arrowsWidth)),
|
||||
onTap: _canGoUp ? () => _update(true) : null))),
|
||||
maxLines: 1,
|
||||
onChanged: (value) {
|
||||
final intValue = int.tryParse(value);
|
||||
widget.onChanged?.call(intValue);
|
||||
_updateArrows(intValue);
|
||||
},
|
||||
inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)]);
|
||||
|
||||
void _update(bool up) {
|
||||
var intValue = int.tryParse(_controller.text);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RoundedCheckbox extends StatelessWidget {
|
||||
RoundedCheckbox({Key? key, required this.value}) : super(key: key);
|
||||
|
||||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return value
|
||||
? Container(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(50.0)),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
size: 14.0,
|
||||
))
|
||||
: Offstage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SimpleCheckbox extends StatefulWidget {
|
||||
SimpleCheckbox({this.onChanged});
|
||||
|
||||
final Function(bool)? onChanged;
|
||||
|
||||
@override
|
||||
State<SimpleCheckbox> createState() => _SimpleCheckboxState();
|
||||
}
|
||||
|
||||
class _SimpleCheckboxState extends State<SimpleCheckbox> {
|
||||
bool initialValue = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
child: Checkbox(
|
||||
value: initialValue,
|
||||
onChanged: (value) => setState(() {
|
||||
initialValue = value!;
|
||||
widget.onChanged?.call(value);
|
||||
}),
|
||||
checkColor: Theme.of(context).textTheme.titleLarge!.color,
|
||||
activeColor: Colors.transparent,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: WidgetStateBorderSide.resolveWith((states) => BorderSide(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color!, width: 1.0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StandardSlideButton extends StatefulWidget {
|
||||
@@ -29,7 +30,9 @@ class _StandardSlideButtonState extends State<StandardSlideButton> {
|
||||
Widget build(BuildContext context) {
|
||||
final bool accessible = MediaQuery.of(context).accessibleNavigation;
|
||||
|
||||
final tileBackgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||
final tileBackgroundColor = widget.currentTheme.isDark
|
||||
? CustomThemeColors.backgroundGradientColorDark
|
||||
: CustomThemeColors.backgroundGradientColorLight;
|
||||
|
||||
return accessible
|
||||
? PrimaryButton(
|
||||
@@ -87,7 +90,7 @@ class _StandardSlideButtonState extends State<StandardSlideButton> {
|
||||
height: widget.height - 8,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TabViewWrapper extends StatefulWidget {
|
||||
const TabViewWrapper({
|
||||
super.key,
|
||||
required this.tabs,
|
||||
required this.views,
|
||||
this.tabBarPadding = const EdgeInsets.only(right: 24),
|
||||
this.labelStyle,
|
||||
this.unselectedLabelStyle,
|
||||
this.indicatorColor,
|
||||
}) : assert(tabs.length == views.length, 'Tabs and views must be of equal length.');
|
||||
|
||||
final List<Tab> tabs;
|
||||
final List<Widget> views;
|
||||
final EdgeInsets tabBarPadding;
|
||||
final TextStyle? labelStyle;
|
||||
final TextStyle? unselectedLabelStyle;
|
||||
final Color? indicatorColor;
|
||||
|
||||
@override
|
||||
State<TabViewWrapper> createState() => _TabViewWrapperState();
|
||||
}
|
||||
|
||||
class _TabViewWrapperState extends State<TabViewWrapper> with SingleTickerProviderStateMixin {
|
||||
late final TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: widget.tabs.length, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = TextStyle(
|
||||
fontSize: 18,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: true,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
labelStyle: widget.labelStyle ?? textStyle,
|
||||
unselectedLabelStyle: widget.unselectedLabelStyle ??
|
||||
textStyle.copyWith(color: textStyle.color?.withAlpha(150)),
|
||||
labelColor: widget.labelStyle?.color ?? textStyle.color,
|
||||
indicatorColor: widget.indicatorColor,
|
||||
indicatorPadding: EdgeInsets.zero,
|
||||
labelPadding: widget.tabBarPadding,
|
||||
tabAlignment: TabAlignment.start,
|
||||
dividerColor: Colors.transparent,
|
||||
padding: EdgeInsets.zero,
|
||||
tabs: widget.tabs,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: widget.views,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user