بنقرة واحدة
flutter-ui-components
Flutter UI 组件开发规范。Invoke when: 1) 创建新的 UI 组件 2) 实现视频列表/卡片 3) 添加广告组件 4) 实现筛选/搜索功能
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Flutter UI 组件开发规范。Invoke when: 1) 创建新的 UI 组件 2) 实现视频列表/卡片 3) 添加广告组件 4) 实现筛选/搜索功能
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Create or update minimal AGENTS.md files in the repository root and nested module directories using progressive disclosure. Works across heterogeneous projects without assuming any fixed agent folder structure.
Code review excellence guidelines and best practices. Invoke when reviewing code, ensuring code quality, identifying bugs, improving performance, and maintaining code standards.
Complete guide for using drift database library in Dart applications (CLI, server-side, non-Flutter). Use when building Dart apps that need local SQLite database storage or PostgreSQL connection with type-safe queries, reactive streams, migrations, and efficient CRUD operations. Includes setup with sqlite3 package, PostgreSQL support with drift_postgres, connection pooling, and server-side patterns.
Build adaptive and responsive Flutter UIs that work beautifully across all platforms and screen sizes. Use when creating Flutter apps that need to adapt layouts based on screen size, support multiple platforms including mobile tablet desktop and web, handle different input devices like touch mouse and keyboard, implement responsive navigation patterns, optimize for large screens and foldables, or use Capability and Policy patterns for platform-specific behavior.
Comprehensive guide for implementing animations in Flutter. Use when adding motion and visual effects to Flutter apps: implicit animations (AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder), explicit animations (AnimationController, Tween, AnimatedWidget/AnimatedBuilder), hero animations (shared element transitions), staggered animations (sequential/overlapping), and physics-based animations. Includes workflow for choosing the right animation type, implementation patterns, and best practices for performance and user experience.
Flutter HTTP API 集成规范。Invoke when: 1) 添加新的 API 接口 2) 处理网络请求 3) 实现数据缓存 4) 处理请求错误
| name | flutter-ui-components |
| description | Flutter UI 组件开发规范。Invoke when: 1) 创建新的 UI 组件 2) 实现视频列表/卡片 3) 添加广告组件 4) 实现筛选/搜索功能 |
lib/components/
├── common/ # 通用组件
│ ├── common_filter_bar.dart # 筛选栏
│ ├── common_search_bar.dart # 搜索栏
│ └── filter_row.dart # 筛选行
├── video_*.dart # 视频相关组件
├── banner_ads.dart # 横幅广告
├── loading.dart # 加载组件
├── no_data.dart # 空数据组件
└── ...
class ColorStyles {
// 主色调
static const Color primary = Color(0xFFFF6B35);
static const Color primaryLight = Color(0xFFFF8A5B);
static const Color primaryDark = Color(0xFFE55A2B);
// 背景色
static const Color background = Color(0xFFF5F5F5);
static const Color cardBackground = Color(0xFFFFFFFF);
// 文字色
static const Color textPrimary = Color(0xFF333333);
static const Color textSecondary = Color(0xFF666666);
static const Color textTertiary = Color(0xFF999999);
// 功能色
static const Color success = Color(0xFF52C41A);
static const Color warning = Color(0xFFFAAD14);
static const Color error = Color(0xFFF5222D);
}
class TextStyles {
static const TextStyle titleLarge = TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorStyles.textPrimary,
);
static const TextStyle titleMedium = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ColorStyles.textPrimary,
);
static const TextStyle bodyMedium = TextStyle(
fontSize: 14,
color: ColorStyles.textSecondary,
);
static const TextStyle caption = TextStyle(
fontSize: 12,
color: ColorStyles.textTertiary,
);
}
class Layout {
// 边距
static const double paddingXS = 4;
static const double paddingSM = 8;
static const double paddingMD = 16;
static const double paddingLG = 24;
static const double paddingXL = 32;
// 圆角
static const double radiusSM = 4;
static const double radiusMD = 8;
static const double radiusLG = 12;
static const double radiusXL = 16;
// 间距
static const double gapXS = 4;
static const double gapSM = 8;
static const double gapMD = 12;
static const double gapLG = 16;
static const double gapXL = 24;
}
import 'package:flutter/material.dart';
import 'package:flutter_app/entity/video_entity.dart';
import 'package:flutter_app/style/color_styles.dart';
import 'package:flutter_app/style/layout.dart';
import 'package:flutter_app/style/text_styles.dart';
class VideoCard extends StatelessWidget {
final VideoEntity video;
final VoidCallback? onTap;
final double width;
const VideoCard({
Key? key,
required this.video,
this.onTap,
this.width = 160,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: width,
decoration: BoxDecoration(
color: ColorStyles.cardBackground,
borderRadius: BorderRadius.circular(Layout.radiusMD),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 封面图
ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(Layout.radiusMD),
),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(
video.cover ?? '',
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(
color: ColorStyles.background,
child: Icon(Icons.image, color: ColorStyles.textTertiary),
),
),
),
),
// 标题
Padding(
padding: EdgeInsets.all(Layout.paddingSM),
child: Text(
video.title ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyles.bodyMedium,
),
),
// 播放量
Padding(
padding: EdgeInsets.symmetric(horizontal: Layout.paddingSM),
child: Row(
children: [
Icon(Icons.play_circle_outline,
size: 14,
color: ColorStyles.textTertiary,
),
SizedBox(width: Layout.gapXS),
Text(
_formatCount(video.viewCount ?? 0),
style: TextStyles.caption,
),
],
),
),
SizedBox(height: Layout.paddingSM),
],
),
),
);
}
String _formatCount(int count) {
if (count >= 10000) {
return '${(count / 10000).toStringAsFixed(1)}万';
}
return count.toString();
}
}
import 'package:flutter/material.dart';
import 'package:flutter_app/style/color_styles.dart';
class LoadingWidget extends StatelessWidget {
final double size;
final Color? color;
const LoadingWidget({
Key? key,
this.size = 40,
this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
width: size,
height: size,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation<Color>(
color ?? ColorStyles.primary,
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_app/style/color_styles.dart';
import 'package:flutter_app/style/text_styles.dart';
class NoDataWidget extends StatelessWidget {
final String? message;
final VoidCallback? onRefresh;
const NoDataWidget({
Key? key,
this.message,
this.onRefresh,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/no_data.png',
width: 120,
height: 120,
),
SizedBox(height: 16),
Text(
message ?? '暂无数据',
style: TextStyles.bodyMedium,
),
if (onRefresh != null) ...[
SizedBox(height: 16),
TextButton(
onPressed: onRefresh,
child: Text('点击刷新'),
),
],
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_unionad/flutter_unionad.dart';
import 'package:flutter_app/ads/config.dart';
class BannerAdsWidget extends StatelessWidget {
final double width;
final double height;
const BannerAdsWidget({
Key? key,
this.width = 300,
this.height = 150,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
child: FlutterUnionadBannerView(
androidCodeId: AdsConfig.bannerAndroidId,
iosCodeId: AdsConfig.bannerIOSId,
width: width.toInt(),
height: height.toInt(),
callBack: FlutterUnionadBannerCallBack(
onShow: () => print('Banner 展示'),
onFail: (error) => print('Banner 错误: $error'),
onClick: () => print('Banner 点击'),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_app/style/color_styles.dart';
import 'package:flutter_app/style/layout.dart';
import 'package:flutter_app/style/text_styles.dart';
class FilterBar extends StatelessWidget {
final List<FilterItem> items;
final int selectedIndex;
final ValueChanged<int> onSelect;
const FilterBar({
Key? key,
required this.items,
required this.selectedIndex,
required this.onSelect,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 44,
color: ColorStyles.cardBackground,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: Layout.paddingMD),
itemCount: items.length,
separatorBuilder: (_, __) => SizedBox(width: Layout.gapLG),
itemBuilder: (context, index) {
final isSelected = index == selectedIndex;
return GestureDetector(
onTap: () => onSelect(index),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
items[index].label,
style: isSelected
? TextStyles.titleMedium.copyWith(
color: ColorStyles.primary,
)
: TextStyles.bodyMedium,
),
if (isSelected)
Container(
margin: EdgeInsets.only(top: 4),
width: 20,
height: 3,
decoration: BoxDecoration(
color: ColorStyles.primary,
borderRadius: BorderRadius.circular(2),
),
),
],
),
);
},
),
);
}
}
class FilterItem {
final String label;
final dynamic value;
FilterItem({required this.label, this.value});
}
class VideoGrid extends StatelessWidget {
final List<VideoEntity> videos;
final Function(VideoEntity) onVideoTap;
@override
Widget build(BuildContext context) {
final crossAxisCount = 2;
final spacing = Layout.gapMD;
final screenWidth = MediaQuery.of(context).size.width;
final itemWidth = (screenWidth - spacing * 3) / crossAxisCount;
return SliverPadding(
padding: EdgeInsets.all(Layout.paddingMD),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
childAspectRatio: 0.75,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
),
delegate: SliverChildBuilderDelegate(
(context, index) => VideoCard(
video: videos[index],
width: itemWidth,
onTap: () => onVideoTap(videos[index]),
),
childCount: videos.length,
),
),
);
}
}
class HorizontalVideoList extends StatelessWidget {
final String title;
final List<VideoEntity> videos;
final VoidCallback? onMore;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题栏
SectionHeader(
title: title,
onMore: onMore,
),
// 横向列表
SizedBox(
height: 200,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: Layout.paddingMD),
itemCount: videos.length,
separatorBuilder: (_, __) => SizedBox(width: Layout.gapMD),
itemBuilder: (context, index) => VideoCard(
video: videos[index],
width: 140,
),
),
),
],
);
}
}
ColorStyles 定义的颜色Layout 定义的常量TextStyles 定义的样式