Merge branch 'develop' into renovate/dependencyanalysis

This commit is contained in:
ganfra
2026-06-05 11:01:34 +02:00
committed by GitHub
16 changed files with 233 additions and 25 deletions
+89
View File
@@ -1,3 +1,92 @@
Changes in Element X v26.06.1
=============================
The release of `v26.06.0` was cancelled because it contained some changes that would prevent some users with devices having no Vulkan support from being able to install the app. This release contains the same changes as `v26.06.0` but with the Vulkan requirement removed.
## What's Changed
### ✨ Features
* Image edition before sending: crop and rotate. by @bmarty in https://github.com/element-hq/element-x-android/pull/6842
* Use a raw key for the SDK stores for new sessions by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6835
* Notification settings: pick custom sounds for messages and call ringtones by @bmarty in https://github.com/element-hq/element-x-android/pull/6897
### 🙌 Improvements
* Do not show membership/profile events in public rooms by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6360
* Use `runBlocking` for the token refresh logic by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6863
* Move empty day separator filtering to a timeline post-processor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6866
* Add better logs to track token update failures by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6859
* Read map tiler custom style from matrix .well-known file by @bmarty in https://github.com/element-hq/element-x-android/pull/6886
* change(permissions) : allow to change ShareLiveLocation permission by @ganfra in https://github.com/element-hq/element-x-android/pull/6890
* Avoid SQLCipher key derivation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6774
* change: replace the maplibre-compose UserLocationState to a simpler one by @ganfra in https://github.com/element-hq/element-x-android/pull/6913
* Change : location provider by @ganfra in https://github.com/element-hq/element-x-android/pull/6935
* Add flip actions to image edition by @bmarty in https://github.com/element-hq/element-x-android/pull/6949
* Remove the `FloatingDateBadge` feature flag by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6950
### 🐛 Bugfixes
* Fix media viewer flickering by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6800
* Fix 'Conversation label cannot be empty' error by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6823
* Release proximity wakelock on Element Call when call ends by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6825
* Hide edit pencil from accessibility by @bmarty in https://github.com/element-hq/element-x-android/pull/6826
* Don't compress images sent through the Files attachment picker by @cizra in https://github.com/element-hq/element-x-android/pull/6755
* [a11y] Improve accessibility of video and audio player by @bmarty in https://github.com/element-hq/element-x-android/pull/6830
* [a11y] Improve accessibility of screen headers. by @bmarty in https://github.com/element-hq/element-x-android/pull/6827
* Add mark as read / unread in room details by @bmarty in https://github.com/element-hq/element-x-android/pull/6818
* Fix formatting inconsistencies in latest event summaries by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6855
* Fix public read receipts being sent by mistake by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6838
* Fix app having a pink top bar in the recent app list when PIN lock is setup by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6903
* Create log messages from `WebView` just once by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6923
* Use secondary color icon for leading icons by @bmarty in https://github.com/element-hq/element-x-android/pull/6926
* Use reverse ordering for `FilterEmptyDayPostProcessor` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6927
* Make Vulkan a *not required* feature by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6961
### 🗣 Translations
* Sync Strings - new translations to Catalan by @ElementBot in https://github.com/element-hq/element-x-android/pull/6856
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6921
### 🧱 Build
* Remove DI-generated code from Kover reports by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6834
* Disable cron jobs and private SSH key jobs in forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6821
* Revert "Disable cron jobs and private SSH key jobs in forks" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6905
* Downgrade detekt compose rules by @bmarty in https://github.com/element-hq/element-x-android/pull/6846
* Add Stefan's etiquette to the project. by @bmarty in https://github.com/element-hq/element-x-android/pull/6909
### 🚧 In development 🚧
* [Link new device] Rotate QrCode instead of showing an error by @bmarty in https://github.com/element-hq/element-x-android/pull/6817
* Fix image resizing behavior. by @bmarty in https://github.com/element-hq/element-x-android/pull/6924
### Dependency upgrades
* Update dependency net.zetetic:sqlcipher-android to v4.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6819
* Update dependencyAnalysis to v3.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6813
* Update metro to v1.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6832
* Update dependency io.element.android:element-call-embedded to v0.19.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6833
* Update kotlin to v2.3.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6822
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.20 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6831
* Update media3 to v1.10.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6816
* Update peaceiris/actions-gh-pages action to v4.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6820
* Update dependencyAnalysis to v3.12.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6840
* Update codecov/codecov-action action to v6.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6864
* Update dependency io.element.android:element-call-embedded to v0.20.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6876
* Update roborazzi to v1.62.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6858
* Update dependency androidx.compose:compose-bom to v2026.05.01 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6888
* Update roborazzi to v1.63.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6893
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.26 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6885
* Update dependency org.maplibre.compose:maplibre-compose to v0.13.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6891
* Update actions/stale action to v10.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6904
* Update tspascoal/get-user-teams-membership action to v4.0.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6900
* Update dependency org.maplibre.gl:android-sdk to v13.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6930
* Update dependency org.unifiedpush.android:connector to v3.3.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6937
* Update kotlin to v2.3.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6946
* Update dependency com.posthog:posthog-android to v3.47.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6920
* Import compound token from release 10.2.1 https://github.com/element-hq/compound-design-tokens/releases/tag/v10.2.1 by @bmarty in https://github.com/element-hq/element-x-android/pull/6942
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6947
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.36.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6939
### Others
* Make the avatar in the room member moderation bottom sheet clickable by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6705
* Do not hide your own media for "Show media in timeline" by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6898
* Report problem: add an optional TextField to provide a GitHub issue number. by @bmarty in https://github.com/element-hq/element-x-android/pull/6911
* Fix read receipts not shown for calls by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6889
* Reorder room detail items by @bmarty in https://github.com/element-hq/element-x-android/pull/6929
* [Media viewer] File preview improvement by @bmarty in https://github.com/element-hq/element-x-android/pull/6933
* [Media bottom sheet] UI iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/6938
* Add unread count to the room unread indicator by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6887
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.05.2...v26.06.1
Changes in Element X v26.05.2
=============================
@@ -0,0 +1,2 @@
Main changes in this version: added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: fixed an issue with devices not supporting Vulkan, added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -75,10 +75,10 @@ internal fun RoomSummaryRow(
room: RoomListRoomSummary,
hideInviteAvatars: Boolean,
isInviteSeen: Boolean,
showUnreadCount: Boolean = false,
onClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvent) -> Unit,
modifier: Modifier = Modifier,
showUnreadCount: Boolean = false,
eventSink: (RoomListEvent) -> Unit,
) {
Box(modifier = modifier) {
when (room.displayType) {
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.api
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun RenderingMapsNotSupportedDialog(onSubmit: () -> Unit) {
ErrorDialog(
title = stringResource(CommonStrings.vulkan_not_supported_dialog_title_android),
content = stringResource(CommonStrings.vulkan_not_supported_dialog_content_android),
onSubmit = onSubmit,
)
}
@PreviewsDayNight
@Composable
internal fun RenderingMapsNotSupportedDialogPreview() = ElementPreview {
RenderingMapsNotSupportedDialog(onSubmit = {})
}
@@ -5,13 +5,21 @@
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
~ Please see LICENSE files in the repository root for full details.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Declare Vulkan as an optional feature to prevent installation issues on devices that do not support it. -->
<uses-feature
android:name="android.hardware.vulkan.version"
android:required="false"
tools:replace="android:required" />
<application>
<service
android:name=".live.service.LiveLocationSharingService"
@@ -11,6 +11,9 @@ package io.element.android.features.messages.impl
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.lifecycle.subscribe
@@ -29,6 +32,7 @@ import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.RenderingMapsNotSupportedDialog
import io.element.android.features.location.api.ShareLocationEntryPoint
import io.element.android.features.location.api.ShowLocationEntryPoint
import io.element.android.features.location.api.ShowLocationMode
@@ -53,6 +57,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.duration
import io.element.android.features.poll.api.create.CreatePollEntryPoint
import io.element.android.features.poll.api.create.CreatePollMode
import io.element.android.libraries.androidutils.system.DeviceHasVulkanSupport
import io.element.android.libraries.architecture.BackstackWithOverlayBox
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.callback
@@ -121,6 +126,7 @@ class MessagesFlowNode(
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
private val dateFormatter: DateFormatter,
private val coroutineDispatchers: CoroutineDispatchers,
private val hasVulkanSupport: DeviceHasVulkanSupport,
) : BaseFlowNode<MessagesFlowNode.NavTarget>(
backstack = BackStack(
initialElement = plugins.filterIsInstance<MessagesEntryPoint.Params>().first().initialTarget.toNavTarget(),
@@ -188,6 +194,8 @@ class MessagesFlowNode(
private val callback: MessagesEntryPoint.Callback = callback()
private var displayVulkanNotSupportedError by mutableStateOf(false)
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
@@ -267,7 +275,11 @@ class MessagesFlowNode(
}
override fun navigateToSendLocation() {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Live))
if (hasVulkanSupport()) {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Live))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToCreatePoll() {
@@ -279,7 +291,11 @@ class MessagesFlowNode(
}
override fun navigateToCurrentLiveLocation() {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
if (hasVulkanSupport()) {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
@@ -506,7 +522,11 @@ class MessagesFlowNode(
}
override fun navigateToSendLocation() {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Thread(navTarget.threadRootId)))
if (hasVulkanSupport()) {
backstack.push(NavTarget.SendLocation(Timeline.Mode.Thread(navTarget.threadRootId)))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToCreatePoll() {
@@ -518,7 +538,11 @@ class MessagesFlowNode(
}
override fun navigateToCurrentLiveLocation() {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
if (hasVulkanSupport()) {
backstack.push(NavTarget.LocationViewer(ShowLocationMode.Live(senderId = sessionId)))
} else {
displayVulkanNotSupportedError = true
}
}
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
@@ -607,18 +631,23 @@ class MessagesFlowNode(
)
}
is TimelineItemLocationContent -> {
val mode = when (event.content.mode) {
is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live(event.senderId)
is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static(
location = event.content.mode.location,
senderName = event.safeSenderName,
senderId = event.senderId,
senderAvatarUrl = event.senderAvatar.url,
timestamp = event.sentTimeMillis,
assetType = event.content.assetType,
)
if (hasVulkanSupport()) {
val mode = when (event.content.mode) {
is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live(event.senderId)
is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static(
location = event.content.mode.location,
senderName = event.safeSenderName,
senderId = event.senderId,
senderAvatarUrl = event.senderAvatar.url,
timestamp = event.sentTimeMillis,
assetType = event.content.assetType,
)
}
NavTarget.LocationViewer(mode = mode).takeIf { locationService.isServiceAvailable() }
} else {
displayVulkanNotSupportedError = true
null
}
NavTarget.LocationViewer(mode = mode).takeIf { locationService.isServiceAvailable() }
}
else -> null
}
@@ -691,6 +720,11 @@ class MessagesFlowNode(
@Composable
override fun View(modifier: Modifier) {
mentionSpanTheme.updateStyles()
if (displayVulkanNotSupportedError) {
RenderingMapsNotSupportedDialog { displayVulkanNotSupportedError = false }
}
CompositionLocalProvider(
LocalMentionSpanUpdater provides mentionSpanUpdater
) {
@@ -23,6 +23,7 @@ import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.impl.pinned.banner.createPinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.createTimelineController
import io.element.android.features.poll.test.create.FakeCreatePollEntryPoint
import io.element.android.libraries.androidutils.system.DeviceHasVulkanSupport
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
@@ -42,6 +43,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import io.element.android.tests.testutils.testCoroutineDispatchers
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -85,6 +87,7 @@ class DefaultMessagesEntryPointTest {
knockRequestsListEntryPoint = FakeKnockRequestsListEntryPoint(),
dateFormatter = FakeDateFormatter(),
coroutineDispatchers = testCoroutineDispatchers(),
hasVulkanSupport = DeviceHasVulkanSupport(mockk(relaxed = true))
)
}
val callback = object : MessagesEntryPoint.Callback {
+1 -1
View File
@@ -102,7 +102,7 @@ androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constr
androidx_camera_lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
androidx_camera_view = { module = "androidx.camera:camera-view", version.ref = "camera" }
androidx_camera_camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" }
androidx_javascriptengine = "androidx.javascriptengine:javascriptengine:1.0.0"
androidx_javascriptengine = "androidx.javascriptengine:javascriptengine:1.1.0"
androidx_workmanager_runtime = { module = "androidx.work:work-runtime-ktx", version.ref = "work" }
androidx_recyclerview = "androidx.recyclerview:recyclerview:1.4.0"
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.androidutils.system
import android.content.Context
import dev.zacsweers.metro.Inject
import io.element.android.libraries.di.annotations.ApplicationContext
private const val VULKAN_VERSION_1_0 = 0x400003
/**
* Checks if the device supports Vulkan 1.0.
*
* This is needed for the location screens that contain maps using MapLibre UI components.
*
* Needed until https://github.com/maplibre/maplibre-native/issues/3079 is resolved and we can automatically choose between OpenGL and Vulkan renderers,
* or no devices support OpenGL anymore.
*/
@Inject
class DeviceHasVulkanSupport(
@ApplicationContext private val context: Context,
) {
operator fun invoke(): Boolean {
return context.packageManager.hasSystemFeature("android.hardware.vulkan.version", VULKAN_VERSION_1_0)
}
}
@@ -29,8 +29,9 @@ class DefaultUnifiedPushGatewayUrlResolver(
): String {
return when (gatewayResult) {
is UnifiedPushGatewayResolverResult.Error -> {
// Use previous gateway if any, or the provided one
unifiedPushStore.getPushGateway(instance) ?: gatewayResult.gateway
// Use previous gateway if any, or the default one
unifiedPushStore.getPushGateway(instance)
?: defaultPushGatewayHttpUrlProvider.provide()
}
UnifiedPushGatewayResolverResult.ErrorInvalidUrl,
UnifiedPushGatewayResolverResult.NoMatrixGateway -> {
@@ -60,7 +60,7 @@ class DefaultUnifiedPushGatewayUrlResolverTest {
}
@Test
fun `resolve Error returns the url if no current url is available`() {
fun `resolve Error returns the default gateway url if no current url is available`() {
val sut = createDefaultUnifiedPushGatewayUrlResolver(
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { instance ->
@@ -73,7 +73,7 @@ class DefaultUnifiedPushGatewayUrlResolverTest {
gatewayResult = UnifiedPushGatewayResolverResult.Error("aUrl"),
instance = "instance",
)
assertThat(result).isEqualTo("aUrl")
assertThat(result).isEqualTo(FakeDefaultPushGatewayHttpUrlProvider().provide())
}
private fun createDefaultUnifiedPushGatewayUrlResolver(
@@ -546,4 +546,6 @@ Are you sure you want to continue?"</string>
<string name="timeline_decryption_failure_historical_event_user_not_joined">"You don\'t have access to this message"</string>
<string name="timeline_decryption_failure_unable_to_decrypt">"Unable to decrypt message"</string>
<string name="timeline_decryption_failure_withheld_unverified">"This message was blocked either because you did not verify your device or because the sender needs to verify your digital identity."</string>
<string name="vulkan_not_supported_dialog_content_android">"Your device is too old, a device with Android 8 or newer is required."</string>
<string name="vulkan_not_supported_dialog_title_android">"Rendering maps is not supported"</string>
</resources>
+2 -2
View File
@@ -39,13 +39,13 @@ private const val versionYear = 26
* Month of the version on 2 digits. Value must be in [1,12].
* Do not update this value. it is updated by the release script.
*/
private const val versionMonth = 5
private const val versionMonth = 6
/**
* Release number in the month. Value must be in [0,99].
* Do not update this value. it is updated by the release script.
*/
private const val versionReleaseNumber = 2
private const val versionReleaseNumber = 1
object Versions {
/**
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f64455657f0f8a5ac7eec71ee206721a79b25dacf470d105a0e6497e570204a
size 23954
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:43bd5f04f170372b120fa2223c632d6a969c45b6d4fe61b161a7109318f9941f
size 22658