2 Angajamente 04c4dfec9b ... f4600832b7

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  盐城新淘科技有限公司 f4600832b7 update 6 zile în urmă
  盐城新淘科技有限公司 633864652f update 6 zile în urmă
27 a modificat fișierele cu 330 adăugiri și 142 ștergeri
  1. 30 1
      xinhuaribao/.claude/settings.local.json
  2. 16 8
      xinhuaribao/android/app/build.gradle.kts
  3. BIN
      xinhuaribao/android/app/src/main/res/drawable-v21/launch_background.png
  4. BIN
      xinhuaribao/android/app/src/main/res/drawable-v21/launch_background_old.png
  5. BIN
      xinhuaribao/android/app/src/main/res/drawable/launch_background.png
  6. BIN
      xinhuaribao/android/app/src/main/res/drawable/launch_background_old.png
  7. 7 3
      xinhuaribao/android/build.gradle.kts
  8. 6 0
      xinhuaribao/android/gradle.properties
  9. 1 1
      xinhuaribao/android/gradle/wrapper/gradle-wrapper.properties
  10. 9 2
      xinhuaribao/android/settings.gradle.kts
  11. BIN
      xinhuaribao/assets/images/splash_bg.png
  12. BIN
      xinhuaribao/assets/images/splash_bg_old.png
  13. 7 7
      xinhuaribao/build.py
  14. 1 1
      xinhuaribao/ios/Runner/Base.lproj/LaunchScreen.storyboard
  15. 2 2
      xinhuaribao/ios/Runner/Info.plist
  16. BIN
      xinhuaribao/ios/Runner/bg.png
  17. BIN
      xinhuaribao/ios/Runner/bg_new.png
  18. 11 0
      xinhuaribao/lib/provider/video_commend_provider.dart
  19. 62 48
      xinhuaribao/lib/ui/login/splash_page.dart
  20. 33 13
      xinhuaribao/lib/ui/video/comment_input_bar_widget.dart
  21. 1 0
      xinhuaribao/lib/ui/video/comment_page.dart
  22. 7 1
      xinhuaribao/lib/ui/video/video_detail_page.dart
  23. 14 1
      xinhuaribao/lib/ui/video/video_play_item_widget.dart
  24. 118 49
      xinhuaribao/lib/ui/video/video_recommend_list_page.dart
  25. 2 2
      xinhuaribao/macos/Flutter/ephemeral/Flutter-Generated.xcconfig
  26. 2 2
      xinhuaribao/macos/Flutter/ephemeral/flutter_export_environment.sh
  27. 1 1
      xinhuaribao/pubspec.yaml

+ 30 - 1
xinhuaribao/.claude/settings.local.json

@@ -26,7 +26,36 @@
       "Read(//Users/user/Library/Android/sdk/platform-tools/**)",
       "Read(//Users/user/Android/Sdk/platform-tools/**)",
       "Bash(~/Library/Android/sdk/platform-tools/adb devices:*)",
-      "Bash(nc localhost:*)"
+      "Bash(nc localhost:*)",
+      "Bash(md5 /Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao/ios/Runner/bg.png)",
+      "Bash(find /Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao/ios/Runner -name *.png -o -name *.jpg -o -name *.jpeg)",
+      "Bash(grep -i \"brand\\\\|logo\\\\|launch\" /Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao/ios/Runner/Assets.xcassets/*/Contents.json)",
+      "Bash(curl -s -o /Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao/ios/Runner/bg.png \"https://maas-log-prod.cn-wlcb.ufileos.com/anthropic/15083ce1-8c82-49a5-95b9-422fbd909629/8c9dc3c3aafee9002968d20069f2fed0.png?UCloudPublicKey=TOKEN_e15ba47a-d098-4fbd-9afc-a0dcf0e4e621&Expires=1774098972&Signature=Z1biXzYYhAFA8pQXNiinOd8y12s=\")",
+      "Bash(find /Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao -type f \\\\\\(-name *.png -o -name *.jpg -o -name *.jpeg -o -name *.gif \\\\\\))",
+      "Bash(__NEW_LINE_94e668923ec7cad9__ echo:*)",
+      "Bash(md5 \"/Users/user/Documents/新华新消费/app/iOS_bg.png\")",
+      "Bash(flutter build:*)",
+      "Bash(pip3 install:*)",
+      "Bash(python3 build.py)",
+      "Bash(./gradlew assembleRelease)",
+      "Bash(./gradlew:*)",
+      "Bash(ls:*)",
+      "Bash(curl:*)",
+      "Bash(keytool:*)",
+      "Bash(/Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao/android/key.properties:*)",
+      "Bash(~/Library/Android/sdk/platform-tools/adb shell:*)",
+      "Bash(~/Library/Android/sdk/platform-tools/adb logcat:*)",
+      "Bash(adb -s V2054A logcat -d)",
+      "Bash(~/Library/Android/sdk/platform-tools/adb -s V2054A logcat -c)",
+      "Bash(~/Library/Android/sdk/platform-tools/adb -s V2054A logcat -s flutter:*)",
+      "Bash(~/Library/Android/sdk/platform-tools/adb -s 31576185360014D logcat -d -s flutter:*)",
+      "Bash(~/Library/Android/sdk/platform-tools/adb -s 31576185360014D shell input text \"r\")",
+      "Bash(/Users/user/Documents/Project/flutter/bin/flutter devices:*)",
+      "Bash(tail -50 ~/.claude/projects/-Users-user-Documents-Project-wwhale-xxf-demo-xinhuaribao/6d91962b-3d1e-4a44-8c2b-f531747b4ec7/tool-results/*.txt)",
+      "Bash(/Users/user/Documents/Project/flutter/bin/flutter run:*)",
+      "Bash(tail -80 ~/.claude/projects/-Users-user-Documents-Project-wwhale-xxf-demo-xinhuaribao/6d91962b-3d1e-4a44-8c2b-f531747b4ec7/tool-results/*.txt)",
+      "Bash(tail -100 ~/.claude/projects/-Users-user-Documents-Project-wwhale-xxf-demo-xinhuaribao/6d91962b-3d1e-4a44-8c2b-f531747b4ec7/tool-results/*.txt)",
+      "Bash(/Users/user/Documents/Project/flutter/bin/flutter build:*)"
     ]
   }
 }

+ 16 - 8
xinhuaribao/android/app/build.gradle.kts

@@ -17,7 +17,8 @@ if (keystorePropertiesFile.exists()) {
 android {
     namespace = "com.xhxxf.newshop"
     compileSdk = flutter.compileSdkVersion
-    ndkVersion = System.getenv("ANDROID_NDK_VERSION") ?: "27.0.12077973"
+    // 移除固定 NDK 版本,让 Gradle 自动选择
+    // ndkVersion = "27.0.12077973"
 
     compileOptions {
         sourceCompatibility = JavaVersion.VERSION_11
@@ -39,31 +40,38 @@ android {
         versionName = flutter.versionName
     }
 
-    packagingOptions {
+    packaging {
         resources.excludes += setOf(
             "META-INF/**",
             // "assets/**",
             //"res/**"
         )
     }
+
+    // 安全的签名配置,处理 key.properties 不存在的情况
     signingConfigs {
         create("release") {
-            storeFile = file(keystoreProperties["storeFile"] as String)
-            storePassword = keystoreProperties["storePassword"] as String
-            keyAlias = keystoreProperties["keyAlias"] as String
-            keyPassword = keystoreProperties["keyPassword"] as String
+            if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
+                storeFile = file(keystoreProperties["storeFile"] as String)
+                storePassword = keystoreProperties["storePassword"] as String
+                keyAlias = keystoreProperties["keyAlias"] as String
+                keyPassword = keystoreProperties["keyPassword"] as String
+            }
         }
     }
     buildTypes {
         getByName("debug") {
             isMinifyEnabled = false
-            signingConfig = signingConfigs.getByName("release") // 给 debug 也用 release 的签名
+            // debug 使用默认签名
         }
 
         getByName("release") {
             isMinifyEnabled = false
             isShrinkResources = false
-            signingConfig = signingConfigs.getByName("release")
+            // 如果有正式签名则使用,否则使用 debug 签名
+            if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
+                signingConfig = signingConfigs.getByName("release")
+            }
             proguardFiles(
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"

BIN
xinhuaribao/android/app/src/main/res/drawable-v21/launch_background.png


BIN
xinhuaribao/android/app/src/main/res/drawable-v21/launch_background_old.png


BIN
xinhuaribao/android/app/src/main/res/drawable/launch_background.png


BIN
xinhuaribao/android/app/src/main/res/drawable/launch_background_old.png


+ 7 - 3
xinhuaribao/android/build.gradle.kts

@@ -1,5 +1,8 @@
 allprojects {
     repositories {
+        // 阿里云镜像加速
+        maven { url = uri("https://maven.aliyun.com/repository/google") }
+        maven { url = uri("https://maven.aliyun.com/repository/public") }
         google()
         mavenCentral()
     }
@@ -12,9 +15,10 @@ subprojects {
     val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
     project.layout.buildDirectory.value(newSubprojectBuildDir)
 }
-subprojects {
-    project.evaluationDependsOn(":app")
-}
+// 移除 evaluationDependsOn 以提升构建速度
+// subprojects {
+//     project.evaluationDependsOn(":app")
+// }
 
 tasks.register<Delete>("clean") {
     delete(rootProject.layout.buildDirectory)

+ 6 - 0
xinhuaribao/android/gradle.properties

@@ -1,3 +1,9 @@
 org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
 android.useAndroidX=true
 android.enableJetifier=true
+
+# 构建优化
+org.gradle.parallel=true
+org.gradle.caching=true
+org.gradle.configureondemand=true
+org.gradle.daemon=true

+ 1 - 1
xinhuaribao/android/gradle/wrapper/gradle-wrapper.properties

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip

+ 9 - 2
xinhuaribao/android/settings.gradle.kts

@@ -10,16 +10,23 @@ pluginManagement {
     includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
 
     repositories {
+        // 腾讯云镜像(更快)
+        maven { url = uri("https://mirrors.cloud.tencent.com/gradle/") }
+        maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") }
+        // 阿里云镜像
+        maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
+        maven { url = uri("https://maven.aliyun.com/repository/google") }
+        maven { url = uri("https://maven.aliyun.com/repository/public") }
+        gradlePluginPortal()
         google()
         mavenCentral()
-        gradlePluginPortal()
     }
 }
 
 plugins {
     id("dev.flutter.flutter-plugin-loader") version "1.0.0"
     id("com.android.application") version "8.7.0" apply false
-    id("org.jetbrains.kotlin.android") version "1.8.22" apply false
+    id("org.jetbrains.kotlin.android") version "2.1.0" apply false
 }
 
 include(":app")

BIN
xinhuaribao/assets/images/splash_bg.png


BIN
xinhuaribao/assets/images/splash_bg_old.png


+ 7 - 7
xinhuaribao/build.py

@@ -3,7 +3,6 @@ import os
 import datetime
 import shutil
 import re
-import yaml
 
 
 # 项目根目录
@@ -15,12 +14,13 @@ APK_DIR = os.path.join(PROJECT_ROOT, "build", "app", "outputs", "flutter-apk")
 RENAMED_DIR = os.path.join(PROJECT_ROOT,"build", "app", "outputs",  "renamed")
 
 def get_version_name():
-# 2. 读取 Flutter 版本号(pubspec.yaml)
+    # 读取 Flutter 版本号(pubspec.yaml),无需 yaml 依赖
     with open("pubspec.yaml", "r", encoding="utf-8") as f:
-        pubspec = yaml.safe_load(f)
-        version_string = pubspec.get("version", "1.0.0+1")
-        version_name = version_string.split('+')[0]  # e.g. "1.0.3"
-    return version_name
+        content = f.read()
+        match = re.search(r'version:\s*([\d.]+)', content)
+        if match:
+            return match.group(1)  # e.g. "1.6.0"
+        return "1.0.0"  # 默认值
 
 # 删除旧 APK 文件
 def clean_old_apks():
@@ -50,7 +50,7 @@ def clean_renamed_apks():
 
 # 执行 flutter build 命令
 def build_apk():
-    result = subprocess.run(["flutter", "build", "apk", "--release"], shell=True)
+    result = subprocess.run("flutter build apk --release", shell=True)
     if result.returncode != 0:
         raise Exception("Flutter build failed")
 

+ 1 - 1
xinhuaribao/ios/Runner/Base.lproj/LaunchScreen.storyboard

@@ -38,6 +38,6 @@
         </scene>
     </scenes>
     <resources>
-        <image name="bg.png" width="1125" height="2436"/>
+        <image name="bg.png" width="750" height="1624"/>
     </resources>
 </document>

+ 2 - 2
xinhuaribao/ios/Runner/Info.plist

@@ -19,7 +19,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.2.0</string>
+	<string>1.6.0</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleURLTypes</key>
@@ -36,7 +36,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>15</string>
+	<string>16</string>
 	<key>LSApplicationQueriesSchemes</key>
 	<array>
 		<string>weixinULAPI</string>

BIN
xinhuaribao/ios/Runner/bg.png


BIN
xinhuaribao/ios/Runner/bg_new.png


+ 11 - 0
xinhuaribao/lib/provider/video_commend_provider.dart

@@ -1,6 +1,7 @@
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:news_app/http/http_util.dart';
 import 'package:news_app/http/model_parser.dart';
+import 'package:news_app/util/log.util.dart';
 
 import '../constant/api_const.dart';
 import '../model/video_new_model.dart';
@@ -20,6 +21,7 @@ class VideoRecommendProvider extends Notifier<List<VideoNewModel>> {
   Future<void> fetchRecommendVideos() async {
     try {
       final jsonData = await HttpUtil().get(apiVideoRecommend);
+      consoleLog('VideoRecommend API response: $jsonData');
 
       // 确保 jsonData 是一个 List
       if (jsonData == null) {
@@ -44,12 +46,21 @@ class VideoRecommendProvider extends Notifier<List<VideoNewModel>> {
         return;
       }
 
+      // 打印每个视频的 URL
+      for (var item in dataList) {
+        if (item is Map) {
+          consoleLog('Video item: contentId=${item['contentId']}, url=${item['url']}, title=${item['title']}');
+        }
+      }
+
       final data = ModelParser.parseList<VideoNewModel>(
         dataList,
         VideoNewModel.fromJson,
       );
+      consoleLog('Parsed ${data.length} videos');
       state = data;
     } catch (e) {
+      consoleLog('VideoRecommend fetch error: $e');
       // 出错时保持空列表状态
       state = const <VideoNewModel>[];
     }

+ 62 - 48
xinhuaribao/lib/ui/login/splash_page.dart

@@ -31,9 +31,9 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
   TapGestureRecognizer? aa;
   List<String> _splashImages = [];
   int _currentIndex = 0;
-  late Timer _sliderTimer;
   Timer? _navigateTimer;
   bool _hasStartedNavigation = false;
+  late SwiperController _swiperController;
 
   // 本地默认开屏图片(当网络不可用时使用)
   static const List<String> _defaultSplashImages = [
@@ -43,83 +43,95 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
   @override
   void initState() {
     super.initState();
+    _swiperController = SwiperController();
     // 初始化使用本地默认图片
     _splashImages = _defaultSplashImages;
-    // 启动轮播
-    _startSlider();
     // 从后台获取开屏页配置
     _fetchSplashConfig();
   }
 
-  /// 开始轮播
-  void _startSlider() {
-    _sliderTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
-      if (mounted && _splashImages.isNotEmpty) {
-        setState(() {
-          _currentIndex = (_currentIndex + 1) % _splashImages.length;
-        });
-      }
-    });
-  }
-
   /// 从后台获取开屏页配置
   Future<void> _fetchSplashConfig() async {
     try {
-      final response = await HttpUtil().get(apiSplashConfig);
-      if (response != null && response['code'] == 200) {
-        final data = response['data'];
-        if (data != null) {
-          // 支持多种数据格式
-          List<String> fetchedImages = [];
+      // HttpUtil().get() 已经返回 data['data'],不是完整 response
+      final data = await HttpUtil().get(apiSplashConfig);
+      consoleLog('Splash API data type: ${data.runtimeType}, value: $data');
 
-          // 格式1: imageUrl 为单个图片
-          if (data['imageUrl'] != null && data['imageUrl'].toString().isNotEmpty) {
-            fetchedImages.add(data['imageUrl']);
-          }
+      if (data != null) {
+        List<String> fetchedImages = [];
 
-          // 格式2: images 为图片数组
-          if (data is List) {
-            for (var img in data) {
-              if (img.toString().isNotEmpty) {
-                fetchedImages.add(img['url']);
+        // 格式1: data 是数组,每个元素包含 url 字段 [{title: "", url: ""}]
+        if (data is List) {
+          consoleLog('Splash: data is List with ${data.length} items');
+          for (int i = 0; i < data.length; i++) {
+            final item = data[i];
+            consoleLog('Splash: item[$i] type = ${item.runtimeType}, value = $item');
+
+            if (item != null && item is Map) {
+              final url = item['url']?.toString();
+              consoleLog('Splash: item[$i][url] = $url');
+              if (url != null && url.isNotEmpty) {
+                fetchedImages.add(url);
+                consoleLog('Splash: added image url: $url');
               }
+            } else if (item is String && item.isNotEmpty) {
+              fetchedImages.add(item);
             }
           }
-
-          // 格式3: imageList 为图片数组
+        }
+        // 格式2: data 是对象,包含 imageUrl 字段
+        else if (data is Map) {
+          consoleLog('Splash: data is Map');
+          if (data['imageUrl'] != null) {
+            final url = data['imageUrl'].toString();
+            if (url.isNotEmpty) {
+              fetchedImages.add(url);
+            }
+          }
+          // 格式3: data 是对象,包含 imageList 数组
           if (data['imageList'] is List) {
             final images = data['imageList'] as List;
             for (var img in images) {
-              if (img.toString().isNotEmpty) {
-                fetchedImages.add(img.toString());
+              if (img is String && img.isNotEmpty) {
+                fetchedImages.add(img);
               }
             }
           }
+        }
 
-          // 如果获取到了图片,更新显示
-          if (fetchedImages.isNotEmpty && mounted) {
-            setState(() {
-              _splashImages = fetchedImages;
-              _currentIndex = 0;
-            });
-          }
+        consoleLog('Splash: fetchedImages = $fetchedImages');
+
+        // 如果获取到了图片,更新显示
+        if (fetchedImages.isNotEmpty && mounted) {
+          setState(() {
+            _splashImages = fetchedImages;
+            _currentIndex = 0;
+          });
+          consoleLog('Splash: updated _splashImages with ${fetchedImages.length} images');
+
+          // 启动导航计时器(每张图片2秒,总时长 = 图片数量 × 2)
+          _startNavigationTimer(fetchedImages.length);
         }
       }
     } catch (e) {
       // 请求失败或无网络权限,继续使用本地默认图片,不做任何处理
       consoleLog('Splash fetch error (will use default): $e');
-    }
 
-    // 延迟 3-5 秒后执行跳转逻辑
-    _startNavigationTimer();
+      // 使用默认图片时,也启动导航计时器
+      _startNavigationTimer(_splashImages.length);
+    }
   }
 
-  /// 启动导航计时器
-  void _startNavigationTimer() {
+  /// 启动导航计时器(每张图片2秒)
+  void _startNavigationTimer(int imageCount) {
     if (_hasStartedNavigation) return;
     _hasStartedNavigation = true;
 
-    _navigateTimer = Timer(const Duration(seconds: 4), () async {
+    // 计算总时长:图片数量 × 2秒
+    final totalSeconds = imageCount * 2;
+    consoleLog('Splash: Navigation will auto-skip after $totalSeconds seconds ($imageCount images × 2s)');
+
+    _navigateTimer = Timer(Duration(seconds: totalSeconds), () async {
       bool? first = await getIsFirst();
       // 确保页面未被销毁
       if (!mounted) return;
@@ -133,8 +145,8 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
 
   @override
   void dispose() {
-    _sliderTimer.cancel();
     _navigateTimer?.cancel();
+    _swiperController.dispose();
     aa?.dispose();
     super.dispose();
   }
@@ -327,6 +339,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
   /// 构建轮播
   Widget _buildCarousel(double w, double h) {
     return Swiper(
+      controller: _swiperController,
       itemCount: _splashImages.length,
       index: _currentIndex,
       onIndexChanged: (index) {
@@ -347,7 +360,8 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
           );
         }
       },
-      autoplay: false, // 使用自定义定时器控制
+      autoplay: true, // 启用自动播放
+      autoplayDelay: 2000, // 每张图片显示2秒
       loop: true,
       viewportFraction: 1.0,
       scale: 1.0,

+ 33 - 13
xinhuaribao/lib/ui/video/comment_input_bar_widget.dart

@@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:fluttertoast/fluttertoast.dart';
 import 'package:image_picker/image_picker.dart';
 import 'package:news_app/constant/color_res.dart';
 import 'package:news_app/constant/size_res.dart';
@@ -56,11 +57,14 @@ class CommentInputBarWidget extends ConsumerStatefulWidget {
 
   final Function(String, List<String>)? onSend; // 改为传递URL列表
 
+  final bool showImageUpload; // 是否显示图片上传按钮
+
   const CommentInputBarWidget(
     this.focusNode,
     this.onSend,
     this.id, {
     super.key,
+    this.showImageUpload = false, // 默认不显示
   });
 
   @override
@@ -313,23 +317,39 @@ class _CommentInputBarState extends ConsumerState<CommentInputBarWidget> {
                   ),
                 ),
               ),
-              // 图片上传按钮
-              GestureDetector(
-                onTap: _pickImage,
-                child: Container(
-                  width: 40.w,
-                  height: 40.h,
-                  margin: EdgeInsets.only(right: 8.w),
-                  alignment: Alignment.center,
-                  child: Icon(
-                    Icons.image,
-                    color: _imageItems.isNotEmpty ? color188FFF : color7788A0,
-                    size: 24.w,
+              // 图片上传按钮(仅指定话题显示)
+              if (widget.showImageUpload)
+                GestureDetector(
+                  onTap: _pickImage,
+                  child: Container(
+                    width: 40.w,
+                    height: 40.h,
+                    margin: EdgeInsets.only(right: 8.w),
+                    alignment: Alignment.center,
+                    child: Icon(
+                      Icons.image,
+                      color: _imageItems.isNotEmpty ? color188FFF : color7788A0,
+                      size: 24.w,
+                    ),
                   ),
                 ),
-              ),
               AuthGestureDetector(
                 onTap: () {
+                  // 校验:没有图片且没有文字时,不允许提交
+                  final hasImages = _imageItems.any(
+                      (item) => item.status == UploadStatus.success && item.remoteUrl != null);
+                  final text = _controller.text.trim();
+
+                  if (!hasImages && text.isEmpty) {
+                    // 没有图片且没有文字,弹出提醒
+                    Fluttertoast.showToast(
+                      msg: "请输入评论内容或上传图片",
+                      toastLength: Toast.LENGTH_SHORT,
+                      gravity: ToastGravity.CENTER,
+                    );
+                    return;
+                  }
+
                   // 获取所有上传成功的图片URL
                   final successUrls = _imageItems
                       .where((item) =>

+ 1 - 0
xinhuaribao/lib/ui/video/comment_page.dart

@@ -244,6 +244,7 @@ class _CommentPageSate extends ConsumerState<CommentPage>
             _focusNode,
             sendComment,
             widget.articleId,
+            showImageUpload: widget.type == CommentType.topic, // 仅指定话题显示图片上传
           ),
         ),
       ],

+ 7 - 1
xinhuaribao/lib/ui/video/video_detail_page.dart

@@ -230,7 +230,13 @@ class _VideoDetailPageState extends ConsumerState<VideoDetailPage>
     if (state == AppLifecycleState.inactive ||
         state == AppLifecycleState.paused) {
       // App进入后台或锁屏,暂停播放
-      _betterPlayerController?.pause();
+      try {
+        if (_betterPlayerController?.isVideoInitialized() == true) {
+          _betterPlayerController?.pause();
+        }
+      } catch (e) {
+        consoleLog("VideoDetailPage: pause error in lifecycle change: $e");
+      }
     } else if (state == AppLifecycleState.resumed) {
       // App回到前台(是否继续播放可根据需求选择)如果希望回到前台自动播放,就取消注释
       // _betterPlayerController?.play();

+ 14 - 1
xinhuaribao/lib/ui/video/video_play_item_widget.dart

@@ -212,10 +212,21 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget>
   }
 
   void _togglePlay() {
-    if (!mounted) return;
+    if (!mounted || !widget.isActive) return;
     final controller = globalVideoController;
     if (controller == null) return;
 
+    // 检查控制器是否已初始化且未释放
+    try {
+      if (controller.isVideoInitialized() != true) {
+        consoleLog("_togglePlay: controller not initialized");
+        return;
+      }
+    } catch (e) {
+      consoleLog("_togglePlay: controller check failed: $e");
+      return;
+    }
+
     // 分别处理每个操作,确保错误被捕获
     if (_isPlaying) {
       try {
@@ -225,6 +236,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget>
           _isPlaying = false;
         });
       } catch (e) {
+        consoleLog("_togglePlay: pause error: $e");
         // 控制器可能已被释放,忽略错误
       }
     } else {
@@ -235,6 +247,7 @@ class _VideoItemWidgetState extends ConsumerState<VideoPlayItemWidget>
           _isPlaying = true;
         });
       } catch (e) {
+        consoleLog("_togglePlay: play error: $e");
         // 控制器可能已被释放,忽略错误
       }
     }

+ 118 - 49
xinhuaribao/lib/ui/video/video_recommend_list_page.dart

@@ -5,6 +5,7 @@ import 'package:news_app/constant/size_res.dart';
 import 'package:news_app/ui/video/video_play_item_widget.dart';
 import 'package:news_app/util/log.util.dart';
 import 'package:visibility_detector/visibility_detector.dart';
+import 'package:fluttertoast/fluttertoast.dart';
 
 import '../../model/video_new_model.dart';
 import '../../provider/video_commend_provider.dart';
@@ -34,6 +35,8 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
   late final PageController _pageController;
   int _currentPageIndex = 0;
   String? _currentVideoUrl;
+  String? _lastFailedVideoUrl; // 记录上次失败的视频URL
+  int _consecutiveFailures = 0; // 连续失败计数
 
   @override
   void initState() {
@@ -61,82 +64,141 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
   }
 
   void _playVideo(String url) {
+    consoleLog('===== _playVideo called with URL: $url =====');
     if (!mounted) return;
 
-    // 如果是同一个视频,不需要重新加载
+    // 检查现有控制器是否可用(未释放且是同一视频)
     if (_currentVideoUrl == url && globalVideoController != null) {
-      _safeControllerOperation(() {
-        globalVideoController!.play();
-      });
-      return;
+      try {
+        if (globalVideoController!.isVideoInitialized() == true) {
+          _safeControllerOperation(() {
+            globalVideoController!.play();
+          });
+          return;
+        }
+      } catch (e) {
+        consoleLog("Controller check failed, creating new one: $e");
+      }
     }
 
     _currentVideoUrl = url;
 
-    // 创建新控制器
-    globalVideoController = null; // 释放旧的
+    // 释放旧控制器
+    try {
+      globalVideoController?.dispose();
+    } catch (e) {
+      consoleLog("Dispose old controller error: $e");
+    }
+    globalVideoController = null;
+
+    consoleLog("Creating BetterPlayerDataSource with url: $url");
+
+    // 创建数据源(与 video_detail_page 一致)
+    // 对于m4v格式,使用other格式让ExoPlayer自动检测
+    final dataSource = BetterPlayerDataSource(
+      BetterPlayerDataSourceType.network,
+      url,
+      videoFormat: BetterPlayerVideoFormat.other, // 让ExoPlayer自动检测格式
+      notificationConfiguration: BetterPlayerNotificationConfiguration(
+        showNotification: false,
+      ),
+    );
+
+    // 创建新控制器,宽度占满、高度自适应
     globalVideoController = BetterPlayerController(
       BetterPlayerConfiguration(
         autoPlay: true,
         looping: true,
-        fit: BoxFit.fitWidth, // 宽度固定,高度自适应
+        fit: BoxFit.fitWidth, // 宽度占满,高度自适应
         controlsConfiguration: const BetterPlayerControlsConfiguration(
           showControls: false,
         ),
         handleLifecycle: false,
         autoDetectFullscreenAspectRatio: false,
+        // 尝试使用软件解码作为后备
         errorBuilder: (context, errorMessage) {
-          consoleLog("GlobalVideoController error: $errorMessage");
-          // 返回空 widget,不显示错误
-          return const SizedBox.shrink();
+          consoleLog("GlobalVideoController errorBuilder: $errorMessage");
+          return Center(
+            child: Text(
+              "视频加载失败",
+              style: TextStyle(color: Colors.white, fontSize: 14),
+            ),
+          );
         },
       ),
+      betterPlayerDataSource: dataSource,
     );
 
-    // 先添加事件监听,然后再设置数据源
+    consoleLog("BetterPlayerController created");
+
+    // 先添加事件监听
     globalVideoController!.addEventsListener((event) {
       consoleLog("GlobalVideoController event: ${event.betterPlayerEventType}");
       if (event.betterPlayerEventType == BetterPlayerEventType.exception) {
+        // 打印详细异常信息
         consoleLog("Video exception occurred");
-        // 静默处理异常
-      }
-    });
+        consoleLog("Exception parameters: ${event.parameters}");
+        consoleLog("Exception all data: ${event.toString()}");
 
-    Future.delayed(const Duration(milliseconds: 50), () {
-      if (!mounted || globalVideoController == null) return;
+        // 检查是否是硬件解码器不支持的错误
+        final exception = event.parameters?['exception']?.toString() ?? '';
+        if (exception.contains('NO_EXCEEDS_CAPABILITIES') ||
+            exception.contains('MediaCodecVideoRenderer error')) {
+          consoleLog("Hardware decoder not supported for this video");
 
-      try {
-        final dataSource = BetterPlayerDataSource(
-          BetterPlayerDataSourceType.network,
-          url,
-          videoFormat: BetterPlayerVideoFormat.other,
-          notificationConfiguration: BetterPlayerNotificationConfiguration(
-            showNotification: false,
-          ),
-        );
-
-        globalVideoController!.setupDataSource(dataSource).then((_) {
-          if (mounted && globalVideoController != null) {
-            _safeControllerOperation(() {
-              globalVideoController!.play();
+          // 显示提示并跳过该视频
+          if (mounted && _consecutiveFailures < 3) {
+            Fluttertoast.showToast(
+              msg: "该视频格式暂不支持,自动跳过",
+              toastLength: Toast.LENGTH_SHORT,
+              gravity: ToastGravity.CENTER,
+            );
+
+            // 延迟后跳到下一个视频
+            Future.delayed(const Duration(milliseconds: 800), () {
+              if (mounted && _currentPageIndex < ref.read(recommendListProvider).length - 1) {
+                _consecutiveFailures++;
+                _pageController.animateToPage(
+                  _currentPageIndex + 1,
+                  duration: const Duration(milliseconds: 300),
+                  curve: Curves.easeInOut,
+                );
+              } else {
+                // 已经是最后一个视频了,重置计数
+                _consecutiveFailures = 0;
+              }
             });
           }
-        }).catchError((error) {
-          consoleLog("Video setup error: $error");
-          // 静默处理错误,不显示错误提示
-        });
-      } catch (e) {
-        consoleLog("Video play error: $e");
-        // 静默处理错误,不显示错误提示
+        }
+      } else if (event.betterPlayerEventType == BetterPlayerEventType.play) {
+        consoleLog("Video started playing");
+        _consecutiveFailures = 0; // 播放成功,重置失败计数
+      } else if (event.betterPlayerEventType == BetterPlayerEventType.bufferingStart) {
+        consoleLog("Buffering started");
+      } else if (event.betterPlayerEventType == BetterPlayerEventType.bufferingEnd) {
+        consoleLog("Buffering ended");
       }
     });
+
+    // 尝试播放
+    Future.delayed(const Duration(milliseconds: 50), () {
+      if (!mounted || globalVideoController == null) return;
+      _safeControllerOperation(() {
+        globalVideoController!.play();
+      });
+    });
   }
 
   @override
   void dispose() {
     _pageController.dispose();
-    // 暂停播放但不dispose,因为可能被其他页面使用
-    globalVideoController?.pause();
+    // 释放全局控制器
+    try {
+      globalVideoController?.dispose();
+    } catch (e) {
+      consoleLog("Dispose globalVideoController error: $e");
+    }
+    globalVideoController = null;
     super.dispose();
   }
 
@@ -152,19 +214,26 @@ class _VideoRecommendListPageState extends ConsumerState<VideoRecommendListPage>
     }
     if (visible) {
       consoleLog("页面可见");
-      // 页面重新可见时,重新初始化视频以确保状态正确
-      final videos = ref.read(recommendListProvider);
-      if (videos.isNotEmpty && _currentPageIndex < videos.length) {
-        final url = videos[_currentPageIndex].url;
-        if (url != null && url.isNotEmpty) {
-          _playVideo(url);
+      // 页面重新可见时,恢复播放(仅在控制器已初始化时)
+      try {
+        if (globalVideoController != null &&
+            globalVideoController!.isVideoInitialized() == true) {
+          globalVideoController!.play();
         }
+      } catch (e) {
+        consoleLog("Visibility play error: $e");
       }
     } else {
       consoleLog("页面不可见");
-      _safeControllerOperation(() {
-        globalVideoController?.pause();
-      });
+      // 暂停播放
+      try {
+        if (globalVideoController != null &&
+            globalVideoController!.isVideoInitialized() == true) {
+          globalVideoController!.pause();
+        }
+      } catch (e) {
+        consoleLog("Visibility pause error: $e");
+      }
     }
   }
 

+ 2 - 2
xinhuaribao/macos/Flutter/ephemeral/Flutter-Generated.xcconfig

@@ -3,8 +3,8 @@ FLUTTER_ROOT=/Users/user/Documents/Project/flutter
 FLUTTER_APPLICATION_PATH=/Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao
 COCOAPODS_PARALLEL_CODE_SIGN=true
 FLUTTER_BUILD_DIR=build
-FLUTTER_BUILD_NAME=1.5.0
-FLUTTER_BUILD_NUMBER=2022
+FLUTTER_BUILD_NAME=1.6.0
+FLUTTER_BUILD_NUMBER=2023
 DART_OBFUSCATION=false
 TRACK_WIDGET_CREATION=true
 TREE_SHAKE_ICONS=false

+ 2 - 2
xinhuaribao/macos/Flutter/ephemeral/flutter_export_environment.sh

@@ -4,8 +4,8 @@ export "FLUTTER_ROOT=/Users/user/Documents/Project/flutter"
 export "FLUTTER_APPLICATION_PATH=/Users/user/Documents/Project/wwhale/xxf/demo/xinhuaribao"
 export "COCOAPODS_PARALLEL_CODE_SIGN=true"
 export "FLUTTER_BUILD_DIR=build"
-export "FLUTTER_BUILD_NAME=1.5.0"
-export "FLUTTER_BUILD_NUMBER=2022"
+export "FLUTTER_BUILD_NAME=1.6.0"
+export "FLUTTER_BUILD_NUMBER=2023"
 export "DART_OBFUSCATION=false"
 export "TRACK_WIDGET_CREATION=true"
 export "TREE_SHAKE_ICONS=false"

+ 1 - 1
xinhuaribao/pubspec.yaml

@@ -4,7 +4,7 @@ description: "A new Flutter project."
 # pub.dev using `flutter pub publish`. This is preferred for private packages.
 publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 
-version: 1.5.0+2022
+version: 1.6.0+2023
 
 environment:
   sdk: ^3.7.2