@@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.StateFlow
1212import kotlinx.coroutines.flow.update
1313import kotlinx.coroutines.launch
1414import kotlinx.coroutines.withContext
15- import java.io.File
1615import java.util.Locale
1716
1817data class MediaItem (val path : String , val type : MediaType )
@@ -28,6 +27,7 @@ data class MainUiState(
2827 val isDownloading : Boolean = false ,
2928 val progressLabel : String = " " ,
3029 val progress : Float = 0f ,
30+ val downloadProgressText : String = " 0%|0kb/s" , // Format: "XX%|XXXkb/s"
3131 val showWebCrawl : Boolean = false ,
3232 val showVideoWarning : Boolean = false
3333)
@@ -43,6 +43,48 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
4343 private var hasUserContinuedAfterVideoWarning = false
4444 private var downloadJob: kotlinx.coroutines.Job ? = null
4545
46+ // Track individual file progress for more accurate overall progress
47+ private val fileProgressMap = mutableMapOf<String , Float >() // Maps file path to progress (0.0 to 1.0)
48+ private var currentFileProgress = 0f // Progress of the currently downloading file (0.0 to 1.0)
49+
50+ // Fields to track download progress and speed for the first callback
51+ private var currentDownloadStartTime: Long = 0
52+ private var currentDownloadStartBytes: Long = 0
53+ private var currentDownloadTotalBytes: Long = 0
54+ private var currentDownloadedBytes: Long = 0
55+ private var lastSpeedCalculationTime: Long = 0
56+ private var lastCalculatedSpeed = " 0kb/s"
57+
58+ private fun formatSpeed (bytesPerSecond : Double ): String {
59+ return when {
60+ bytesPerSecond >= 1024 * 1024 -> { // >= 1 MB/s
61+ val mbps = bytesPerSecond / (1024 * 1024 )
62+ " ${String .format(" %.2f" , mbps)} MB/s"
63+ }
64+ bytesPerSecond >= 1024 -> { // >= 1 KB/s
65+ val kbps = bytesPerSecond / 1024
66+ " ${String .format(" %.1f" , kbps)} KB/s"
67+ }
68+ else -> {
69+ " ${bytesPerSecond.toInt()} B/s"
70+ }
71+ }
72+ }
73+
74+ private fun resetDownloadTracking () {
75+ currentDownloadStartTime = System .currentTimeMillis()
76+ currentDownloadStartBytes = 0
77+ currentDownloadTotalBytes = 0
78+ currentDownloadedBytes = 0
79+ lastSpeedCalculationTime = 0
80+ lastCalculatedSpeed = " 0KB/s"
81+
82+ // Update UI to show initial state
83+ _uiState .update { currentState ->
84+ currentState.copy(downloadProgressText = " 0.0%|0KB/s" )
85+ }
86+ }
87+
4688 fun updateUrl (value : String ) {
4789 _uiState .update { it.copy(urlInput = value, showWebCrawl = false ) }
4890 }
@@ -59,6 +101,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
59101 downloadedCount = 0
60102 totalMediaCount = 0
61103 displayedFiles.clear()
104+
105+ // Reset download tracking variables
106+ resetDownloadTracking()
107+
62108 _uiState .update {
63109 it.copy(
64110 isDownloading = true ,
@@ -83,6 +129,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
83129 if (displayedFiles.add(filePath)) {
84130 addMedia(filePath)
85131 downloadedCount++
132+ currentFileProgress = 0f // Reset current file progress when file is completed
86133 updateProgress()
87134 }
88135 }
@@ -93,7 +140,48 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
93140 }
94141
95142 override fun onDownloadProgressUpdate (downloaded : Long , total : Long ) {
96- // 保留单个文件进度供后续扩展
143+ // Calculate progress percentage - always update this
144+ val progressPercent = if (total > 0 ) {
145+ (downloaded.toDouble() / total.toDouble() * 100 ).toFloat()
146+ } else 0f
147+
148+ // Calculate individual file progress (0.0 to 1.0)
149+ currentFileProgress = if (total > 0 ) {
150+ downloaded.toFloat() / total.toFloat()
151+ } else {
152+ 0f
153+ }
154+
155+ // Calculate download speed more responsively
156+ val currentTime = System .currentTimeMillis()
157+ val deltaTime = currentTime - lastSpeedCalculationTime
158+
159+ // Update speed calculation more frequently for better responsiveness
160+ if (deltaTime > 500 ) { // Update every 0.5 seconds instead of 1 second
161+ val deltaBytes = downloaded - currentDownloadedBytes
162+ val deltaTimeSec = deltaTime.toDouble() / 1000.0 // Convert to seconds
163+
164+ val speedBps = if (deltaTimeSec > 0 ) {
165+ deltaBytes.toDouble() / deltaTimeSec
166+ } else 0.0
167+
168+ // Format speed with appropriate units (KB/s or MB/s)
169+ lastCalculatedSpeed = formatSpeed(speedBps)
170+ lastSpeedCalculationTime = currentTime
171+ }
172+
173+ // Update the current download stats
174+ currentDownloadedBytes = downloaded
175+ currentDownloadTotalBytes = total
176+
177+ // Update overall progress
178+ updateProgress()
179+
180+ // Always update UI state with current progress and speed
181+ val progressText = " ${String .format(" %.1f" , progressPercent)} %|$lastCalculatedSpeed "
182+ _uiState .update { currentState ->
183+ currentState.copy(downloadProgressText = progressText)
184+ }
97185 }
98186
99187 override fun onDownloadError (status : String , originalUrl : String ) {
@@ -140,6 +228,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
140228 withContext(Dispatchers .Main ) {
141229 val jobStillActive = downloadJob?.isActive == true
142230 if (jobStillActive) {
231+ // Reset download tracking when download completes
232+ resetDownloadTracking()
143233 _uiState .update { it.copy(isDownloading = false ) }
144234 if (! success) {
145235 appendStatus(" 下载失败,请检查链接或网络" )
@@ -182,6 +272,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
182272 }
183273 downloadedCount = 0
184274 totalMediaCount = urls.size
275+
276+ // Reset download tracking variables for web crawl
277+ resetDownloadTracking()
278+
185279 _uiState .update {
186280 it.copy(
187281 isDownloading = true ,
@@ -206,6 +300,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
206300 if (displayedFiles.add(filePath)) {
207301 addMedia(filePath)
208302 downloadedCount++
303+ currentFileProgress = 0f // Reset current file progress when file is completed
209304 updateProgress()
210305 }
211306 }
@@ -215,7 +310,50 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
215310 appendStatus(status)
216311 }
217312
218- override fun onDownloadProgressUpdate (downloaded : Long , total : Long ) {}
313+ override fun onDownloadProgressUpdate (downloaded : Long , total : Long ) {
314+ // Calculate progress percentage - always update this
315+ val progressPercent = if (total > 0 ) {
316+ (downloaded.toDouble() / total.toDouble() * 100 ).toFloat()
317+ } else 0f
318+
319+ // Calculate individual file progress (0.0 to 1.0)
320+ currentFileProgress = if (total > 0 ) {
321+ downloaded.toFloat() / total.toFloat()
322+ } else {
323+ 0f
324+ }
325+
326+ // Calculate download speed more responsively
327+ val currentTime = System .currentTimeMillis()
328+ val deltaTime = currentTime - lastSpeedCalculationTime
329+
330+ // Update speed calculation more frequently for better responsiveness
331+ if (deltaTime > 500 ) { // Update every 0.5 seconds instead of 1 second
332+ val deltaBytes = downloaded - currentDownloadedBytes
333+ val deltaTimeSec = deltaTime.toDouble() / 1000.0 // Convert to seconds
334+
335+ val speedBps = if (deltaTimeSec > 0 ) {
336+ deltaBytes.toDouble() / deltaTimeSec
337+ } else 0.0
338+
339+ // Format speed with appropriate units (KB/s or MB/s)
340+ lastCalculatedSpeed = formatSpeed(speedBps)
341+ lastSpeedCalculationTime = currentTime
342+ }
343+
344+ // Update the current download stats
345+ currentDownloadedBytes = downloaded
346+ currentDownloadTotalBytes = total
347+
348+ // Update overall progress
349+ updateProgress()
350+
351+ // Always update UI state with current progress and speed
352+ val progressText = " ${String .format(" %.1f" , progressPercent)} %|$lastCalculatedSpeed "
353+ _uiState .update { currentState ->
354+ currentState.copy(downloadProgressText = progressText)
355+ }
356+ }
219357
220358 override fun onDownloadError (status : String , originalUrl : String ) {
221359 appendStatus(" 错误:$status ($originalUrl )" )
@@ -237,7 +375,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
237375 }
238376 // Reset the stop flag for new download
239377 downloader.resetStopDownload()
240- val postId = currentUrl?.let { downloader.extractPostId(it) } ? : " webview"
378+ // Extract all valid XHS URLs from the input
379+ val url: List <String >? = downloader.extractLinks(currentUrl)
380+ val postIdTemp: String = url?.let { downloader.extractPostId(it.firstOrNull()) } ? : currentDownloadStartTime.toString()
381+ val postId = " webview_$postIdTemp "
241382 urls.forEachIndexed { index, rawUrl ->
242383 val transformed = downloader.transformXhsCdnUrl(rawUrl).takeUnless { it.isNullOrEmpty() } ? : rawUrl
243384 val extension = determineFileExtension(transformed)
@@ -247,6 +388,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
247388 withContext(Dispatchers .Main ) {
248389 updateProgress()
249390 appendStatus(" 网页转存完成" )
391+ // Reset download tracking when download completes
392+ resetDownloadTracking()
250393 _uiState .update { it.copy(showWebCrawl = false , isDownloading = false ) }
251394 // Reset the flag after download completes (whether successful or not)
252395 hasUserContinuedAfterVideoWarning = false
@@ -350,12 +493,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
350493 } else {
351494 " $downloadedCount /?"
352495 }
353- val fraction = if (totalMediaCount > 0 ) {
354- downloadedCount.toFloat() / totalMediaCount
496+ val overallProgress = if (totalMediaCount > 0 ) {
497+ // Calculate progress as (completed files + current file progress) / total files
498+ (downloadedCount + currentFileProgress) / totalMediaCount.toFloat()
355499 } else {
356500 0f
357501 }
358- _uiState .update { it.copy(progressLabel = label, progress = fraction ) }
502+ _uiState .update { it.copy(progressLabel = label, progress = overallProgress ) }
359503 }
360504
361505 private fun appendStatus (message : String ) {
0 commit comments