summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/res/drawable/inset_resolver_profile_tab_bg.xml21
-rw-r--r--java/res/layout/chooser_action_view.xml2
-rw-r--r--java/res/layout/chooser_grid_item.xml4
-rw-r--r--java/res/layout/chooser_headline_row.xml2
-rw-r--r--java/res/layout/resolve_grid_item.xml4
-rw-r--r--java/res/layout/resolver_profile_tab_button.xml5
-rw-r--r--java/res/values/dimens.xml4
-rw-r--r--java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt5
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ImageLoader.kt3
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselCardComposable.kt33
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt85
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselPreviewViewModel.kt3
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt20
13 files changed, 118 insertions, 73 deletions
diff --git a/java/res/drawable/inset_resolver_profile_tab_bg.xml b/java/res/drawable/inset_resolver_profile_tab_bg.xml
new file mode 100644
index 00000000..bc62b047
--- /dev/null
+++ b/java/res/drawable/inset_resolver_profile_tab_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/resolver_profile_tab_bg"
+ android:insetLeft="0dp"
+ android:insetRight="0dp"
+ android:insetTop="6dp"
+ android:insetBottom="6dp" />
diff --git a/java/res/layout/chooser_action_view.xml b/java/res/layout/chooser_action_view.xml
index d045a7e3..6177821a 100644
--- a/java/res/layout/chooser_action_view.xml
+++ b/java/res/layout/chooser_action_view.xml
@@ -28,4 +28,4 @@
android:gravity="center"
android:maxLines="1"
android:textColor="?androidprv:attr/materialColorOnSurface"
- android:textSize="12sp" />
+ android:textSize="@dimen/chooser_action_view_text_size" />
diff --git a/java/res/layout/chooser_grid_item.xml b/java/res/layout/chooser_grid_item.xml
index 18abc7bc..547a9944 100644
--- a/java/res/layout/chooser_grid_item.xml
+++ b/java/res/layout/chooser_grid_item.xml
@@ -51,14 +51,14 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?androidprv:attr/materialColorOnSurface"
- android:textSize="12sp"
+ android:textSize="@dimen/chooser_grid_target_name_text_size"
android:maxLines="1"
android:ellipsize="end" />
<!-- Activity name if set, gone for Direct Share targets -->
<TextView android:id="@android:id/text2"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textSize="12sp"
+ android:textSize="@dimen/chooser_grid_activity_name_text_size"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/java/res/layout/chooser_headline_row.xml b/java/res/layout/chooser_headline_row.xml
index bfce7473..01be653f 100644
--- a/java/res/layout/chooser_headline_row.xml
+++ b/java/res/layout/chooser_headline_row.xml
@@ -35,7 +35,7 @@
app:layout_constrainedWidth="true"
style="@style/TextAppearance.ChooserDefault"
android:fontFamily="@androidprv:string/config_headlineFontFamily"
- android:textSize="18sp"
+ android:textSize="@dimen/chooser_headline_text_size"
/>
<TextView
diff --git a/java/res/layout/resolve_grid_item.xml b/java/res/layout/resolve_grid_item.xml
index 25088773..e5a00429 100644
--- a/java/res/layout/resolve_grid_item.xml
+++ b/java/res/layout/resolve_grid_item.xml
@@ -50,7 +50,7 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?androidprv:attr/materialColorOnSurface"
- android:textSize="12sp"
+ android:textSize="@dimen/chooser_grid_target_name_text_size"
android:gravity="top|center_horizontal"
android:maxLines="1"
android:ellipsize="end" />
@@ -58,7 +58,7 @@
<!-- Activity name if set, gone for Direct Share targets -->
<TextView android:id="@android:id/text2"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textSize="12sp"
+ android:textSize="@dimen/chooser_grid_activity_name_text_size"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/java/res/layout/resolver_profile_tab_button.xml b/java/res/layout/resolver_profile_tab_button.xml
index 1c2bc1ca..52a1aacf 100644
--- a/java/res/layout/resolver_profile_tab_button.xml
+++ b/java/res/layout/resolver_profile_tab_button.xml
@@ -19,11 +19,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="0dp"
- android:layout_height="36dp"
+ android:layout_height="48dp"
android:layout_weight="1"
- android:layout_marginVertical="6dp"
android:layout_marginHorizontal="@dimen/resolver_profile_tab_margin"
- android:background="@drawable/resolver_profile_tab_bg"
+ android:background="@drawable/inset_resolver_profile_tab_bg"
android:textColor="@color/resolver_profile_tab_text"
android:textSize="@dimen/resolver_tab_text_size"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index bd868c9f..a1f03276 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -34,6 +34,9 @@
<dimen name="chooser_max_collapsed_height">288dp</dimen>
<dimen name="chooser_icon_size">56dp</dimen>
<dimen name="chooser_badge_size">22dp</dimen>
+ <dimen name="chooser_headline_text_size">18sp</dimen>
+ <dimen name="chooser_grid_target_name_text_size">12sp</dimen>
+ <dimen name="chooser_grid_activity_name_text_size">12sp</dimen>
<dimen name="resolver_icon_size">32dp</dimen>
<dimen name="resolver_button_bar_spacing">0dp</dimen>
<dimen name="resolver_badge_size">18dp</dimen>
@@ -51,6 +54,7 @@
<dimen name="resolver_empty_state_container_padding_bottom">8dp</dimen>
<dimen name="resolver_profile_tab_margin">4dp</dimen>
<dimen name="chooser_action_view_icon_size">22dp</dimen>
+ <dimen name="chooser_action_view_text_size">12sp</dimen>
<dimen name="chooser_action_margin">0dp</dimen>
<dimen name="modify_share_text_toggle_max_width">150dp</dimen>
<dimen name="chooser_view_spacing">16dp</dimen>
diff --git a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt
index ce064cdf..2e2aa938 100644
--- a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt
+++ b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt
@@ -28,6 +28,7 @@ import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
@@ -104,6 +105,10 @@ constructor(
// [CancellationExceptions]s so that they don't cancel the calling coroutine/scope.
runCatching { cache[uri].await() }.getOrNull()
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun getCachedBitmap(uri: Uri): Bitmap? =
+ kotlin.runCatching { cache[uri].getCompleted() }.getOrNull()
+
companion object {
private const val TAG = "CachingImgPrevLoader"
}
diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt
index 629651a3..81913a8e 100644
--- a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt
+++ b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt
@@ -35,6 +35,9 @@ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitm
/** Prepopulate the image loader cache. */
fun prePopulate(uris: List<Uri>)
+ /** Returns a bitmap for the given URI if it's already cached, otherwise null */
+ fun getCachedBitmap(uri: Uri): Bitmap? = null
+
/** Load preview image; caching is allowed. */
override suspend fun invoke(uri: Uri) = invoke(uri, true)
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselCardComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselCardComposable.kt
index 71d16da9..197d6858 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselCardComposable.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselCardComposable.kt
@@ -40,28 +40,25 @@ fun ShareouselCard(
image: @Composable () -> Unit,
contentType: ContentType,
selected: Boolean,
- loadComplete: Boolean,
modifier: Modifier = Modifier,
) {
Box(modifier) {
image()
- if (loadComplete) {
- val topButtonPadding = 12.dp
- Box(modifier = Modifier.padding(topButtonPadding).matchParentSize()) {
- SelectionIcon(selected, modifier = Modifier.align(Alignment.TopStart))
- when (contentType) {
- ContentType.Video ->
- TypeIcon(
- R.drawable.ic_play_circle_filled_24px,
- modifier = Modifier.align(Alignment.TopEnd)
- )
- ContentType.Other ->
- TypeIcon(
- R.drawable.chooser_file_generic,
- modifier = Modifier.align(Alignment.TopEnd)
- )
- ContentType.Image -> Unit // No additional icon needed.
- }
+ val topButtonPadding = 12.dp
+ Box(modifier = Modifier.padding(topButtonPadding).matchParentSize()) {
+ SelectionIcon(selected, modifier = Modifier.align(Alignment.TopStart))
+ when (contentType) {
+ ContentType.Video ->
+ TypeIcon(
+ R.drawable.ic_play_circle_filled_24px,
+ modifier = Modifier.align(Alignment.TopEnd)
+ )
+ ContentType.Other ->
+ TypeIcon(
+ R.drawable.chooser_file_generic,
+ modifier = Modifier.align(Alignment.TopEnd)
+ )
+ ContentType.Image -> Unit // No additional icon needed.
}
}
}
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt
index a40a9c50..c63055d2 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt
@@ -15,6 +15,7 @@
*/
package com.android.intentresolver.contentpreview.payloadtoggle.ui.composable
+import androidx.compose.animation.Crossfade
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -126,15 +127,14 @@ private fun PreviewCarousel(
}
}
- ShareouselCard(viewModel.preview(model, previewIndex))
+ ShareouselCard(viewModel.preview(model, previewIndex, rememberCoroutineScope()))
}
}
}
@Composable
private fun ShareouselCard(viewModel: ShareouselPreviewViewModel) {
- val bitmapLoadState by
- viewModel.bitmapLoadState.collectAsStateWithLifecycle(initialValue = ValueUpdate.Absent)
+ val bitmapLoadState by viewModel.bitmapLoadState.collectAsStateWithLifecycle()
val selected by viewModel.isSelected.collectAsStateWithLifecycle(initialValue = false)
val borderColor = MaterialTheme.colorScheme.primary
val scope = rememberCoroutineScope()
@@ -144,47 +144,56 @@ private fun ShareouselCard(viewModel: ShareouselPreviewViewModel) {
ContentType.Video -> stringResource(R.string.selectable_video)
else -> stringResource(R.string.selectable_item)
}
- // Image load is complete (but may have failed)
- val loadComplete = bitmapLoadState is ValueUpdate.Value
- ShareouselCard(
- image = {
- // TODO: max ratio is actually equal to the viewport ratio
- val aspectRatio = viewModel.aspectRatio.coerceIn(MIN_ASPECT_RATIO, MAX_ASPECT_RATIO)
- bitmapLoadState.getOrDefault(null)?.let { bitmap ->
- Image(
- bitmap = bitmap.asImageBitmap(),
- contentDescription = null,
- contentScale = ContentScale.Crop,
- modifier = Modifier.aspectRatio(aspectRatio),
- )
- }
- ?: run {
- // TODO: look at ScrollableImagePreviewView.setLoading()
- Box(
- modifier =
- Modifier.fillMaxHeight()
- .aspectRatio(aspectRatio)
- .background(color = MaterialTheme.colorScheme.surfaceContainerHigh)
- )
- }
- },
- contentType = viewModel.contentType,
- loadComplete = loadComplete,
- selected = selected,
+ Crossfade(
+ targetState = bitmapLoadState,
modifier =
- Modifier.thenIf(selected && loadComplete) {
- Modifier.border(
- width = 4.dp,
- color = borderColor,
- shape = RoundedCornerShape(size = 12.dp),
- )
- }
- .semantics { this.contentDescription = contentDescription }
+ Modifier.semantics { this.contentDescription = contentDescription }
.clip(RoundedCornerShape(size = 12.dp))
.toggleable(
value = selected,
onValueChange = { scope.launch { viewModel.setSelected(it) } },
)
+ ) { state ->
+ // TODO: max ratio is actually equal to the viewport ratio
+ val aspectRatio = viewModel.aspectRatio.coerceIn(MIN_ASPECT_RATIO, MAX_ASPECT_RATIO)
+ if (state is ValueUpdate.Value) {
+ state.getOrDefault(null).let { bitmap ->
+ ShareouselCard(
+ image = {
+ bitmap?.let {
+ Image(
+ bitmap = bitmap.asImageBitmap(),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.aspectRatio(aspectRatio),
+ )
+ } ?: PlaceholderBox(aspectRatio)
+ },
+ contentType = viewModel.contentType,
+ selected = selected,
+ modifier =
+ Modifier.thenIf(selected) {
+ Modifier.border(
+ width = 4.dp,
+ color = borderColor,
+ shape = RoundedCornerShape(size = 12.dp),
+ )
+ }
+ )
+ }
+ } else {
+ PlaceholderBox(aspectRatio)
+ }
+ }
+}
+
+@Composable
+private fun PlaceholderBox(aspectRatio: Float) {
+ Box(
+ modifier =
+ Modifier.fillMaxHeight()
+ .aspectRatio(aspectRatio)
+ .background(color = MaterialTheme.colorScheme.surfaceContainerHigh)
)
}
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselPreviewViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselPreviewViewModel.kt
index 1acdcf7a..de435290 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselPreviewViewModel.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselPreviewViewModel.kt
@@ -20,11 +20,12 @@ import android.graphics.Bitmap
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate
import com.android.intentresolver.contentpreview.payloadtoggle.shared.ContentType
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** An individual preview within Shareousel. */
data class ShareouselPreviewViewModel(
/** Image to be shared. */
- val bitmapLoadState: Flow<ValueUpdate<Bitmap?>>,
+ val bitmapLoadState: StateFlow<ValueUpdate<Bitmap?>>,
/** Type of data to be shared. */
val contentType: ContentType,
/** Whether this preview has been selected by the user. */
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt
index 19acb318..d0b89860 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt
@@ -56,7 +56,8 @@ data class ShareouselViewModel(
/** List of action chips presented underneath Shareousel. */
val actions: Flow<List<ActionChipViewModel>>,
/** Creates a [ShareouselPreviewViewModel] for a [PreviewModel] present in [previews]. */
- val preview: (key: PreviewModel, index: Int?) -> ShareouselPreviewViewModel,
+ val preview:
+ (key: PreviewModel, index: Int?, scope: CoroutineScope) -> ShareouselPreviewViewModel,
)
@Module
@@ -113,7 +114,7 @@ interface ShareouselViewModelModule {
}
}
},
- preview = { key, index ->
+ preview = { key, index, previewScope ->
keySet.value?.maybeLoad(index)
val previewInteractor = interactor.preview(key)
val contentType =
@@ -122,14 +123,19 @@ interface ShareouselViewModelModule {
mimeTypeClassifier.isVideoType(key.mimeType) -> ContentType.Video
else -> ContentType.Other
}
+ val initialBitmapValue =
+ key.previewUri?.let {
+ imageLoader.getCachedBitmap(it)?.let { ValueUpdate.Value(it) }
+ } ?: ValueUpdate.Absent
ShareouselPreviewViewModel(
bitmapLoadState =
flow {
- emit(
- key.previewUri?.let { ValueUpdate.Value(imageLoader(it)) }
- ?: ValueUpdate.Absent
- )
- },
+ emit(
+ key.previewUri?.let { ValueUpdate.Value(imageLoader(it)) }
+ ?: ValueUpdate.Absent
+ )
+ }
+ .stateIn(previewScope, SharingStarted.Eagerly, initialBitmapValue),
contentType = contentType,
isSelected = previewInteractor.isSelected,
setSelected = previewInteractor::setSelected,