feat(ui): 设置页第一版(缓存管理 / 诊断导出 / 连接状态 / 关于)
设置页此前没有任何入口,且内容全是占位。现在: - 首页右上角菜单按钮 → 设置页 - 缓存:标签条数、封面数量/占用、临时探测文件占用,一键清理(保留歌单与收藏) - 诊断:导出 版本/连接/缓存/当前播放/最近错误 到 txt,经 FileProvider 走系统分享 - 连接:显示 WebDAV 地址与用户,支持测试连接、退出登录 - 关于:版本、构建号、播放内核与 UI 许可说明 - 删除未被引用的 FullPlayerPage(与 player_page 重复) 真机验证:设置页正常打开,诊断导出成功调起系统分享面板。
This commit is contained in:
@@ -4,6 +4,7 @@ import '../services/audio_service.dart';
|
|||||||
import '../services/webdav_service.dart';
|
import '../services/webdav_service.dart';
|
||||||
import '../widgets/magnetic_scroll_physics.dart';
|
import '../widgets/magnetic_scroll_physics.dart';
|
||||||
import 'webdav_setup_page.dart';
|
import 'webdav_setup_page.dart';
|
||||||
|
import 'settings_page.dart';
|
||||||
import 'webdav_file_list_page.dart';
|
import 'webdav_file_list_page.dart';
|
||||||
import '../constants/ui_constants.dart';
|
import '../constants/ui_constants.dart';
|
||||||
import '../base/base_state.dart';
|
import '../base/base_state.dart';
|
||||||
@@ -365,7 +366,14 @@ class _HomePageState extends BaseState<HomePage> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.menu, color: Colors.white54),
|
icon: const Icon(Icons.menu, color: Colors.white54),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => const SettingsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+352
-44
@@ -1,59 +1,367 @@
|
|||||||
import 'package:flutter/material.dart';
|
// lib/pages/settings_page.dart
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
class SettingsPage extends StatelessWidget {
|
import 'package:android_intent_plus/android_intent.dart';
|
||||||
|
import 'package:android_intent_plus/flag.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../database/song_database.dart';
|
||||||
|
import '../services/audio_service.dart';
|
||||||
|
import '../services/webdav_service.dart';
|
||||||
|
import '../utils/file_provider_utils.dart';
|
||||||
|
|
||||||
|
/// 与 pubspec.yaml 的 version 保持同步(发版时一起改)
|
||||||
|
const String kAppVersion = '1.1.0-dev.1';
|
||||||
|
const int kAppBuild = 2;
|
||||||
|
|
||||||
|
class SettingsPage extends StatefulWidget {
|
||||||
const SettingsPage({super.key});
|
const SettingsPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<SettingsPage> createState() => _SettingsPageState();
|
||||||
return Scaffold(
|
}
|
||||||
appBar: AppBar(title: const Text('设置')),
|
|
||||||
body: ListView(
|
class _SettingsPageState extends State<SettingsPage> {
|
||||||
children: [
|
bool _loading = true;
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildTile(
|
int _songRows = 0;
|
||||||
icon: Icons.folder_open_outlined,
|
int _metadataRows = 0;
|
||||||
title: '本地文件 (SAF)',
|
int _favoriteRows = 0;
|
||||||
subtitle: '通过系统文件选择器导入',
|
int _artworkCount = 0;
|
||||||
onTap: () {
|
int _artworkBytes = 0;
|
||||||
// 🟢 这里粘贴你旧项目的 SAF 逻辑
|
int _tempCount = 0;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
int _tempBytes = 0;
|
||||||
const SnackBar(content: Text('待接入 SAF 逻辑')),
|
|
||||||
);
|
@override
|
||||||
},
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadStats() async {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
try {
|
||||||
|
final db = await SongDatabase().database;
|
||||||
|
final songRows =
|
||||||
|
(await db.rawQuery('SELECT COUNT(*) AS c FROM songs')).first['c'];
|
||||||
|
final metaRows = (await db.rawQuery(
|
||||||
|
'SELECT COUNT(*) AS c FROM metadata_cache'))
|
||||||
|
.first['c'];
|
||||||
|
final favRows =
|
||||||
|
(await db.rawQuery('SELECT COUNT(*) AS c FROM favorites')).first['c'];
|
||||||
|
|
||||||
|
var artworkCount = 0, artworkBytes = 0;
|
||||||
|
final docs = await getApplicationDocumentsDirectory();
|
||||||
|
final artDir = Directory('${docs.path}/artworks');
|
||||||
|
if (await artDir.exists()) {
|
||||||
|
await for (final entity in artDir.list()) {
|
||||||
|
if (entity is File) {
|
||||||
|
artworkCount++;
|
||||||
|
artworkBytes += await entity.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempCount = 0, tempBytes = 0;
|
||||||
|
final tmp = await getTemporaryDirectory();
|
||||||
|
if (await tmp.exists()) {
|
||||||
|
await for (final entity in tmp.list()) {
|
||||||
|
if (entity is File &&
|
||||||
|
entity.path.contains('/metadata_') &&
|
||||||
|
entity.path.endsWith('.part')) {
|
||||||
|
tempCount++;
|
||||||
|
tempBytes += await entity.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_songRows = songRows as int? ?? 0;
|
||||||
|
_metadataRows = metaRows as int? ?? 0;
|
||||||
|
_favoriteRows = favRows as int? ?? 0;
|
||||||
|
_artworkCount = artworkCount;
|
||||||
|
_artworkBytes = artworkBytes;
|
||||||
|
_tempCount = tempCount;
|
||||||
|
_tempBytes = tempBytes;
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('⚠️ [Settings] 统计失败: $e');
|
||||||
|
if (mounted) setState(() => _loading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _clearMetadataCache() async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: const Color(0xFF1A1F1E),
|
||||||
|
title: const Text('清理元数据缓存', style: TextStyle(color: Colors.white)),
|
||||||
|
content: const Text(
|
||||||
|
'会删除歌曲标签缓存和封面文件,歌单与收藏不受影响。\n下次播放时重新抓取。',
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
),
|
),
|
||||||
_buildTile(
|
actions: [
|
||||||
icon: Icons.cloud_outlined,
|
TextButton(
|
||||||
title: 'WebDAV 云盘',
|
onPressed: () => Navigator.pop(context, false),
|
||||||
subtitle: '连接你的 NAS 或网盘',
|
child: const Text('取消'),
|
||||||
onTap: () {
|
|
||||||
// 🟢 这里调用你现成的 WebDAV 通道
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('待接入 WebDAV 逻辑')),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
_buildTile(
|
TextButton(
|
||||||
icon: Icons.equalizer_outlined,
|
onPressed: () => Navigator.pop(context, true),
|
||||||
title: '均衡器',
|
child: const Text('清理', style: TextStyle(color: Colors.redAccent)),
|
||||||
subtitle: '简单低音增强 (后续)',
|
),
|
||||||
onTap: () {},
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirmed != true) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final db = await SongDatabase().database;
|
||||||
|
await db.delete('songs');
|
||||||
|
await db.delete('metadata_cache');
|
||||||
|
|
||||||
|
final docs = await getApplicationDocumentsDirectory();
|
||||||
|
final artDir = Directory('${docs.path}/artworks');
|
||||||
|
if (await artDir.exists()) {
|
||||||
|
await artDir.delete(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final tmp = await getTemporaryDirectory();
|
||||||
|
if (await tmp.exists()) {
|
||||||
|
await for (final entity in tmp.list()) {
|
||||||
|
if (entity is File &&
|
||||||
|
entity.path.contains('/metadata_') &&
|
||||||
|
entity.path.endsWith('.part')) {
|
||||||
|
await entity.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('元数据缓存已清理')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await _loadStats();
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(SnackBar(content: Text('清理失败: $e')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportDiagnostics() async {
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final docs = await getApplicationDocumentsDirectory();
|
||||||
|
final file =
|
||||||
|
File('${docs.path}/diagnostics-${now.millisecondsSinceEpoch}.txt');
|
||||||
|
|
||||||
|
final song = AudioService().currentSong;
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('清听 诊断信息')
|
||||||
|
..writeln('时间: ${now.toIso8601String()}')
|
||||||
|
..writeln('版本: $kAppVersion ($kAppBuild)')
|
||||||
|
..writeln('平台: ${Platform.operatingSystem} ${Platform.operatingSystemVersion}')
|
||||||
|
..writeln()
|
||||||
|
..writeln('[连接]')
|
||||||
|
..writeln('baseUrl: ${WebDAVService.instance.baseUrl ?? '(未配置)'}')
|
||||||
|
..writeln('username: ${WebDAVService.instance.username ?? '(未配置)'}')
|
||||||
|
..writeln('connected: ${WebDAVService.instance.isConnected}')
|
||||||
|
..writeln()
|
||||||
|
..writeln('[数据]')
|
||||||
|
..writeln('songs: $_songRows')
|
||||||
|
..writeln('metadata_cache: $_metadataRows')
|
||||||
|
..writeln('favorites: $_favoriteRows')
|
||||||
|
..writeln('artwork 文件: $_artworkCount 个 / ${_formatBytes(_artworkBytes)}')
|
||||||
|
..writeln('临时探测文件: $_tempCount 个 / ${_formatBytes(_tempBytes)}')
|
||||||
|
..writeln()
|
||||||
|
..writeln('[当前播放]')
|
||||||
|
..writeln('song: ${song?.title ?? '(无)'} - ${song?.artist ?? ''}')
|
||||||
|
..writeln('path: ${song?.id ?? ''}')
|
||||||
|
..writeln('position: ${AudioService().position}')
|
||||||
|
..writeln('duration: ${AudioService().duration}')
|
||||||
|
..writeln('lastError: ${AudioService().lastPlaybackError ?? '(无)'}');
|
||||||
|
|
||||||
|
await file.writeAsString(buffer.toString());
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final uri = await FileProviderUtils.getContentUri(file);
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
final intent = AndroidIntent(
|
||||||
|
action: 'android.intent.action.SEND',
|
||||||
|
type: 'text/plain',
|
||||||
|
arguments: {
|
||||||
|
'android.intent.extra.STREAM': uri.toString(),
|
||||||
|
'android.intent.extra.SUBJECT': '清听诊断信息',
|
||||||
|
},
|
||||||
|
flags: const [Flag.FLAG_GRANT_READ_URI_PERMISSION],
|
||||||
|
);
|
||||||
|
await intent.launch();
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('⚠️ [Settings] 分享失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: const Color(0xFF1A1F1E),
|
||||||
|
title: const Text('诊断信息已导出', style: TextStyle(color: Colors.white)),
|
||||||
|
content: SelectableText(
|
||||||
|
file.path,
|
||||||
|
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('好'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(SnackBar(content: Text('导出失败: $e')));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTile(
|
Future<void> _testConnection() async {
|
||||||
{required IconData icon,
|
try {
|
||||||
required String title,
|
await WebDAVService.instance.listDirectory(path: '/');
|
||||||
required String subtitle,
|
if (mounted) {
|
||||||
required VoidCallback onTap}) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
return ListTile(
|
const SnackBar(content: Text('连接正常')),
|
||||||
leading: Icon(icon, color: const Color(0xFF7C9A9E)),
|
|
||||||
title: Text(title),
|
|
||||||
subtitle: Text(subtitle, style: const TextStyle(fontSize: 12)),
|
|
||||||
trailing: const Icon(Icons.chevron_right, color: Colors.grey),
|
|
||||||
onTap: onTap,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('连接失败: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _logout() async {
|
||||||
|
await WebDAVService.instance.clearCredentials();
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('已退出登录,下次进入会要求重新配置')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatBytes(int bytes) {
|
||||||
|
if (bytes < 1024) return '$bytes B';
|
||||||
|
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||||
|
return '${(bytes / 1024 / 1024).toStringAsFixed(1)} MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF0E1211),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
title: const Text('设置'),
|
||||||
|
),
|
||||||
|
body: _loading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
children: [
|
||||||
|
_sectionTitle('缓存'),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.cleaning_services_outlined,
|
||||||
|
color: Color(0xFF7C9A9E)),
|
||||||
|
title: const Text('元数据缓存'),
|
||||||
|
subtitle: Text(
|
||||||
|
'标签 $_songRows 首 · 封面 $_artworkCount 张 '
|
||||||
|
'(${_formatBytes(_artworkBytes)}) · 临时文件 ${_formatBytes(_tempBytes)}',
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
trailing: const Icon(Icons.chevron_right, color: Colors.grey),
|
||||||
|
onTap: _clearMetadataCache,
|
||||||
|
),
|
||||||
|
_sectionTitle('诊断'),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.bug_report_outlined,
|
||||||
|
color: Color(0xFF7C9A9E)),
|
||||||
|
title: const Text('导出诊断信息'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'版本、连接状态、缓存统计、当前播放与最近错误',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
trailing: const Icon(Icons.ios_share, color: Colors.grey),
|
||||||
|
onTap: _exportDiagnostics,
|
||||||
|
),
|
||||||
|
_sectionTitle('连接'),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.cloud_outlined,
|
||||||
|
color: Color(0xFF7C9A9E)),
|
||||||
|
title: Text(WebDAVService.instance.baseUrl ?? '(未配置)'),
|
||||||
|
subtitle: Text(
|
||||||
|
'用户 ${WebDAVService.instance.username ?? '-'} · '
|
||||||
|
'${WebDAVService.instance.isConnected ? '已连接' : '未连接'}',
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.wifi_tethering,
|
||||||
|
color: Color(0xFF7C9A9E)),
|
||||||
|
title: const Text('测试连接'),
|
||||||
|
onTap: _testConnection,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading:
|
||||||
|
const Icon(Icons.logout, color: Colors.redAccent),
|
||||||
|
title: const Text('退出登录'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'清除本机保存的 WebDAV 账号',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
onTap: _logout,
|
||||||
|
),
|
||||||
|
_sectionTitle('关于'),
|
||||||
|
ListTile(
|
||||||
|
leading:
|
||||||
|
const Icon(Icons.info_outline, color: Color(0xFF7C9A9E)),
|
||||||
|
title: const Text('清听'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'版本 $kAppVersion ($kAppBuild)\n'
|
||||||
|
'播放内核 media_kit (libmpv, LGPL/GPL) · UI Flutter (BSD)',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _sectionTitle(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(4, 20, 4, 8),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFFB8D4D0),
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
// lib/widgets/full_player_page.dart
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import '../services/audio_service.dart';
|
|
||||||
|
|
||||||
class FullPlayerPage extends StatelessWidget {
|
|
||||||
const FullPlayerPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final service = context.watch<AudioService>();
|
|
||||||
final song = service.currentSong!;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color(0xFF0E1211),
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios_new),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.playlist_play_outlined),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 280,
|
|
||||||
height: 280,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: const Color(0xFF2A3332),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: const Color(0xFF7C9A9E).withOpacity(0.2),
|
|
||||||
blurRadius: 60,
|
|
||||||
spreadRadius: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child:
|
|
||||||
const Icon(Icons.music_note, size: 80, color: Colors.white24),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 60),
|
|
||||||
Text(
|
|
||||||
song.title,
|
|
||||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
song.artist,
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey[400]),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
Slider(
|
|
||||||
value: 0.3,
|
|
||||||
onChanged: (v) {},
|
|
||||||
activeColor: const Color(0xFFB8D4D0),
|
|
||||||
inactiveColor: Colors.grey[800],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(Icons.skip_previous, size: 32),
|
|
||||||
color: Colors.white60,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
CircleAvatar(
|
|
||||||
radius: 32,
|
|
||||||
backgroundColor: const Color(0xFF7C9A9E),
|
|
||||||
child: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
service.isPlaying ? Icons.pause : Icons.play_arrow,
|
|
||||||
color: Colors.black87,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
onPressed: () => context.read<AudioService>().togglePlay(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(Icons.skip_next, size: 32),
|
|
||||||
color: Colors.white60,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(Icons.volume_up_outlined),
|
|
||||||
color: Colors.white38,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user