Update UI + Agora Call
This commit is contained in:
@@ -10,6 +10,7 @@ import '../../core/services/hardware_shortcut_listener.dart';
|
||||
import '../../core/services/stt_service.dart';
|
||||
import '../../core/services/tts_service.dart';
|
||||
import '../../core/services/voice_command_handler.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
|
||||
class UserShell extends StatefulWidget {
|
||||
final Widget child;
|
||||
@@ -75,7 +76,8 @@ class _UserShellState extends State<UserShell> {
|
||||
if (data is! List) return;
|
||||
final commands = data
|
||||
.whereType<Map>()
|
||||
.map((item) => _voiceCommandFromJson(Map<String, dynamic>.from(item)))
|
||||
.map((item) =>
|
||||
_voiceCommandFromJson(Map<String, dynamic>.from(item)))
|
||||
.whereType<VoiceCommand>()
|
||||
.toList();
|
||||
if (commands.isNotEmpty) {
|
||||
@@ -181,18 +183,40 @@ class _AppShell extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: child,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (index) => context.go(items[index].route),
|
||||
destinations: [
|
||||
for (final item in items)
|
||||
NavigationDestination(
|
||||
icon: Icon(item.icon),
|
||||
selectedIcon: Icon(item.selectedIcon),
|
||||
label: item.label,
|
||||
backgroundColor: AppColors.surface,
|
||||
body: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
switchInCurve: Curves.easeOutCubic,
|
||||
switchOutCurve: Curves.easeInCubic,
|
||||
child: KeyedSubtree(
|
||||
key: ValueKey(location),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: const Border(top: BorderSide(color: AppColors.border)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, -8),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: NavigationBar(
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (index) => context.go(items[index].route),
|
||||
destinations: [
|
||||
for (final item in items)
|
||||
NavigationDestination(
|
||||
icon: Icon(item.icon),
|
||||
selectedIcon: Icon(item.selectedIcon),
|
||||
label: item.label,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/theme/app_colors.dart';
|
||||
|
||||
class FeaturePage extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
@@ -18,33 +20,51 @@ class FeaturePage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.fromLTRB(16, 14, 16, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style:
|
||||
Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(color: Color(0xFF64748B)),
|
||||
),
|
||||
],
|
||||
),
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 12, end: 0),
|
||||
duration: const Duration(milliseconds: 360),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (_, offset, child) => Opacity(
|
||||
opacity: (1 - offset / 12).clamp(0.0, 1.0),
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, offset),
|
||||
child: child,
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppColors.text,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(child: child),
|
||||
@@ -77,7 +97,16 @@ class FeatureEmptyPanel extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 48, color: const Color(0xFF64748B)),
|
||||
Container(
|
||||
width: 72,
|
||||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Icon(icon, size: 36, color: AppColors.primary),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
@@ -88,7 +117,7 @@ class FeatureEmptyPanel extends StatelessWidget {
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Color(0xFF64748B), height: 1.35),
|
||||
style: const TextStyle(color: AppColors.muted, height: 1.35),
|
||||
),
|
||||
if (action != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
@@ -120,7 +149,7 @@ class FeatureErrorPanel extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFEF2F2),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFFECACA)),
|
||||
),
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user