782 lines
22 KiB
Dart
782 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||
import '../../../../routes/route_paths.dart';
|
||
import '../providers/auth_provider.dart';
|
||
|
||
/// 向导页数据模型
|
||
class GuidePageData {
|
||
final String? imagePath;
|
||
final String title;
|
||
final String subtitle;
|
||
final Widget? customContent;
|
||
|
||
const GuidePageData({
|
||
this.imagePath,
|
||
required this.title,
|
||
required this.subtitle,
|
||
this.customContent,
|
||
});
|
||
}
|
||
|
||
/// 向导页面 - 用户首次打开应用时展示
|
||
/// 支持左右滑动切换页面
|
||
class GuidePage extends ConsumerStatefulWidget {
|
||
const GuidePage({super.key});
|
||
|
||
@override
|
||
ConsumerState<GuidePage> createState() => _GuidePageState();
|
||
}
|
||
|
||
class _GuidePageState extends ConsumerState<GuidePage> {
|
||
final PageController _pageController = PageController();
|
||
int _currentPage = 0;
|
||
|
||
// 向导页1-4的数据
|
||
final List<GuidePageData> _guidePages = const [
|
||
GuidePageData(
|
||
imagePath: 'assets/images/guide_1.png',
|
||
title: '认种一棵榴莲树\n拥有真实RWA资产',
|
||
subtitle: '绑定真实果园20年收益,让区块链与农业完美结合',
|
||
),
|
||
GuidePageData(
|
||
imagePath: 'assets/images/guide_2.png',
|
||
title: '认种即可开启算力\n自动挖矿持续收益',
|
||
subtitle: '每一棵树都对应真实资产注入,为算力提供真实价值支撑',
|
||
),
|
||
GuidePageData(
|
||
imagePath: 'assets/images/guide_3.png',
|
||
title: '分享链接\n获得团队算力与收益',
|
||
subtitle: '真实认种数据透明可信 · 团队越大算力越强',
|
||
),
|
||
GuidePageData(
|
||
imagePath: 'assets/images/guide_4.png',
|
||
title: 'MPC多方安全\n所有地址与收益可审计',
|
||
subtitle: '你的资产 · 安全透明 · 不可被篡改',
|
||
),
|
||
];
|
||
|
||
@override
|
||
void dispose() {
|
||
_pageController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _onPageChanged(int page) {
|
||
setState(() {
|
||
_currentPage = page;
|
||
});
|
||
}
|
||
|
||
void _goToNextPage() {
|
||
if (_currentPage < 4) {
|
||
_pageController.nextPage(
|
||
duration: const Duration(milliseconds: 300),
|
||
curve: Curves.easeInOut,
|
||
);
|
||
}
|
||
}
|
||
|
||
void _goToOnboarding() async {
|
||
// 标记已查看向导页
|
||
await ref.read(authProvider.notifier).markGuideAsSeen();
|
||
if (!mounted) return;
|
||
context.go(RoutePaths.onboarding);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: Colors.white,
|
||
body: SafeArea(
|
||
child: PageView.builder(
|
||
controller: _pageController,
|
||
onPageChanged: _onPageChanged,
|
||
itemCount: 5, // 4个介绍页 + 1个欢迎加入页
|
||
itemBuilder: (context, index) {
|
||
if (index < 4) {
|
||
return _buildGuidePage(_guidePages[index], index);
|
||
} else {
|
||
return _buildWelcomePage();
|
||
}
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建向导页 (页面1-4)
|
||
Widget _buildGuidePage(GuidePageData data, int index) {
|
||
return Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||
child: Column(
|
||
children: [
|
||
SizedBox(height: 64.h),
|
||
// 图片区域
|
||
Expanded(
|
||
flex: 5,
|
||
child: Container(
|
||
width: 312.w,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(12.r),
|
||
color: const Color(0xFFFFF8E7),
|
||
),
|
||
child: data.imagePath != null
|
||
? ClipRRect(
|
||
borderRadius: BorderRadius.circular(12.r),
|
||
child: Image.asset(
|
||
data.imagePath!,
|
||
fit: BoxFit.cover,
|
||
errorBuilder: (context, error, stackTrace) {
|
||
return _buildPlaceholderImage(index);
|
||
},
|
||
),
|
||
)
|
||
: _buildPlaceholderImage(index),
|
||
),
|
||
),
|
||
SizedBox(height: 48.h),
|
||
// 标题
|
||
Text(
|
||
data.title,
|
||
style: TextStyle(
|
||
fontSize: 24.sp,
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.33,
|
||
color: const Color(0xFF292524),
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
SizedBox(height: 16.h),
|
||
// 副标题
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||
child: Text(
|
||
data.subtitle,
|
||
style: TextStyle(
|
||
fontSize: 14.sp,
|
||
height: 1.43,
|
||
color: const Color(0xFF57534E),
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
),
|
||
SizedBox(height: 48.h),
|
||
// 页面指示器
|
||
_buildPageIndicator(),
|
||
SizedBox(height: 80.h),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建占位图片
|
||
Widget _buildPlaceholderImage(int index) {
|
||
final icons = [
|
||
Icons.nature,
|
||
Icons.memory,
|
||
Icons.people,
|
||
Icons.security,
|
||
];
|
||
final colors = [
|
||
const Color(0xFF8BC34A),
|
||
const Color(0xFFD4AF37),
|
||
const Color(0xFFFF9800),
|
||
const Color(0xFF2196F3),
|
||
];
|
||
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(
|
||
icons[index],
|
||
size: 80.sp,
|
||
color: colors[index],
|
||
),
|
||
SizedBox(height: 16.h),
|
||
Text(
|
||
'向导页 ${index + 1}',
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
color: const Color(0xFF57534E),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建欢迎加入页面 (页面5)
|
||
Widget _buildWelcomePage() {
|
||
return _WelcomePageContent(
|
||
onNext: _goToOnboarding,
|
||
);
|
||
}
|
||
|
||
/// 构建页面指示器
|
||
Widget _buildPageIndicator() {
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: List.generate(5, (index) {
|
||
final isActive = index == _currentPage;
|
||
return Container(
|
||
width: 8.w,
|
||
height: 8.w,
|
||
margin: EdgeInsets.symmetric(horizontal: 4.w),
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
color: isActive
|
||
? const Color(0xFF8E794A)
|
||
: const Color(0xFFEAE0CD),
|
||
),
|
||
);
|
||
}),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 欢迎加入页面内容 (第5页)
|
||
class _WelcomePageContent extends StatefulWidget {
|
||
final VoidCallback onNext;
|
||
|
||
const _WelcomePageContent({required this.onNext});
|
||
|
||
@override
|
||
State<_WelcomePageContent> createState() => _WelcomePageContentState();
|
||
}
|
||
|
||
class _WelcomePageContentState extends State<_WelcomePageContent> {
|
||
bool _hasReferrer = true;
|
||
final TextEditingController _referralCodeController = TextEditingController();
|
||
|
||
@override
|
||
void dispose() {
|
||
_referralCodeController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
/// 打开二维码扫描页面
|
||
Future<void> _openQrScanner() async {
|
||
final result = await Navigator.of(context).push<String>(
|
||
MaterialPageRoute(
|
||
builder: (context) => const _QrScannerPage(),
|
||
),
|
||
);
|
||
if (result != null && result.isNotEmpty) {
|
||
// 从扫描结果中提取推荐码
|
||
final referralCode = _extractReferralCode(result);
|
||
setState(() {
|
||
_referralCodeController.text = referralCode;
|
||
_hasReferrer = true;
|
||
});
|
||
}
|
||
}
|
||
|
||
/// 从扫描结果中提取推荐码
|
||
/// 支持以下格式:
|
||
/// - 完整 URL: https://app.rwadurian.com/r/ABC123 -> ABC123
|
||
/// - 短链: https://rwa.link/ABC123 -> ABC123
|
||
/// - 纯推荐码: ABC123 -> ABC123
|
||
String _extractReferralCode(String scannedData) {
|
||
// 去除首尾空格
|
||
String data = scannedData.trim();
|
||
|
||
// 如果是 URL,尝试提取推荐码
|
||
if (data.startsWith('http://') || data.startsWith('https://')) {
|
||
try {
|
||
final uri = Uri.parse(data);
|
||
|
||
// 尝试从路径中提取 (如 /r/ABC123 或 /ABC123)
|
||
final pathSegments = uri.pathSegments;
|
||
if (pathSegments.isNotEmpty) {
|
||
// 取最后一个路径段作为推荐码
|
||
final lastSegment = pathSegments.last;
|
||
if (lastSegment.isNotEmpty) {
|
||
return lastSegment;
|
||
}
|
||
// 如果最后一段是 'r',取倒数第二段
|
||
if (pathSegments.length >= 2 && lastSegment == 'r') {
|
||
return pathSegments[pathSegments.length - 2];
|
||
}
|
||
}
|
||
|
||
// 尝试从查询参数中提取 (如 ?ref=ABC123 或 ?code=ABC123)
|
||
final refCode = uri.queryParameters['ref'] ??
|
||
uri.queryParameters['code'] ??
|
||
uri.queryParameters['referral'];
|
||
if (refCode != null && refCode.isNotEmpty) {
|
||
return refCode;
|
||
}
|
||
} catch (e) {
|
||
// URL 解析失败,返回原始数据
|
||
debugPrint('URL 解析失败: $e');
|
||
}
|
||
}
|
||
|
||
// 不是 URL 或解析失败,返回原始数据
|
||
return data;
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return GestureDetector(
|
||
onTap: () => FocusScope.of(context).unfocus(),
|
||
child: LayoutBuilder(
|
||
builder: (context, constraints) {
|
||
return SingleChildScrollView(
|
||
child: ConstrainedBox(
|
||
constraints: BoxConstraints(
|
||
minHeight: constraints.maxHeight,
|
||
),
|
||
child: IntrinsicHeight(
|
||
child: Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||
child: Column(
|
||
children: [
|
||
// 退出按钮
|
||
Align(
|
||
alignment: Alignment.topRight,
|
||
child: Padding(
|
||
padding: EdgeInsets.only(top: 32.h),
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// 退出向导
|
||
Navigator.of(context).maybePop();
|
||
},
|
||
child: Text(
|
||
'退出 Exit',
|
||
style: TextStyle(
|
||
fontSize: 14.sp,
|
||
color: const Color(0xFFA99F93),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 80.h),
|
||
// 欢迎标题
|
||
Text(
|
||
'欢迎加入',
|
||
style: TextStyle(
|
||
fontSize: 24.sp,
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.33,
|
||
color: const Color(0xFF6F6354),
|
||
),
|
||
),
|
||
SizedBox(height: 12.h),
|
||
// 副标题
|
||
Text(
|
||
'创建账号前的最后一步 · 请选择是否有推荐人',
|
||
style: TextStyle(
|
||
fontSize: 14.sp,
|
||
height: 1.43,
|
||
color: const Color(0xFFA99F93),
|
||
),
|
||
),
|
||
SizedBox(height: 48.h),
|
||
// 选项区域
|
||
_buildReferrerOptions(),
|
||
const Spacer(),
|
||
// 下一步按钮
|
||
GestureDetector(
|
||
onTap: widget.onNext,
|
||
child: Container(
|
||
width: double.infinity,
|
||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||
child: Text(
|
||
'下一步 (创建账号)',
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: const Color(0xFFD9C8A9),
|
||
),
|
||
textAlign: TextAlign.right,
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 48.h),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建推荐人选项
|
||
Widget _buildReferrerOptions() {
|
||
return Column(
|
||
children: [
|
||
// 有推荐人选项
|
||
GestureDetector(
|
||
onTap: () {
|
||
setState(() {
|
||
_hasReferrer = true;
|
||
});
|
||
},
|
||
child: Row(
|
||
children: [
|
||
_buildRadio(_hasReferrer),
|
||
SizedBox(width: 12.w),
|
||
Text(
|
||
'我有推荐人',
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
height: 1.5,
|
||
color: const Color(0xFF6F6354),
|
||
),
|
||
),
|
||
const Spacer(),
|
||
// 扫码图标 - 点击打开扫描页面
|
||
GestureDetector(
|
||
onTap: _openQrScanner,
|
||
child: Container(
|
||
padding: EdgeInsets.all(8.w),
|
||
child: Icon(
|
||
Icons.qr_code_scanner,
|
||
size: 24.sp,
|
||
color: const Color(0xFF8E794A),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 14.h),
|
||
// 推荐码输入框
|
||
if (_hasReferrer)
|
||
Container(
|
||
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w),
|
||
decoration: const BoxDecoration(
|
||
border: Border(
|
||
bottom: BorderSide(
|
||
width: 1,
|
||
color: Color(0xFFEAE1D2),
|
||
),
|
||
),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: TextField(
|
||
controller: _referralCodeController,
|
||
decoration: InputDecoration(
|
||
hintText: '请输入推荐码 / 序列号',
|
||
hintStyle: TextStyle(
|
||
fontSize: 16.sp,
|
||
color: const Color(0xFFA99F93),
|
||
),
|
||
border: InputBorder.none,
|
||
isDense: true,
|
||
contentPadding: EdgeInsets.zero,
|
||
),
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
color: const Color(0xFF6F6354),
|
||
),
|
||
),
|
||
),
|
||
// 扫码按钮
|
||
GestureDetector(
|
||
onTap: _openQrScanner,
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: 8.w),
|
||
child: Icon(
|
||
Icons.camera_alt_outlined,
|
||
size: 20.sp,
|
||
color: const Color(0xFFA99F93),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
// 没有推荐人选项
|
||
GestureDetector(
|
||
onTap: () {
|
||
setState(() {
|
||
_hasReferrer = false;
|
||
});
|
||
},
|
||
child: Row(
|
||
children: [
|
||
_buildRadio(!_hasReferrer),
|
||
SizedBox(width: 12.w),
|
||
Text(
|
||
'我没有推荐人',
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
height: 1.5,
|
||
color: const Color(0xFF6F6354),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 构建单选按钮
|
||
Widget _buildRadio(bool isSelected) {
|
||
return Container(
|
||
width: 20.w,
|
||
height: 20.w,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
border: Border.all(
|
||
width: isSelected ? 6.w : 2.w,
|
||
color: isSelected
|
||
? const Color(0xFF2563EB)
|
||
: const Color(0xFFA99F93),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 二维码扫描页面
|
||
class _QrScannerPage extends StatefulWidget {
|
||
const _QrScannerPage();
|
||
|
||
@override
|
||
State<_QrScannerPage> createState() => _QrScannerPageState();
|
||
}
|
||
|
||
class _QrScannerPageState extends State<_QrScannerPage> {
|
||
MobileScannerController? _controller;
|
||
bool _hasScanned = false;
|
||
bool _torchOn = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_controller = MobileScannerController(
|
||
detectionSpeed: DetectionSpeed.normal,
|
||
facing: CameraFacing.back,
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_controller?.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _onDetect(BarcodeCapture capture) {
|
||
if (_hasScanned) return;
|
||
|
||
final List<Barcode> barcodes = capture.barcodes;
|
||
for (final barcode in barcodes) {
|
||
if (barcode.rawValue != null && barcode.rawValue!.isNotEmpty) {
|
||
_hasScanned = true;
|
||
Navigator.of(context).pop(barcode.rawValue);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _toggleTorch() async {
|
||
await _controller?.toggleTorch();
|
||
setState(() {
|
||
_torchOn = !_torchOn;
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: Colors.black,
|
||
appBar: AppBar(
|
||
backgroundColor: Colors.black,
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
title: Text(
|
||
'扫描推荐码',
|
||
style: TextStyle(
|
||
fontSize: 18.sp,
|
||
color: Colors.white,
|
||
),
|
||
),
|
||
centerTitle: true,
|
||
actions: [
|
||
IconButton(
|
||
icon: Icon(
|
||
_torchOn ? Icons.flash_on : Icons.flash_off,
|
||
color: Colors.white,
|
||
),
|
||
onPressed: _toggleTorch,
|
||
),
|
||
],
|
||
),
|
||
body: Stack(
|
||
children: [
|
||
// 扫描区域
|
||
MobileScanner(
|
||
controller: _controller,
|
||
onDetect: _onDetect,
|
||
),
|
||
// 扫描框遮罩
|
||
_buildScanOverlay(),
|
||
// 底部提示
|
||
Positioned(
|
||
bottom: 100.h,
|
||
left: 0,
|
||
right: 0,
|
||
child: Text(
|
||
'将二维码放入框内,即可自动扫描',
|
||
style: TextStyle(
|
||
fontSize: 14.sp,
|
||
color: Colors.white70,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建扫描框遮罩
|
||
Widget _buildScanOverlay() {
|
||
return LayoutBuilder(
|
||
builder: (context, constraints) {
|
||
final scanAreaSize = 250.w;
|
||
final left = (constraints.maxWidth - scanAreaSize) / 2;
|
||
final top = (constraints.maxHeight - scanAreaSize) / 2 - 50.h;
|
||
|
||
return Stack(
|
||
children: [
|
||
// 半透明背景
|
||
ColorFiltered(
|
||
colorFilter: ColorFilter.mode(
|
||
Colors.black.withValues(alpha: 0.6),
|
||
BlendMode.srcOut,
|
||
),
|
||
child: Stack(
|
||
children: [
|
||
Container(
|
||
decoration: const BoxDecoration(
|
||
color: Colors.black,
|
||
backgroundBlendMode: BlendMode.dstOut,
|
||
),
|
||
),
|
||
Positioned(
|
||
left: left,
|
||
top: top,
|
||
child: Container(
|
||
width: scanAreaSize,
|
||
height: scanAreaSize,
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(12.r),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
// 扫描框边角
|
||
Positioned(
|
||
left: left,
|
||
top: top,
|
||
child: _buildCorner(true, true),
|
||
),
|
||
Positioned(
|
||
right: left,
|
||
top: top,
|
||
child: _buildCorner(false, true),
|
||
),
|
||
Positioned(
|
||
left: left,
|
||
bottom: constraints.maxHeight - top - scanAreaSize,
|
||
child: _buildCorner(true, false),
|
||
),
|
||
Positioned(
|
||
right: left,
|
||
bottom: constraints.maxHeight - top - scanAreaSize,
|
||
child: _buildCorner(false, false),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 构建边角装饰
|
||
Widget _buildCorner(bool isLeft, bool isTop) {
|
||
return SizedBox(
|
||
width: 24.w,
|
||
height: 24.w,
|
||
child: CustomPaint(
|
||
painter: _CornerPainter(
|
||
isLeft: isLeft,
|
||
isTop: isTop,
|
||
color: const Color(0xFFD4A84B),
|
||
strokeWidth: 3.w,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 边角绘制器
|
||
class _CornerPainter extends CustomPainter {
|
||
final bool isLeft;
|
||
final bool isTop;
|
||
final Color color;
|
||
final double strokeWidth;
|
||
|
||
_CornerPainter({
|
||
required this.isLeft,
|
||
required this.isTop,
|
||
required this.color,
|
||
required this.strokeWidth,
|
||
});
|
||
|
||
@override
|
||
void paint(Canvas canvas, Size size) {
|
||
final paint = Paint()
|
||
..color = color
|
||
..strokeWidth = strokeWidth
|
||
..style = PaintingStyle.stroke
|
||
..strokeCap = StrokeCap.round;
|
||
|
||
final path = Path();
|
||
|
||
if (isLeft && isTop) {
|
||
path.moveTo(0, size.height);
|
||
path.lineTo(0, 0);
|
||
path.lineTo(size.width, 0);
|
||
} else if (!isLeft && isTop) {
|
||
path.moveTo(0, 0);
|
||
path.lineTo(size.width, 0);
|
||
path.lineTo(size.width, size.height);
|
||
} else if (isLeft && !isTop) {
|
||
path.moveTo(0, 0);
|
||
path.lineTo(0, size.height);
|
||
path.lineTo(size.width, size.height);
|
||
} else {
|
||
path.moveTo(0, size.height);
|
||
path.lineTo(size.width, size.height);
|
||
path.lineTo(size.width, 0);
|
||
}
|
||
|
||
canvas.drawPath(path, paint);
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||
}
|