Skip to content

Commit 24c9747

Browse files
committed
UI hardening
Prioritize the visible area for downloading the files and generating the thumbnails. Add MAX_CONCURRENT_THUMBNAILS_THREADS to limit the number of usecase-threads generated. Do not start a double download of the same file and, finally, remove the progress bars for dowloads and substitute them with simple icons to lighten th UI.
1 parent 340338a commit 24c9747

File tree

5 files changed

+89
-40
lines changed

5 files changed

+89
-40
lines changed

presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ class BrowseFilesPresenter @Inject constructor( //
137137
private lateinit var existingFilesForUpload: MutableMap<String, UploadFile>
138138
private lateinit var downloadFiles: MutableList<DownloadFile>
139139

140+
private var availableThumbnailsThreads = MAX_CONCURRENT_THUMBNAILS_THREADS
141+
private val filesBeingDownloaded: MutableSet<CloudFileModel> = mutableSetOf()
142+
140143
private var resumedAfterAuthentication = false
141144

142145
@InjectIntent
@@ -161,48 +164,24 @@ class BrowseFilesPresenter @Inject constructor( //
161164
@JvmField
162165
var openWritableFileNotification: OpenWritableFileNotification? = null
163166

164-
fun thumbnailsForVisibleNodes(visibleCloudNodes: List<CloudNodeModel<*>>) {
165-
if (!sharedPreferencesHandler.useLruCache() || (sharedPreferencesHandler.generateThumbnails() != ThumbnailsOption.PER_FOLDER)) {
166-
return
167-
}
168-
val toDownload = ArrayList<CloudFileModel>()
169-
visibleCloudNodes.forEach { node ->
170-
if (node is CloudFileModel && isImageMediaType(node.name) && node.thumbnail == null) {
171-
toDownload.add(node)
172-
}
173-
}
174-
if (toDownload.isEmpty()) {
175-
return
176-
}
177-
downloadAndGenerateThumbnails(toDownload)
178-
}
179-
180167
private fun downloadAndGenerateThumbnails(visibleCloudFiles: List<CloudFileModel>) {
181-
view?.showProgress(
182-
visibleCloudFiles, //
183-
ProgressModel(
184-
progressStateModelMapper.toModel( //
185-
DownloadState.download(visibleCloudFiles[0].toCloudNode())
186-
), 0
187-
)
168+
filesBeingDownloaded.addAll(visibleCloudFiles)
169+
view?.replaceImagesWithDownloadIcon(
170+
visibleCloudFiles
188171
)
189172
downloadFilesUseCase //
190173
.withDownloadFiles(downloadFileUtil.createDownloadFilesFor(this, visibleCloudFiles)) //
191174
.run(object : DefaultProgressAwareResultHandler<List<CloudFile>, DownloadState>() {
192175
override fun onFinished() {
193-
view?.hideProgress(visibleCloudFiles)
176+
availableThumbnailsThreads++ // releasing the passed baton
177+
Timber.tag("THUMBNAILS").i("[RELEASE] downloadAndGen (${availableThumbnailsThreads}/${MAX_CONCURRENT_THUMBNAILS_THREADS})")
194178
}
195179

196180
override fun onProgress(progress: Progress<DownloadState>) {
197-
if (!progress.isOverallComplete) {
198-
view?.showProgress(
199-
cloudFileModelMapper.toModel(progress.state().file()), //
200-
progressModelMapper.toModel(progress)
201-
)
202-
}
203181
if (progress.isCompleteAndHasState) {
204182
val cloudFile = progress.state().file()
205183
val cloudFileModel = cloudFileModelMapper.toModel(cloudFile)
184+
filesBeingDownloaded.remove(cloudFileModel)
206185
view?.addOrUpdateCloudNode(cloudFileModel)
207186
}
208187
}
@@ -236,6 +215,9 @@ class BrowseFilesPresenter @Inject constructor( //
236215

237216
fun onBackPressed() {
238217
unsubscribeAll()
218+
Timber.tag("THUMBNAILS").i("[RESET] unsubscribe to all")
219+
availableThumbnailsThreads = MAX_CONCURRENT_THUMBNAILS_THREADS
220+
filesBeingDownloaded.clear()
239221
}
240222

241223
fun onFolderDisplayed(folder: CloudFolderModel) {
@@ -258,7 +240,10 @@ class BrowseFilesPresenter @Inject constructor( //
258240
clearCloudList()
259241
} else {
260242
showCloudNodesCollectionInView(cloudNodes)
261-
associateThumbnails(cloudNodes)
243+
val images = view?.renderedCloudNodes()?.filterIsInstance<CloudFileModel>()?.filter { file ->
244+
isImageMediaType(file.name)
245+
} ?: return
246+
associateThumbnails(images.take(10))
262247
}
263248
view?.showLoading(false)
264249
}
@@ -287,26 +272,60 @@ class BrowseFilesPresenter @Inject constructor( //
287272
})
288273
}
289274

290-
private fun associateThumbnails(cloudNodes: List<CloudNode>) {
275+
fun associateThumbnails(cloudNodes: List<CloudNodeModel<*>>) {
291276
if (!sharedPreferencesHandler.useLruCache() || sharedPreferencesHandler.generateThumbnails() == ThumbnailsOption.NEVER) {
292277
return
293278
}
294-
associateThumbnailsUseCase.withList(cloudNodes)
279+
if (cloudNodes.isEmpty()) {
280+
return
281+
}
282+
if (availableThumbnailsThreads == 0) {
283+
Timber.tag("THUMBNAILS").i("[DROP] all threads are in use!")
284+
return
285+
}
286+
287+
availableThumbnailsThreads--
288+
Timber.tag("THUMBNAILS").i("[ACQUIRE] associate (${availableThumbnailsThreads}/${MAX_CONCURRENT_THUMBNAILS_THREADS})")
289+
290+
val associatedCloudNodes = ArrayList<CloudNodeModel<*>>()
291+
associateThumbnailsUseCase.withList(cloudNodeModelMapper.fromModels(cloudNodes))
295292
.run(object : DefaultProgressAwareResultHandler<Void, FileTransferState>() {
296293
override fun onProgress(progress: Progress<FileTransferState>) {
297294
val state = progress.state()
295+
Timber.tag("THUMBNAILS").i("associateThumbnailsUseCase - onProgress")
296+
298297
state?.let { state ->
299-
view?.addOrUpdateCloudNode(cloudFileModelMapper.toModel(state.file()))
298+
val file = cloudFileModelMapper.toModel(state.file())
299+
view?.addOrUpdateCloudNode(file)
300+
associatedCloudNodes.add(file)
300301
}
301302
}
302303

303304
override fun onFinished() {
304-
val images = view?.renderedCloudNodes()?.filterIsInstance<CloudFileModel>()?.filter { file -> isImageMediaType(file.name) } ?: return
305-
images.take(10).filter { img -> img.thumbnail == null }.let { firstImagesWithoutThumbnails ->
306-
if (firstImagesWithoutThumbnails.isNotEmpty()) {
307-
thumbnailsForVisibleNodes(firstImagesWithoutThumbnails)
305+
Timber.tag("THUMBNAILS").i("associateThumbnailsUseCase - onFinished")
306+
307+
if (sharedPreferencesHandler.generateThumbnails() != ThumbnailsOption.PER_FOLDER) {
308+
availableThumbnailsThreads++
309+
Timber.tag("THUMBNAILS").i("[RELEASE] associate (${availableThumbnailsThreads}/${MAX_CONCURRENT_THUMBNAILS_THREADS})")
310+
return
311+
}
312+
val toDownload = ArrayList<CloudFileModel>()
313+
cloudNodes.filter { node -> !associatedCloudNodes.contains(node) }.forEach { node ->
314+
if (node is CloudFileModel && isImageMediaType(node.name) && node.thumbnail == null) {
315+
if (filesBeingDownloaded.contains(node)) {
316+
Timber.tag("THUMBNAILS").i("[SKIP] No double download!")
317+
} else {
318+
toDownload.add(node)
319+
}
308320
}
309321
}
322+
if (toDownload.isEmpty()) {
323+
availableThumbnailsThreads++
324+
Timber.tag("THUMBNAILS").i("[RELEASE] associate (${availableThumbnailsThreads}/${MAX_CONCURRENT_THUMBNAILS_THREADS})")
325+
return
326+
}
327+
328+
downloadAndGenerateThumbnails(toDownload) // passing of the baton, do not increase the number of threads
310329
}
311330
})
312331
}
@@ -970,6 +989,9 @@ class BrowseFilesPresenter @Inject constructor( //
970989

971990
fun onFolderClicked(cloudFolderModel: CloudFolderModel) {
972991
unsubscribeAll()
992+
Timber.tag("THUMBNAILS").i("[RESET] unsubscribe to all")
993+
availableThumbnailsThreads = MAX_CONCURRENT_THUMBNAILS_THREADS
994+
filesBeingDownloaded.clear()
973995
view?.navigateTo(cloudFolderModel)
974996
}
975997

@@ -1342,6 +1364,7 @@ class BrowseFilesPresenter @Inject constructor( //
13421364
companion object {
13431365

13441366
const val OPEN_FILE_FINISHED = 12
1367+
private const val MAX_CONCURRENT_THUMBNAILS_THREADS = 2
13451368

13461369
val EXPORT_AFTER_APP_CHOOSER: ExportOperation = object : ExportOperation {
13471370
override fun export(presenter: BrowseFilesPresenter, downloadFiles: List<DownloadFile>) {

presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,14 @@ class BrowseFilesActivity : BaseActivity<ActivityLayoutBinding>(ActivityLayoutBi
541541
browseFilesFragment().showProgress(nodes, progress)
542542
}
543543

544+
override fun replaceImageWithDownloadIcon(nodes: CloudNodeModel<*>) {
545+
browseFilesFragment().replaceImageWithDownloadIcon(nodes)
546+
}
547+
548+
override fun replaceImagesWithDownloadIcon(nodes: List<CloudNodeModel<*>>) {
549+
browseFilesFragment().replaceImagesWithDownloadIcon(nodes)
550+
}
551+
544552
override fun hideProgress(node: CloudNodeModel<*>) {
545553
browseFilesFragment().hideProgress(node)
546554
}

presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/BrowseFilesView.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ interface BrowseFilesView : View {
3636
fun showSymLinkDialog()
3737
fun showNoDirFileOrEmptyDialog(cryptoFolderName: String, cloudFolderPath: String)
3838
fun updateActiveFolderDueToAuthenticationProblem(folder: CloudFolderModel)
39-
39+
fun replaceImageWithDownloadIcon(nodes: CloudNodeModel<*>)
40+
fun replaceImagesWithDownloadIcon(nodes: List<CloudNodeModel<*>>)
4041
}

presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BrowseFilesAdapter.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ constructor(
332332
bound?.progress = null
333333
}
334334

335+
fun replaceImageWithDownloadIcon() {
336+
binding.cloudNodeImage.setImageResource(R.drawable.ic_file_download)
337+
}
338+
335339
private fun switchTo(state: UiStateTest) {
336340
if (uiState !== state) {
337341
uiState = state

presentation/src/main/java/org/cryptomator/presentation/ui/fragment/BrowseFilesFragment.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class BrowseFilesFragment : BaseFragment<FragmentBrowseFilesBinding>(FragmentBro
150150
}
151151
val visibleCloudNodes = cloudNodesAdapter.renderedCloudNodes().subList(first, last + 1)
152152
if (!binding.swipeRefreshLayout.isRefreshing) {
153-
browseFilesPresenter.thumbnailsForVisibleNodes(visibleCloudNodes)
153+
browseFilesPresenter.associateThumbnails(visibleCloudNodes)
154154
}
155155
}
156156

@@ -238,6 +238,19 @@ class BrowseFilesFragment : BaseFragment<FragmentBrowseFilesBinding>(FragmentBro
238238
}
239239
}
240240

241+
fun replaceImagesWithDownloadIcon(nodes: List<CloudNodeModel<*>>?) {
242+
nodes?.forEach { node ->
243+
replaceImageWithDownloadIcon(node)
244+
}
245+
}
246+
247+
fun replaceImageWithDownloadIcon(node: CloudNodeModel<*>?) {
248+
val viewHolder = viewHolderFor(node)
249+
if (viewHolder.isPresent) {
250+
viewHolder.get().replaceImageWithDownloadIcon()
251+
}
252+
}
253+
241254
fun hideProgress(nodes: List<CloudNodeModel<*>>?) {
242255
nodes?.forEach { node ->
243256
hideProgress(node)

0 commit comments

Comments
 (0)