1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor 18 19 import android.net.Uri 20 import com.android.intentresolver.Flags.unselectFinalItem 21 import com.android.intentresolver.contentpreview.MimeTypeClassifier 22 import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PreviewSelectionsRepository 23 import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.TargetIntentModifier 24 import com.android.intentresolver.contentpreview.payloadtoggle.shared.ContentType 25 import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel 26 import javax.inject.Inject 27 import kotlinx.coroutines.flow.Flow 28 import kotlinx.coroutines.flow.distinctUntilChanged 29 import kotlinx.coroutines.flow.map 30 import kotlinx.coroutines.flow.update 31 import kotlinx.coroutines.flow.updateAndGet 32 33 class SelectionInteractor 34 @Inject 35 constructor( 36 private val selectionsRepo: PreviewSelectionsRepository, 37 private val targetIntentModifier: TargetIntentModifier<PreviewModel>, 38 private val updateTargetIntentInteractor: UpdateTargetIntentInteractor, 39 private val mimeTypeClassifier: MimeTypeClassifier, 40 ) { 41 /** List of selected previews. */ 42 val selections: Flow<Set<Uri>> = <lambda>null43 selectionsRepo.selections.map { it.keys }.distinctUntilChanged() 44 45 /** Amount of selected previews. */ <lambda>null46 val amountSelected: Flow<Int> = selectionsRepo.selections.map { it.size } 47 48 val aggregateContentType: Flow<ContentType> = <lambda>null49 selectionsRepo.selections.map { aggregateContentType(it.values) } 50 updateSelectionnull51 fun updateSelection(model: PreviewModel) { 52 selectionsRepo.selections.update { 53 if (it.containsKey(model.uri)) it + (model.uri to model) else it 54 } 55 } 56 selectnull57 fun select(model: PreviewModel) { 58 updateChooserRequest( 59 selectionsRepo.selections.updateAndGet { it + (model.uri to model) }.values 60 ) 61 } 62 unselectnull63 fun unselect(model: PreviewModel) { 64 if (selectionsRepo.selections.value.size > 1 || unselectFinalItem()) { 65 selectionsRepo.selections 66 .updateAndGet { it - model.uri } 67 .values 68 .takeIf { it.isNotEmpty() } 69 ?.let { updateChooserRequest(it) } 70 } 71 } 72 updateChooserRequestnull73 private fun updateChooserRequest(selections: Collection<PreviewModel>) { 74 val sorted = selections.sortedBy { it.order } 75 val intent = targetIntentModifier.intentFromSelection(sorted) 76 updateTargetIntentInteractor.updateTargetIntent(intent) 77 } 78 aggregateContentTypenull79 private fun aggregateContentType( 80 items: Collection<PreviewModel>, 81 ): ContentType { 82 if (items.isEmpty()) { 83 return ContentType.Other 84 } 85 86 var allImages = true 87 var allVideos = true 88 for (item in items) { 89 allImages = allImages && mimeTypeClassifier.isImageType(item.mimeType) 90 allVideos = allVideos && mimeTypeClassifier.isVideoType(item.mimeType) 91 92 if (!allImages && !allVideos) { 93 break 94 } 95 } 96 97 return when { 98 allImages -> ContentType.Image 99 allVideos -> ContentType.Video 100 else -> ContentType.Other 101 } 102 } 103 } 104