DocumentScanViewModel.kt 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Álvaro Brey
  5. * Copyright (C) 2022 Álvaro Brey
  6. * Copyright (C) 2022 Nextcloud GmbH
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. package com.nextcloud.client.documentscan
  23. import android.app.Application
  24. import androidx.lifecycle.AndroidViewModel
  25. import androidx.lifecycle.LiveData
  26. import androidx.lifecycle.MutableLiveData
  27. import androidx.lifecycle.viewModelScope
  28. import com.nextcloud.client.account.CurrentAccountProvider
  29. import com.nextcloud.client.di.IoDispatcher
  30. import com.nextcloud.client.files.uploader.FileUploadWorker
  31. import com.nextcloud.client.jobs.BackgroundJobManager
  32. import com.nextcloud.client.logger.Logger
  33. import com.owncloud.android.datamodel.OCFile
  34. import com.owncloud.android.files.services.NameCollisionPolicy
  35. import com.owncloud.android.operations.UploadFileOperation
  36. import com.owncloud.android.ui.helpers.FileOperationsHelper
  37. import com.nextcloud.client.files.uploader.FileUploadHelper
  38. import kotlinx.coroutines.CoroutineDispatcher
  39. import kotlinx.coroutines.Dispatchers
  40. import kotlinx.coroutines.launch
  41. import java.io.File
  42. import javax.inject.Inject
  43. @Suppress("Detekt.LongParameterList") // satisfied by DI
  44. class DocumentScanViewModel @Inject constructor(
  45. @IoDispatcher private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
  46. app: Application,
  47. private val logger: Logger,
  48. private val backgroundJobManager: BackgroundJobManager,
  49. private val currentAccountProvider: CurrentAccountProvider
  50. ) : AndroidViewModel(app) {
  51. init {
  52. logger.d(TAG, "DocumentScanViewModel created")
  53. }
  54. sealed interface UIState {
  55. sealed class BaseState(val pageList: List<String>) : UIState {
  56. val isEmpty: Boolean
  57. get() = pageList.isEmpty()
  58. }
  59. class NormalState(
  60. pageList: List<String> = emptyList(),
  61. val shouldRequestScan: Boolean = false
  62. ) : BaseState(pageList)
  63. class RequestExportState(
  64. pageList: List<String> = emptyList(),
  65. val shouldRequestExportType: Boolean = true
  66. ) : BaseState(pageList)
  67. object DoneState : UIState
  68. object CanceledState : UIState
  69. }
  70. private var uploadFolder: String? = null
  71. private val initialState = UIState.NormalState(shouldRequestScan = true)
  72. private val _uiState = MutableLiveData<UIState>(initialState)
  73. val uiState: LiveData<UIState>
  74. get() = _uiState
  75. /**
  76. * @param result should be the path to the scanned page on the disk
  77. */
  78. fun onScanPageResult(result: String?) {
  79. logger.d(TAG, "onScanPageResult() called with: result = $result")
  80. val state = _uiState.value
  81. require(state is UIState.NormalState)
  82. viewModelScope.launch(ioDispatcher) {
  83. if (result != null) {
  84. val newPath = renameCapturedImage(result)
  85. val pageList = state.pageList.toMutableList()
  86. pageList.add(newPath)
  87. _uiState.postValue(UIState.NormalState(pageList))
  88. } else {
  89. // result == null means cancellation or error
  90. if (state.isEmpty) {
  91. // close only if no pages have been added yet
  92. _uiState.postValue(UIState.CanceledState)
  93. }
  94. }
  95. }
  96. }
  97. // TODO extract to usecase
  98. private fun renameCapturedImage(originalPath: String): String {
  99. val file = File(originalPath)
  100. val renamedFile =
  101. File(
  102. getApplication<Application>().cacheDir.path +
  103. File.separator + FileOperationsHelper.getCapturedImageName()
  104. )
  105. file.renameTo(renamedFile)
  106. return renamedFile.absolutePath
  107. }
  108. fun onScanRequestHandled() {
  109. val state = uiState.value
  110. require(state is UIState.NormalState)
  111. _uiState.postValue(UIState.NormalState(state.pageList, shouldRequestScan = false))
  112. }
  113. fun onAddPageClicked() {
  114. val state = uiState.value
  115. require(state is UIState.NormalState)
  116. if (!state.shouldRequestScan) {
  117. _uiState.postValue(UIState.NormalState(state.pageList, shouldRequestScan = true))
  118. }
  119. }
  120. fun onClickDone() {
  121. val state = _uiState.value
  122. if (state is UIState.BaseState && !state.isEmpty) {
  123. _uiState.postValue(UIState.RequestExportState(state.pageList))
  124. }
  125. }
  126. fun setUploadFolder(folder: String) {
  127. this.uploadFolder = folder
  128. }
  129. fun onRequestTypeHandled() {
  130. val state = _uiState.value
  131. require(state is UIState.RequestExportState)
  132. _uiState.postValue(UIState.RequestExportState(state.pageList, false))
  133. }
  134. fun onExportTypeSelected(exportType: ExportType) {
  135. val state = _uiState.value
  136. require(state is UIState.RequestExportState)
  137. when (exportType) {
  138. ExportType.PDF -> {
  139. exportToPdf(state.pageList)
  140. }
  141. ExportType.IMAGES -> {
  142. exportToImages(state.pageList)
  143. }
  144. }
  145. _uiState.postValue(UIState.DoneState)
  146. }
  147. private fun exportToPdf(pageList: List<String>) {
  148. val genPath =
  149. getApplication<Application>().cacheDir.path + File.separator + FileOperationsHelper.getTimestampedFileName(
  150. ".pdf"
  151. )
  152. backgroundJobManager.startPdfGenerateAndUploadWork(
  153. currentAccountProvider.user,
  154. uploadFolder!!,
  155. pageList,
  156. genPath
  157. )
  158. // after job is started, finish the application.
  159. _uiState.postValue(UIState.DoneState)
  160. }
  161. private fun exportToImages(pageList: List<String>) {
  162. val uploadPaths = pageList.map {
  163. uploadFolder + OCFile.PATH_SEPARATOR + File(it).name
  164. }.toTypedArray()
  165. FileUploadHelper().uploadNewFiles(
  166. currentAccountProvider.user,
  167. pageList.toTypedArray(),
  168. uploadPaths,
  169. FileUploadWorker.LOCAL_BEHAVIOUR_DELETE,
  170. true,
  171. UploadFileOperation.CREATED_BY_USER,
  172. false,
  173. false,
  174. NameCollisionPolicy.ASK_USER
  175. )
  176. }
  177. fun onExportCanceled() {
  178. val state = _uiState.value
  179. if (state is UIState.BaseState) {
  180. _uiState.postValue(UIState.NormalState(state.pageList))
  181. }
  182. }
  183. private companion object {
  184. private const val TAG = "DocumentScanViewModel"
  185. }
  186. enum class ExportType {
  187. PDF,
  188. IMAGES
  189. }
  190. }