forked from GitHub-Mirror/riotX-android
Timeline : start to handle media images/gif. Still a lot to do, but it's a first step.
This commit is contained in:
parent
dbb812ad84
commit
1d400180bc
@ -1,9 +1,10 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
@ -27,7 +28,7 @@ object AvatarRenderer {
|
|||||||
if (name.isNullOrEmpty()) {
|
if (name.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val resolvedUrl = avatarUrl?.replace(MXC_PREFIX, MEDIA_URL)
|
val resolvedUrl = ContentUrlResolver.resolve(avatarUrl)
|
||||||
val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal)
|
val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal)
|
||||||
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
|
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
|
|
||||||
|
abstract class AbsMessageItem(private val informationData: MessageInformationData,
|
||||||
|
@LayoutRes layoutRes: Int
|
||||||
|
) : KotlinModel(layoutRes) {
|
||||||
|
|
||||||
|
protected abstract val avatarImageView: ImageView
|
||||||
|
protected abstract val memberNameView: TextView
|
||||||
|
protected abstract val timeView: TextView
|
||||||
|
|
||||||
|
override fun bind() {
|
||||||
|
if (informationData.showInformation) {
|
||||||
|
avatarImageView.visibility = View.VISIBLE
|
||||||
|
memberNameView.visibility = View.VISIBLE
|
||||||
|
timeView.visibility = View.VISIBLE
|
||||||
|
timeView.text = informationData.time
|
||||||
|
memberNameView.text = informationData.memberName
|
||||||
|
AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), avatarImageView)
|
||||||
|
} else {
|
||||||
|
avatarImageView.visibility = View.GONE
|
||||||
|
memberNameView.visibility = View.GONE
|
||||||
|
timeView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.features.media.MessageImageRenderer
|
||||||
|
|
||||||
|
class MessageImageItem(
|
||||||
|
private val messageContent: MessageImageContent,
|
||||||
|
informationData: MessageInformationData
|
||||||
|
) : AbsMessageItem(informationData, R.layout.item_timeline_event_image_message) {
|
||||||
|
|
||||||
|
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||||
|
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
|
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
|
private val imageView by bind<ImageView>(R.id.messageImageView)
|
||||||
|
|
||||||
|
override fun bind() {
|
||||||
|
super.bind()
|
||||||
|
MessageImageRenderer.render(messageContent, imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
|
data class MessageInformationData(
|
||||||
|
val time: CharSequence? = null,
|
||||||
|
val avatarUrl: String?,
|
||||||
|
val memberName: CharSequence? = null,
|
||||||
|
val showInformation: Boolean = true
|
||||||
|
)
|
@ -8,6 +8,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||||||
import im.vector.matrix.android.api.session.events.model.TimelineEvent
|
import im.vector.matrix.android.api.session.events.model.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte
|
|||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
callback: TimelineEventController.Callback?
|
callback: TimelineEventController.Callback?
|
||||||
): MessageTextItem? {
|
): AbsMessageItem? {
|
||||||
|
|
||||||
val roomMember = event.roomMember
|
val roomMember = event.roomMember
|
||||||
val nextRoomMember = nextEvent?.roomMember
|
val nextRoomMember = nextEvent?.roomMember
|
||||||
@ -41,21 +42,24 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte
|
|||||||
val time = timelineDateFormatter.formatMessageHour(date)
|
val time = timelineDateFormatter.formatMessageHour(date)
|
||||||
val avatarUrl = roomMember?.avatarUrl
|
val avatarUrl = roomMember?.avatarUrl
|
||||||
val memberName = roomMember?.displayName ?: event.root.sender
|
val memberName = roomMember?.displayName ?: event.root.sender
|
||||||
|
val informationData = MessageInformationData(time, avatarUrl, memberName, showInformation)
|
||||||
|
|
||||||
return when (messageContent) {
|
return when (messageContent) {
|
||||||
is MessageTextContent -> buildTextMessageItem(messageContent, memberName, avatarUrl, time, showInformation, callback)
|
is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback)
|
||||||
|
is MessageImageContent -> buildImageMessageItem(messageContent, informationData)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildImageMessageItem(messageContent: MessageImageContent, informationData: MessageInformationData): MessageImageItem? {
|
||||||
|
return MessageImageItem(messageContent, informationData)
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
||||||
memberName: String?,
|
informationData: MessageInformationData,
|
||||||
avatarUrl: String?,
|
|
||||||
time: String,
|
|
||||||
showInformation: Boolean,
|
|
||||||
callback: TimelineEventController.Callback?): MessageTextItem? {
|
callback: TimelineEventController.Callback?): MessageTextItem? {
|
||||||
|
|
||||||
val message = messageContent.body?.let {
|
val message = messageContent.body.let {
|
||||||
val spannable = SpannableStringBuilder(it)
|
val spannable = SpannableStringBuilder(it)
|
||||||
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
||||||
override fun onUrlClicked(url: String) {
|
override fun onUrlClicked(url: String) {
|
||||||
@ -67,10 +71,7 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte
|
|||||||
}
|
}
|
||||||
return MessageTextItem(
|
return MessageTextItem(
|
||||||
message = message,
|
message = message,
|
||||||
avatarUrl = avatarUrl,
|
informationData = informationData
|
||||||
showInformation = showInformation,
|
|
||||||
time = time,
|
|
||||||
memberName = memberName
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,40 +1,23 @@
|
|||||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
|
||||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
|
||||||
|
|
||||||
class MessageTextItem(
|
class MessageTextItem(
|
||||||
val message: CharSequence? = null,
|
val message: CharSequence? = null,
|
||||||
val time: CharSequence? = null,
|
informationData: MessageInformationData
|
||||||
val avatarUrl: String?,
|
) : AbsMessageItem(informationData, R.layout.item_timeline_event_text_message) {
|
||||||
val memberName: CharSequence? = null,
|
|
||||||
val showInformation: Boolean = true
|
|
||||||
) : KotlinModel(R.layout.item_timeline_event_message) {
|
|
||||||
|
|
||||||
private val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||||
private val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
private val timeView by bind<TextView>(R.id.messageTimeView)
|
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
private val messageView by bind<TextView>(R.id.messageTextView)
|
private val messageView by bind<TextView>(R.id.messageTextView)
|
||||||
|
|
||||||
override fun bind() {
|
override fun bind() {
|
||||||
|
super.bind()
|
||||||
messageView.text = message
|
messageView.text = message
|
||||||
MatrixLinkify.addLinkMovementMethod(messageView)
|
MatrixLinkify.addLinkMovementMethod(messageView)
|
||||||
if (showInformation) {
|
|
||||||
avatarImageView.visibility = View.VISIBLE
|
|
||||||
memberNameView.visibility = View.VISIBLE
|
|
||||||
timeView.visibility = View.VISIBLE
|
|
||||||
timeView.text = time
|
|
||||||
memberNameView.text = memberName
|
|
||||||
AvatarRenderer.render(avatarUrl, memberName?.toString(), avatarImageView)
|
|
||||||
} else {
|
|
||||||
avatarImageView.visibility = View.GONE
|
|
||||||
memberNameView.visibility = View.GONE
|
|
||||||
timeView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package im.vector.riotredesign.features.media
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.media.ExifInterface
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||||
|
import im.vector.riotredesign.core.glide.GlideApp
|
||||||
|
|
||||||
|
object MessageImageRenderer {
|
||||||
|
|
||||||
|
fun render(messageContent: MessageImageContent, imageView: ImageView) {
|
||||||
|
val (maxImageWidth, maxImageHeight) = computeMaxSize(imageView.context)
|
||||||
|
val imageInfo = messageContent.info
|
||||||
|
val rotationAngle = imageInfo.rotation ?: 0
|
||||||
|
val orientation = imageInfo.orientation ?: ExifInterface.ORIENTATION_NORMAL
|
||||||
|
var width = imageInfo.width
|
||||||
|
var height = imageInfo.height
|
||||||
|
|
||||||
|
var finalHeight = -1
|
||||||
|
var finalWidth = -1
|
||||||
|
|
||||||
|
// if the image size is known
|
||||||
|
// compute the expected height
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
// swap width and height if the image is side oriented
|
||||||
|
if (rotationAngle == 90 || rotationAngle == 270) {
|
||||||
|
val tmp = width
|
||||||
|
width = height
|
||||||
|
height = tmp
|
||||||
|
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) {
|
||||||
|
val tmp = width
|
||||||
|
width = height
|
||||||
|
height = tmp
|
||||||
|
}
|
||||||
|
finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight)
|
||||||
|
finalWidth = finalHeight * width / height
|
||||||
|
}
|
||||||
|
// ensure that some values are properly initialized
|
||||||
|
if (finalHeight < 0) {
|
||||||
|
finalHeight = maxImageHeight
|
||||||
|
}
|
||||||
|
if (finalWidth < 0) {
|
||||||
|
finalWidth = maxImageWidth
|
||||||
|
}
|
||||||
|
imageView.layoutParams.height = finalHeight
|
||||||
|
imageView.layoutParams.width = finalWidth
|
||||||
|
|
||||||
|
val resolvedUrl = ContentUrlResolver.resolve(messageContent.url) ?: return
|
||||||
|
GlideApp
|
||||||
|
.with(imageView)
|
||||||
|
.load(resolvedUrl)
|
||||||
|
.override(finalWidth, finalHeight)
|
||||||
|
.thumbnail(0.3f)
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeMaxSize(context: Context): Pair<Int, Int> {
|
||||||
|
val size = Point(0, 0)
|
||||||
|
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||||
|
wm.defaultDisplay.getSize(size)
|
||||||
|
val screenWidth = size.x
|
||||||
|
val screenHeight = size.y
|
||||||
|
val maxImageWidth: Int
|
||||||
|
val maxImageHeight: Int
|
||||||
|
// landscape / portrait
|
||||||
|
if (screenWidth < screenHeight) {
|
||||||
|
maxImageWidth = Math.round(screenWidth * 0.6f)
|
||||||
|
maxImageHeight = Math.round(screenHeight * 0.4f)
|
||||||
|
} else {
|
||||||
|
maxImageWidth = Math.round(screenWidth * 0.4f)
|
||||||
|
maxImageHeight = Math.round(screenHeight * 0.6f)
|
||||||
|
}
|
||||||
|
return Pair(maxImageWidth, maxImageHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp">
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageAvatarImageView"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageMemberNameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="64dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageTimeView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textColor="@color/brown_grey"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
|
||||||
|
tools:text="@tools:sample/date/hhmm" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageImageView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="64dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,31 @@
|
|||||||
|
package im.vector.matrix.android.api.session.content
|
||||||
|
|
||||||
|
object ContentUrlResolver {
|
||||||
|
|
||||||
|
private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||||
|
private const val MEDIA_URL = "https://matrix.org/_matrix/media/v1/download/"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual URL for accessing the full-size image of a Matrix media content URI.
|
||||||
|
*
|
||||||
|
* @param contentUrl the Matrix media content URI (in the form of "mxc://...").
|
||||||
|
* @return the URL to access the described resource, or null if the url is invalid.
|
||||||
|
*/
|
||||||
|
fun resolve(contentUrl: String?): String? {
|
||||||
|
if (contentUrl.isValidMatrixContentUrl()) {
|
||||||
|
return contentUrl?.replace(MATRIX_CONTENT_URI_SCHEME, MEDIA_URL)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an url is a valid matrix content url.
|
||||||
|
*
|
||||||
|
* @param contentUrl the content URL (in the form of "mxc://...").
|
||||||
|
* @return true if contentUrl is valid.
|
||||||
|
*/
|
||||||
|
private fun String?.isValidMatrixContentUrl(): Boolean {
|
||||||
|
return !this.isNullOrEmpty() && startsWith(MATRIX_CONTENT_URI_SCHEME)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,9 +6,9 @@ import com.squareup.moshi.JsonClass
|
|||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ImageInfo(
|
data class ImageInfo(
|
||||||
@Json(name = "mimetype") val mimeType: String,
|
@Json(name = "mimetype") val mimeType: String,
|
||||||
@Json(name = "w") val w: Int,
|
@Json(name = "w") val width: Int,
|
||||||
@Json(name = "h") val h: Int,
|
@Json(name = "h") val height: Int,
|
||||||
@Json(name = "size") val size: Long,
|
@Json(name = "size") val size: Int,
|
||||||
@Json(name = "rotation") val rotation: Int? = null,
|
@Json(name = "rotation") val rotation: Int? = null,
|
||||||
@Json(name = "orientation") val orientation: Int? = null,
|
@Json(name = "orientation") val orientation: Int? = null,
|
||||||
@Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
|
@Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
|
||||||
|
@ -129,16 +129,8 @@ public final class RuntimeJsonAdapterFactory<T> implements JsonAdapter.Factory {
|
|||||||
Object jsonValue = reader.readJsonValue();
|
Object jsonValue = reader.readJsonValue();
|
||||||
Map<String, Object> jsonObject = (Map<String, Object>) jsonValue;
|
Map<String, Object> jsonObject = (Map<String, Object>) jsonValue;
|
||||||
Object label = jsonObject.get(labelKey);
|
Object label = jsonObject.get(labelKey);
|
||||||
if (label == null) {
|
|
||||||
throw new JsonDataException("Missing label for " + labelKey);
|
|
||||||
}
|
|
||||||
if (!(label instanceof String)) {
|
if (!(label instanceof String)) {
|
||||||
throw new JsonDataException("Label for '"
|
return null;
|
||||||
+ labelKey
|
|
||||||
+ "' must be a string but was "
|
|
||||||
+ label
|
|
||||||
+ ", a "
|
|
||||||
+ label.getClass());
|
|
||||||
}
|
}
|
||||||
JsonAdapter<Object> adapter = labelToAdapter.get(label);
|
JsonAdapter<Object> adapter = labelToAdapter.get(label);
|
||||||
if (adapter == null) {
|
if (adapter == null) {
|
||||||
|
Loading…
Reference in New Issue
Block a user