feat(app): 穿搭生成 + 方案流 + 效果图 + 3D 化身查看
- outfit_provider:方案列表/生成任务轮询(pending→planning→scoring→rendering→done)/详情/选定主方案/收藏 - outfit_page:日期范围 + 地点生成表单,生成中显示任务进度,方案列表卡片 - plan_viewer_page:PageView 手指滑动切换方案,穿衣清单(衣橱/新品标记)、发型发色、设为主方案/收藏 - plan_effect_page:效果图 3 视角(正面/侧面/背面)查看 - avatar_viewer:three_dart + flutter_gl 3D 渲染实现(自动旋转),GLB 资产未接入时占位展示,kEnable3D 开关 - avatar_viewer_page:化身查看页 + 重新构建 - 新增 /plan-viewer /plan-effect /avatar-viewer 路由
This commit is contained in:
@@ -1,10 +1,257 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class OutfitPage extends StatelessWidget {
|
||||
import '../../shared/widgets/empty_view.dart';
|
||||
import '../../shared/widgets/error_view.dart';
|
||||
import '../../shared/widgets/loading_view.dart';
|
||||
import 'outfit_provider.dart';
|
||||
|
||||
/// 穿搭:生成表单 + 方案列表
|
||||
class OutfitPage extends ConsumerStatefulWidget {
|
||||
const OutfitPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<OutfitPage> createState() => _OutfitPageState();
|
||||
}
|
||||
|
||||
class _OutfitPageState extends ConsumerState<OutfitPage> {
|
||||
DateTimeRange? _dateRange;
|
||||
final _locationCtrl = TextEditingController();
|
||||
bool _submitting = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_locationCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _pickDateRange() async {
|
||||
final now = DateTime.now();
|
||||
final range = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: now,
|
||||
lastDate: now.add(const Duration(days: 30)),
|
||||
initialDateRange: _dateRange ??
|
||||
DateTimeRange(start: now, end: now.add(const Duration(days: 2))),
|
||||
);
|
||||
if (range != null) setState(() => _dateRange = range);
|
||||
}
|
||||
|
||||
String _fmt(DateTime d) =>
|
||||
'${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
|
||||
|
||||
Future<void> _generate() async {
|
||||
final range = _dateRange;
|
||||
final location = _locationCtrl.text.trim();
|
||||
if (range == null) {
|
||||
Fluttertoast.showToast(msg: '请选择日期范围');
|
||||
return;
|
||||
}
|
||||
if (location.isEmpty) {
|
||||
Fluttertoast.showToast(msg: '请输入地点');
|
||||
return;
|
||||
}
|
||||
if (range.duration.inDays < 1) {
|
||||
Fluttertoast.showToast(msg: '日期范围至少 1 天');
|
||||
return;
|
||||
}
|
||||
setState(() => _submitting = true);
|
||||
final ok = await ref.read(generateProvider.notifier).generate(
|
||||
startDate: _fmt(range.start),
|
||||
endDate: _fmt(range.end),
|
||||
location: location,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => _submitting = false);
|
||||
if (ok) {
|
||||
Fluttertoast.showToast(msg: '生成完成');
|
||||
ref.read(generateProvider.notifier).reset();
|
||||
context.go('/plan-viewer');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(child: Text('穿搭方案(开发中)'));
|
||||
final plans = ref.watch(outfitPlanProvider);
|
||||
final gen = ref.watch(generateProvider);
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('穿搭方案')),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () => ref.read(outfitPlanProvider.notifier).refresh(),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('生成穿搭方案',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: scheme.primary)),
|
||||
const SizedBox(height: 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _submitting ? null : _pickDateRange,
|
||||
icon: const Icon(Icons.date_range_outlined),
|
||||
label: Text(_dateRange == null
|
||||
? '选择日期范围'
|
||||
: '${_fmt(_dateRange!.start)} ~ ${_fmt(_dateRange!.end)}'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _locationCtrl,
|
||||
enabled: !_submitting,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '地点(如:上海·外滩)',
|
||||
hintText: '影响天气与穿搭建议',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (gen.phase == GeneratePhase.running)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(gen.statusText,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: _submitting ? null : _generate,
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14)),
|
||||
child: const Text('生成'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('已有方案',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
plans.when(
|
||||
loading: () => const Padding(
|
||||
padding: EdgeInsets.only(top: 48),
|
||||
child: LoadingView(text: '加载方案...')),
|
||||
error: (e, _) => Padding(
|
||||
padding: const EdgeInsets.only(top: 48),
|
||||
child: ErrorView(
|
||||
message: e.toString().replaceFirst('Exception: ', ''),
|
||||
onRetry: () => ref.read(outfitPlanProvider.notifier).refresh(),
|
||||
),
|
||||
),
|
||||
data: (list) {
|
||||
if (list.isEmpty) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 48),
|
||||
child: EmptyView(
|
||||
message: '还没有穿搭方案,先设置日期与地点生成吧'),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
for (final p in list) ...[
|
||||
_PlanCard(plan: p),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlanCard extends ConsumerWidget {
|
||||
final OutfitPlanInfo plan;
|
||||
|
||||
const _PlanCard({required this.plan});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Card(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => context.go('/plan-viewer'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(Icons.checkroom, color: scheme.primary),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(plan.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 15)),
|
||||
),
|
||||
if (plan.isMain) ...[
|
||||
const SizedBox(width: 6),
|
||||
const Chip(
|
||||
label: Text('主方案'),
|
||||
labelStyle:
|
||||
TextStyle(color: Colors.white, fontSize: 11),
|
||||
backgroundColor: Colors.indigo,
|
||||
visualDensity: VisualDensity.compact,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${plan.dateRange} · ${plan.location} · 评分 ${plan.score}'
|
||||
'${plan.fromAi ? ' · AI 生成' : ' · 规则生成'}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/auth/auth_provider.dart';
|
||||
|
||||
class OutfitPlanInfo {
|
||||
final int id;
|
||||
final String title;
|
||||
final String source; // ai / rule
|
||||
final int score;
|
||||
final int mainFlag;
|
||||
final String dateRange;
|
||||
final String location;
|
||||
final int hairstyleId;
|
||||
final String hairColor;
|
||||
|
||||
const OutfitPlanInfo({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.source,
|
||||
required this.score,
|
||||
required this.mainFlag,
|
||||
required this.dateRange,
|
||||
required this.location,
|
||||
required this.hairstyleId,
|
||||
required this.hairColor,
|
||||
});
|
||||
|
||||
bool get isMain => mainFlag == 1;
|
||||
bool get fromAi => source == 'ai';
|
||||
}
|
||||
|
||||
class PlanItemInfo {
|
||||
final int id;
|
||||
final String slot; // 上衣/下装/鞋/配饰
|
||||
final String source; // wardrobe / new
|
||||
final int wardrobeItemId;
|
||||
final String name;
|
||||
final String desc;
|
||||
|
||||
const PlanItemInfo({
|
||||
required this.id,
|
||||
required this.slot,
|
||||
required this.source,
|
||||
required this.wardrobeItemId,
|
||||
required this.name,
|
||||
required this.desc,
|
||||
});
|
||||
|
||||
bool get fromWardrobe => source == 'wardrobe';
|
||||
}
|
||||
|
||||
class PlanEffectImageInfo {
|
||||
final int id;
|
||||
final String angle; // front / side / back
|
||||
final String url;
|
||||
final String status; // pending / rendering / done / failed
|
||||
|
||||
const PlanEffectImageInfo({
|
||||
required this.id,
|
||||
required this.angle,
|
||||
required this.url,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
bool get done => status == 'done' && url.isNotEmpty;
|
||||
}
|
||||
|
||||
class PlanDetail {
|
||||
final OutfitPlanInfo plan;
|
||||
final List<PlanItemInfo> items;
|
||||
final List<PlanEffectImageInfo> images;
|
||||
final String hairstyleName;
|
||||
|
||||
const PlanDetail({
|
||||
required this.plan,
|
||||
required this.items,
|
||||
required this.images,
|
||||
required this.hairstyleName,
|
||||
});
|
||||
}
|
||||
|
||||
/// 方案列表
|
||||
class OutfitPlanNotifier extends AsyncNotifier<List<OutfitPlanInfo>> {
|
||||
@override
|
||||
Future<List<OutfitPlanInfo>> build() async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final list = await api.get<List<dynamic>>('/outfit/plan/list');
|
||||
return list
|
||||
.map((e) => OutfitPlanInfo(
|
||||
id: (e['id'] as num).toInt(),
|
||||
title: e['title'] as String? ?? '',
|
||||
source: e['source'] as String? ?? 'rule',
|
||||
score: (e['score'] as num?)?.toInt() ?? 0,
|
||||
mainFlag: (e['main_flag'] as num?)?.toInt() ?? 0,
|
||||
dateRange: e['date_range'] as String? ?? '',
|
||||
location: e['location'] as String? ?? '',
|
||||
hairstyleId: (e['hairstyle_id'] as num?)?.toInt() ?? 0,
|
||||
hairColor: e['hair_color'] as String? ?? '',
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(build);
|
||||
}
|
||||
|
||||
Future<void> selectMain(int planId) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
await api.post('/outfit/plan/select-main', {'plan_id': planId});
|
||||
await refresh();
|
||||
}
|
||||
|
||||
Future<void> review(int planId, String action) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
await api.post('/outfit/plan/review', {'plan_id': planId, 'action': action});
|
||||
}
|
||||
}
|
||||
|
||||
final outfitPlanProvider =
|
||||
AsyncNotifierProvider<OutfitPlanNotifier, List<OutfitPlanInfo>>(
|
||||
OutfitPlanNotifier.new);
|
||||
|
||||
/// 生成任务状态(轮询后端任务状态机)
|
||||
enum GeneratePhase { idle, running, done, failed }
|
||||
|
||||
class GenerateState {
|
||||
final GeneratePhase phase;
|
||||
final String statusText;
|
||||
final String error;
|
||||
|
||||
const GenerateState({
|
||||
this.phase = GeneratePhase.idle,
|
||||
this.statusText = '',
|
||||
this.error = '',
|
||||
});
|
||||
}
|
||||
|
||||
class GenerateNotifier extends Notifier<GenerateState> {
|
||||
@override
|
||||
GenerateState build() => const GenerateState();
|
||||
|
||||
Future<bool> generate({
|
||||
required String startDate,
|
||||
required String endDate,
|
||||
required String location,
|
||||
}) async {
|
||||
state =
|
||||
const GenerateState(phase: GeneratePhase.running, statusText: '任务创建中...');
|
||||
final api = ref.read(apiClientProvider);
|
||||
final data = await api.post<Map<String, dynamic>>('/outfit/generate', {
|
||||
'start_date': startDate,
|
||||
'end_date': endDate,
|
||||
'location': location,
|
||||
});
|
||||
final taskId = (data['task_id'] as num).toInt();
|
||||
|
||||
for (var i = 0; i < 90; i++) {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
final st = await api.get<Map<String, dynamic>>('/outfit/task/status',
|
||||
query: {'task_id': taskId});
|
||||
final status = st['status'] as String? ?? '';
|
||||
final err = st['error'] as String? ?? '';
|
||||
state = GenerateState(
|
||||
phase: GeneratePhase.running, statusText: _statusText(status));
|
||||
if (status == 'done') {
|
||||
state = const GenerateState(phase: GeneratePhase.done, statusText: '完成');
|
||||
break;
|
||||
}
|
||||
if (status == 'failed') {
|
||||
state = GenerateState(
|
||||
phase: GeneratePhase.failed,
|
||||
error: err.isEmpty ? '生成失败,请稍后重试' : err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await ref.read(outfitPlanProvider.notifier).refresh();
|
||||
return state.phase == GeneratePhase.done;
|
||||
}
|
||||
|
||||
String _statusText(String status) {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return '排队中...';
|
||||
case 'planning':
|
||||
return 'AI 规划穿搭中...';
|
||||
case 'scoring':
|
||||
return '方案评分中...';
|
||||
case 'rendering':
|
||||
return '生成效果图中...';
|
||||
case 'failed':
|
||||
return '生成失败';
|
||||
default:
|
||||
return '处理中...';
|
||||
}
|
||||
}
|
||||
|
||||
void reset() => state = const GenerateState();
|
||||
}
|
||||
|
||||
final generateProvider =
|
||||
NotifierProvider<GenerateNotifier, GenerateState>(GenerateNotifier.new);
|
||||
|
||||
/// 方案详情
|
||||
final planDetailProvider = FutureProvider.family<PlanDetail, int>((ref, planId) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final data = await api.get<Map<String, dynamic>>('/outfit/plan/detail',
|
||||
query: {'plan_id': planId});
|
||||
final planData = data['plan'] as Map<String, dynamic>? ?? {};
|
||||
final items = (data['items'] as List<dynamic>? ?? [])
|
||||
.map((e) => PlanItemInfo(
|
||||
id: (e['id'] as num).toInt(),
|
||||
slot: e['slot'] as String? ?? '',
|
||||
source: e['source'] as String? ?? 'new',
|
||||
wardrobeItemId: (e['wardrobe_item_id'] as num?)?.toInt() ?? 0,
|
||||
name: e['name'] as String? ?? '',
|
||||
desc: e['desc'] as String? ?? '',
|
||||
))
|
||||
.toList();
|
||||
final images = (data['images'] as List<dynamic>? ?? [])
|
||||
.map((e) => PlanEffectImageInfo(
|
||||
id: (e['id'] as num).toInt(),
|
||||
angle: e['angle'] as String? ?? '',
|
||||
url: e['url'] as String? ?? '',
|
||||
status: e['status'] as String? ?? 'pending',
|
||||
))
|
||||
.toList();
|
||||
final hairstyle = data['hairstyle'] as Map<String, dynamic>?;
|
||||
return PlanDetail(
|
||||
plan: OutfitPlanInfo(
|
||||
id: (planData['id'] as num).toInt(),
|
||||
title: planData['title'] as String? ?? '',
|
||||
source: planData['source'] as String? ?? 'rule',
|
||||
score: (planData['score'] as num?)?.toInt() ?? 0,
|
||||
mainFlag: (planData['main_flag'] as num?)?.toInt() ?? 0,
|
||||
dateRange: planData['date_range'] as String? ?? '',
|
||||
location: planData['location'] as String? ?? '',
|
||||
hairstyleId: (planData['hairstyle_id'] as num?)?.toInt() ?? 0,
|
||||
hairColor: planData['hair_color'] as String? ?? '',
|
||||
),
|
||||
items: items,
|
||||
images: images,
|
||||
hairstyleName: hairstyle?['name'] as String? ?? '',
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/config/app_config.dart';
|
||||
import '../../shared/widgets/empty_view.dart';
|
||||
import '../../shared/widgets/loading_view.dart';
|
||||
import 'outfit_provider.dart';
|
||||
|
||||
const _angleLabels = {
|
||||
'front': '正面',
|
||||
'side': '侧面',
|
||||
'back': '背面',
|
||||
};
|
||||
|
||||
/// 效果图查看:3 视角(正面/侧面/背面)切换
|
||||
class PlanEffectPage extends StatefulWidget {
|
||||
final List<PlanEffectImageInfo> images;
|
||||
|
||||
const PlanEffectPage({super.key, required this.images});
|
||||
|
||||
@override
|
||||
State<PlanEffectPage> createState() => _PlanEffectPageState();
|
||||
}
|
||||
|
||||
class _PlanEffectPageState extends State<PlanEffectPage> {
|
||||
String? _selected; // 当前角度,null = 展示全部
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final done = widget.images.where((i) => i.done).toList();
|
||||
final pending = widget.images.where((i) => !i.done).toList();
|
||||
final angles = widget.images.map((i) => i.angle).toSet();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('效果图')),
|
||||
body: Column(
|
||||
children: [
|
||||
if (angles.length > 1)
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
label: const Text('全部'),
|
||||
selected: _selected == null,
|
||||
onSelected: (_) => setState(() => _selected = null),
|
||||
),
|
||||
),
|
||||
for (final a in angles)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
label: Text(_angleLabels[a] ?? a),
|
||||
selected: _selected == a,
|
||||
onSelected: (_) => setState(() => _selected = a),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: done.isEmpty
|
||||
? (pending.isEmpty
|
||||
? const EmptyView(
|
||||
message: '暂无效果图,选定主方案后自动生成(每日限 3 次)')
|
||||
: const LoadingView(text: '效果图生成中,请稍后刷新...'))
|
||||
: GridView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 0.8,
|
||||
),
|
||||
itemCount: _selected == null
|
||||
? done.length
|
||||
: done.where((i) => i.angle == _selected).length,
|
||||
itemBuilder: (ctx, idx) {
|
||||
final shown = _selected == null
|
||||
? done
|
||||
: done.where((i) => i.angle == _selected).toList();
|
||||
final img = shown[idx];
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Image.network(
|
||||
AppConfig.resolveUrl(img.url),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, _, _) => const Icon(
|
||||
Icons.broken_image_outlined,
|
||||
size: 48,
|
||||
color: Colors.grey),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(_angleLabels[img.angle] ?? img.angle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../core/config/app_config.dart';
|
||||
import '../../shared/widgets/loading_view.dart';
|
||||
import 'outfit_provider.dart';
|
||||
|
||||
/// 方案流:手指左右滑动切换方案,底部操作(效果图/选定/收藏)
|
||||
class PlanViewerPage extends ConsumerWidget {
|
||||
const PlanViewerPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final plans = ref.watch(outfitPlanProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('穿搭方案')),
|
||||
body: plans.when(
|
||||
loading: () => const LoadingView(text: '加载方案...'),
|
||||
error: (e, _) => Center(child: Text('加载失败:$e')),
|
||||
data: (list) {
|
||||
if (list.isEmpty) {
|
||||
return const Center(child: Text('暂无方案'));
|
||||
}
|
||||
return PageView.builder(
|
||||
itemCount: list.length,
|
||||
itemBuilder: (ctx, i) =>
|
||||
_PlanDetailView(planId: list[i].id, key: ValueKey(list[i].id)),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlanDetailView extends ConsumerStatefulWidget {
|
||||
final int planId;
|
||||
|
||||
const _PlanDetailView({required this.planId, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<_PlanDetailView> createState() => _PlanDetailViewState();
|
||||
}
|
||||
|
||||
class _PlanDetailViewState extends ConsumerState<_PlanDetailView> {
|
||||
bool _selecting = false;
|
||||
|
||||
Future<void> _selectMain(int planId) async {
|
||||
setState(() => _selecting = true);
|
||||
try {
|
||||
await ref.read(outfitPlanProvider.notifier).selectMain(planId);
|
||||
if (!mounted) return;
|
||||
Fluttertoast.showToast(msg: '已设为主方案,正在生成效果图');
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
Fluttertoast.showToast(
|
||||
msg: '操作失败:${e.toString().replaceFirst('Exception: ', '')}');
|
||||
} finally {
|
||||
if (mounted) setState(() => _selecting = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _review(int planId, String action) async {
|
||||
try {
|
||||
await ref.read(outfitPlanProvider.notifier).review(planId, action);
|
||||
if (!mounted) return;
|
||||
Fluttertoast.showToast(
|
||||
msg: action == 'fav' ? '已收藏' : '已取消收藏');
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
Fluttertoast.showToast(
|
||||
msg: '操作失败:${e.toString().replaceFirst('Exception: ', '')}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final detail = ref.watch(planDetailProvider(widget.planId));
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: detail.when(
|
||||
loading: () => const LoadingView(text: '加载方案详情...'),
|
||||
error: (e, _) => Center(child: Text('加载失败:$e')),
|
||||
data: (d) {
|
||||
final plan = d.plan;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(plan.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
if (plan.isMain)
|
||||
const Chip(
|
||||
label: Text('主方案'),
|
||||
labelStyle:
|
||||
TextStyle(color: Colors.white, fontSize: 11),
|
||||
backgroundColor: Colors.indigo,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
if (!plan.isMain)
|
||||
Chip(
|
||||
label: Text(plan.fromAi ? 'AI 生成' : '规则生成'),
|
||||
labelStyle: TextStyle(
|
||||
color: scheme.primary, fontSize: 11),
|
||||
backgroundColor: scheme.primaryContainer,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${plan.dateRange} · ${plan.location} · 评分 ${plan.score}',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (d.hairstyleName.isNotEmpty || plan.hairColor.isNotEmpty)
|
||||
Card(
|
||||
color: scheme.primaryContainer.withValues(alpha: 0.4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.content_cut, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'发型:${d.hairstyleName.isNotEmpty ? d.hairstyleName : '默认'}'
|
||||
'${plan.hairColor.isNotEmpty ? ' · 发色:${plan.hairColor}' : ''}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('穿衣清单',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: scheme.primary)),
|
||||
const SizedBox(height: 8),
|
||||
if (d.items.isEmpty)
|
||||
const Text('暂无穿搭单品',
|
||||
style: TextStyle(color: Colors.grey)),
|
||||
for (final item in d.items)
|
||||
Card(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
leading: Icon(_slotIcon(item.slot)),
|
||||
title: Text(item.name),
|
||||
subtitle: Text(item.desc),
|
||||
trailing: item.fromWardrobe
|
||||
? const Chip(
|
||||
label: Text('衣橱'),
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white, fontSize: 11),
|
||||
backgroundColor: Colors.teal,
|
||||
visualDensity: VisualDensity.compact,
|
||||
padding: EdgeInsets.zero,
|
||||
)
|
||||
: const Chip(
|
||||
label: Text('新品'),
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white, fontSize: 11),
|
||||
backgroundColor: Colors.orange,
|
||||
visualDensity: VisualDensity.compact,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('效果图',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: scheme.primary)),
|
||||
const SizedBox(height: 8),
|
||||
if (d.images.every((img) => !img.done))
|
||||
const Text('效果图生成中,选定主方案后生成(每日限 3 次)',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 13))
|
||||
else
|
||||
SizedBox(
|
||||
height: 96,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
for (final img in d.images.where((i) => i.done))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () => context.push('/plan-effect',
|
||||
extra: d.images
|
||||
.where((i) => i.done)
|
||||
.toList()),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
AppConfig.resolveUrl(img.url),
|
||||
width: 80,
|
||||
height: 96,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, _, _) => Container(
|
||||
width: 80,
|
||||
height: 96,
|
||||
color: Colors.grey.shade200,
|
||||
child: const Icon(Icons.image,
|
||||
color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: plan.isMain
|
||||
? null
|
||||
: _selecting
|
||||
? null
|
||||
: () => _selectMain(plan.id),
|
||||
icon: const Icon(Icons.star_outline),
|
||||
label: Text(plan.isMain ? '主方案' : '设为主方案'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => context.push('/plan-effect',
|
||||
extra: d.images),
|
||||
icon: const Icon(Icons.image_outlined),
|
||||
label: const Text('效果图'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _review(plan.id, 'fav'),
|
||||
icon: const Icon(Icons.favorite_outline),
|
||||
label: const Text('收藏'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _slotIcon(String slot) {
|
||||
switch (slot) {
|
||||
case '上衣':
|
||||
return Icons.checkroom;
|
||||
case '下装':
|
||||
return Icons.airline_seat_legroom_normal;
|
||||
case '鞋':
|
||||
return Icons.directions_walk;
|
||||
case '配饰':
|
||||
return Icons.watch_outlined;
|
||||
default:
|
||||
return Icons.style_outlined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import '../../shared/widgets/avatar_viewer.dart';
|
||||
import '../../shared/widgets/loading_view.dart';
|
||||
import 'avatar_provider.dart';
|
||||
|
||||
/// 3D 化身查看页
|
||||
class AvatarViewerPage extends ConsumerWidget {
|
||||
const AvatarViewerPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final avatar = ref.watch(avatarProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('我的 3D 化身')),
|
||||
body: avatar.when(
|
||||
loading: () => const LoadingView(text: '加载化身...'),
|
||||
error: (e, _) => Center(child: Text('加载失败:$e')),
|
||||
data: (a) => ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
AvatarViewer(
|
||||
glbUrl: a.glbUrl,
|
||||
title: '我的 3D 化身',
|
||||
subtitle: a.built
|
||||
? '脸型模板 ${a.faceTemplateId} · 体型模板 ${a.bodyTemplateId} · 肤色 ${a.skinToneIndex}'
|
||||
: '尚未构建化身',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('构建说明',
|
||||
style: TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'化身由脸型(20 档)× 体型(6 档)× 肤色(5 档)模板组合而成,'
|
||||
'依据大头照、全身照与身形参数自动匹配。'
|
||||
'3D 渲染在预烘焙 GLB 资产接入后启用。',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(avatarProvider.notifier)
|
||||
.buildAvatar();
|
||||
if (context.mounted) {
|
||||
Fluttertoast.showToast(msg: '化身构建完成');
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('重新构建化身'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -67,13 +67,15 @@ class ProfilePage extends ConsumerWidget {
|
||||
text: a.built
|
||||
? '已构建(模板 ${a.faceTemplateId}/${a.bodyTemplateId})'
|
||||
: '尚未构建化身,需要先完成照片与身形',
|
||||
actionText: a.built ? '重新构建' : '立即构建',
|
||||
onAction: () async {
|
||||
await ref.read(avatarProvider.notifier).buildAvatar();
|
||||
if (context.mounted) {
|
||||
Fluttertoast.showToast(msg: '化身构建完成');
|
||||
}
|
||||
},
|
||||
actionText: a.built ? '查看' : '立即构建',
|
||||
onAction: a.built
|
||||
? () => context.go('/avatar-viewer')
|
||||
: () async {
|
||||
await ref.read(avatarProvider.notifier).buildAvatar();
|
||||
if (context.mounted) {
|
||||
Fluttertoast.showToast(msg: '化身构建完成');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -6,6 +6,10 @@ import 'features/auth/login_page.dart';
|
||||
import 'features/commercial/commercial_page.dart';
|
||||
import 'features/home/home_page.dart';
|
||||
import 'features/outfit/outfit_page.dart';
|
||||
import 'features/outfit/outfit_provider.dart';
|
||||
import 'features/outfit/plan_effect_page.dart';
|
||||
import 'features/outfit/plan_viewer_page.dart';
|
||||
import 'features/profile/avatar_viewer_page.dart';
|
||||
import 'features/profile/body_tune_page.dart';
|
||||
import 'features/profile/photo_guide_page.dart';
|
||||
import 'features/profile/profile_page.dart';
|
||||
@@ -75,6 +79,19 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
path: '/commercial',
|
||||
builder: (ctx, state) => const CommercialPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/plan-viewer',
|
||||
builder: (ctx, state) => const PlanViewerPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/plan-effect',
|
||||
builder: (ctx, state) =>
|
||||
PlanEffectPage(images: state.extra as List<PlanEffectImageInfo>),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/avatar-viewer',
|
||||
builder: (ctx, state) => const AvatarViewerPage(),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gl/flutter_gl.dart';
|
||||
import 'package:three_dart/three_dart.dart' as three;
|
||||
import 'package:three_dart_jsm/three_dart_jsm.dart' as three_jsm;
|
||||
|
||||
import '../../core/config/app_config.dart';
|
||||
|
||||
/// 是否启用 three_dart 3D 渲染。
|
||||
/// MVP 阶段服务端尚无预烘焙 GLB 资产(/workspace/templates/*.glb 为构建产物),
|
||||
/// 默认关闭展示信息占位;接入真实 GLB 资源后置为 true 即可启用。
|
||||
const bool kEnable3D = false;
|
||||
|
||||
/// 3D 化身查看组件:three_dart 渲染 GLB,加载失败/未启用时展示信息占位
|
||||
class AvatarViewer extends StatelessWidget {
|
||||
final String? glbUrl;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final double height;
|
||||
|
||||
const AvatarViewer({
|
||||
super.key,
|
||||
required this.glbUrl,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
this.height = 360,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!kEnable3D || glbUrl == null || glbUrl!.isEmpty) {
|
||||
return _AvatarPlaceholder(
|
||||
title: title, subtitle: subtitle, height: height);
|
||||
}
|
||||
return _ThreeDAvatarViewer(
|
||||
url: AppConfig.resolveUrl(glbUrl!),
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
height: height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// three_dart 实现:flutter_gl 初始化 + GLTFLoader 加载 + 自动旋转渲染
|
||||
class _ThreeDAvatarViewer extends StatefulWidget {
|
||||
final String url;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final double height;
|
||||
|
||||
const _ThreeDAvatarViewer({
|
||||
required this.url,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_ThreeDAvatarViewer> createState() => _ThreeDAvatarViewerState();
|
||||
}
|
||||
|
||||
class _ThreeDAvatarViewerState extends State<_ThreeDAvatarViewer> {
|
||||
FlutterGlPlugin? _gl;
|
||||
three.WebGLRenderer? _renderer;
|
||||
three.Scene? _scene;
|
||||
three.Camera? _camera;
|
||||
three.Object3D? _model;
|
||||
dynamic _sourceTexture;
|
||||
double _width = 300;
|
||||
double _dpr = 1;
|
||||
bool _ready = false;
|
||||
bool _failed = false;
|
||||
bool _disposed = false;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _init());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
final mq = MediaQuery.of(context);
|
||||
_width = mq.size.width;
|
||||
_dpr = mq.devicePixelRatio;
|
||||
try {
|
||||
final gl = FlutterGlPlugin();
|
||||
await gl.initialize(options: {
|
||||
'antialias': true,
|
||||
'alpha': false,
|
||||
'width': _width.toInt(),
|
||||
'height': widget.height.toInt(),
|
||||
'dpr': _dpr,
|
||||
});
|
||||
await gl.prepareContext();
|
||||
|
||||
final renderer = three.WebGLRenderer({
|
||||
'width': _width,
|
||||
'height': widget.height,
|
||||
'gl': gl.gl,
|
||||
'antialias': true,
|
||||
'canvas': gl.element,
|
||||
});
|
||||
renderer.setPixelRatio(_dpr);
|
||||
renderer.setSize(_width, widget.height, false);
|
||||
|
||||
final target = three.WebGLMultisampleRenderTarget(
|
||||
(_width * _dpr).toInt(), (widget.height * _dpr).toInt(),
|
||||
three.WebGLRenderTargetOptions({'format': three.RGBAFormat}));
|
||||
target.samples = 4;
|
||||
renderer.setRenderTarget(target);
|
||||
final sourceTexture = renderer.getRenderTargetGLTexture(target);
|
||||
|
||||
await _loadModel(gl, renderer);
|
||||
|
||||
_gl = gl;
|
||||
_renderer = renderer;
|
||||
_sourceTexture = sourceTexture;
|
||||
if (mounted) setState(() => _ready = true);
|
||||
_timer = Timer.periodic(
|
||||
const Duration(milliseconds: 33), (_) => _render());
|
||||
} catch (e) {
|
||||
debugPrint('3D 渲染不可用,回退占位:$e');
|
||||
if (mounted) setState(() => _failed = true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadModel(
|
||||
FlutterGlPlugin gl, three.WebGLRenderer renderer) async {
|
||||
final scene = three.Scene();
|
||||
final camera = three.PerspectiveCamera(45, _width / widget.height, 0.1, 100);
|
||||
camera.position.set(0, 1.4, 3.2);
|
||||
scene.add(three.AmbientLight(0xffffff, 0.9));
|
||||
final keyLight = three.DirectionalLight(0xffffff, 0.9);
|
||||
keyLight.position.set(2, 4, 3);
|
||||
scene.add(keyLight);
|
||||
scene.add(camera);
|
||||
camera.lookAt(three.Vector3(0, 1, 0));
|
||||
|
||||
final loader = three_jsm.GLTFLoader(null);
|
||||
final result = await loader.loadAsync(widget.url);
|
||||
final model = result['scene'] as three.Object3D?;
|
||||
if (model != null) {
|
||||
model.rotation.y = 0.6;
|
||||
scene.add(model);
|
||||
}
|
||||
|
||||
_scene = scene;
|
||||
_camera = camera;
|
||||
_model = model;
|
||||
}
|
||||
|
||||
void _render() {
|
||||
if (_disposed || _renderer == null || _scene == null || _camera == null) {
|
||||
return;
|
||||
}
|
||||
final model = _model;
|
||||
if (model != null) {
|
||||
model.rotation.y += 0.01; // 自动旋转
|
||||
}
|
||||
_renderer!.render(_scene!, _camera!);
|
||||
_gl!.gl.flush();
|
||||
if (!kIsWeb) {
|
||||
_gl!.updateTexture(_sourceTexture);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_failed) {
|
||||
return _AvatarPlaceholder(
|
||||
title: widget.title, subtitle: widget.subtitle, height: widget.height);
|
||||
}
|
||||
return SizedBox(
|
||||
height: widget.height,
|
||||
width: double.infinity,
|
||||
child: _ready
|
||||
? (kIsWeb
|
||||
? HtmlElementView(viewType: _gl!.textureId!.toString())
|
||||
: Texture(textureId: _gl!.textureId!))
|
||||
: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 占位:GLB 资产未接入或加载失败时展示化身信息
|
||||
class _AvatarPlaceholder extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final double height;
|
||||
|
||||
const _AvatarPlaceholder({
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Container(
|
||||
height: height,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [scheme.primaryContainer, scheme.primary.withValues(alpha: 0.3)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.accessibility_new, size: 72, color: scheme.primary),
|
||||
const SizedBox(height: 12),
|
||||
Text(title,
|
||||
style:
|
||||
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
subtitle.isEmpty ? '3D 渲染接入中(v2)' : subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+131
-123
@@ -6,7 +6,7 @@ packages:
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "99.0.0"
|
||||
analyzer:
|
||||
@@ -14,7 +14,7 @@ packages:
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.1.0"
|
||||
archive:
|
||||
@@ -22,7 +22,7 @@ packages:
|
||||
description:
|
||||
name: archive
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.1"
|
||||
args:
|
||||
@@ -30,7 +30,7 @@ packages:
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
@@ -38,7 +38,7 @@ packages:
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
@@ -46,7 +46,7 @@ packages:
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
@@ -54,7 +54,7 @@ packages:
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
charcode:
|
||||
@@ -62,7 +62,7 @@ packages:
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
cli_config:
|
||||
@@ -70,7 +70,7 @@ packages:
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clock:
|
||||
@@ -78,7 +78,7 @@ packages:
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
@@ -86,7 +86,7 @@ packages:
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
@@ -94,7 +94,7 @@ packages:
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
@@ -102,7 +102,7 @@ packages:
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.1"
|
||||
cross_file:
|
||||
@@ -110,7 +110,7 @@ packages:
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5+4"
|
||||
crypto:
|
||||
@@ -118,7 +118,7 @@ packages:
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
csslib:
|
||||
@@ -126,7 +126,7 @@ packages:
|
||||
description:
|
||||
name: csslib
|
||||
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
cupertino_icons:
|
||||
@@ -134,31 +134,31 @@ packages:
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.10.0"
|
||||
version: "5.11.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
@@ -166,7 +166,7 @@ packages:
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
file:
|
||||
@@ -174,7 +174,7 @@ packages:
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector_linux:
|
||||
@@ -182,7 +182,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4"
|
||||
file_selector_macos:
|
||||
@@ -190,7 +190,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
file_selector_platform_interface:
|
||||
@@ -198,7 +198,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
file_selector_windows:
|
||||
@@ -206,7 +206,7 @@ packages:
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
fixnum:
|
||||
@@ -214,7 +214,7 @@ packages:
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
@@ -223,11 +223,11 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_gl:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_gl
|
||||
sha256: de74c88f77228f47dd280e2092b50eb49fe1fc008de74047d195a27f2db0b491
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.21"
|
||||
flutter_gl_macos:
|
||||
@@ -235,7 +235,7 @@ packages:
|
||||
description:
|
||||
name: flutter_gl_macos
|
||||
sha256: "62aa244d4aa9127115df651baec070893718dd27571c8dbca5374dbcb9fd4849"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
flutter_gl_platform_interface:
|
||||
@@ -243,7 +243,7 @@ packages:
|
||||
description:
|
||||
name: flutter_gl_platform_interface
|
||||
sha256: "03062491fac26d0fda80703e4a01894ecc4fec4f7e5cec131ef021fbf46b2fda"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
flutter_gl_web:
|
||||
@@ -251,7 +251,7 @@ packages:
|
||||
description:
|
||||
name: flutter_gl_web
|
||||
sha256: "005dc72618ee14659dff7dfe72d6b1fce0efdda745cfebfe4618ce7ac2044af9"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
flutter_gl_windows:
|
||||
@@ -259,7 +259,7 @@ packages:
|
||||
description:
|
||||
name: flutter_gl_windows
|
||||
sha256: cd9259fb8178863de9e667129f73447fa174447a3c4abd16defa07af648850e4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
flutter_lints:
|
||||
@@ -267,7 +267,7 @@ packages:
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
@@ -275,17 +275,17 @@ packages:
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.35"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "9255e1e3ad6e38906a1b4f8287678f95f378744c5b46b1985588543f3f19046e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "56e81e662e6e54e59b231da57e90ac0d06ac67d8e3f2273c129a37ae6282b40c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
version: "3.4.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -301,7 +301,7 @@ packages:
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: "7903c9d5339173497bfecbc23bc4212f5a87e0edfac2e1693fb74465ea67da7e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
frontend_server_client:
|
||||
@@ -309,7 +309,7 @@ packages:
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
@@ -317,7 +317,7 @@ packages:
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
go_router:
|
||||
@@ -325,7 +325,7 @@ packages:
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "17.3.0"
|
||||
html:
|
||||
@@ -333,7 +333,7 @@ packages:
|
||||
description:
|
||||
name: html
|
||||
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.6"
|
||||
http:
|
||||
@@ -341,7 +341,7 @@ packages:
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
http_multi_server:
|
||||
@@ -349,7 +349,7 @@ packages:
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
@@ -357,7 +357,7 @@ packages:
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
@@ -365,7 +365,7 @@ packages:
|
||||
description:
|
||||
name: image
|
||||
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
image_picker:
|
||||
@@ -373,7 +373,7 @@ packages:
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
image_picker_android:
|
||||
@@ -381,7 +381,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+19"
|
||||
image_picker_for_web:
|
||||
@@ -389,7 +389,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
image_picker_ios:
|
||||
@@ -397,7 +397,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+6"
|
||||
image_picker_linux:
|
||||
@@ -405,7 +405,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
image_picker_macos:
|
||||
@@ -413,7 +413,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2+1"
|
||||
image_picker_platform_interface:
|
||||
@@ -421,7 +421,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.1"
|
||||
image_picker_windows:
|
||||
@@ -429,7 +429,7 @@ packages:
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
io:
|
||||
@@ -437,7 +437,7 @@ packages:
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
leak_tracker:
|
||||
@@ -445,7 +445,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
@@ -453,7 +453,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
@@ -461,7 +461,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
@@ -469,15 +469,23 @@ packages:
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
listen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: listen
|
||||
sha256: cb0ad74d7453d8f83a7c1fae401828681ef5df21d41a3a26a04af590c985d085
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0-beta.4"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
@@ -485,7 +493,7 @@ packages:
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
@@ -493,7 +501,7 @@ packages:
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
@@ -501,7 +509,7 @@ packages:
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
mime:
|
||||
@@ -509,7 +517,7 @@ packages:
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
node_preamble:
|
||||
@@ -517,7 +525,7 @@ packages:
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
opentype_dart:
|
||||
@@ -525,7 +533,7 @@ packages:
|
||||
description:
|
||||
name: opentype_dart
|
||||
sha256: "4bd96aeed494289a87e92bde20afe60f59648dcef253c0a7159b65ffa23899dc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.1"
|
||||
package_config:
|
||||
@@ -533,7 +541,7 @@ packages:
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
@@ -541,7 +549,7 @@ packages:
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider_linux:
|
||||
@@ -549,7 +557,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
path_provider_platform_interface:
|
||||
@@ -557,7 +565,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
path_provider_windows:
|
||||
@@ -565,7 +573,7 @@ packages:
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
@@ -573,7 +581,7 @@ packages:
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
@@ -581,7 +589,7 @@ packages:
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
@@ -589,7 +597,7 @@ packages:
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pool:
|
||||
@@ -597,7 +605,7 @@ packages:
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
@@ -605,23 +613,23 @@ packages:
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "17100416c51db7810c71a7bb2c34d1f881faa0074fd452afb0c4db6f8f126c76"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "84351b3472a447f7b89f961f95c73287009d58c59d8cc1267425d995f78e50f1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
version: "3.4.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
shared_preferences_android:
|
||||
@@ -629,7 +637,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.27"
|
||||
shared_preferences_foundation:
|
||||
@@ -637,7 +645,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
@@ -645,7 +653,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
@@ -653,7 +661,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_web:
|
||||
@@ -661,7 +669,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
@@ -669,7 +677,7 @@ packages:
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
@@ -677,7 +685,7 @@ packages:
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
@@ -685,7 +693,7 @@ packages:
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
@@ -693,7 +701,7 @@ packages:
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
@@ -701,7 +709,7 @@ packages:
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
@@ -714,7 +722,7 @@ packages:
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
@@ -722,7 +730,7 @@ packages:
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
@@ -730,7 +738,7 @@ packages:
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
@@ -738,7 +746,7 @@ packages:
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
state_notifier:
|
||||
@@ -746,7 +754,7 @@ packages:
|
||||
description:
|
||||
name: state_notifier
|
||||
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
stream_channel:
|
||||
@@ -754,7 +762,7 @@ packages:
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
@@ -762,7 +770,7 @@ packages:
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
@@ -770,7 +778,7 @@ packages:
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
@@ -778,7 +786,7 @@ packages:
|
||||
description:
|
||||
name: test
|
||||
sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.31.0"
|
||||
test_api:
|
||||
@@ -786,7 +794,7 @@ packages:
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
test_core:
|
||||
@@ -794,7 +802,7 @@ packages:
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.17"
|
||||
three_dart:
|
||||
@@ -802,7 +810,7 @@ packages:
|
||||
description:
|
||||
name: three_dart
|
||||
sha256: "102ff2cc65c2dcb805166c7dc5fa00a9a3252b5aec34fb435cc558a7487dc40b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.16"
|
||||
three_dart_jsm:
|
||||
@@ -810,7 +818,7 @@ packages:
|
||||
description:
|
||||
name: three_dart_jsm
|
||||
sha256: "26a71aff4aa842ac8178d2ba8e64b997c434ce9f604fa4fa3ee49f343d2c84b4"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.10"
|
||||
typed_data:
|
||||
@@ -818,7 +826,7 @@ packages:
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
typr_dart:
|
||||
@@ -826,7 +834,7 @@ packages:
|
||||
description:
|
||||
name: typr_dart
|
||||
sha256: e8aa717c1445ceccd77bd6ba471683c8e17a9b14797ac09db3bd52d6fbd2fe7b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
universal_html:
|
||||
@@ -834,7 +842,7 @@ packages:
|
||||
description:
|
||||
name: universal_html
|
||||
sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
universal_io:
|
||||
@@ -842,7 +850,7 @@ packages:
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
uuid:
|
||||
@@ -850,7 +858,7 @@ packages:
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.0"
|
||||
vector_math:
|
||||
@@ -858,7 +866,7 @@ packages:
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
@@ -866,7 +874,7 @@ packages:
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
watcher:
|
||||
@@ -874,7 +882,7 @@ packages:
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
web:
|
||||
@@ -882,7 +890,7 @@ packages:
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
@@ -890,7 +898,7 @@ packages:
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
@@ -898,7 +906,7 @@ packages:
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
@@ -906,7 +914,7 @@ packages:
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
xdg_directories:
|
||||
@@ -914,7 +922,7 @@ packages:
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
@@ -922,7 +930,7 @@ packages:
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
@@ -930,7 +938,7 @@ packages:
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
|
||||
@@ -42,6 +42,7 @@ dependencies:
|
||||
fluttertoast: ^9.1.0
|
||||
three_dart: ^0.0.16
|
||||
three_dart_jsm: ^0.0.10
|
||||
flutter_gl: ^0.0.20
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user