11package com.paulcoding.pindownloader
22
3+ import android.content.Context
4+ import android.graphics.drawable.BitmapDrawable
35import androidx.lifecycle.ViewModel
46import androidx.lifecycle.viewModelScope
7+ import coil3.Bitmap
8+ import coil3.BitmapImage
9+ import coil3.ImageLoader
10+ import coil3.request.ImageRequest
11+ import coil3.request.SuccessResult
12+ import coil3.request.allowHardware
513import com.paulcoding.pindownloader.App.Companion.appContext
6- import com.paulcoding.pindownloader.extractor.PinData
714import com.paulcoding.pindownloader.extractor.PinSource
8- import com.paulcoding.pindownloader.extractor.PinType
915import com.paulcoding.pindownloader.extractor.pinterest.PinterestExtractor
1016import com.paulcoding.pindownloader.extractor.pixiv.PixivExtractor
1117import com.paulcoding.pindownloader.helper.Downloader
1218import com.paulcoding.pindownloader.helper.NetworkUtil
19+ import com.paulcoding.pindownloader.ui.model.DownloadInfo
1320import kotlinx.coroutines.Dispatchers
1421import kotlinx.coroutines.flow.MutableStateFlow
1522import kotlinx.coroutines.flow.asStateFlow
1623import kotlinx.coroutines.flow.update
1724import kotlinx.coroutines.launch
1825
1926class MainViewModel (private val downloader : Downloader ) : ViewModel() {
20- private var _uiStateFlow = MutableStateFlow (UiState ())
21- val uiStateFlow = _uiStateFlow .asStateFlow()
27+ private var _state = MutableStateFlow < UiState > (UiState ())
28+ val uiStateFlow = _state .asStateFlow()
2229
2330 private val pinterestExtractor = PinterestExtractor ()
2431 private val pixivExtractor = PixivExtractor ()
2532
26- data class UiState (
27- val input : String = " " ,
28- val exception : AppException ? = null ,
29- val pinData : PinData ? = null ,
30- val isFetchingImages : Boolean = false ,
31- val isFetched : Boolean = false ,
32- val isDownloadingImage : Boolean = false ,
33- val isDownloadingVideo : Boolean = false ,
34- val isDownloaded : Boolean = false ,
35- )
3633
3734 private suspend fun extract (link : String ) {
38- _uiStateFlow .update { UiState () .copy(input = link) }
35+ _state .update { it .copy(extractState = ExtractState . Loading ( link) ) }
3936
4037 val extractor =
4138 when {
@@ -45,93 +42,98 @@ class MainViewModel(private val downloader: Downloader) : ViewModel() {
4542 }
4643
4744 if (extractor == null ) {
48- setError (AppException .InvalidUrlError (link))
45+ setExtractError (AppException .InvalidUrlError (link))
4946 return
5047 }
5148
52- _uiStateFlow .update { it.copy(isFetchingImages = true ) }
53-
5449 try {
5550 extractor.extract(link).let { data ->
56- _uiStateFlow .update { it.copy(pinData = data) }
51+ val bitmap = data.image?.let {
52+ downloadImageBitmap(appContext, it)
53+ }
54+ _state .update { it.copy(extractState = ExtractState .Success (data, bitmap)) }
55+
5756 }
58- } catch (e: AppException ) {
59- e.printStackTrace()
60- setError(e)
6157 } catch (e: Exception ) {
6258 e.printStackTrace()
63- setError(AppException .UnknownError ())
59+ if (e is AppException ) {
60+ setExtractError(e)
61+ }
62+ setExtractError(AppException .UnknownError ())
6463 }
65-
66- _uiStateFlow .update { it.copy(isFetchingImages = false , isFetched = true ) }
6764 }
6865
69- private fun setError (appException : AppException ) {
70- _uiStateFlow .update { it.copy(exception = appException) }
66+ private fun setExtractError (appException : AppException ) {
67+ _state .update { it.copy(extractState = ExtractState . Error ( appException) ) }
7168 }
7269
7370 fun clearPinData () {
74- _uiStateFlow .update { UiState () }
71+ _state .value = UiState ()
72+ }
73+
74+ private suspend fun downloadImageBitmap (context : Context , url : String ): Bitmap ? {
75+ val loader = ImageLoader (context)
76+ val request = ImageRequest .Builder (context)
77+ .data(url)
78+ .allowHardware(false ) // Disable hardware bitmaps if needed
79+ .build()
80+
81+ val result = loader.execute(request)
82+ if (result is SuccessResult ) {
83+ return (result.image as BitmapImage ).bitmap
84+ }
85+ return null
7586 }
7687
77- fun download (
78- link : String ,
79- type : PinType = PinType .IMAGE ,
80- source : PinSource = PinSource .PINTEREST ,
81- fileName : String? = null,
82- onSuccess : (path: String ) -> Unit = {}
83- ) {
88+ fun dispatch (action : MainAction ) {
89+ when (action) {
90+ is MainAction .ExtractLink -> extractLink(action.url)
91+ is MainAction .Download -> download(action.downloadInfo)
92+ is MainAction .ClearPinData -> clearPinData()
93+ }
94+ }
95+
96+ private fun download (downloadInfo : DownloadInfo ) {
97+ val (link, type, source, fileName) = downloadInfo
98+
8499 val headers =
85100 if (source == PinSource .PIXIV ) mapOf (" referer" to " https://www.pixiv.net/" ) else mapOf ()
86101
87102 viewModelScope.launch(Dispatchers .IO ) {
88- if (type == PinType .VIDEO ) {
89- _uiStateFlow .update { it.copy(isDownloadingVideo = true ) }
90- } else {
91- _uiStateFlow .update { it.copy(isDownloadingImage = true ) }
92- }
103+ _state .update { it.copy(downloadState = DownloadState .Loading (link)) }
93104 checkInternetOrExec {
94105 try {
95106 val downloadPath = downloader.download(appContext, link, fileName, headers)
96- _uiStateFlow .update { it.copy(isDownloadingVideo = false ) }
97- onSuccess(downloadPath)
98- } catch (e: AppException ) {
107+ _state .update { it.copy(downloadState = DownloadState .Success (downloadPath)) }
108+ } catch (e: Exception ) {
99109 e.printStackTrace()
100- setError(e)
101- }
102- }
103110
104- if (type == PinType .VIDEO ) {
105- _uiStateFlow .update { it.copy(isDownloadingVideo = false ) }
106- } else {
107- _uiStateFlow .update { it.copy(isDownloadingImage = false ) }
111+ if (e is AppException )
112+ _state .update { it.copy(downloadState = DownloadState .Error (e)) }
113+ else
114+ _state .update { it.copy(downloadState = DownloadState .Error (AppException .DownloadError (link))) }
115+ }
108116 }
109117 }
110118 }
111119
112- fun setLink (link : String ) {
113- _uiStateFlow .update { it.copy(input = link) }
114- }
115-
116120 private suspend fun checkInternetOrExec (block : suspend () -> Unit ) {
117121 if (NetworkUtil .isInternetAvailable()) {
118122 return block()
119123 }
120- return setError (AppException .NetworkError ())
124+ return setExtractError (AppException .NetworkError ())
121125 }
122126
123127 fun extractLink (msg : String ) {
124- _uiStateFlow .update { UiState ().copy(input = msg) }
125128 viewModelScope.launch(Dispatchers .IO ) {
126129 checkInternetOrExec {
127130 val urlPattern = """ (https?://\S+)""" .toRegex()
128131 val link = urlPattern.find(msg)?.value
129132
130133 if (link == null ) {
131- setError (AppException .MessageError ())
134+ setExtractError (AppException .MessageError ())
132135 return @checkInternetOrExec
133136 }
134- setLink(link)
135137 extract(link)
136138 }
137139 }
0 commit comments