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