forked from upstream-mirrors/quik
Merge pull request #232 from gavine99/fix_pr_225_render_issue
Fix pr 225 render issue
This commit is contained in:
@@ -9,6 +9,7 @@ object QkMediaPlayer : MediaPlayer() {
|
||||
private var onCompletionListener: OnCompletionListener? = null
|
||||
|
||||
enum class PlayingState {
|
||||
None,
|
||||
Stopped,
|
||||
Playing,
|
||||
Paused
|
||||
|
||||
@@ -53,20 +53,29 @@ fun EditText.hideKeyboard() {
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
|
||||
fun ImageView.setTint(color: Int) {
|
||||
imageTintList = ColorStateList.valueOf(color)
|
||||
fun ImageView.setTint(color: Int?) {
|
||||
imageTintList =
|
||||
if (color == null) null
|
||||
else ColorStateList.valueOf(color)
|
||||
}
|
||||
|
||||
fun ProgressBar.setTint(color: Int) {
|
||||
indeterminateTintList = ColorStateList.valueOf(color)
|
||||
progressTintList = ColorStateList.valueOf(color)
|
||||
fun ProgressBar.setTint(color: Int?) {
|
||||
indeterminateTintList =
|
||||
if (color == null) null
|
||||
else ColorStateList.valueOf(color)
|
||||
progressTintList =
|
||||
if (color == null) null
|
||||
else ColorStateList.valueOf(color)
|
||||
|
||||
}
|
||||
|
||||
fun View.setBackgroundTint(color: Int) {
|
||||
fun View.setBackgroundTint(color: Int?) {
|
||||
|
||||
// API 21 doesn't support this
|
||||
|
||||
backgroundTintList = ColorStateList.valueOf(color)
|
||||
backgroundTintList =
|
||||
if (color == null) null
|
||||
else ColorStateList.valueOf(color)
|
||||
}
|
||||
|
||||
fun View.setPadding(left: Int? = null, top: Int? = null, right: Int? = null, bottom: Int? = null) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.jakewharton.rxbinding2.view.clicks
|
||||
import com.moez.QKSMS.common.QkMediaPlayer
|
||||
import dev.octoshrimpy.quik.R
|
||||
import dev.octoshrimpy.quik.common.base.QkRealmAdapter
|
||||
import dev.octoshrimpy.quik.common.base.QkViewHolder
|
||||
@@ -55,6 +56,7 @@ import dev.octoshrimpy.quik.model.Message
|
||||
import dev.octoshrimpy.quik.model.Recipient
|
||||
import dev.octoshrimpy.quik.util.PhoneNumberUtils
|
||||
import dev.octoshrimpy.quik.util.Preferences
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import io.reactivex.subjects.Subject
|
||||
import io.realm.RealmResults
|
||||
@@ -83,6 +85,13 @@ class MessagesAdapter @Inject constructor(
|
||||
private val textViewStyler: TextViewStyler
|
||||
) : QkRealmAdapter<Message>() {
|
||||
|
||||
class AudioState(
|
||||
var partId: Long = -1,
|
||||
var state: QkMediaPlayer.PlayingState = QkMediaPlayer.PlayingState.Stopped,
|
||||
var seekBarUpdater: Disposable? = null,
|
||||
var viewHolder: QkViewHolder? = null
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val VIEW_TYPE_MESSAGE_IN = 0
|
||||
private const val VIEW_TYPE_MESSAGE_OUT = 1
|
||||
@@ -127,11 +136,12 @@ class MessagesAdapter @Inject constructor(
|
||||
|
||||
private val contactCache = ContactCache()
|
||||
private val expanded = HashMap<Long, Boolean>()
|
||||
private val partsViewPool = RecyclerView.RecycledViewPool()
|
||||
private val subs = subscriptionManager.activeSubscriptionInfoList
|
||||
|
||||
var theme: Colors.Theme = colors.theme()
|
||||
|
||||
private val audioState = AudioState()
|
||||
|
||||
/**
|
||||
* If the viewType is negative, then the viewHolder has an attachment. We'll consider
|
||||
* this a unique viewType even though it uses the same view, so that regular messages
|
||||
@@ -156,7 +166,6 @@ class MessagesAdapter @Inject constructor(
|
||||
val partsAdapter = partsAdapterProvider.get()
|
||||
partsAdapter.clicks.subscribe(partClicks)
|
||||
view.attachments.adapter = partsAdapter
|
||||
view.attachments.setRecycledViewPool(partsViewPool)
|
||||
view.body.forwardTouches(view)
|
||||
|
||||
return QkViewHolder(view).apply {
|
||||
@@ -283,7 +292,7 @@ class MessagesAdapter @Inject constructor(
|
||||
// Bind the attachments
|
||||
val partsAdapter = holder.attachments.adapter as PartsAdapter
|
||||
partsAdapter.theme = theme
|
||||
partsAdapter.setData(message, previous, next, holder)
|
||||
partsAdapter.setData(message, previous, next, holder, audioState)
|
||||
}
|
||||
|
||||
private fun bindStatus(holder: QkViewHolder, message: Message, next: Message?) {
|
||||
|
||||
@@ -33,16 +33,14 @@ import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint
|
||||
import dev.octoshrimpy.quik.common.util.extensions.setTint
|
||||
import dev.octoshrimpy.quik.common.widget.BubbleImageView
|
||||
import dev.octoshrimpy.quik.extensions.isAudio
|
||||
import dev.octoshrimpy.quik.feature.compose.MessagesAdapter
|
||||
import dev.octoshrimpy.quik.model.Message
|
||||
import dev.octoshrimpy.quik.model.MmsPart
|
||||
import dev.octoshrimpy.quik.util.GlideApp
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.mms_audio_preview_list_item.*
|
||||
import kotlinx.android.synthetic.main.mms_audio_preview_list_item.view.playPause
|
||||
import kotlinx.android.synthetic.main.mms_audio_preview_list_item.view.seekBar
|
||||
import kotlinx.android.synthetic.main.mms_image_preview_list_item.thumbnail
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
@@ -53,91 +51,132 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte
|
||||
|
||||
override val partLayout = R.layout.mms_audio_preview_list_item
|
||||
override var theme = colors.theme()
|
||||
private var thisPlayingState = QkMediaPlayer.PlayingState.Stopped
|
||||
|
||||
override fun canBindPart(part: MmsPart) = part.isAudio()
|
||||
|
||||
var audioState = MessagesAdapter.AudioState(-1, QkMediaPlayer.PlayingState.Stopped)
|
||||
|
||||
private fun startSeekBarUpdateTimer() {
|
||||
audioState.apply {
|
||||
seekBarUpdater?.dispose()
|
||||
seekBarUpdater = Observable.interval(500, TimeUnit.MILLISECONDS)
|
||||
.subscribeOn(Schedulers.single())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext {
|
||||
viewHolder?.seekBar?.progress = QkMediaPlayer.currentPosition
|
||||
}
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
private fun uiToPlaying(viewHolder: QkViewHolder) {
|
||||
viewHolder.seekBar.max = QkMediaPlayer.duration
|
||||
viewHolder.seekBar.isEnabled = true
|
||||
viewHolder.seekBar.progress = QkMediaPlayer.currentPosition
|
||||
viewHolder.playPause.setImageResource(R.drawable.exo_icon_pause)
|
||||
viewHolder.playPause.tag = QkMediaPlayer.PlayingState.Playing
|
||||
viewHolder.metadataTitle.isSelected = true // start marquee
|
||||
}
|
||||
|
||||
private fun uiToPaused(viewHolder: QkViewHolder) {
|
||||
viewHolder.playPause.setImageResource(R.drawable.exo_icon_play)
|
||||
viewHolder.playPause.tag = QkMediaPlayer.PlayingState.Paused
|
||||
}
|
||||
|
||||
private fun uiToStopped(viewHolder: QkViewHolder) {
|
||||
viewHolder.seekBar.progress = 0
|
||||
viewHolder.seekBar.max = 0
|
||||
viewHolder.seekBar.isEnabled = false
|
||||
viewHolder.playPause.setImageResource(R.drawable.exo_icon_play)
|
||||
viewHolder.playPause.tag = QkMediaPlayer.PlayingState.Stopped
|
||||
viewHolder.metadataTitle.isSelected = false // stop marquee
|
||||
}
|
||||
|
||||
override fun bindPart(
|
||||
holder: QkViewHolder,
|
||||
part: MmsPart,
|
||||
message: Message,
|
||||
canGroupWithPrevious: Boolean,
|
||||
canGroupWithNext: Boolean
|
||||
canGroupWithNext: Boolean,
|
||||
) {
|
||||
// play/pause button click handling
|
||||
holder.playPause.setOnClickListener { view ->
|
||||
var seekBarUpdater: Disposable? = null
|
||||
holder.playPause.setOnClickListener {
|
||||
when (holder.playPause.tag) {
|
||||
QkMediaPlayer.PlayingState.Playing -> {
|
||||
if (audioState.partId == part.id) {
|
||||
QkMediaPlayer.pause()
|
||||
uiToPaused(holder)
|
||||
audioState.state = QkMediaPlayer.PlayingState.Paused
|
||||
|
||||
when (thisPlayingState) {
|
||||
QkMediaPlayer.PlayingState.Stopped -> {
|
||||
QkMediaPlayer.apply {
|
||||
reset() // make sure reset before trying to (re-)use
|
||||
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // music, maybe?? could be voice. don't want to use CONTENT_TYPE_UNKNOWN though
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
|
||||
setDataSource(context, part.getUri())
|
||||
|
||||
setOnPreparedListener {
|
||||
// start timer to update seek bar
|
||||
seekBarUpdater = Observable.interval(500, TimeUnit.MILLISECONDS)
|
||||
.subscribeOn(Schedulers.single())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map { System.nanoTime() }
|
||||
.doOnNext { holder.seekBar.progress = QkMediaPlayer.currentPosition }
|
||||
.subscribe()
|
||||
|
||||
// set seek bar max value
|
||||
holder.seekBar.max = QkMediaPlayer.duration
|
||||
|
||||
start()
|
||||
|
||||
thisPlayingState = QkMediaPlayer.PlayingState.Playing
|
||||
view.playPause.setImageResource(R.drawable.exo_icon_pause)
|
||||
holder.containerView.seekBar.isEnabled = true
|
||||
holder.metadataTitle.isSelected = true
|
||||
}
|
||||
|
||||
setOnCompletionListener { // also called on error because we don't have an onerrorlistener
|
||||
seekBarUpdater?.dispose() // stop timer
|
||||
|
||||
thisPlayingState = QkMediaPlayer.PlayingState.Stopped
|
||||
holder.containerView.seekBar.seekBar.isEnabled = false
|
||||
holder.containerView.seekBar.seekBar.progress = 0
|
||||
view.playPause.setImageResource(R.drawable.exo_icon_play)
|
||||
holder.metadataTitle.isSelected = false
|
||||
}
|
||||
|
||||
// prepare to play
|
||||
prepareAsync()
|
||||
// stop progress bar update timer
|
||||
audioState.seekBarUpdater?.dispose()
|
||||
}
|
||||
}
|
||||
QkMediaPlayer.PlayingState.Playing -> {
|
||||
QkMediaPlayer.pause()
|
||||
view.playPause.setImageResource(R.drawable.exo_icon_play)
|
||||
thisPlayingState = QkMediaPlayer.PlayingState.Paused
|
||||
}
|
||||
QkMediaPlayer.PlayingState.Paused -> {
|
||||
QkMediaPlayer.start()
|
||||
view.playPause.setImageResource(R.drawable.exo_icon_pause)
|
||||
thisPlayingState = QkMediaPlayer.PlayingState.Playing
|
||||
if (audioState.partId == part.id) {
|
||||
QkMediaPlayer.start()
|
||||
uiToPlaying(holder)
|
||||
audioState.state = QkMediaPlayer.PlayingState.Playing
|
||||
|
||||
// start progress bar update timer
|
||||
startSeekBarUpdateTimer()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
QkMediaPlayer.reset() // make sure reset before trying to (re-)use
|
||||
|
||||
QkMediaPlayer.setOnPreparedListener {
|
||||
// start media playing
|
||||
QkMediaPlayer.start()
|
||||
|
||||
uiToPlaying(holder)
|
||||
|
||||
// set current view holder and part as active
|
||||
audioState.apply {
|
||||
audioState.state = QkMediaPlayer.PlayingState.Playing
|
||||
partId = part.id
|
||||
viewHolder = holder
|
||||
}
|
||||
|
||||
// start progress bar update timer
|
||||
startSeekBarUpdateTimer()
|
||||
}
|
||||
|
||||
QkMediaPlayer.setOnCompletionListener { // also called on error because we don't have an onerrorlistener
|
||||
audioState.apply {
|
||||
// if this part is currently active, set it to stopped and inactive
|
||||
if ((partId == part.id) && (viewHolder != null))
|
||||
uiToStopped(viewHolder!!)
|
||||
|
||||
state = QkMediaPlayer.PlayingState.Stopped
|
||||
partId = -1
|
||||
viewHolder = null
|
||||
}
|
||||
}
|
||||
|
||||
// start the media player play sequence
|
||||
QkMediaPlayer.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // music, maybe?? could be voice. don't want to use CONTENT_TYPE_UNKNOWN though
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
|
||||
QkMediaPlayer.setDataSource(context, part.getUri())
|
||||
|
||||
QkMediaPlayer.prepareAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.thumbnail.bubbleStyle = when {
|
||||
!canGroupWithPrevious && canGroupWithNext ->
|
||||
if (message.isMe()) BubbleImageView.Style.OUT_FIRST else BubbleImageView.Style.IN_FIRST
|
||||
canGroupWithPrevious && canGroupWithNext ->
|
||||
if (message.isMe()) BubbleImageView.Style.OUT_MIDDLE else BubbleImageView.Style.IN_MIDDLE
|
||||
canGroupWithPrevious && !canGroupWithNext ->
|
||||
if (message.isMe()) BubbleImageView.Style.OUT_LAST else BubbleImageView.Style.IN_LAST
|
||||
else -> BubbleImageView.Style.ONLY
|
||||
}
|
||||
holder.containerView.setOnClickListener { clicks.onNext(part.id) }
|
||||
|
||||
// if this item is the active active audio item update the active view holder
|
||||
if (audioState.partId == part.id)
|
||||
audioState.viewHolder = holder
|
||||
// else, this is not the active item so ensure the stored view holder is not this one
|
||||
else if (audioState.viewHolder == holder)
|
||||
audioState.viewHolder = null
|
||||
|
||||
// tint colours
|
||||
val secondaryColor =
|
||||
@@ -151,9 +190,6 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte
|
||||
else
|
||||
holder.containerView.context.resolveThemeColor(android.R.attr.textColorPrimary)
|
||||
|
||||
val metaDataRetriever = MediaMetadataRetriever()
|
||||
metaDataRetriever.setDataSource(context, part.getUri())
|
||||
|
||||
// sound wave
|
||||
holder.soundWave.setTint(primaryColor)
|
||||
|
||||
@@ -161,11 +197,11 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte
|
||||
holder.seekBar.apply {
|
||||
setTint(secondaryColor)
|
||||
thumbTintList = ColorStateList.valueOf(secondaryColor)
|
||||
max = 0
|
||||
isEnabled = false
|
||||
|
||||
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(p0: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
if ((thisPlayingState != QkMediaPlayer.PlayingState.Stopped) && fromUser)
|
||||
// if seek was initiated by the user and this part is currently playing
|
||||
if (fromUser)
|
||||
QkMediaPlayer.seekTo(progress)
|
||||
}
|
||||
override fun onStartTrackingTouch(p0: SeekBar?) { /* nothing */ }
|
||||
@@ -175,35 +211,65 @@ class AudioBinder @Inject constructor(colors: Colors, private val context: Conte
|
||||
|
||||
// playPause button
|
||||
holder.playPause. apply {
|
||||
if ((audioState.partId == part.id) &&
|
||||
(audioState.state == QkMediaPlayer.PlayingState.Playing))
|
||||
uiToPlaying(holder)
|
||||
else if ((audioState.partId == part.id) &&
|
||||
(audioState.state == QkMediaPlayer.PlayingState.Paused))
|
||||
uiToPaused(holder)
|
||||
else
|
||||
uiToStopped(holder)
|
||||
|
||||
setTint(secondaryColor)
|
||||
setBackgroundTint(primaryColor)
|
||||
}
|
||||
|
||||
holder.metadataTitle.apply {
|
||||
// metadata title
|
||||
text = metaDataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) ?: ""
|
||||
MediaMetadataRetriever().use {
|
||||
it.setDataSource(context, part.getUri())
|
||||
|
||||
if (text == "")
|
||||
visibility = View.GONE
|
||||
else {
|
||||
setTextColor(primaryColor)
|
||||
setBackgroundTint(0xccffffff.toInt() and secondaryColor) // hex value is alpha
|
||||
// metadata title
|
||||
holder.metadataTitle.apply {
|
||||
text = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
|
||||
|
||||
if (text.isEmpty())
|
||||
visibility = View.GONE
|
||||
else {
|
||||
visibility = View.VISIBLE
|
||||
setTextColor(primaryColor)
|
||||
setBackgroundTint(0xccffffff.toInt() and secondaryColor) // hex value is alpha
|
||||
}
|
||||
}
|
||||
|
||||
// bubble / embedded image
|
||||
holder.thumbnail.apply {
|
||||
bubbleStyle = when {
|
||||
!canGroupWithPrevious && canGroupWithNext ->
|
||||
if (message.isMe()) BubbleImageView.Style.OUT_FIRST else BubbleImageView.Style.IN_FIRST
|
||||
canGroupWithPrevious && canGroupWithNext ->
|
||||
if (message.isMe()) BubbleImageView.Style.OUT_MIDDLE else BubbleImageView.Style.IN_MIDDLE
|
||||
canGroupWithPrevious && !canGroupWithNext ->
|
||||
if (message.isMe()) BubbleImageView.Style.OUT_LAST else BubbleImageView.Style.IN_LAST
|
||||
else -> BubbleImageView.Style.ONLY
|
||||
}
|
||||
|
||||
val embeddedPicture = it.embeddedPicture
|
||||
if (embeddedPicture == null) {
|
||||
holder.frame.layoutParams.height = (holder.frame.layoutParams.width / 2)
|
||||
setTint(secondaryColor)
|
||||
setImageResource(R.drawable.rectangle)
|
||||
} else {
|
||||
holder.frame.layoutParams.height = holder.frame.layoutParams.width
|
||||
setTint(null)
|
||||
GlideApp.with(context)
|
||||
.asBitmap()
|
||||
.load(embeddedPicture)
|
||||
.override(
|
||||
holder.frame.layoutParams.width,
|
||||
holder.frame.layoutParams.height
|
||||
)
|
||||
.into(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// embedded image
|
||||
val embeddedPicture = metaDataRetriever.embeddedPicture
|
||||
if (embeddedPicture == null) {
|
||||
holder.thumbnail.setTint(secondaryColor)
|
||||
holder.frame.layoutParams.height /= 2
|
||||
holder.thumbnail.requestLayout()
|
||||
} else {
|
||||
GlideApp.with(context)
|
||||
.asBitmap()
|
||||
.load(embeddedPicture)
|
||||
.into(holder.thumbnail)
|
||||
}
|
||||
|
||||
metaDataRetriever.close()
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import dev.octoshrimpy.quik.common.util.extensions.forwardTouches
|
||||
import dev.octoshrimpy.quik.extensions.isSmil
|
||||
import dev.octoshrimpy.quik.extensions.isText
|
||||
import dev.octoshrimpy.quik.feature.compose.BubbleUtils.canGroup
|
||||
import dev.octoshrimpy.quik.feature.compose.MessagesAdapter
|
||||
import dev.octoshrimpy.quik.model.Message
|
||||
import dev.octoshrimpy.quik.model.MmsPart
|
||||
import io.reactivex.Observable
|
||||
@@ -39,7 +40,7 @@ class PartsAdapter @Inject constructor(
|
||||
fileBinder: FileBinder,
|
||||
imageBinder: ImageBinder,
|
||||
audioBinder: AudioBinder,
|
||||
vCardBinder: VCardBinder
|
||||
vCardBinder: VCardBinder,
|
||||
) : QkAdapter<MmsPart>() {
|
||||
|
||||
private val partBinders = listOf(audioBinder, imageBinder, vCardBinder, fileBinder)
|
||||
@@ -57,14 +58,22 @@ class PartsAdapter @Inject constructor(
|
||||
private var next: Message? = null
|
||||
private var holder: QkViewHolder? = null
|
||||
private var bodyVisible: Boolean = true
|
||||
private var audioState: MessagesAdapter.AudioState? = null
|
||||
|
||||
fun setData(message: Message, previous: Message?, next: Message?, holder: QkViewHolder) {
|
||||
fun setData(
|
||||
message: Message,
|
||||
previous: Message?,
|
||||
next: Message?,
|
||||
holder: QkViewHolder,
|
||||
audioState: MessagesAdapter.AudioState?
|
||||
) {
|
||||
this.message = message
|
||||
this.previous = previous
|
||||
this.next = next
|
||||
this.holder = holder
|
||||
this.bodyVisible = holder.body.visibility == View.VISIBLE
|
||||
this.data = message.parts.filter { !it.isSmil() && !it.isText() }
|
||||
this.audioState = audioState
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder {
|
||||
@@ -80,9 +89,13 @@ class PartsAdapter @Inject constructor(
|
||||
val canGroupWithPrevious = canGroup(message, previous) || position > 0
|
||||
val canGroupWithNext = canGroup(message, next) || position < itemCount - 1 || bodyVisible
|
||||
|
||||
partBinders
|
||||
.firstOrNull { it.canBindPart(part) }
|
||||
?.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext)
|
||||
val binder = partBinders.firstOrNull { it.canBindPart(part) }
|
||||
|
||||
// if audioState is set and binder is audio type, set audioState into it
|
||||
if ((audioState != null) && (binder is AudioBinder))
|
||||
binder.audioState = audioState!!
|
||||
|
||||
binder?.bindPart(holder, part, message, canGroupWithPrevious, canGroupWithNext)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 52 KiB |
@@ -22,7 +22,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/frame"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="280dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:clipChildren="false">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user