优化:导航栏功能移动到顶部

This commit is contained in:
ivan 2026-04-19 18:03:18 +08:00
parent 01362f00a5
commit 07a047f031
11 changed files with 325 additions and 282 deletions

View File

@ -162,94 +162,6 @@
} }
] ]
}, },
{
"type": "frame",
"id": "NV7Kd",
"name": "tabRow",
"width": "fill_container",
"fill": "#00000000",
"gap": 14,
"padding": [
6,
0,
16,
0
],
"justifyContent": "center",
"alignItems": "center",
"children": [
{
"type": "text",
"id": "douq8",
"name": "t1",
"fill": "#FFFFFF",
"content": "Dance",
"fontFamily": "Inter",
"fontSize": 15,
"fontWeight": "700"
},
{
"type": "text",
"id": "NKz0E",
"name": "d1",
"fill": "#FFFFFF66",
"content": "|",
"fontFamily": "Inter",
"fontSize": 14,
"fontWeight": "normal"
},
{
"type": "text",
"id": "AZTis",
"name": "t2",
"fill": "#FFFFFF",
"content": "Dress",
"fontFamily": "Inter",
"fontSize": 15,
"fontWeight": "700"
},
{
"type": "text",
"id": "Nh9EQ",
"name": "d2",
"fill": "#FFFFFF66",
"content": "|",
"fontFamily": "Inter",
"fontSize": 14,
"fontWeight": "normal"
},
{
"type": "text",
"id": "H8vvk",
"name": "t3",
"fill": "#FFFFFF",
"content": "Bottle",
"fontFamily": "Inter",
"fontSize": 15,
"fontWeight": "700"
},
{
"type": "text",
"id": "DvuJh",
"name": "d3",
"fill": "#FFFFFF66",
"content": "|",
"fontFamily": "Inter",
"fontSize": 14,
"fontWeight": "normal"
},
{
"type": "text",
"id": "RDOkw",
"name": "t4",
"fill": "#FFFFFF",
"content": "Photo Repair",
"fontFamily": "Inter",
"fontSize": 15,
"fontWeight": "700"
}
]
},
{ {
"type": "frame", "type": "frame",
"id": "3lNJT", "id": "3lNJT",
@ -272,8 +184,9 @@
"type": "frame", "type": "frame",
"id": "aHMps", "id": "aHMps",
"name": "createBtn", "name": "createBtn",
"width": 212, "opacity": 0.88,
"height": 50, "width": 186,
"height": 42,
"fill": { "fill": {
"type": "gradient", "type": "gradient",
"gradientType": "linear", "gradientType": "linear",
@ -313,10 +226,10 @@
}, },
"blur": 28 "blur": 28
}, },
"gap": 12, "gap": 10,
"padding": [ "padding": [
14, 11,
28 22
], ],
"justifyContent": "center", "justifyContent": "center",
"alignItems": "center", "alignItems": "center",
@ -325,8 +238,8 @@
"type": "frame", "type": "frame",
"id": "TAocZ", "id": "TAocZ",
"name": "plusCirc", "name": "plusCirc",
"width": 28, "width": 24,
"height": 28, "height": 24,
"fill": "#FFFFFF", "fill": "#FFFFFF",
"cornerRadius": 20, "cornerRadius": 20,
"stroke": { "stroke": {
@ -351,8 +264,8 @@
"type": "icon_font", "type": "icon_font",
"id": "9PFVT", "id": "9PFVT",
"name": "plusIc", "name": "plusIc",
"width": 14, "width": 12,
"height": 14, "height": 12,
"iconFontName": "plus", "iconFontName": "plus",
"iconFontFamily": "lucide", "iconFontFamily": "lucide",
"fill": "#B45309" "fill": "#B45309"
@ -366,7 +279,7 @@
"fill": "#1C1917", "fill": "#1C1917",
"content": "Create Now", "content": "Create Now",
"fontFamily": "Inter", "fontFamily": "Inter",
"fontSize": 18, "fontSize": 16,
"fontWeight": "800", "fontWeight": "800",
"letterSpacing": 0.4 "letterSpacing": 0.4
} }
@ -487,21 +400,27 @@
{ {
"type": "frame", "type": "frame",
"id": "rULLj", "id": "rULLj",
"name": "backF", "name": "closeF",
"width": 44, "width": 44,
"height": 44, "height": 44,
"fill": "#00000000", "fill": "#FFFFFF",
"cornerRadius": 14,
"stroke": {
"align": "inside",
"thickness": 1,
"fill": "#E7E5E4"
},
"justifyContent": "center", "justifyContent": "center",
"alignItems": "center", "alignItems": "center",
"children": [ "children": [
{ {
"type": "icon_font", "type": "icon_font",
"id": "byDOX", "id": "byDOX",
"width": 28, "width": 26,
"height": 28, "height": 26,
"iconFontName": "chevron-left", "iconFontName": "x",
"iconFontFamily": "lucide", "iconFontFamily": "lucide",
"fill": "#404040" "fill": "#374151"
} }
] ]
}, },
@ -1077,21 +996,27 @@
{ {
"type": "frame", "type": "frame",
"id": "dYND0", "id": "dYND0",
"name": "backCr", "name": "closeCr",
"width": 44, "width": 44,
"height": 44, "height": 44,
"fill": "#00000000", "fill": "#FFFFFF",
"cornerRadius": 14,
"stroke": {
"align": "inside",
"thickness": 1,
"fill": "#E7E5E4"
},
"justifyContent": "center", "justifyContent": "center",
"alignItems": "center", "alignItems": "center",
"children": [ "children": [
{ {
"type": "icon_font", "type": "icon_font",
"id": "nh3Nf", "id": "nh3Nf",
"width": 28, "width": 26,
"height": 28, "height": 26,
"iconFontName": "chevron-left", "iconFontName": "x",
"iconFontFamily": "lucide", "iconFontFamily": "lucide",
"fill": "#404040" "fill": "#374151"
} }
] ]
}, },

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'core/auth/auth_service.dart'; import 'core/auth/auth_service.dart';
import 'core/funymee_route_observer.dart';
import 'core/theme/app_colors.dart'; import 'core/theme/app_colors.dart';
import 'core/theme/app_theme.dart'; import 'core/theme/app_theme.dart';
import 'features/shell/main_screen.dart'; import 'features/shell/main_screen.dart';
@ -56,6 +57,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
title: widget.title, title: widget.title,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: buildFunyMeeTheme(), theme: buildFunyMeeTheme(),
navigatorObservers: [funymeeRouteObserver],
home: const MainScreen(), home: const MainScreen(),
builder: (context, child) { builder: (context, child) {
return Stack( return Stack(

View File

@ -70,7 +70,10 @@ class AppAuthCallbacks implements AuthServiceCallbacks {
@override @override
void onCommonInfoLoaded(CommonInfoResponse data) { void onCommonInfoLoaded(CommonInfoResponse data) {
if (data.credits != null) UserState.setCredits(data.credits!); if (data.credits != null) UserState.setCredits(data.credits!);
if (data.avatar != null) UserState.setAvatar(data.avatar!); if (data.avatar != null) {
final t = data.avatar!.trim();
UserState.setAvatar(t.isEmpty ? null : t);
}
if (data.userName != null) UserState.setUserName(data.userName!); if (data.userName != null) UserState.setUserName(data.userName!);
} }

View File

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
/// [MaterialApp.navigatorObservers]
final RouteObserver<ModalRoute<dynamic>> funymeeRouteObserver =
RouteObserver<ModalRoute<dynamic>>();

View File

@ -24,7 +24,10 @@ class UserState {
}) { }) {
if (userId != null) UserState.userId.value = userId; if (userId != null) UserState.userId.value = userId;
if (credits != null) UserState.credits.value = credits; if (credits != null) UserState.credits.value = credits;
if (avatar != null) UserState.avatar.value = avatar; if (avatar != null) {
final t = avatar.trim();
UserState.avatar.value = t.isEmpty ? null : t;
}
if (userName != null) UserState.userName.value = userName; if (userName != null) UserState.userName.value = userName;
} }

View File

@ -127,12 +127,9 @@ class _HistoryScreenState extends State<HistoryScreen> {
const EdgeInsets.symmetric(horizontal: 12), const EdgeInsets.symmetric(horizontal: 12),
child: Row( child: Row(
children: [ children: [
if (widget.isRootTab) PencilRoundCloseButton(
const SizedBox(width: 44) onPressed: () => Navigator.maybePop(context),
else ),
PencilRoundBackButton(
onPressed: () => Navigator.of(context).pop(),
),
Expanded( Expanded(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View File

@ -8,12 +8,15 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import '../../core/auth/auth_service.dart'; import '../../core/auth/auth_service.dart';
import '../../core/funymee_route_observer.dart';
import '../../core/open_purchase_store.dart'; import '../../core/open_purchase_store.dart';
import '../../core/user/user_state.dart'; import '../../core/user/user_state.dart';
import '../../core/video_file_cache.dart'; import '../../core/video_file_cache.dart';
import '../../design/pencil_theme.dart'; import '../../design/pencil_theme.dart';
import '../../widgets/pencil_chrome.dart'; import '../../widgets/pencil_chrome.dart';
import '../generate/generate_screen.dart'; import '../generate/generate_screen.dart';
import '../history/history_screen.dart';
import '../profile/profile_screen.dart';
/// Create Now **480p ** [GenerateScreen] 480p /// Create Now **480p ** [GenerateScreen] 480p
int _homeCostDisplay480p(ExtConfigItem? t) { int _homeCostDisplay480p(ExtConfigItem? t) {
@ -64,6 +67,29 @@ class _HomeScreenState extends State<HomeScreen> {
int _lastCategoryTabBarCount = 0; int _lastCategoryTabBarCount = 0;
Widget _topNavIconButton({
required IconData icon,
required VoidCallback onTap,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
width: 35,
height: 35,
decoration: BoxDecoration(
color: const Color(0x99C4C4C4),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: Icon(icon, size: 20, color: const Color(0xFF374151)),
),
),
);
}
List<ExtConfigItem> _visibleExtItems(ExtConfigData? ext) => List<ExtConfigItem> _visibleExtItems(ExtConfigData? ext) =>
ext?.items.where((e) => e.isUsableOnHome).toList() ?? []; ext?.items.where((e) => e.isUsableOnHome).toList() ?? [];
@ -379,17 +405,46 @@ class _HomeScreenState extends State<HomeScreen> {
SizedBox( SizedBox(
height: 56, height: 56,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
ValueListenableBuilder<int>( _topNavIconButton(
valueListenable: UserState.credits, icon: Icons.history_rounded,
builder: (_, credits, _) { onTap: () {
return PencilGlassCreditsPill( Navigator.of(context).push(
amountText: credits.toStringAsFixed(2), MaterialPageRoute<void>(
onTap: () => openPurchaseStore(context), builder: (_) => const HistoryScreen(),
),
); );
}, },
), ),
const SizedBox(width: 8),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ValueListenableBuilder<int>(
valueListenable: UserState.credits,
builder: (_, credits, _) {
return PencilGlassCreditsPill(
amountText: credits.toStringAsFixed(2),
onTap: () => openPurchaseStore(context),
);
},
),
const SizedBox(width: 10),
_topNavIconButton(
icon: Icons.person_rounded,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ProfileScreen(),
),
);
},
),
],
),
),
], ],
), ),
), ),
@ -726,9 +781,17 @@ class _HomeItemVideoBackground extends StatefulWidget {
_HomeItemVideoBackgroundState(); _HomeItemVideoBackgroundState();
} }
class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> { class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground>
with WidgetsBindingObserver, RouteAware {
VideoPlayerController? _controller; VideoPlayerController? _controller;
bool _failed = false; bool _failed = false;
bool _shouldActuallyPlay = false;
bool _appInForeground = true;
/// [Navigator.push] [ModalRoute.isCurrent] rebuild [RouteAware]
bool _coveredByPushedRoute = false;
ModalRoute<dynamic>? _routeAwareSubscription;
/// ExoPlayer 416UnrecognizedInputFormat /// ExoPlayer 416UnrecognizedInputFormat
static const int _maxOpenRetries = 6; static const int _maxOpenRetries = 6;
@ -744,6 +807,9 @@ class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> {
ImageStream? _coverImageStream; ImageStream? _coverImageStream;
ImageStreamListener? _coverImageListener; ImageStreamListener? _coverImageListener;
/// [build] [addPostFrameCallback]
bool _playbackGateCallbackPending = false;
String get _playUrl { String get _playUrl {
final v = widget.item.videoUrl?.trim(); final v = widget.item.videoUrl?.trim();
if (v != null && v.isNotEmpty) return v; if (v != null && v.isNotEmpty) return v;
@ -753,11 +819,49 @@ class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
final lifecycle = WidgetsBinding.instance.lifecycleState;
_appInForeground =
lifecycle == null || lifecycle == AppLifecycleState.resumed;
if (widget.isActive) { if (widget.isActive) {
_preloadCoverThenStartVideo(); _preloadCoverThenStartVideo();
} }
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_appInForeground = state == AppLifecycleState.resumed;
unawaited(_syncPlaybackGate());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
if (route != null && route != _routeAwareSubscription) {
funymeeRouteObserver.unsubscribe(this);
funymeeRouteObserver.subscribe(this, route);
_routeAwareSubscription = route;
}
}
@override
void didPushNext() {
_coveredByPushedRoute = true;
unawaited(_syncPlaybackGate());
}
@override
void didPopNext() {
_coveredByPushedRoute = false;
unawaited(_syncPlaybackGate());
// pop [ModalRoute.isCurrent] true
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
unawaited(_syncPlaybackGate());
});
}
@override @override
void didUpdateWidget(_HomeItemVideoBackground oldWidget) { void didUpdateWidget(_HomeItemVideoBackground oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
@ -866,6 +970,31 @@ class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> {
} }
} }
bool _computeShouldActuallyPlay() {
if (!mounted || !widget.isActive || !_appInForeground) return false;
if (_coveredByPushedRoute) return false;
// [RouteAware.didPushNext]/[didPopNext]
// [ModalRoute.isCurrent]pop false rebuild resume
final tickerEnabled = TickerMode.valuesOf(context).enabled;
return tickerEnabled;
}
Future<void> _syncPlaybackGate() async {
final c = _controller;
if (c == null) return;
if (!c.value.isInitialized) return;
final shouldPlay = _computeShouldActuallyPlay();
if (shouldPlay == _shouldActuallyPlay) return;
_shouldActuallyPlay = shouldPlay;
try {
if (shouldPlay) {
await c.play();
} else {
await c.pause();
}
} catch (_) {}
}
/// [VideoPlayerController.file] /// [VideoPlayerController.file]
/// [initialize] + [play] [downloadFile] HTML/ UnrecognizedInputFormat /// [initialize] + [play] [downloadFile] HTML/ UnrecognizedInputFormat
Future<void> _startPlaybackAsync() async { Future<void> _startPlaybackAsync() async {
@ -926,8 +1055,13 @@ class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> {
_openRetries = 0; _openRetries = 0;
controller.setLooping(true); controller.setLooping(true);
_shouldActuallyPlay = _computeShouldActuallyPlay();
try { try {
await controller.play(); if (_shouldActuallyPlay) {
await controller.play();
} else {
await controller.pause();
}
} catch (_) { } catch (_) {
if (mounted) _scheduleRecoverFromError(); if (mounted) _scheduleRecoverFromError();
return; return;
@ -982,6 +1116,9 @@ class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> {
@override @override
void dispose() { void dispose() {
funymeeRouteObserver.unsubscribe(this);
_routeAwareSubscription = null;
WidgetsBinding.instance.removeObserver(this);
_retryTimer?.cancel(); _retryTimer?.cancel();
_removeCoverListener(); _removeCoverListener();
_disposePlayback(); _disposePlayback();
@ -1025,6 +1162,15 @@ class _HomeItemVideoBackgroundState extends State<_HomeItemVideoBackground> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final shouldPlayNow = _computeShouldActuallyPlay();
if (shouldPlayNow != _shouldActuallyPlay && !_playbackGateCallbackPending) {
_playbackGateCallbackPending = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_playbackGateCallbackPending = false;
if (!mounted) return;
unawaited(_syncPlaybackGate());
});
}
if (!widget.isActive) { if (!widget.isActive) {
return SizedBox.expand( return SizedBox.expand(
child: Stack( child: Stack(

View File

@ -1,3 +1,4 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'package:client_proxy_framework/client_proxy_framework.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
@ -9,7 +10,6 @@ import '../../core/ext_config_document_urls.dart';
import '../../core/user/user_state.dart'; import '../../core/user/user_state.dart';
import '../../design/pencil_theme.dart'; import '../../design/pencil_theme.dart';
import '../../widgets/pencil_chrome.dart'; import '../../widgets/pencil_chrome.dart';
import '../purchase/purchase_screen.dart';
import '../web/app_web_view_screen.dart'; import '../web/app_web_view_screen.dart';
import 'delete_account_flow.dart'; import 'delete_account_flow.dart';
@ -51,23 +51,12 @@ class _ProfileScreenState extends State<ProfileScreen> {
padding: const EdgeInsets.fromLTRB(2, 0, 14, 10), padding: const EdgeInsets.fromLTRB(2, 0, 14, 10),
child: SizedBox( child: SizedBox(
height: 56, height: 56,
child: widget.isRootTab child: Align(
? Center( alignment: Alignment.centerRight,
child: Text( child: PencilRoundCloseButton(
'Profile', onPressed: () => Navigator.maybePop(context),
style: GoogleFonts.inter( ),
fontSize: 18, ),
fontWeight: FontWeight.w600,
color: PencilTheme.ink,
),
),
)
: Align(
alignment: Alignment.centerRight,
child: PencilRoundCloseButton(
onPressed: () => Navigator.of(context).pop(),
),
),
), ),
), ),
Expanded( Expanded(
@ -100,12 +89,27 @@ class _ProfileScreenState extends State<ProfileScreen> {
builder: (context, url, _) { builder: (context, url, _) {
if (url != null && url.isNotEmpty) { if (url != null && url.isNotEmpty) {
return ClipOval( return ClipOval(
child: Image.network( child: CachedNetworkImage(
url, imageUrl: url,
fit: BoxFit.cover, fit: BoxFit.cover,
width: 100, width: 100,
height: 100, height: 100,
errorBuilder: (_, _, _) => memCacheWidth: 200,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
placeholder: (context, url) => Center(
child: SizedBox(
width: 28,
height: 28,
child: CircularProgressIndicator(
strokeWidth: 2,
color: PencilTheme
.profileAvatarIcon
.withValues(alpha: 0.5),
),
),
),
errorWidget: (context, url, error) =>
_avatarFallback(), _avatarFallback(),
), ),
); );

View File

@ -1,18 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../core/theme/app_colors.dart';
import '../history/history_screen.dart';
import '../home/home_screen.dart'; import '../home/home_screen.dart';
import '../profile/profile_screen.dart';
/// + / [Shadow] /// Root shell: home only (no bottom tab bar).
const List<Shadow> _bottomNavItemShadows = [
Shadow(offset: Offset(0, 1), blurRadius: 3, color: Color(0x72000000)),
Shadow(offset: Offset(0, 0), blurRadius: 8, color: Color(0x40000000)),
];
/// Root shell: bottom tabs **Home**, **History**, **Profile** (English labels).
class MainScreen extends StatefulWidget { class MainScreen extends StatefulWidget {
const MainScreen({super.key}); const MainScreen({super.key});
@ -21,88 +11,10 @@ class MainScreen extends StatefulWidget {
} }
class _MainScreenState extends State<MainScreen> { class _MainScreenState extends State<MainScreen> {
int _index = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final parentNav = NavigationBarTheme.of(context);
return Scaffold( return Scaffold(
// All tabs: body extends under the bottom bar (faint gradient + [NavigationBar]). body: const HomeScreen(),
extendBody: true,
body: IndexedStack(
index: _index,
children: [
const HomeScreen(),
HistoryScreen(isRootTab: true, isTabSelected: _index == 1),
const ProfileScreen(isRootTab: true),
],
),
bottomNavigationBar: Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
AppColors.surface.withValues(alpha: 0.40),
AppColors.surface.withValues(alpha: 0.15),
AppColors.surface.withValues(alpha: 0),
],
stops: const [0.0, 0.48, 1.0],
),
),
),
),
NavigationBarTheme(
data: parentNav.copyWith(
labelTextStyle: WidgetStateProperty.resolveWith((states) {
final selected = states.contains(WidgetState.selected);
return GoogleFonts.inter(
fontSize: 12,
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
color: selected ? AppColors.primary : AppColors.onSurface,
shadows: _bottomNavItemShadows,
);
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
final selected = states.contains(WidgetState.selected);
return IconThemeData(
color: selected ? AppColors.primary : AppColors.onSurface,
shadows: _bottomNavItemShadows,
);
}),
),
child: NavigationBar(
backgroundColor: Colors.transparent,
elevation: 0,
surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent,
selectedIndex: _index,
onDestinationSelected: (i) => setState(() => _index = i),
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home_rounded),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.history_outlined),
selectedIcon: Icon(Icons.history_rounded),
label: 'History',
),
NavigationDestination(
icon: Icon(Icons.person_outline_rounded),
selectedIcon: Icon(Icons.person_rounded),
label: 'Profile',
),
],
),
),
],
),
); );
} }
} }

View File

@ -100,58 +100,104 @@ class PencilGlassCreditsPill extends StatelessWidget {
} }
} }
/// bi8Au Create Now `desgin/funymee_home.pen` [aHMps] /// bi8Au Create Now `desgin/funymee_home.pen` [aHMps]
class PencilCreateNowButton extends StatelessWidget { class PencilCreateNowButton extends StatelessWidget {
const PencilCreateNowButton({super.key, required this.onPressed}); const PencilCreateNowButton({super.key, required this.onPressed});
final VoidCallback onPressed; final VoidCallback onPressed;
static const double _w = 212; /// `aHMps` createBtn
static const double _h = 50; static const double _w = 186;
static const double _h = 42;
/// `aHMps` opacity
static const double _opacity = 0.88;
/// `aHMps` gap padding pen `padding` 22 `(42-24)/2` 24×24
static const double _gap = 10;
static const EdgeInsets _padding =
EdgeInsets.symmetric(horizontal: 22, vertical: 9);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Opacity(
color: Colors.transparent, opacity: _opacity,
child: InkWell( child: Material(
onTap: onPressed, color: Colors.transparent,
borderRadius: BorderRadius.circular(999), child: InkWell(
child: Ink( onTap: onPressed,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(999),
borderRadius: BorderRadius.circular(999), child: Ink(
gradient: const LinearGradient( decoration: BoxDecoration(
begin: Alignment.topCenter, borderRadius: BorderRadius.circular(999),
end: Alignment.bottomCenter, gradient: const LinearGradient(
colors: [ begin: Alignment.topCenter,
Color(0xFFFFFDE7), end: Alignment.bottomCenter,
Color(0xFFFDE047), colors: [
Color(0xFFF59E0B), Color(0xFFFFFDE7),
], Color(0xFFFDE047),
stops: [0.0, 0.42, 1.0], Color(0xFFF59E0B),
), ],
border: Border.all( stops: [0.0, 0.42, 1.0],
color: Color(0xD9FFFFFF),
width: 2,
),
boxShadow: const [
BoxShadow(
color: Color(0x52B45309),
offset: Offset(0, 10),
blurRadius: 28,
), ),
], border: Border.all(
), color: Color(0xD9FFFFFF),
child: SizedBox( width: 2,
width: _w, ),
height: _h, boxShadow: const [
child: Center( BoxShadow(
child: Text( color: Color(0x52B45309),
'Create Now', offset: Offset(0, 10),
style: GoogleFonts.inter( blurRadius: 28,
fontSize: 18, ),
fontWeight: FontWeight.w800, ],
color: PencilTheme.stone900, ),
letterSpacing: 0.4, child: SizedBox(
width: _w,
height: _h,
child: Padding(
padding: _padding,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Color(0x99F59E0B),
width: 1.5,
),
boxShadow: const [
BoxShadow(
color: Color(0x14000000),
offset: Offset(0, 2),
blurRadius: 6,
),
],
),
child: const SizedBox(
width: 24,
height: 24,
child: Icon(
Icons.add_rounded,
size: 12,
color: Color(0xFFB45309),
),
),
),
const SizedBox(width: _gap),
Text(
'Create Now',
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w800,
color: PencilTheme.stone900,
letterSpacing: 0.4,
),
),
],
), ),
), ),
), ),

View File

@ -2,7 +2,7 @@ name: funymee_ai
description: "FunyMee AI Application." description: "FunyMee AI Application."
publish_to: 'none' publish_to: 'none'
version: 1.0.14+14 version: 1.0.15+15
environment: environment:
sdk: ^3.11.1 sdk: ^3.11.1