diff --git a/lib/constants/app_info.dart b/lib/constants/app_info.dart index 1ed07b06..b0c35b73 100644 --- a/lib/constants/app_info.dart +++ b/lib/constants/app_info.dart @@ -18,4 +18,5 @@ class AppInfo { static const String kofiUrl = 'https://ko-fi.com/zarzet'; static const String bmacUrl = 'https://buymeacoffee.com/zarzet'; + static const String githubSponsorsUrl = 'https://github.com/sponsors/zarzet/'; } diff --git a/lib/screens/settings/donate_page.dart b/lib/screens/settings/donate_page.dart index 9778be84..c7ba0069 100644 --- a/lib/screens/settings/donate_page.dart +++ b/lib/screens/settings/donate_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:spotiflac_android/constants/app_info.dart'; +import 'package:spotiflac_android/widgets/donate_icons.dart'; class DonatePage extends StatelessWidget { const DonatePage({super.key}); @@ -105,28 +106,51 @@ class DonatePage extends StatelessWidget { const SizedBox(height: 12), // Notice - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - Icons.info_outline, - size: 14, - color: colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 6), - Expanded( - child: Text( - 'Your name will remain in this version permanently as a token of appreciation. ' - 'The supporter list is updated manually each month and embedded directly in the app ' - '-- no remote server is used. Even if your support period ends, your name stays in ' - 'every version it was included in.', - style: Theme.of(context).textTheme.bodySmall - ?.copyWith(color: colorScheme.onSurfaceVariant), + Card( + elevation: 0, + color: colorScheme.surfaceContainerLow, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.info_outline_rounded, + size: 20, + color: colorScheme.onSurfaceVariant, ), - ), - ], + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'About Supporters', + style: Theme.of(context).textTheme.titleSmall + ?.copyWith( + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + const SizedBox(height: 6), + Text( + 'Your name will remain in this version permanently as a token of appreciation. ' + 'The supporter list is updated manually each month and embedded directly in the app ' + '-- no remote server is used. Even if your support period ends, your name stays in ' + 'every version it was included in.', + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + ], + ), ), ), ], @@ -230,7 +254,7 @@ class _DonateLinksCard extends StatelessWidget { _DonateCardItem( title: 'Ko-fi', subtitle: 'ko-fi.com/zarzet', - icon: Icons.coffee_rounded, + customIcon: const KofiIcon(size: 22, color: Colors.white), color: const Color(0xFFFF5E5B), url: AppInfo.kofiUrl, colorScheme: colorScheme, @@ -245,12 +269,26 @@ class _DonateLinksCard extends StatelessWidget { _DonateCardItem( title: 'Buy Me a Coffee', subtitle: 'buymeacoffee.com/zarzet', - icon: Icons.local_cafe_rounded, + customIcon: const BmacIcon(size: 22, color: Colors.black87), color: const Color(0xFFFFDD00), - iconColor: Colors.black87, url: AppInfo.bmacUrl, colorScheme: colorScheme, ), + Divider( + height: 1, + thickness: 1, + indent: 74, + endIndent: 16, + color: colorScheme.outlineVariant.withValues(alpha: 0.3), + ), + _DonateCardItem( + title: 'GitHub Sponsors', + subtitle: 'github.com/sponsors/zarzet', + customIcon: const GitHubIcon(size: 22, color: Colors.white), + color: const Color(0xFF2D333B), + url: AppInfo.githubSponsorsUrl, + colorScheme: colorScheme, + ), ], ), ); @@ -260,20 +298,18 @@ class _DonateLinksCard extends StatelessWidget { class _DonateCardItem extends StatelessWidget { final String title; final String subtitle; - final IconData icon; + final Widget customIcon; final Color color; - final Color? iconColor; final String url; final ColorScheme colorScheme; const _DonateCardItem({ required this.title, required this.subtitle, - required this.icon, + required this.customIcon, required this.color, required this.url, required this.colorScheme, - this.iconColor, }); @override @@ -292,7 +328,7 @@ class _DonateCardItem extends StatelessWidget { color: color, borderRadius: BorderRadius.circular(12), ), - child: Icon(icon, color: iconColor ?? Colors.white, size: 22), + child: Center(child: customIcon), ), const SizedBox(width: 14), Expanded( diff --git a/lib/widgets/donate_icons.dart b/lib/widgets/donate_icons.dart new file mode 100644 index 00000000..80edfe7d --- /dev/null +++ b/lib/widgets/donate_icons.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; + +/// Custom painted icons for donate platforms + +class KofiIcon extends StatelessWidget { + final double size; + final Color color; + + const KofiIcon({super.key, this.size = 22, this.color = Colors.white}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + size: Size(size, size), + painter: _KofiPainter(color), + ); + } +} + +class _KofiPainter extends CustomPainter { + final Color color; + _KofiPainter(this.color); + + @override + void paint(Canvas canvas, Size size) { + final s = size.width; + final paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + // Cup body + final cup = RRect.fromRectAndRadius( + Rect.fromLTWH(s * 0.08, s * 0.28, s * 0.62, s * 0.52), + Radius.circular(s * 0.12), + ); + canvas.drawRRect(cup, paint); + + // Handle + final handlePaint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = s * 0.08 + ..strokeCap = StrokeCap.round; + + final handlePath = Path() + ..moveTo(s * 0.70, s * 0.40) + ..quadraticBezierTo(s * 0.92, s * 0.40, s * 0.92, s * 0.54) + ..quadraticBezierTo(s * 0.92, s * 0.68, s * 0.70, s * 0.68); + canvas.drawPath(handlePath, handlePaint); + + // Heart on cup + final heartPaint = Paint() + ..color = const Color(0xFFFF5E5B) + ..style = PaintingStyle.fill; + + final hx = s * 0.39; + final hy = s * 0.46; + final hs = s * 0.12; + + final heart = Path() + ..moveTo(hx, hy + hs * 0.3) + ..cubicTo(hx - hs, hy - hs * 0.3, hx - hs * 0.5, hy - hs, hx, hy - hs * 0.4) + ..cubicTo(hx + hs * 0.5, hy - hs, hx + hs, hy - hs * 0.3, hx, hy + hs * 0.3) + ..close(); + canvas.drawPath(heart, heartPaint); + + // Steam lines + final steamPaint = Paint() + ..color = color.withValues(alpha: 0.6) + ..style = PaintingStyle.stroke + ..strokeWidth = s * 0.04 + ..strokeCap = StrokeCap.round; + + for (var i = 0; i < 2; i++) { + final sx = s * (0.30 + i * 0.18); + final steam = Path() + ..moveTo(sx, s * 0.24) + ..quadraticBezierTo(sx - s * 0.04, s * 0.18, sx, s * 0.12); + canvas.drawPath(steam, steamPaint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class BmacIcon extends StatelessWidget { + final double size; + final Color color; + + const BmacIcon({super.key, this.size = 22, this.color = Colors.black87}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + size: Size(size, size), + painter: _BmacPainter(color), + ); + } +} + +class _BmacPainter extends CustomPainter { + final Color color; + _BmacPainter(this.color); + + @override + void paint(Canvas canvas, Size size) { + final s = size.width; + final paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + // Cup body (slightly tapered) + final cup = Path() + ..moveTo(s * 0.15, s * 0.35) + ..lineTo(s * 0.20, s * 0.82) + ..quadraticBezierTo(s * 0.20, s * 0.90, s * 0.28, s * 0.90) + ..lineTo(s * 0.56, s * 0.90) + ..quadraticBezierTo(s * 0.64, s * 0.90, s * 0.64, s * 0.82) + ..lineTo(s * 0.69, s * 0.35) + ..close(); + canvas.drawPath(cup, paint); + + // Cup rim + final rim = RRect.fromRectAndRadius( + Rect.fromLTWH(s * 0.10, s * 0.30, s * 0.64, s * 0.10), + Radius.circular(s * 0.05), + ); + canvas.drawRRect(rim, paint); + + // Handle + final handlePaint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = s * 0.07 + ..strokeCap = StrokeCap.round; + + final handle = Path() + ..moveTo(s * 0.69, s * 0.42) + ..quadraticBezierTo(s * 0.90, s * 0.42, s * 0.90, s * 0.56) + ..quadraticBezierTo(s * 0.90, s * 0.70, s * 0.69, s * 0.70); + canvas.drawPath(handle, handlePaint); + + // Steam + final steamPaint = Paint() + ..color = color.withValues(alpha: 0.5) + ..style = PaintingStyle.stroke + ..strokeWidth = s * 0.04 + ..strokeCap = StrokeCap.round; + + for (var i = 0; i < 3; i++) { + final sx = s * (0.26 + i * 0.14); + final steam = Path() + ..moveTo(sx, s * 0.26) + ..quadraticBezierTo(sx + s * 0.03, s * 0.18, sx, s * 0.10); + canvas.drawPath(steam, steamPaint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class GitHubIcon extends StatelessWidget { + final double size; + final Color color; + + const GitHubIcon({super.key, this.size = 22, this.color = Colors.white}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + size: Size(size, size), + painter: _GitHubPainter(color), + ); + } +} + +class _GitHubPainter extends CustomPainter { + final Color color; + _GitHubPainter(this.color); + + @override + void paint(Canvas canvas, Size size) { + final s = size.width; + final paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + // GitHub octocat silhouette (simplified mark) + // Based on the GitHub logo path, scaled to fit + final scale = s / 24.0; + + final path = Path(); + // Outer circle/head shape + path.moveTo(12 * scale, 0.5 * scale); + path.cubicTo( + 5.37 * scale, 0.5 * scale, + 0 * scale, 5.87 * scale, + 0 * scale, 12.5 * scale, + ); + path.cubicTo( + 0 * scale, 17.78 * scale, + 3.44 * scale, 22.27 * scale, + 8.21 * scale, 23.85 * scale, + ); + path.cubicTo( + 8.81 * scale, 23.96 * scale, + 9.02 * scale, 23.59 * scale, + 9.02 * scale, 23.27 * scale, + ); + path.cubicTo( + 9.02 * scale, 22.98 * scale, + 9.01 * scale, 22.01 * scale, + 9.01 * scale, 21.01 * scale, + ); + // Left arm + path.cubicTo( + 5.67 * scale, 21.71 * scale, + 4.97 * scale, 19.56 * scale, + 4.97 * scale, 19.56 * scale, + ); + path.cubicTo( + 4.42 * scale, 18.22 * scale, + 3.63 * scale, 17.85 * scale, + 3.63 * scale, 17.85 * scale, + ); + path.cubicTo( + 2.55 * scale, 17.12 * scale, + 3.71 * scale, 17.13 * scale, + 3.71 * scale, 17.13 * scale, + ); + path.cubicTo( + 4.90 * scale, 17.22 * scale, + 5.53 * scale, 18.36 * scale, + 5.53 * scale, 18.36 * scale, + ); + path.cubicTo( + 6.58 * scale, 20.05 * scale, + 8.36 * scale, 19.53 * scale, + 9.05 * scale, 19.22 * scale, + ); + path.cubicTo( + 9.16 * scale, 18.45 * scale, + 9.47 * scale, 17.93 * scale, + 9.81 * scale, 17.63 * scale, + ); + // Bottom + path.cubicTo( + 7.15 * scale, 17.33 * scale, + 4.34 * scale, 16.33 * scale, + 4.34 * scale, 11.93 * scale, + ); + path.cubicTo( + 4.34 * scale, 10.68 * scale, + 4.81 * scale, 9.66 * scale, + 5.55 * scale, 8.86 * scale, + ); + path.cubicTo( + 5.43 * scale, 8.56 * scale, + 5.02 * scale, 7.40 * scale, + 5.67 * scale, 5.82 * scale, + ); + path.cubicTo( + 5.67 * scale, 5.82 * scale, + 6.66 * scale, 5.50 * scale, + 8.98 * scale, 6.99 * scale, + ); + path.cubicTo( + 9.94 * scale, 6.72 * scale, + 10.98 * scale, 6.59 * scale, + 12.0 * scale, 6.58 * scale, + ); + path.cubicTo( + 13.02 * scale, 6.59 * scale, + 14.06 * scale, 6.72 * scale, + 15.02 * scale, 6.99 * scale, + ); + path.cubicTo( + 17.34 * scale, 5.50 * scale, + 18.33 * scale, 5.82 * scale, + 18.33 * scale, 5.82 * scale, + ); + path.cubicTo( + 18.98 * scale, 7.40 * scale, + 18.57 * scale, 8.56 * scale, + 18.45 * scale, 8.86 * scale, + ); + path.cubicTo( + 19.19 * scale, 9.66 * scale, + 19.66 * scale, 10.68 * scale, + 19.66 * scale, 11.93 * scale, + ); + path.cubicTo( + 19.66 * scale, 16.34 * scale, + 16.84 * scale, 17.32 * scale, + 14.17 * scale, 17.62 * scale, + ); + path.cubicTo( + 14.59 * scale, 17.99 * scale, + 14.97 * scale, 18.70 * scale, + 14.97 * scale, 19.80 * scale, + ); + path.cubicTo( + 14.97 * scale, 21.40 * scale, + 14.95 * scale, 22.67 * scale, + 14.95 * scale, 23.27 * scale, + ); + path.cubicTo( + 14.95 * scale, 23.60 * scale, + 15.16 * scale, 23.97 * scale, + 15.77 * scale, 23.85 * scale, + ); + path.cubicTo( + 20.55 * scale, 22.26 * scale, + 24.0 * scale, 17.78 * scale, + 24.0 * scale, 12.5 * scale, + ); + path.cubicTo( + 24.0 * scale, 5.87 * scale, + 18.63 * scale, 0.5 * scale, + 12.0 * scale, 0.5 * scale, + ); + path.close(); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +}