Merge branch 'develop' of https://github.com/vector-im/element-x-android into langleyd/live_waveform
This commit is contained in:
commit
54f276c125
37 changed files with 153 additions and 91 deletions
|
|
@ -38,6 +38,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.FloatingActionButtonDefaults
|
import androidx.compose.material3.FloatingActionButtonDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -59,6 +60,8 @@ import io.element.android.features.messages.impl.timeline.components.TimelineIte
|
||||||
import io.element.android.features.messages.impl.timeline.components.TimelineItemVirtualRow
|
import io.element.android.features.messages.impl.timeline.components.TimelineItemVirtualRow
|
||||||
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
|
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
|
||||||
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineLoadingMoreIndicator
|
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineLoadingMoreIndicator
|
||||||
|
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||||
|
import io.element.android.features.messages.impl.timeline.di.aFakeTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider
|
||||||
|
|
@ -333,15 +336,19 @@ internal fun TimelineViewPreview(
|
||||||
@PreviewParameter(TimelineItemEventContentProvider::class) content: TimelineItemEventContent
|
@PreviewParameter(TimelineItemEventContentProvider::class) content: TimelineItemEventContent
|
||||||
) = ElementPreview {
|
) = ElementPreview {
|
||||||
val timelineItems = aTimelineItemList(content)
|
val timelineItems = aTimelineItemList(content)
|
||||||
TimelineView(
|
CompositionLocalProvider(
|
||||||
state = aTimelineState(timelineItems),
|
LocalTimelineItemPresenterFactories provides aFakeTimelineItemPresenterFactories(),
|
||||||
onMessageClicked = {},
|
) {
|
||||||
onTimestampClicked = {},
|
TimelineView(
|
||||||
onUserDataClicked = {},
|
state = aTimelineState(timelineItems),
|
||||||
onMessageLongClicked = {},
|
onMessageClicked = {},
|
||||||
onReactionClicked = { _, _ -> },
|
onTimestampClicked = {},
|
||||||
onReactionLongClicked = { _, _ -> },
|
onUserDataClicked = {},
|
||||||
onMoreReactionsClicked = {},
|
onMessageLongClicked = {},
|
||||||
onSwipeToReply = {},
|
onReactionClicked = { _, _ -> },
|
||||||
)
|
onReactionLongClicked = { _, _ -> },
|
||||||
|
onMoreReactionsClicked = {},
|
||||||
|
onSwipeToReply = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.di
|
||||||
|
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||||
|
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
|
||||||
|
import io.element.android.features.messages.impl.voicemessages.timeline.aVoiceMessageState
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fake [TimelineItemPresenterFactories] for screenshot tests.
|
||||||
|
*/
|
||||||
|
fun aFakeTimelineItemPresenterFactories() = TimelineItemPresenterFactories(
|
||||||
|
mapOf(
|
||||||
|
Pair(
|
||||||
|
TimelineItemVoiceContent::class.java,
|
||||||
|
TimelineItemPresenterFactory<TimelineItemVoiceContent, VoiceMessageState> { Presenter { aVoiceMessageState() } },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -29,7 +29,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||||
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
|
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
|
||||||
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
||||||
import io.element.android.features.messages.impl.voicemessages.fromMSC3246range
|
|
||||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
|
@ -118,7 +117,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
waveform = messageType.details?.waveform?.fromMSC3246range()?.toImmutableList() ?: persistentListOf(),
|
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
|
||||||
)
|
)
|
||||||
else -> TimelineItemAudioContent(
|
else -> TimelineItemAudioContent(
|
||||||
body = messageType.body,
|
body = messageType.body,
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,12 @@ class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEv
|
||||||
aTimelineItemVideoContent(),
|
aTimelineItemVideoContent(),
|
||||||
aTimelineItemFileContent(),
|
aTimelineItemFileContent(),
|
||||||
aTimelineItemFileContent("A bigger file name which doesn't fit.pdf"),
|
aTimelineItemFileContent("A bigger file name which doesn't fit.pdf"),
|
||||||
|
aTimelineItemAudioContent(),
|
||||||
|
aTimelineItemAudioContent("An even bigger bigger bigger bigger bigger bigger bigger sound name which doesn't fit .mp3"),
|
||||||
|
aTimelineItemVoiceContent(),
|
||||||
aTimelineItemLocationContent(),
|
aTimelineItemLocationContent(),
|
||||||
aTimelineItemLocationContent("Location description"),
|
aTimelineItemLocationContent("Location description"),
|
||||||
|
aTimelineItemPollContent(),
|
||||||
aTimelineItemNoticeContent(),
|
aTimelineItemNoticeContent(),
|
||||||
aTimelineItemRedactedContent(),
|
aTimelineItemRedactedContent(),
|
||||||
aTimelineItemTextContent(),
|
aTimelineItemTextContent(),
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023 New Vector Ltd
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.voicemessages
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
|
|
||||||
* to a [0;1] range float list to be used for waveform rendering.
|
|
||||||
*/
|
|
||||||
internal fun List<Int>.fromMSC3246range(): List<Float> = map { it / 1024f }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes the given [0;1] float list to [0;1024] int list as per unstable MSC3246 spec.
|
|
||||||
*/
|
|
||||||
internal fun List<Float>.toMSC3246range(): List<Int> = map { (it * 1024).toInt() }
|
|
||||||
|
|
@ -28,7 +28,6 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
|
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
|
||||||
import io.element.android.features.messages.impl.voicemessages.toMSC3246range
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
@ -220,7 +219,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
|
||||||
val result = mediaSender.sendVoiceMessage(
|
val result = mediaSender.sendVoiceMessage(
|
||||||
uri = file.toUri(),
|
uri = file.toUri(),
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
waveForm = waveform.toMSC3246range(),
|
waveForm = waveform,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result.isFailure) {
|
if (result.isFailure) {
|
||||||
|
|
|
||||||
|
|
@ -21,35 +21,41 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
open class VoiceMessageStateProvider : PreviewParameterProvider<VoiceMessageState> {
|
open class VoiceMessageStateProvider : PreviewParameterProvider<VoiceMessageState> {
|
||||||
override val values: Sequence<VoiceMessageState>
|
override val values: Sequence<VoiceMessageState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
VoiceMessageState(
|
aVoiceMessageState(
|
||||||
VoiceMessageState.Button.Downloading,
|
VoiceMessageState.Button.Downloading,
|
||||||
progress = 0f,
|
progress = 0f,
|
||||||
time = "0:00",
|
time = "0:00",
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
VoiceMessageState(
|
aVoiceMessageState(
|
||||||
VoiceMessageState.Button.Retry,
|
VoiceMessageState.Button.Retry,
|
||||||
progress = 0.5f,
|
progress = 0.5f,
|
||||||
time = "0:01",
|
time = "0:01",
|
||||||
eventSink = {}
|
|
||||||
),
|
),
|
||||||
VoiceMessageState(
|
aVoiceMessageState(
|
||||||
VoiceMessageState.Button.Play,
|
VoiceMessageState.Button.Play,
|
||||||
progress = 1f,
|
progress = 1f,
|
||||||
time = "1:00",
|
time = "1:00",
|
||||||
eventSink = {}
|
|
||||||
),
|
),
|
||||||
VoiceMessageState(
|
aVoiceMessageState(
|
||||||
VoiceMessageState.Button.Pause,
|
VoiceMessageState.Button.Pause,
|
||||||
progress = 0.2f,
|
progress = 0.2f,
|
||||||
time = "10:00",
|
time = "10:00",
|
||||||
eventSink = {}
|
|
||||||
),
|
),
|
||||||
VoiceMessageState(
|
aVoiceMessageState(
|
||||||
VoiceMessageState.Button.Disabled,
|
VoiceMessageState.Button.Disabled,
|
||||||
progress = 0.2f,
|
progress = 0.2f,
|
||||||
time = "30:00",
|
time = "30:00",
|
||||||
eventSink = {}
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun aVoiceMessageState(
|
||||||
|
button: VoiceMessageState.Button = VoiceMessageState.Button.Play,
|
||||||
|
progress: Float = 0f,
|
||||||
|
time: String = "1:00",
|
||||||
|
) = VoiceMessageState(
|
||||||
|
button = button,
|
||||||
|
progress = progress,
|
||||||
|
time = time,
|
||||||
|
eventSink = {},
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package io.element.android.libraries.architecture
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
interface Presenter<State> {
|
fun interface Presenter<State> {
|
||||||
@Composable
|
@Composable
|
||||||
fun present(): State
|
fun present(): State
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,5 @@ import java.time.Duration
|
||||||
|
|
||||||
data class AudioDetails(
|
data class AudioDetails(
|
||||||
val duration: Duration,
|
val duration: Duration,
|
||||||
val waveform: List<Int>,
|
val waveform: List<Float>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ interface MatrixRoom : Closeable {
|
||||||
suspend fun sendVoiceMessage(
|
suspend fun sendVoiceMessage(
|
||||||
file: File,
|
file: File,
|
||||||
audioInfo: AudioInfo,
|
audioInfo: AudioInfo,
|
||||||
waveform: List<Int>,
|
waveform: List<Float>,
|
||||||
progressCallback: ProgressCallback?
|
progressCallback: ProgressCallback?
|
||||||
): Result<MediaUploadHandler>
|
): Result<MediaUploadHandler>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,26 @@ import org.matrix.rustcomponents.sdk.UnstableAudioDetailsContent as RustAudioDet
|
||||||
|
|
||||||
fun RustAudioDetails.map(): AudioDetails = AudioDetails(
|
fun RustAudioDetails.map(): AudioDetails = AudioDetails(
|
||||||
duration = duration,
|
duration = duration,
|
||||||
waveform = waveform.map { it.toInt() },
|
waveform = waveform.fromMSC3246range(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun AudioDetails.map(): RustAudioDetails = RustAudioDetails(
|
fun AudioDetails.map(): RustAudioDetails = RustAudioDetails(
|
||||||
duration = duration,
|
duration = duration,
|
||||||
waveform = waveform.map { it.toUShort() }
|
waveform = waveform.toMSC3246range()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
|
||||||
|
* to a [0;1] float list to be used for waveform rendering.
|
||||||
|
*
|
||||||
|
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
|
||||||
|
*/
|
||||||
|
internal fun List<UShort>.fromMSC3246range(): List<Float> = map { it.toInt() / 1024f }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the given [0;1] float list as per unstable MSC3246 spec
|
||||||
|
* to a [0;1024] int list to be used for waveform rendering.
|
||||||
|
*
|
||||||
|
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
|
||||||
|
*/
|
||||||
|
internal fun List<Float>.toMSC3246range(): List<UShort> = map { (it * 1024).toInt().toUShort() }
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
||||||
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
|
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
|
||||||
import io.element.android.libraries.matrix.impl.media.map
|
import io.element.android.libraries.matrix.impl.media.map
|
||||||
|
import io.element.android.libraries.matrix.impl.media.toMSC3246range
|
||||||
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
|
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.impl.poll.toInner
|
import io.element.android.libraries.matrix.impl.poll.toInner
|
||||||
import io.element.android.libraries.matrix.impl.room.location.toInner
|
import io.element.android.libraries.matrix.impl.room.location.toInner
|
||||||
|
|
@ -499,13 +500,13 @@ class RustMatrixRoom(
|
||||||
override suspend fun sendVoiceMessage(
|
override suspend fun sendVoiceMessage(
|
||||||
file: File,
|
file: File,
|
||||||
audioInfo: AudioInfo,
|
audioInfo: AudioInfo,
|
||||||
waveform: List<Int>,
|
waveform: List<Float>,
|
||||||
progressCallback: ProgressCallback?,
|
progressCallback: ProgressCallback?,
|
||||||
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
|
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
|
||||||
innerRoom.sendVoiceMessage(
|
innerRoom.sendVoiceMessage(
|
||||||
url = file.path,
|
url = file.path,
|
||||||
audioInfo = audioInfo.map(),
|
audioInfo = audioInfo.map(),
|
||||||
waveform = waveform.map { it.toUShort() },
|
waveform = waveform.toMSC3246range(),
|
||||||
progressWatcher = progressCallback?.toProgressWatcher(),
|
progressWatcher = progressCallback?.toProgressWatcher(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -387,7 +387,7 @@ class FakeMatrixRoom(
|
||||||
override suspend fun sendVoiceMessage(
|
override suspend fun sendVoiceMessage(
|
||||||
file: File,
|
file: File,
|
||||||
audioInfo: AudioInfo,
|
audioInfo: AudioInfo,
|
||||||
waveform: List<Int>,
|
waveform: List<Float>,
|
||||||
progressCallback: ProgressCallback?
|
progressCallback: ProgressCallback?
|
||||||
): Result<MediaUploadHandler> = fakeSendMedia(progressCallback)
|
): Result<MediaUploadHandler> = fakeSendMedia(progressCallback)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class MediaSender @Inject constructor(
|
||||||
suspend fun sendVoiceMessage(
|
suspend fun sendVoiceMessage(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
mimeType: String,
|
mimeType: String,
|
||||||
waveForm: List<Int>,
|
waveForm: List<Float>,
|
||||||
progressCallback: ProgressCallback? = null
|
progressCallback: ProgressCallback? = null
|
||||||
): Result<Unit> {
|
): Result<Unit> {
|
||||||
return preProcessor
|
return preProcessor
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,6 @@ sealed interface MediaUploadInfo {
|
||||||
data class Image(override val file: File, val imageInfo: ImageInfo, val thumbnailFile: File) : MediaUploadInfo
|
data class Image(override val file: File, val imageInfo: ImageInfo, val thumbnailFile: File) : MediaUploadInfo
|
||||||
data class Video(override val file: File, val videoInfo: VideoInfo, val thumbnailFile: File) : MediaUploadInfo
|
data class Video(override val file: File, val videoInfo: VideoInfo, val thumbnailFile: File) : MediaUploadInfo
|
||||||
data class Audio(override val file: File, val audioInfo: AudioInfo) : MediaUploadInfo
|
data class Audio(override val file: File, val audioInfo: AudioInfo) : MediaUploadInfo
|
||||||
data class VoiceMessage(override val file: File, val audioInfo: AudioInfo, val waveform: List<Int>) : MediaUploadInfo
|
data class VoiceMessage(override val file: File, val audioInfo: AudioInfo, val waveform: List<Float>) : MediaUploadInfo
|
||||||
data class AnyFile(override val file: File, val fileInfo: FileInfo) : MediaUploadInfo
|
data class AnyFile(override val file: File, val fileInfo: FileInfo) : MediaUploadInfo
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8f531805df6b1b7541e1c4d443979cb5434d3ea6c688725debc7f8845abebccf
|
oid sha256:ee02c20c1ab0b514e12461c95428ae89985ae86fab9e8538eb2301eeaa910603
|
||||||
size 50375
|
size 347767
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d98aac1065f283b232c96f5a0032865e81bca8816bd235615e131d4bb104a9a5
|
oid sha256:1fc3c477b1ce2c74b32cce19bb94c61f49bcf01f751b136c0ff75d573bc66a35
|
||||||
size 67474
|
size 80231
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2b06025fe43c1cd3f39bcee49222952f3750d0cf7ab2b9281716fcc670ea6934
|
oid sha256:7a121baf52bf6997fa6dcfeee9ce77e6d0f3b374227addae4a28305551a6914f
|
||||||
size 57499
|
size 53893
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4c433475dd2eb980e95ef67bf04c2465db1449bf34b3c0eca7f8c6f43b35113e
|
||||||
|
size 66532
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8f531805df6b1b7541e1c4d443979cb5434d3ea6c688725debc7f8845abebccf
|
||||||
|
size 50375
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d98aac1065f283b232c96f5a0032865e81bca8816bd235615e131d4bb104a9a5
|
||||||
|
size 67474
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2b06025fe43c1cd3f39bcee49222952f3750d0cf7ab2b9281716fcc670ea6934
|
||||||
|
size 57499
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4f14b44ab65ac39c868de2c28644f886923fb4058f29a0d5b12dca7b6d978238
|
oid sha256:eea033f081eb0b62671b3b9c5a626a4d1bfb0b386ff0a8e0011effc40858add3
|
||||||
size 393377
|
size 74258
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ee02c20c1ab0b514e12461c95428ae89985ae86fab9e8538eb2301eeaa910603
|
oid sha256:fc7461871275f11e5ee7abbb6dafaecf7b2c6eac38b3a538ac80b6b722aa0e16
|
||||||
size 347767
|
size 105810
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7a121baf52bf6997fa6dcfeee9ce77e6d0f3b374227addae4a28305551a6914f
|
oid sha256:a4dd52ecde13c76639225b482238924485b8cdbbc56f1b270254689f06cae7e8
|
||||||
size 53893
|
size 58016
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4c433475dd2eb980e95ef67bf04c2465db1449bf34b3c0eca7f8c6f43b35113e
|
oid sha256:4f14b44ab65ac39c868de2c28644f886923fb4058f29a0d5b12dca7b6d978238
|
||||||
size 66532
|
size 393377
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:93482a9053b5efc9331457b907964409e85855e4988358df6a01f1bea7ff1b9f
|
oid sha256:1b1164272dc31a867c7dbc2bae37ac960168f31da63b233531d509583ea0cea2
|
||||||
size 48797
|
size 178173
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:70e0a380cca7ba52a3b695b82c31e68c82c1e03ad6364e6ba73e84f070295528
|
oid sha256:eafc87ff92c2d9b1e50bb97251a1bba132afdd6f6ec6769146c3e322ecd95da6
|
||||||
size 64748
|
size 79207
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:bab28935e51e099811884a707858e5f463bb591a8a2766a276044c0dbbae92a1
|
oid sha256:bd5f7838b304537265aa2616fd3b96db72346e963a785aa683826870e1a0190a
|
||||||
size 55244
|
size 51920
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9157f4531a208f2605bfc93314a669d188904b43b2f29bfd154786a50607d8db
|
||||||
|
size 63928
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:93482a9053b5efc9331457b907964409e85855e4988358df6a01f1bea7ff1b9f
|
||||||
|
size 48797
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:70e0a380cca7ba52a3b695b82c31e68c82c1e03ad6364e6ba73e84f070295528
|
||||||
|
size 64748
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:bab28935e51e099811884a707858e5f463bb591a8a2766a276044c0dbbae92a1
|
||||||
|
size 55244
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5cf70a69d194c8d297e03ee75be7659d06515c9698751181bdfc8f9da7da6055
|
oid sha256:c475d64ef991b3438afd84d8b93d40eedc8060ed862e64523efc0458e6a2a477
|
||||||
size 189520
|
size 71450
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1b1164272dc31a867c7dbc2bae37ac960168f31da63b233531d509583ea0cea2
|
oid sha256:b1eaf05f8e22a9b22f75d6dd28c1c9c004918f6e85cd60ee0ae245e43affccef
|
||||||
size 178173
|
size 99788
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:bd5f7838b304537265aa2616fd3b96db72346e963a785aa683826870e1a0190a
|
oid sha256:be817a2061d43e210c105b193f1baed6060c51f11d97f45d0593cb40ab7acd98
|
||||||
size 51920
|
size 56403
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9157f4531a208f2605bfc93314a669d188904b43b2f29bfd154786a50607d8db
|
oid sha256:5cf70a69d194c8d297e03ee75be7659d06515c9698751181bdfc8f9da7da6055
|
||||||
size 63928
|
size 189520
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue