4 changed files with 579 additions and 626 deletions
@ -0,0 +1,261 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import '../controllers/agent_controller.dart'; |
||||
|
|
||||
|
/// 构建音乐项目 |
||||
|
Widget buildMusicItem(BuildContext context, String id, String name, String url, |
||||
|
String singer, String image) { |
||||
|
final controller = Get.find<AgentController>(); // 获取控制器 |
||||
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark; |
||||
|
|
||||
|
Map<String, String> song = { |
||||
|
'id': id, |
||||
|
'url': url, |
||||
|
'title': name, |
||||
|
'artist': singer, |
||||
|
'coverUrl': image, |
||||
|
}; |
||||
|
controller.musicStatus(); //刷新状态 |
||||
|
return Container( |
||||
|
padding: const EdgeInsets.all(14), |
||||
|
decoration: BoxDecoration( |
||||
|
color: isDarkMode ? Color(0xFF1E1E1E) : Colors.white, |
||||
|
borderRadius: BorderRadius.circular(12), |
||||
|
boxShadow: [ |
||||
|
BoxShadow( |
||||
|
color: Colors.black.withOpacity(isDarkMode ? 0.2 : 0.03), |
||||
|
blurRadius: 3, |
||||
|
spreadRadius: 1, |
||||
|
offset: const Offset(0, 1), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
child: Row( |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 50, |
||||
|
height: 50, |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.grey[100], |
||||
|
borderRadius: BorderRadius.circular(8), |
||||
|
image: image.isNotEmpty |
||||
|
? DecorationImage( |
||||
|
image: NetworkImage(image), |
||||
|
fit: BoxFit.cover, |
||||
|
) |
||||
|
: null, |
||||
|
), |
||||
|
child: image.isEmpty |
||||
|
? Icon( |
||||
|
Icons.music_note, |
||||
|
size: 18, |
||||
|
color: Theme.of(context).primaryColor, |
||||
|
) |
||||
|
: null, |
||||
|
), |
||||
|
const SizedBox(width: 12), |
||||
|
Expanded( |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
name, |
||||
|
style: TextStyle( |
||||
|
fontSize: 13, |
||||
|
fontWeight: FontWeight.w500, |
||||
|
color: Colors.grey[800], |
||||
|
), |
||||
|
overflow: TextOverflow.ellipsis, |
||||
|
), |
||||
|
SizedBox(height: 2), |
||||
|
Text( |
||||
|
singer, |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
color: Colors.grey[600], |
||||
|
), |
||||
|
overflow: TextOverflow.ellipsis, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
Obx(() { |
||||
|
final isThisPlaying = |
||||
|
controller.isPlaying.value && controller.currentUrl.value == url; |
||||
|
|
||||
|
return IconButton( |
||||
|
onPressed: () { |
||||
|
if (controller.isPlaying.value) { |
||||
|
if (controller.currentUrl.value == url) { |
||||
|
controller.musicPause(); |
||||
|
} else { |
||||
|
controller.musicPlay(song); |
||||
|
} |
||||
|
} else { |
||||
|
if (controller.currentUrl.value == url) { |
||||
|
controller.musicResume(); |
||||
|
} else { |
||||
|
controller.musicPlay(song); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
icon: Icon( |
||||
|
isThisPlaying ? Icons.pause_circle : Icons.play_circle, |
||||
|
color: Theme.of(context).primaryColor, |
||||
|
), |
||||
|
padding: EdgeInsets.zero, |
||||
|
constraints: BoxConstraints(), |
||||
|
iconSize: 36, |
||||
|
); |
||||
|
}), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// 构建音乐内容卡片 |
||||
|
Widget buildMusicContent( |
||||
|
BuildContext context, List<Map<String, dynamic>> playlist) { |
||||
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark; |
||||
|
|
||||
|
return Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
'以下是一些推荐的音乐:', |
||||
|
style: TextStyle( |
||||
|
color: isDarkMode ? Colors.white.withOpacity(0.9) : Colors.black87, |
||||
|
fontSize: 14, |
||||
|
height: 1.4, |
||||
|
fontWeight: FontWeight.w400, |
||||
|
), |
||||
|
), |
||||
|
const SizedBox(height: 12), |
||||
|
// Container( |
||||
|
// padding: const EdgeInsets.all(12), |
||||
|
// decoration: BoxDecoration( |
||||
|
// color: Colors.white, |
||||
|
// borderRadius: BorderRadius.circular(12), |
||||
|
// boxShadow: [ |
||||
|
// BoxShadow( |
||||
|
// color: Colors.black.withOpacity(0.03), |
||||
|
// blurRadius: 3, |
||||
|
// spreadRadius: 1, |
||||
|
// offset: const Offset(0, 1), |
||||
|
// ), |
||||
|
// ], |
||||
|
// ), |
||||
|
// child: |
||||
|
Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
Icon( |
||||
|
Icons.music_note, |
||||
|
size: 16, |
||||
|
color: Theme.of(context).primaryColor.withOpacity(0.7), |
||||
|
), |
||||
|
const SizedBox(width: 6), |
||||
|
Text( |
||||
|
'播放列表'.tr, |
||||
|
style: TextStyle( |
||||
|
fontSize: 13, |
||||
|
fontWeight: FontWeight.w600, |
||||
|
color: Theme.of(context).brightness == Brightness.dark |
||||
|
? Colors.grey[300] |
||||
|
: Colors.grey[700], |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
const SizedBox(height: 10), |
||||
|
|
||||
|
// 动态生成音乐条目 |
||||
|
...playlist.asMap().entries.take(4).map((entry) { |
||||
|
final index = entry.key; |
||||
|
final item = entry.value; |
||||
|
final id = item["id"]?.toString() ?? ""; |
||||
|
final name = item["name"] ?? "未知歌曲"; |
||||
|
final url = item["url"] ?? ""; |
||||
|
final singer = item["sgener"] ?? "未知歌手"; |
||||
|
final image = item["image"] ?? ""; |
||||
|
return Column( |
||||
|
children: [ |
||||
|
buildMusicItem(context, id, name, url, singer, image), |
||||
|
if (index != playlist.length - 1) |
||||
|
Divider( |
||||
|
height: 1, |
||||
|
thickness: 1, |
||||
|
color: Theme.of(context).brightness == Brightness.dark |
||||
|
? Colors.grey[800] |
||||
|
: Colors.grey[100], |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
}).toList(), |
||||
|
const SizedBox(height: 8), |
||||
|
Center( |
||||
|
child: TextButton( |
||||
|
onPressed: () {}, |
||||
|
style: TextButton.styleFrom( |
||||
|
minimumSize: Size.zero, |
||||
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), |
||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, |
||||
|
), |
||||
|
child: Text( |
||||
|
'查看更多推荐', |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
color: Theme.of(context).primaryColor, |
||||
|
fontWeight: FontWeight.w500, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
//), |
||||
|
//const SizedBox(height: 10), |
||||
|
// Row( |
||||
|
// mainAxisSize: MainAxisSize.min, |
||||
|
// children: [ |
||||
|
// Expanded( |
||||
|
// child: OutlinedButton.icon( |
||||
|
// onPressed: () {}, |
||||
|
// icon: Icon(Icons.play_arrow, size: 16), |
||||
|
// label: Text('播放全部'), |
||||
|
// style: OutlinedButton.styleFrom( |
||||
|
// foregroundColor: Theme.of(context).primaryColor, |
||||
|
// side: BorderSide( |
||||
|
// color: Theme.of(context).primaryColor.withOpacity(0.5)), |
||||
|
// padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), |
||||
|
// shape: RoundedRectangleBorder( |
||||
|
// borderRadius: BorderRadius.circular(16), |
||||
|
// ), |
||||
|
// textStyle: TextStyle(fontSize: 12), |
||||
|
// ), |
||||
|
// ), |
||||
|
// ), |
||||
|
// SizedBox(width: 8), |
||||
|
// Expanded( |
||||
|
// child: OutlinedButton.icon( |
||||
|
// onPressed: () {}, |
||||
|
// icon: Icon(Icons.shuffle, size: 16), |
||||
|
// label: Text('随机播放'), |
||||
|
// style: OutlinedButton.styleFrom( |
||||
|
// foregroundColor: Colors.grey[700], |
||||
|
// side: BorderSide(color: Colors.grey[400]!), |
||||
|
// padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), |
||||
|
// shape: RoundedRectangleBorder( |
||||
|
// borderRadius: BorderRadius.circular(16), |
||||
|
// ), |
||||
|
// textStyle: TextStyle(fontSize: 12), |
||||
|
// ), |
||||
|
// ), |
||||
|
// ), |
||||
|
// ], |
||||
|
// ), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,313 @@ |
|||||
|
import 'package:common_utils/common_utils.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
/// 构建天气内容卡片 |
||||
|
Widget buildWeatherContent(BuildContext context, String city, |
||||
|
Map<String, dynamic> now, List<Map<String, dynamic>> days) { |
||||
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark; |
||||
|
|
||||
|
return Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
'查询天气情况:', |
||||
|
style: TextStyle( |
||||
|
color: isDarkMode ? Colors.white.withOpacity(0.9) : Colors.black87, |
||||
|
fontSize: 14, |
||||
|
height: 1.4, |
||||
|
fontWeight: FontWeight.w400, |
||||
|
), |
||||
|
), |
||||
|
const SizedBox(height: 12), |
||||
|
Container( |
||||
|
padding: const EdgeInsets.all(16), |
||||
|
decoration: BoxDecoration( |
||||
|
gradient: isDarkMode |
||||
|
? LinearGradient( |
||||
|
begin: Alignment.topLeft, |
||||
|
end: Alignment.bottomRight, |
||||
|
colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)], |
||||
|
) |
||||
|
: LinearGradient( |
||||
|
begin: Alignment.topLeft, |
||||
|
end: Alignment.bottomRight, |
||||
|
colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)], |
||||
|
), |
||||
|
borderRadius: BorderRadius.circular(12), |
||||
|
boxShadow: [ |
||||
|
BoxShadow( |
||||
|
color: Colors.black.withOpacity(isDarkMode ? 0.3 : 0.1), |
||||
|
blurRadius: 8, |
||||
|
offset: const Offset(0, 2), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
"${city} ${now["date"]}", |
||||
|
style: const TextStyle( |
||||
|
fontSize: 16, |
||||
|
fontWeight: FontWeight.w600, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 4), |
||||
|
Text( |
||||
|
now["weatherCondition"], |
||||
|
style: TextStyle( |
||||
|
fontSize: 14, |
||||
|
color: Colors.white.withOpacity(0.9), |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 8), |
||||
|
Row( |
||||
|
children: [ |
||||
|
Text( |
||||
|
"${now["temperature"]}°", |
||||
|
style: const TextStyle( |
||||
|
fontSize: 36, |
||||
|
fontWeight: FontWeight.bold, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
), |
||||
|
SizedBox(width: 8), |
||||
|
Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
'最高: ${now["maxTemperature"]}°', |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
color: Colors.white.withOpacity(0.9), |
||||
|
), |
||||
|
), |
||||
|
Text( |
||||
|
'最低: ${now["minTemperature"]}°', |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
color: Colors.white.withOpacity(0.9), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
const Icon( |
||||
|
Icons.wb_sunny, |
||||
|
size: 48, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
SizedBox(height: 16), |
||||
|
Container( |
||||
|
height: 1, |
||||
|
color: Colors.white.withOpacity(isDarkMode ? 0.15 : 0.2), |
||||
|
), |
||||
|
SizedBox(height: 16), |
||||
|
const Text( |
||||
|
'未来预报', |
||||
|
style: TextStyle( |
||||
|
fontSize: 14, |
||||
|
fontWeight: FontWeight.w600, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 8), |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: List.generate( |
||||
|
days.length, |
||||
|
(index) { |
||||
|
String dateStr = days[index]['date']; |
||||
|
DateTime? date = DateUtil.getDateTime(dateStr); |
||||
|
IconData weatherIcon = |
||||
|
getWeatherIcon(days[index]['weatherCondition']); |
||||
|
String weekday = DateUtil.getWeekday(date, |
||||
|
languageCode: "zh", short: true); |
||||
|
String temp = "${days[index]['temperature']}°"; |
||||
|
return _buildForecastDay( |
||||
|
weekday, |
||||
|
weatherIcon, |
||||
|
temp, |
||||
|
); |
||||
|
}, |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 16), |
||||
|
Container( |
||||
|
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), |
||||
|
decoration: BoxDecoration( |
||||
|
color: isDarkMode |
||||
|
? Colors.white.withOpacity(0.15) |
||||
|
: Colors.white.withOpacity(0.2), |
||||
|
borderRadius: BorderRadius.circular(8), |
||||
|
), |
||||
|
child: const Row( |
||||
|
children: [ |
||||
|
Icon( |
||||
|
Icons.info_outline, |
||||
|
size: 16, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
SizedBox(width: 8), |
||||
|
Flexible( |
||||
|
child: Text( |
||||
|
'今日紫外线指数较高,外出请做好防晒', |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// 构建单日天气预报 |
||||
|
Widget _buildForecastDay(String day, IconData icon, String temp) { |
||||
|
return Column( |
||||
|
children: [ |
||||
|
Text( |
||||
|
day, |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
color: Colors.white.withOpacity(0.9), |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 4), |
||||
|
Icon( |
||||
|
icon, |
||||
|
size: 20, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
SizedBox(height: 4), |
||||
|
Text( |
||||
|
temp, |
||||
|
style: TextStyle( |
||||
|
fontSize: 12, |
||||
|
fontWeight: FontWeight.w500, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// 获取天气图标 |
||||
|
IconData getWeatherIcon(String description) { |
||||
|
description = description.trim(); |
||||
|
|
||||
|
if (["晴", "少云", "晴间多云"].contains(description)) { |
||||
|
return Icons.wb_sunny; |
||||
|
} |
||||
|
|
||||
|
if (["多云", "阴"].contains(description)) { |
||||
|
return Icons.cloud; |
||||
|
} |
||||
|
|
||||
|
if (description.contains("雾") || |
||||
|
["雾", "浓雾", "强浓雾", "轻雾", "大雾", "特强浓雾", "霾", "中度霾", "重度霾", "严重霾"] |
||||
|
.contains(description)) { |
||||
|
return Icons.blur_on; |
||||
|
} |
||||
|
|
||||
|
if (description.contains("风") || |
||||
|
[ |
||||
|
"有风", |
||||
|
"平静", |
||||
|
"微风", |
||||
|
"和风", |
||||
|
"清风", |
||||
|
"强风/劲风", |
||||
|
"疾风", |
||||
|
"大风", |
||||
|
"烈风", |
||||
|
"风暴", |
||||
|
"狂爆风", |
||||
|
"飓风", |
||||
|
"热带风暴" |
||||
|
].contains(description)) { |
||||
|
return Icons.air; |
||||
|
} |
||||
|
|
||||
|
if (description.contains("雨") || |
||||
|
[ |
||||
|
"阵雨", |
||||
|
"雷阵雨", |
||||
|
"雷阵雨并伴有冰雹", |
||||
|
"小雨", |
||||
|
"中雨", |
||||
|
"大雨", |
||||
|
"暴雨", |
||||
|
"大暴雨", |
||||
|
"特大暴雨", |
||||
|
"强阵雨", |
||||
|
"强雷阵雨", |
||||
|
"极端降雨", |
||||
|
"毛毛雨/细雨", |
||||
|
"雨", |
||||
|
"小雨-中雨", |
||||
|
"中雨-大雨", |
||||
|
"大雨-暴雨", |
||||
|
"暴雨-大暴雨", |
||||
|
"大暴雨-特大暴雨" |
||||
|
].contains(description)) { |
||||
|
return Icons.grain; // 或使用 community icon 包:weather_icons 中的 rain |
||||
|
} |
||||
|
|
||||
|
if (description.contains("雪") || |
||||
|
[ |
||||
|
"雨雪天气", |
||||
|
"雨夹雪", |
||||
|
"阵雨夹雪", |
||||
|
"冻雨", |
||||
|
"雪", |
||||
|
"阵雪", |
||||
|
"小雪", |
||||
|
"中雪", |
||||
|
"大雪", |
||||
|
"暴雪", |
||||
|
"小雪-中雪", |
||||
|
"中雪-大雪", |
||||
|
"大雪-暴雪" |
||||
|
].contains(description)) { |
||||
|
return Icons.ac_unit; |
||||
|
} |
||||
|
|
||||
|
if (["浮尘", "扬沙", "沙尘暴", "强沙尘暴"].contains(description)) { |
||||
|
return Icons.filter_drama; |
||||
|
} |
||||
|
|
||||
|
if (["龙卷风"].contains(description)) { |
||||
|
return Icons.waves; |
||||
|
} |
||||
|
|
||||
|
if (["热"].contains(description)) { |
||||
|
return Icons.local_fire_department; |
||||
|
} |
||||
|
|
||||
|
if (["冷"].contains(description)) { |
||||
|
return Icons.ac_unit; |
||||
|
} |
||||
|
|
||||
|
// 默认或未知天气 |
||||
|
return Icons.help_outline; |
||||
|
} |
||||
Loading…
Reference in new issue