chore: prepare yanting monorepo handoff
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../data/api/report_data_source.dart';
|
||||
import '../data/models/models.dart';
|
||||
import '../theme/wise_tokens.dart';
|
||||
import '../widgets/mini_player.dart';
|
||||
import 'feed/feed_page.dart';
|
||||
import 'institutions/institutions_page.dart';
|
||||
import 'listen/listen_page.dart';
|
||||
import 'profile/profile_page.dart';
|
||||
import 'reports/reports_page.dart';
|
||||
|
||||
class ShellPage extends StatefulWidget {
|
||||
const ShellPage({required this.dataSource, super.key});
|
||||
|
||||
final ReportDataSource dataSource;
|
||||
|
||||
@override
|
||||
State<ShellPage> createState() => _ShellPageState();
|
||||
}
|
||||
|
||||
class _ShellPageState extends State<ShellPage> {
|
||||
int index = 0;
|
||||
PlayerStateModel player = const PlayerStateModel();
|
||||
Timer? timer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void startAudio(AudioItem item) {
|
||||
timer?.cancel();
|
||||
setState(() {
|
||||
player = PlayerStateModel(
|
||||
audioId: item.audioId,
|
||||
reportId: item.reportId,
|
||||
title: item.titleCn,
|
||||
durationSec: item.durationSec,
|
||||
playing: true,
|
||||
speed: player.speed,
|
||||
);
|
||||
});
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (_) => tick());
|
||||
}
|
||||
|
||||
void startModuleAudio(String audioId, String reportId, String title, int durationSec) {
|
||||
startAudio(
|
||||
AudioItem(
|
||||
audioId: audioId,
|
||||
reportId: reportId,
|
||||
titleCn: title,
|
||||
reportTitleCn: title,
|
||||
durationSec: durationSec,
|
||||
institution: const Institution(id: '', nameCn: ''),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void tick() {
|
||||
if (!player.playing) return;
|
||||
final next = player.positionSec + player.speed.round().clamp(1, 2);
|
||||
setState(() {
|
||||
player = player.copyWith(
|
||||
positionSec: next >= player.durationSec ? player.durationSec : next,
|
||||
playing: next < player.durationSec,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void toggleAudio() {
|
||||
if (!player.hasAudio) return;
|
||||
setState(() => player = player.copyWith(playing: !player.playing));
|
||||
}
|
||||
|
||||
void seekAudio(int delta) {
|
||||
if (!player.hasAudio) return;
|
||||
setState(() {
|
||||
player = player.copyWith(
|
||||
positionSec: (player.positionSec + delta).clamp(0, player.durationSec),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void cycleSpeed() {
|
||||
const speeds = [1.0, 1.25, 1.5, 2.0];
|
||||
final current = speeds.indexOf(player.speed);
|
||||
setState(() => player = player.copyWith(speed: speeds[(current + 1) % speeds.length]));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pages = [
|
||||
FeedPage(
|
||||
dataSource: widget.dataSource,
|
||||
onPlay: startAudio,
|
||||
player: player,
|
||||
onStartModuleAudio: startModuleAudio,
|
||||
onToggleAudio: toggleAudio,
|
||||
onSeekAudio: seekAudio,
|
||||
onSpeed: cycleSpeed,
|
||||
),
|
||||
ReportsPage(
|
||||
dataSource: widget.dataSource,
|
||||
onPlay: startAudio,
|
||||
player: player,
|
||||
onStartModuleAudio: startModuleAudio,
|
||||
onToggleAudio: toggleAudio,
|
||||
onSeekAudio: seekAudio,
|
||||
onSpeed: cycleSpeed,
|
||||
),
|
||||
InstitutionsPage(dataSource: widget.dataSource),
|
||||
ListenPage(dataSource: widget.dataSource, onPlay: startAudio),
|
||||
ProfilePage(dataSource: widget.dataSource),
|
||||
];
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('研听'),
|
||||
Text(
|
||||
'全球机构研报中文解读',
|
||||
style: TextStyle(fontSize: 12, color: WiseColors.textSecondary, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: pages[index],
|
||||
bottomNavigationBar: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MiniPlayer(player: player, onToggle: toggleAudio),
|
||||
NavigationBar(
|
||||
selectedIndex: index,
|
||||
onDestinationSelected: (value) => setState(() => index = value),
|
||||
destinations: const [
|
||||
NavigationDestination(icon: Icon(Icons.auto_awesome_outlined), selectedIcon: Icon(Icons.auto_awesome), label: '推荐'),
|
||||
NavigationDestination(icon: Icon(Icons.article_outlined), selectedIcon: Icon(Icons.article), label: '研报'),
|
||||
NavigationDestination(icon: Icon(Icons.account_balance_outlined), selectedIcon: Icon(Icons.account_balance), label: '机构'),
|
||||
NavigationDestination(icon: Icon(Icons.headphones_outlined), selectedIcon: Icon(Icons.headphones), label: '听单'),
|
||||
NavigationDestination(icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: '我的'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user