kasy-cli 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/bin/kasy.js +140 -12
  2. package/lib/commands/add.js +2 -2
  3. package/lib/commands/codemagic.js +11 -4
  4. package/lib/commands/deploy.js +3 -3
  5. package/lib/commands/favicon.js +115 -0
  6. package/lib/commands/icon.js +143 -0
  7. package/lib/commands/ios.js +28 -7
  8. package/lib/commands/new.js +8 -20
  9. package/lib/commands/remove.js +1 -1
  10. package/lib/commands/reset.js +385 -0
  11. package/lib/commands/run.js +24 -17
  12. package/lib/commands/splash.js +14 -4
  13. package/lib/commands/update.js +1 -1
  14. package/lib/scaffold/backends/api/patch/README.md +1 -1
  15. package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +1 -1
  16. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +53 -0
  17. package/lib/scaffold/backends/api/pubspec.yaml.tpl +11 -1
  18. package/lib/scaffold/backends/firebase/tokens.js +2 -2
  19. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -2
  20. package/lib/scaffold/backends/supabase/migrations/20240101000011_dedupe_device_tokens.sql +34 -0
  21. package/lib/scaffold/backends/supabase/patch/README.md +1 -1
  22. package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +1 -1
  23. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +43 -0
  24. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +11 -1
  25. package/lib/utils/apple-release.js +115 -16
  26. package/lib/utils/checks.js +45 -107
  27. package/lib/utils/debug.js +75 -0
  28. package/lib/utils/flutter-run.js +173 -0
  29. package/lib/utils/friendly-error.js +91 -0
  30. package/lib/utils/i18n/messages-en.js +970 -0
  31. package/lib/utils/i18n/messages-es.js +968 -0
  32. package/lib/utils/i18n/messages-pt.js +968 -0
  33. package/lib/utils/i18n.js +21 -2483
  34. package/lib/utils/mobile-identity.js +35 -0
  35. package/lib/utils/png-padding.js +120 -0
  36. package/lib/utils/ui.js +114 -0
  37. package/package.json +8 -4
  38. package/templates/firebase/README.en.md +1 -1
  39. package/templates/firebase/README.es.md +1 -1
  40. package/templates/firebase/README.md +1 -1
  41. package/templates/firebase/android/app/build.gradle.kts +10 -1
  42. package/templates/firebase/android/app/src/main/AndroidManifest.xml +1 -1
  43. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +25 -1
  44. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +161 -11
  45. package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +15 -0
  46. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +9 -0
  47. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +12 -0
  48. package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +5 -0
  49. package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +17 -0
  50. package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +5 -0
  51. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  52. package/templates/firebase/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png +0 -0
  53. package/templates/firebase/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png +0 -0
  54. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  55. package/templates/firebase/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png +0 -0
  56. package/templates/firebase/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png +0 -0
  57. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  58. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  59. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  60. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  61. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  62. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  63. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png +0 -0
  64. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png +0 -0
  65. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  66. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png +0 -0
  67. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png +0 -0
  68. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  69. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png +0 -0
  70. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png +0 -0
  71. package/templates/firebase/android/app/src/main/res/layout/widget_loading.xml +8 -0
  72. package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +53 -0
  73. package/templates/firebase/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
  74. package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  75. package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/xml/mywidget_info.xml +9 -3
  80. package/templates/firebase/assets/images/favicon.png +0 -0
  81. package/templates/firebase/assets/images/icon.png +0 -0
  82. package/templates/firebase/assets/images/icon_android.png +0 -0
  83. package/templates/firebase/assets/images/icon_foreground_empty.png +0 -0
  84. package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
  85. package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
  86. package/templates/firebase/firestore.indexes.json +10 -0
  87. package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +3 -0
  88. package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +17 -1
  89. package/templates/firebase/functions/src/index.ts +1 -0
  90. package/templates/firebase/functions/src/notifications/device_triggers.ts +58 -0
  91. package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +116 -33
  92. package/templates/firebase/ios/Runner/AppDelegate.swift +17 -1
  93. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  94. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  95. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  96. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  98. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  99. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  100. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  101. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  102. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  103. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
  104. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
  105. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
  106. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
  107. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  108. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  109. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
  110. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
  111. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  112. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  113. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  114. package/templates/firebase/ios/Runner/Info.plist +2 -2
  115. package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +1 -1
  116. package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +1 -1
  117. package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +1 -1
  118. package/templates/firebase/lib/components/components.dart +1 -0
  119. package/templates/firebase/lib/components/kasy_avatar.dart +88 -57
  120. package/templates/firebase/lib/components/kasy_avatar_presets.dart +116 -74
  121. package/templates/firebase/lib/components/kasy_button.dart +8 -0
  122. package/templates/firebase/lib/components/kasy_tabs.dart +431 -0
  123. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +18 -0
  124. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +73 -19
  125. package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +22 -8
  126. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +16 -4
  127. package/templates/firebase/lib/core/states/components/maybeshow_component.dart +4 -8
  128. package/templates/firebase/lib/core/states/user_state_notifier.dart +13 -1
  129. package/templates/firebase/lib/features/home/home_components_page.dart +1 -1
  130. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +316 -93
  131. package/templates/firebase/lib/features/home/home_page.dart +0 -6
  132. package/templates/firebase/lib/features/notifications/api/device_api.dart +57 -0
  133. package/templates/firebase/lib/features/notifications/providers/models/notification.dart +11 -1
  134. package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +9 -0
  135. package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +1 -4
  136. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +28 -8
  137. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -1
  138. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +44 -11
  139. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +31 -29
  140. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +21 -5
  141. package/templates/firebase/lib/i18n/en.i18n.json +4 -1
  142. package/templates/firebase/lib/i18n/es.i18n.json +4 -1
  143. package/templates/firebase/lib/i18n/pt.i18n.json +4 -1
  144. package/templates/firebase/pubspec.yaml +10 -3
  145. package/templates/firebase/test/features/notifications/data/device_api_fake.dart +9 -0
  146. package/templates/firebase/web/favicon.png +0 -0
  147. package/templates/firebase/web/icons/Icon-192.png +0 -0
  148. package/templates/firebase/web/icons/Icon-512.png +0 -0
  149. package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
  150. package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
  151. package/templates/firebase/web/index.html +9 -0
  152. package/templates/firebase/web/manifest.json +3 -3
  153. package/templates/firebase/assets/images/app_icon.png +0 -0
  154. package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
  155. package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
  156. package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +0 -88
  157. package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +0 -144
@@ -18,7 +18,7 @@
18
18
  <string>pt-BR</string>
19
19
  </array>
20
20
  <key>CFBundleDisplayName</key>
21
- <string>AppFirebase</string>
21
+ <string>Kasy App</string>
22
22
  <key>CFBundleExecutable</key>
23
23
  <string>$(EXECUTABLE_NAME)</string>
24
24
  <key>CFBundleIdentifier</key>
@@ -57,7 +57,7 @@
57
57
  <key>FacebookClientToken</key>
58
58
  <string>00000000000000000000000000000000</string>
59
59
  <key>FacebookDisplayName</key>
60
- <string>AppFirebase</string>
60
+ <string>Kasy App</string>
61
61
  <key>LSRequiresIPhoneOS</key>
62
62
  <true/>
63
63
  <key>NSPhotoLibraryAddUsageDescription</key>
@@ -1,5 +1,5 @@
1
1
  /* Localized Info.plist strings — es resources for system picker/camera UI (iOS). */
2
- "CFBundleDisplayName" = "AppFirebase";
2
+ "CFBundleDisplayName" = "Kasy App";
3
3
  "NSCameraUsageDescription" = "Necesitamos la cámara para tomar fotos y vídeos.";
4
4
  "NSPhotoLibraryAddUsageDescription" = "Necesitamos acceso para guardar fotos y vídeos en la galería.";
5
5
  "NSPhotoLibraryUsageDescription" = "Necesitamos acceso para mostrar tus fotos recientes y abrir la galería desde la cámara.";
@@ -1,5 +1,5 @@
1
1
  /* Localized Info.plist strings — presence of pt-BR resources lets iOS use Portuguese for system UI (e.g. image picker). */
2
- "CFBundleDisplayName" = "AppFirebase";
2
+ "CFBundleDisplayName" = "Kasy App";
3
3
  "NSCameraUsageDescription" = "Precisamos da câmera para tirar fotos e vídeos.";
4
4
  "NSPhotoLibraryAddUsageDescription" = "Precisamos de acesso para salvar fotos e vídeos na galeria.";
5
5
  "NSPhotoLibraryUsageDescription" = "Precisamos de acesso para mostrar suas fotos recentes e abrir a galeria a partir da câmera.";
@@ -1,5 +1,5 @@
1
1
  /* Localized Info.plist strings — presence of pt-BR resources lets iOS use Portuguese for system UI (e.g. image picker). */
2
- "CFBundleDisplayName" = "AppFirebase";
2
+ "CFBundleDisplayName" = "Kasy App";
3
3
  "NSCameraUsageDescription" = "Precisamos da câmera para tirar fotos e vídeos.";
4
4
  "NSPhotoLibraryAddUsageDescription" = "Precisamos de acesso para salvar fotos e vídeos na galeria.";
5
5
  "NSPhotoLibraryUsageDescription" = "Precisamos de acesso para mostrar suas fotos recentes e abrir a galeria a partir da câmera.";
@@ -22,6 +22,7 @@ export 'kasy_dialog.dart';
22
22
  export 'kasy_otp_verification_bottom_sheet.dart';
23
23
  export 'kasy_skeleton.dart';
24
24
  export 'kasy_swipe_action.dart';
25
+ export 'kasy_tabs.dart';
25
26
  export 'kasy_text_area.dart';
26
27
  export 'kasy_text_field.dart';
27
28
  export 'kasy_text_field_otp.dart';
@@ -28,7 +28,7 @@ enum KasyAvatarStatus { online }
28
28
 
29
29
  /// Design-system avatar: image, initials, icon, gradient, or custom child.
30
30
  ///
31
- /// Decorative radial fills: optional [KasyAvatarOrbGradients] (barrel presets).
31
+ /// Decorative linear fills: optional [KasyAvatarGradients] (barrel presets).
32
32
  class KasyAvatar extends StatelessWidget {
33
33
  final KasyAvatarSize size;
34
34
 
@@ -60,7 +60,7 @@ class KasyAvatar extends StatelessWidget {
60
60
  this.tone = KasyAvatarTone.blue,
61
61
  this.fallbackSurface = KasyAvatarFallbackSurface.default_,
62
62
  this.shape = KasyAvatarShape.circle,
63
- this.showShadow = true,
63
+ this.showShadow = false,
64
64
  this.showStoryRing = false,
65
65
  this.storyRingGradient,
66
66
  this.status,
@@ -244,7 +244,7 @@ class KasyAvatar extends StatelessWidget {
244
244
  return ColoredBox(
245
245
  color: bg,
246
246
  child: Center(
247
- child: Icon(glyph, size: _d * 0.48, color: fg),
247
+ child: Icon(glyph, size: _d * 0.444, color: fg),
248
248
  ),
249
249
  );
250
250
  }
@@ -253,11 +253,12 @@ class KasyAvatar extends StatelessWidget {
253
253
  child: Center(
254
254
  child: Text(
255
255
  label ?? '',
256
- style: context.textTheme.titleSmall?.copyWith(
256
+ style: TextStyle(
257
257
  color: fg,
258
- fontWeight: FontWeight.w600,
259
- fontSize: _d * 0.32,
258
+ fontWeight: FontWeight.w500,
259
+ fontSize: _d * 0.333,
260
260
  height: 1,
261
+ leadingDistribution: TextLeadingDistribution.even,
261
262
  ),
262
263
  ),
263
264
  ),
@@ -320,7 +321,10 @@ class KasyAvatar extends StatelessWidget {
320
321
  }
321
322
  }
322
323
 
323
- /// Horizontal stacked avatars with [KasyColors.surface] separation ring and optional +N.
324
+ /// Horizontal stacked avatars with [KasyColors.surface] separation ring,
325
+ /// optional +N counter, and optional add-action button.
326
+ ///
327
+ /// Set [onAdd] to show the "+" button at the end (12 px gap, per Figma spec).
324
328
  class KasyAvatarGroup extends StatelessWidget {
325
329
  final List<Widget> avatars;
326
330
  final int maxVisible;
@@ -329,14 +333,18 @@ class KasyAvatarGroup extends StatelessWidget {
329
333
  final bool showShadow;
330
334
  final double itemDiameter;
331
335
 
336
+ /// When non-null a circular "+" button is shown after the group.
337
+ final VoidCallback? onAdd;
338
+
332
339
  const KasyAvatarGroup({
333
340
  super.key,
334
341
  required this.avatars,
335
342
  this.maxVisible = 4,
336
343
  this.extraCount = 0,
337
- this.overlapFactor = 0.34,
338
- this.showShadow = true,
344
+ this.overlapFactor = 0.28,
345
+ this.showShadow = false,
339
346
  this.itemDiameter = 48,
347
+ this.onAdd,
340
348
  });
341
349
 
342
350
  @override
@@ -346,9 +354,10 @@ class KasyAvatarGroup extends StatelessWidget {
346
354
  final double step = d * (1 - overlapFactor);
347
355
  final int showCounter = extraCount > 0 ? 1 : 0;
348
356
  final int totalSlots = n + showCounter;
349
- final double w = totalSlots <= 0 ? 0 : step * (totalSlots - 1) + d + 4;
350
- return SizedBox(
351
- width: w,
357
+ final double stackW = totalSlots <= 0 ? 0 : step * (totalSlots - 1) + d + 4;
358
+
359
+ final Widget stack = SizedBox(
360
+ width: stackW,
352
361
  height: d + 4,
353
362
  child: Stack(
354
363
  clipBehavior: Clip.none,
@@ -371,9 +380,14 @@ class KasyAvatarGroup extends StatelessWidget {
371
380
  child: Center(
372
381
  child: Text(
373
382
  '+$extraCount',
374
- style: context.textTheme.labelLarge?.copyWith(
375
- color: context.colors.primary,
376
- fontWeight: FontWeight.w600,
383
+ style: TextStyle(
384
+ color: context.isDark
385
+ ? const Color(0xFFFCFCFC)
386
+ : const Color(0xFF18181B),
387
+ fontWeight: FontWeight.w500,
388
+ fontSize: 12,
389
+ height: 16 / 12,
390
+ leadingDistribution: TextLeadingDistribution.even,
377
391
  ),
378
392
  ),
379
393
  ),
@@ -384,22 +398,50 @@ class KasyAvatarGroup extends StatelessWidget {
384
398
  ],
385
399
  ),
386
400
  );
401
+
402
+ if (onAdd == null) return stack;
403
+
404
+ // Add-action button: circle with "+" icon, 12 px gap (Figma layout_EJ08D4).
405
+ final Widget addBtn = _KasyAvatarPressable(
406
+ onPressed: onAdd!,
407
+ semanticLabel: 'Add member',
408
+ minSide: d < 44 ? 44.0 : d,
409
+ child: Container(
410
+ width: d,
411
+ height: d,
412
+ decoration: BoxDecoration(
413
+ color: context.colors.avatarFallbackFill,
414
+ shape: BoxShape.circle,
415
+ ),
416
+ child: Center(
417
+ child: Icon(
418
+ KasyIcons.add,
419
+ size: d * 0.444,
420
+ color: context.colors.primary,
421
+ ),
422
+ ),
423
+ ),
424
+ );
425
+
426
+ return Row(
427
+ mainAxisSize: MainAxisSize.min,
428
+ children: [stack, const SizedBox(width: 12), addBtn],
429
+ );
387
430
  }
388
431
 
389
432
  Widget _ringWrap(BuildContext context, double d, Widget child, bool shadow) {
390
- final Widget bordered = Container(
433
+ return Container(
391
434
  width: d,
392
435
  height: d,
393
436
  decoration: BoxDecoration(
394
437
  shape: BoxShape.circle,
395
- border: Border.all(color: context.colors.surface, width: 2.5),
438
+ border: Border.all(color: context.colors.surface, width: 2.0),
396
439
  boxShadow: shadow ? [KasyShadows.component(context)] : null,
397
440
  ),
398
441
  child: ClipOval(
399
442
  child: SizedBox(width: d, height: d, child: child),
400
443
  ),
401
444
  );
402
- return bordered;
403
445
  }
404
446
  }
405
447
 
@@ -418,52 +460,41 @@ _KasyAvatarColors _colorsForTone(
418
460
  final KasyColors k = context.colors;
419
461
  final bool dark = context.isDark;
420
462
 
421
- Color accent() {
422
- return switch (tone) {
423
- KasyAvatarTone.blue => k.primary,
424
- KasyAvatarTone.neutral => k.grey3,
425
- KasyAvatarTone.green => k.success,
426
- KasyAvatarTone.orange => k.warning,
427
- KasyAvatarTone.red => k.error,
428
- };
429
- }
463
+ // Accent color per tone (Figma: accent=#0485F7, success=#17C964,
464
+ // warning=#F5A524, danger=#FF383C, neutral uses neutral fg).
465
+ Color accentColor() => switch (tone) {
466
+ KasyAvatarTone.blue => k.primary,
467
+ KasyAvatarTone.neutral => k.grey3,
468
+ KasyAvatarTone.green => k.success,
469
+ KasyAvatarTone.orange => k.warning,
470
+ KasyAvatarTone.red => k.error,
471
+ };
430
472
 
473
+ // Solid surface: all types share the same #EBEBEC bg (Figma fill_J18KAR).
474
+ // Foreground: neutral → onSurface (#18181B light), others → accent color.
431
475
  if (fallbackSurface == KasyAvatarFallbackSurface.default_) {
476
+ final Color fg = tone == KasyAvatarTone.neutral
477
+ ? k.onSurface
478
+ : accentColor();
479
+ return _KasyAvatarColors(background: k.avatarFallbackFill, foreground: fg);
480
+ }
481
+
482
+ // Neutral soft = same as neutral solid (Figma: no tint for default type).
483
+ if (tone == KasyAvatarTone.neutral) {
432
484
  return _KasyAvatarColors(
433
485
  background: k.avatarFallbackFill,
434
- foreground: accent(),
486
+ foreground: k.onSurface,
435
487
  );
436
488
  }
437
489
 
438
- if (dark) {
439
- final Color a = accent();
440
- final Color bg = Color.alphaBlend(a.withValues(alpha: 0.28), k.surface);
441
- final Color fg = Color.lerp(a, k.onSurface, 0.45)!;
442
- return _KasyAvatarColors(background: bg, foreground: fg);
443
- }
444
-
445
- return switch (tone) {
446
- KasyAvatarTone.blue => const _KasyAvatarColors(
447
- background: Color(0xFFE3F2FD),
448
- foreground: Color(0xFF1565C0),
449
- ),
450
- KasyAvatarTone.neutral => const _KasyAvatarColors(
451
- background: Color(0xFFF0F0F0),
452
- foreground: Color(0xFF212121),
453
- ),
454
- KasyAvatarTone.green => const _KasyAvatarColors(
455
- background: Color(0xFFE8F5E9),
456
- foreground: Color(0xFF1B5E20),
457
- ),
458
- KasyAvatarTone.orange => const _KasyAvatarColors(
459
- background: Color(0xFFFFF3E0),
460
- foreground: Color(0xFFE65100),
461
- ),
462
- KasyAvatarTone.red => const _KasyAvatarColors(
463
- background: Color(0xFFFFEBEE),
464
- foreground: Color(0xFFB71C1C),
465
- ),
466
- };
490
+ // Colored soft: rgba(accent, 0.15) blended over surface (white light / dark).
491
+ // Figma fill_MVOTCC: rgba(4,133,247,0.15) + #FFFFFF light
492
+ // rgba(4,133,247,0.15) + #18181B dark
493
+ // Foreground: pure accent color (Figma uses raw accent even in soft).
494
+ final Color a = accentColor();
495
+ final Color base = dark ? k.surface : const Color(0xFFFFFFFF);
496
+ final Color bg = Color.alphaBlend(a.withValues(alpha: 0.15), base);
497
+ return _KasyAvatarColors(background: bg, foreground: a);
467
498
  }
468
499
 
469
500
  class _KasyAvatarPressable extends StatefulWidget {
@@ -1,94 +1,136 @@
1
1
  import 'package:flutter/material.dart';
2
2
 
3
- /// Optional radial orb presets for [KasyAvatar] via `backgroundGradient` or
3
+ /// Linear gradient presets for [KasyAvatar] via `backgroundGradient` or
4
4
  /// [KasyAvatar.gradientFill].
5
5
  ///
6
- /// Remote photo URLs ([KasyAvatarDemoPhotos]) are for demos/catalog only (needs network).
7
- abstract final class KasyAvatarOrbGradients {
8
- static const RadialGradient sphereBlue = RadialGradient(
9
- center: Alignment(-0.42, -0.48),
10
- radius: 1.08,
11
- colors: [
12
- Color(0xFFE3F4FF),
13
- Color(0xFF64B5F6),
14
- Color(0xFF1E88E5),
15
- Color(0xFF0A3068),
16
- ],
17
- stops: [0.0, 0.32, 0.68, 1.0],
6
+ /// Matches the HeroUI Figma Kit V3 avatar palette exactly.
7
+ /// All gradients use 141° (upper-left → lower-right), flat from 0%→27%,
8
+ /// then transition to the second color at 100% — same as Figma specification.
9
+ abstract final class KasyAvatarGradients {
10
+ // 141° CSS angle → Flutter Alignment
11
+ // begin = upper-left region (-sin141°, cos141°) ≈ (-0.63, -0.78)
12
+ // end = lower-right region (+sin141°, -cos141°) ≈ (+0.63, +0.78)
13
+ static const Alignment _b = Alignment(-0.63, -0.78);
14
+ static const Alignment _e = Alignment(0.63, 0.78);
15
+
16
+ /// Cornflower blue → deep blue
17
+ static const LinearGradient blue = LinearGradient(
18
+ begin: _b,
19
+ end: _e,
20
+ colors: [Color(0xFF5D9BE7), Color(0xFF5D9BE7), Color(0xFF0026FF)],
21
+ stops: [0.0, 0.27, 1.0],
22
+ );
23
+
24
+ /// Cyan-blue → violet
25
+ static const LinearGradient sky = LinearGradient(
26
+ begin: _b,
27
+ end: _e,
28
+ colors: [Color(0xFF5DD0E7), Color(0xFF5DD0E7), Color(0xFF7300FF)],
29
+ stops: [0.0, 0.27, 1.0],
30
+ );
31
+
32
+ /// Aqua → deep blue
33
+ static const LinearGradient teal = LinearGradient(
34
+ begin: _b,
35
+ end: _e,
36
+ colors: [Color(0xFF5DE7E7), Color(0xFF5DE7E7), Color(0xFF001AFF)],
37
+ stops: [0.0, 0.27, 1.0],
38
+ );
39
+
40
+ /// Mint green → royal blue
41
+ static const LinearGradient emerald = LinearGradient(
42
+ begin: _b,
43
+ end: _e,
44
+ colors: [Color(0xFF5DE79D), Color(0xFF5DE79D), Color(0xFF0033FF)],
45
+ stops: [0.0, 0.27, 1.0],
46
+ );
47
+
48
+ /// Seafoam → dark forest green
49
+ static const LinearGradient green = LinearGradient(
50
+ begin: _b,
51
+ end: _e,
52
+ colors: [Color(0xFF52DEB0), Color(0xFF52DEB0), Color(0xFF0D5C45)],
53
+ stops: [0.0, 0.27, 1.0],
54
+ );
55
+
56
+ /// Turquoise → deep forest green
57
+ static const LinearGradient forest = LinearGradient(
58
+ begin: _b,
59
+ end: _e,
60
+ colors: [Color(0xFF5DD9AF), Color(0xFF5DD9AF), Color(0xFF094E39)],
61
+ stops: [0.0, 0.27, 1.0],
62
+ );
63
+
64
+ /// Golden yellow → deep red-orange
65
+ static const LinearGradient orange = LinearGradient(
66
+ begin: _b,
67
+ end: _e,
68
+ colors: [Color(0xFFE7BD5D), Color(0xFFE7BD5D), Color(0xFFFF1E00)],
69
+ stops: [0.0, 0.27, 1.0],
70
+ );
71
+
72
+ /// Salmon → scarlet
73
+ static const LinearGradient red = LinearGradient(
74
+ begin: _b,
75
+ end: _e,
76
+ colors: [Color(0xFFE7885D), Color(0xFFE7885D), Color(0xFFFF0004)],
77
+ stops: [0.0, 0.27, 1.0],
18
78
  );
19
79
 
20
- static const RadialGradient sphereFuchsia = RadialGradient(
21
- center: Alignment(-0.36, -0.46),
22
- radius: 1.1,
23
- colors: [
24
- Color(0xFFFFE4EE),
25
- Color(0xFFF48FB1),
26
- Color(0xFFE91E63),
27
- Color(0xFF880E4F),
28
- ],
29
- stops: [0.0, 0.35, 0.71, 1.0],
80
+ /// Soft purple cobalt blue
81
+ static const LinearGradient indigo = LinearGradient(
82
+ begin: _b,
83
+ end: _e,
84
+ colors: [Color(0xFFB45DE7), Color(0xFFB45DE7), Color(0xFF0055FF)],
85
+ stops: [0.0, 0.27, 1.0],
30
86
  );
31
87
 
32
- static const RadialGradient sphereSunset = RadialGradient(
33
- center: Alignment(-0.45, -0.38),
34
- radius: 1.06,
35
- colors: [
36
- Color(0xFFFFF8E1),
37
- Color(0xFFFFB74D),
38
- Color(0xFFFF6E40),
39
- Color(0xFFB71C1C),
40
- ],
41
- stops: [0.0, 0.33, 0.69, 1.0],
88
+ /// Medium purple crimson
89
+ static const LinearGradient purple = LinearGradient(
90
+ begin: _b,
91
+ end: _e,
92
+ colors: [Color(0xFF8D5DE7), Color(0xFF8D5DE7), Color(0xFFFF0009)],
93
+ stops: [0.0, 0.27, 1.0],
42
94
  );
43
95
 
44
- static const RadialGradient sphereTeal = RadialGradient(
45
- center: Alignment(-0.4, -0.42),
46
- radius: 1.07,
47
- colors: [
48
- Color(0xFFE0F7F4),
49
- Color(0xFF4DD0E1),
50
- Color(0xFF00838F),
51
- Color(0xFF004D40),
52
- ],
53
- stops: [0.0, 0.34, 0.7, 1.0],
96
+ /// Orchid pink deep magenta-red
97
+ static const LinearGradient rose = LinearGradient(
98
+ begin: _b,
99
+ end: _e,
100
+ colors: [Color(0xFFE75DCB), Color(0xFFE75DCB), Color(0xFFFF000D)],
101
+ stops: [0.0, 0.27, 1.0],
54
102
  );
55
103
 
56
- static const RadialGradient sphereViolet = RadialGradient(
57
- center: Alignment(-0.38, -0.5),
58
- radius: 1.09,
59
- colors: [
60
- Color(0xFFF3E5F5),
61
- Color(0xFFCE93D8),
62
- Color(0xFF8E24AA),
63
- Color(0xFF311B92),
64
- ],
65
- stops: [0.0, 0.36, 0.72, 1.0],
104
+ /// Hot pink coral red
105
+ static const LinearGradient hotPink = LinearGradient(
106
+ begin: _b,
107
+ end: _e,
108
+ colors: [Color(0xFFE43673), Color(0xFFE43673), Color(0xFFFB5059)],
109
+ stops: [0.0, 0.27, 1.0],
66
110
  );
67
111
 
68
- static const RadialGradient sphereAmber = RadialGradient(
69
- center: Alignment(-0.45, -0.35),
70
- radius: 1.06,
71
- colors: [
72
- Color(0xFFFFFDE7),
73
- Color(0xFFFFCA28),
74
- Color(0xFFFF8F00),
75
- Color(0xFFE65100),
76
- ],
77
- stops: [0.0, 0.33, 0.69, 1.0],
112
+ /// Silver near-black
113
+ static const LinearGradient silver = LinearGradient(
114
+ begin: _b,
115
+ end: _e,
116
+ colors: [Color(0xFF949494), Color(0xFF949494), Color(0xFF080808)],
117
+ stops: [0.0, 0.27, 1.0],
78
118
  );
79
119
 
80
- /// Dual-tone orb pairing well with initials on top.
81
- static const RadialGradient sphereGb = RadialGradient(
82
- center: Alignment(-0.28, -0.52),
83
- radius: 1.12,
84
- colors: [
85
- Color(0xFFFFCCBC),
86
- Color(0xFFFF4081),
87
- Color(0xFFB388FF),
88
- Color(0xFF4527A0),
89
- ],
90
- stops: [0.0, 0.3, 0.62, 1.0],
120
+ /// Mid-grey very dark grey
121
+ static const LinearGradient black = LinearGradient(
122
+ begin: _b,
123
+ end: _e,
124
+ colors: [Color(0xFF7F7F7F), Color(0xFF7F7F7F), Color(0xFF1F1F1F)],
125
+ stops: [0.0, 0.27, 1.0],
91
126
  );
127
+
128
+ /// All 14 presets in display order (matches Figma "Image Use" section).
129
+ static const List<LinearGradient> all = [
130
+ blue, sky, teal, emerald, green, forest,
131
+ orange, red, indigo, purple, rose, hotPink,
132
+ silver, black,
133
+ ];
92
134
  }
93
135
 
94
136
  /// Fixed Unsplash URLs for demos/catalog (network + permissions in prod).
@@ -377,6 +377,9 @@ class KasyButton extends StatelessWidget {
377
377
  }
378
378
  final KasyColors c = context.colors;
379
379
  final Color soft = c.surfaceNeutralSoft;
380
+ // Variants used on non-theme-matched backgrounds (e.g. inverse on the paywall gradient)
381
+ // need an explicit case — the generic disabled fallback blends with surfaceNeutralSoft,
382
+ // which goes near-black in dark mode and kills contrast on colored surfaces.
380
383
  return switch (variant) {
381
384
  KasyButtonVariant.primary => _KasyButtonPalette(
382
385
  background: Color.alphaBlend(c.primary.withValues(alpha: 0.62), soft),
@@ -388,6 +391,11 @@ class KasyButton extends StatelessWidget {
388
391
  foreground: c.primary.withValues(alpha: 0.90),
389
392
  border: Colors.transparent,
390
393
  ),
394
+ KasyButtonVariant.inverse => _KasyButtonPalette(
395
+ background: c.onPrimary,
396
+ foreground: c.primary.withValues(alpha: 0.62),
397
+ border: Colors.transparent,
398
+ ),
391
399
  _ => null,
392
400
  };
393
401
  }