mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-30 21:40:21 +02:00
Added autoplay feature.
This commit is contained in:
parent
e39d862ef3
commit
ec370dd94b
@ -290,6 +290,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
private var _commentsCount = 0;
|
private var _commentsCount = 0;
|
||||||
private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null;
|
private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null;
|
||||||
private var _slideUpOverlay: SlideUpMenuOverlay? = null;
|
private var _slideUpOverlay: SlideUpMenuOverlay? = null;
|
||||||
|
private var _autoplayVideo: IPlatformVideo? = null
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
val onMinimize = Event0();
|
val onMinimize = Event0();
|
||||||
@ -720,6 +721,17 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
StatePlayer.instance.autoplayChanged.subscribe(this) {
|
||||||
|
if (it) {
|
||||||
|
val url = _url
|
||||||
|
val autoPlayVideo = _autoplayVideo
|
||||||
|
if (url != null && autoPlayVideo == null) {
|
||||||
|
_taskLoadRecommendations.cancel()
|
||||||
|
_taskLoadRecommendations.run(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_layoutResume.setOnClickListener {
|
_layoutResume.setOnClickListener {
|
||||||
handleSeek(_historicalPosition * 1000);
|
handleSeek(_historicalPosition * 1000);
|
||||||
|
|
||||||
@ -1006,6 +1018,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
_container_content_queue.cleanup();
|
_container_content_queue.cleanup();
|
||||||
_container_content_description.cleanup();
|
_container_content_description.cleanup();
|
||||||
_container_content_support.cleanup();
|
_container_content_support.cleanup();
|
||||||
|
StatePlayer.instance.autoplayChanged.remove(this)
|
||||||
StateCasting.instance.onActiveDevicePlayChanged.remove(this);
|
StateCasting.instance.onActiveDevicePlayChanged.remove(this);
|
||||||
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
|
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
|
||||||
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
|
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
|
||||||
@ -1102,6 +1115,8 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
this.video = null;
|
this.video = null;
|
||||||
cleanupPlaybackTracker();
|
cleanupPlaybackTracker();
|
||||||
_searchVideo = video;
|
_searchVideo = video;
|
||||||
|
_autoplayVideo = null
|
||||||
|
Logger.i(TAG, "Autoplay video cleared (setVideoOverview)")
|
||||||
_videoResumePositionMilliseconds = resumeSeconds * 1000;
|
_videoResumePositionMilliseconds = resumeSeconds * 1000;
|
||||||
setLastPositionMilliseconds(_videoResumePositionMilliseconds, false);
|
setLastPositionMilliseconds(_videoResumePositionMilliseconds, false);
|
||||||
_addCommentView.setContext(null, null);
|
_addCommentView.setContext(null, null);
|
||||||
@ -1191,6 +1206,8 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
|
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
|
||||||
_didTriggerDatasourceErrroCount = 0;
|
_didTriggerDatasourceErrroCount = 0;
|
||||||
_didTriggerDatasourceError = false;
|
_didTriggerDatasourceError = false;
|
||||||
|
_autoplayVideo = null
|
||||||
|
Logger.i(TAG, "Autoplay video cleared (setVideoDetails)")
|
||||||
|
|
||||||
if(newVideo && this.video?.url == videoDetail.url)
|
if(newVideo && this.video?.url == videoDetail.url)
|
||||||
return;
|
return;
|
||||||
@ -1511,6 +1528,11 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
_layoutRating.visibility = View.VISIBLE
|
_layoutRating.visibility = View.VISIBLE
|
||||||
_layoutChangeBottomSection.visibility = View.VISIBLE
|
_layoutChangeBottomSection.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StatePlayer.instance.autoplay) {
|
||||||
|
_taskLoadRecommendations.cancel()
|
||||||
|
_taskLoadRecommendations.run(videoDetail.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fun loadLiveChat(video: IPlatformVideoDetails) {
|
fun loadLiveChat(video: IPlatformVideoDetails) {
|
||||||
_liveChat?.stop();
|
_liveChat?.stop();
|
||||||
@ -1779,6 +1801,14 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
fun nextVideo(forceLoop: Boolean = false, withoutRemoval: Boolean = false, bypassVideoLoop: Boolean = false): Boolean {
|
fun nextVideo(forceLoop: Boolean = false, withoutRemoval: Boolean = false, bypassVideoLoop: Boolean = false): Boolean {
|
||||||
Logger.i(TAG, "nextVideo")
|
Logger.i(TAG, "nextVideo")
|
||||||
var next = StatePlayer.instance.nextQueueItem(withoutRemoval || _player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9, bypassVideoLoop);
|
var next = StatePlayer.instance.nextQueueItem(withoutRemoval || _player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9, bypassVideoLoop);
|
||||||
|
val autoplayVideo = _autoplayVideo
|
||||||
|
if (next == null && autoplayVideo != null && StatePlayer.instance.autoplay) {
|
||||||
|
Logger.i(TAG, "Found autoplay video!")
|
||||||
|
StatePlayer.instance.setAutoplayed(autoplayVideo.url)
|
||||||
|
next = autoplayVideo
|
||||||
|
}
|
||||||
|
_autoplayVideo = null
|
||||||
|
Logger.i(TAG, "Autoplay video cleared (nextVideo)")
|
||||||
if(next == null && forceLoop)
|
if(next == null && forceLoop)
|
||||||
next = StatePlayer.instance.restartQueue();
|
next = StatePlayer.instance.restartQueue();
|
||||||
if(next != null) {
|
if(next != null) {
|
||||||
@ -2347,43 +2377,49 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setRecommendations(pager: IPager<IPlatformContent>?, message: String? = null) {
|
private fun setRecommendations(results: List<IPlatformVideo>?, message: String? = null) {
|
||||||
_layoutRecommended.removeAllViews()
|
if (results != null && StatePlayer.instance.autoplay) {
|
||||||
if (pager == null) {
|
_autoplayVideo = results.firstOrNull { !StatePlayer.instance.wasAutoplayed(it.url) }
|
||||||
_layoutRecommended.addView(TextView(context).apply {
|
Logger.i(TAG, "Autoplay video set (url = ${_autoplayVideo?.url})")
|
||||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
|
|
||||||
setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources))
|
|
||||||
}
|
|
||||||
textAlignment = TEXT_ALIGNMENT_CENTER
|
|
||||||
textSize = 14.0f
|
|
||||||
text = message
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val results = pager.getResults().filter { it is IPlatformVideo }
|
if (_tabIndex == 2) {
|
||||||
for (result in results) {
|
_layoutRecommended.removeAllViews()
|
||||||
_layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply {
|
if (results == null) {
|
||||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
_layoutRecommended.addView(TextView(context).apply {
|
||||||
bind(result)
|
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
|
||||||
|
setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources))
|
||||||
hideAddTo()
|
|
||||||
|
|
||||||
onVideoClicked.subscribe { video, _ ->
|
|
||||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
|
||||||
}
|
|
||||||
|
|
||||||
onChannelClicked.subscribe {
|
|
||||||
fragment.navigate<ChannelFragment>(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddToWatchLaterClicked.subscribe(this) {
|
|
||||||
if(it is IPlatformVideo) {
|
|
||||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
|
||||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
|
||||||
}
|
}
|
||||||
}
|
textAlignment = TEXT_ALIGNMENT_CENTER
|
||||||
})
|
textSize = 14.0f
|
||||||
|
text = message
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (result in results) {
|
||||||
|
_layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||||
|
bind(result)
|
||||||
|
|
||||||
|
hideAddTo()
|
||||||
|
|
||||||
|
onVideoClicked.subscribe { video, _ ->
|
||||||
|
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
||||||
|
}
|
||||||
|
|
||||||
|
onChannelClicked.subscribe {
|
||||||
|
fragment.navigate<ChannelFragment>(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddToWatchLaterClicked.subscribe(this) {
|
||||||
|
if(it is IPlatformVideo) {
|
||||||
|
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
||||||
|
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2732,7 +2768,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
} else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope});
|
} else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope});
|
||||||
|
|
||||||
private val _taskLoadRecommendations = TaskHandler<String, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) })
|
private val _taskLoadRecommendations = TaskHandler<String, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) })
|
||||||
.success { setRecommendations(it, "No recommendations found") }
|
.success { setRecommendations(it?.getResults()?.filter { it is IPlatformVideo }?.map { it as IPlatformVideo }, "No recommendations found") }
|
||||||
.exception<Throwable> {
|
.exception<Throwable> {
|
||||||
setRecommendations(null, it.message)
|
setRecommendations(null, it.message)
|
||||||
Logger.w(TAG, "Failed to load recommendations.", it);
|
Logger.w(TAG, "Failed to load recommendations.", it);
|
||||||
|
@ -45,6 +45,33 @@ class StatePlayer {
|
|||||||
onRotationLockChanged.emit(value)
|
onRotationLockChanged.emit(value)
|
||||||
}
|
}
|
||||||
val onRotationLockChanged = Event1<Boolean>()
|
val onRotationLockChanged = Event1<Boolean>()
|
||||||
|
var autoplay: Boolean = false
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
if (field != value)
|
||||||
|
_autoplayed.clear()
|
||||||
|
field = value
|
||||||
|
autoplayChanged.emit(value)
|
||||||
|
}
|
||||||
|
private val _autoplayed = hashSetOf<String>()
|
||||||
|
fun wasAutoplayed(url: String?): Boolean {
|
||||||
|
if (url == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
synchronized(_autoplayed) {
|
||||||
|
return _autoplayed.contains(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setAutoplayed(url: String?) {
|
||||||
|
if (url == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
synchronized(_autoplayed) {
|
||||||
|
_autoplayed.add(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val autoplayChanged = Event1<Boolean>()
|
||||||
var loopVideo : Boolean = false;
|
var loopVideo : Boolean = false;
|
||||||
|
|
||||||
val isPlaying: Boolean get() = _exoplayer?.player?.playWhenReady ?: false;
|
val isPlaying: Boolean get() = _exoplayer?.player?.playWhenReady ?: false;
|
||||||
@ -138,6 +165,12 @@ class StatePlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isUrlInQueue(url : String) : Boolean {
|
||||||
|
synchronized(_queue) {
|
||||||
|
return _queue.any { it.url == url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getQueueType() : String {
|
fun getQueueType() : String {
|
||||||
return _queueType;
|
return _queueType;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import android.widget.ImageButton
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.setMargins
|
import androidx.core.view.setMargins
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.PlaybackParameters
|
import androidx.media3.common.PlaybackParameters
|
||||||
@ -74,6 +75,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||||||
|
|
||||||
//Custom buttons
|
//Custom buttons
|
||||||
private val _control_fullscreen: ImageButton;
|
private val _control_fullscreen: ImageButton;
|
||||||
|
private val _control_autoplay: ImageButton;
|
||||||
private val _control_videosettings: ImageButton;
|
private val _control_videosettings: ImageButton;
|
||||||
private val _control_minimize: ImageButton;
|
private val _control_minimize: ImageButton;
|
||||||
private val _control_rotate_lock: ImageButton;
|
private val _control_rotate_lock: ImageButton;
|
||||||
@ -92,6 +94,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||||||
private val _control_videosettings_fullscreen: ImageButton;
|
private val _control_videosettings_fullscreen: ImageButton;
|
||||||
private val _control_minimize_fullscreen: ImageButton;
|
private val _control_minimize_fullscreen: ImageButton;
|
||||||
private val _control_rotate_lock_fullscreen: ImageButton;
|
private val _control_rotate_lock_fullscreen: ImageButton;
|
||||||
|
private val _control_autoplay_fullscreen: ImageButton;
|
||||||
private val _control_loop_fullscreen: ImageButton;
|
private val _control_loop_fullscreen: ImageButton;
|
||||||
private val _control_cast_fullscreen: ImageButton;
|
private val _control_cast_fullscreen: ImageButton;
|
||||||
private val _control_play_fullscreen: ImageButton;
|
private val _control_play_fullscreen: ImageButton;
|
||||||
@ -149,6 +152,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||||||
|
|
||||||
videoControls = findViewById(R.id.video_player_controller);
|
videoControls = findViewById(R.id.video_player_controller);
|
||||||
_control_fullscreen = videoControls.findViewById(R.id.button_fullscreen);
|
_control_fullscreen = videoControls.findViewById(R.id.button_fullscreen);
|
||||||
|
_control_autoplay = videoControls.findViewById(R.id.button_autoplay);
|
||||||
_control_videosettings = videoControls.findViewById(R.id.button_settings);
|
_control_videosettings = videoControls.findViewById(R.id.button_settings);
|
||||||
_control_minimize = videoControls.findViewById(R.id.button_minimize);
|
_control_minimize = videoControls.findViewById(R.id.button_minimize);
|
||||||
_control_rotate_lock = videoControls.findViewById(R.id.button_rotate_lock);
|
_control_rotate_lock = videoControls.findViewById(R.id.button_rotate_lock);
|
||||||
@ -164,6 +168,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||||||
_control_duration = videoControls.findViewById(R.id.text_duration);
|
_control_duration = videoControls.findViewById(R.id.text_duration);
|
||||||
|
|
||||||
_videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen);
|
_videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen);
|
||||||
|
_control_autoplay_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_autoplay);
|
||||||
_control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_fullscreen);
|
_control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_fullscreen);
|
||||||
_control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_minimize);
|
_control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_minimize);
|
||||||
_control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_settings);
|
_control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_settings);
|
||||||
@ -386,6 +391,18 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||||||
UIDialogs.showCastingDialog(context);
|
UIDialogs.showCastingDialog(context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_control_autoplay.setOnClickListener {
|
||||||
|
StatePlayer.instance.autoplay = !StatePlayer.instance.autoplay;
|
||||||
|
updateAutoplayButton()
|
||||||
|
}
|
||||||
|
updateAutoplayButton()
|
||||||
|
|
||||||
|
_control_autoplay_fullscreen.setOnClickListener {
|
||||||
|
StatePlayer.instance.autoplay = !StatePlayer.instance.autoplay;
|
||||||
|
updateAutoplayButton()
|
||||||
|
}
|
||||||
|
updateAutoplayButton()
|
||||||
|
|
||||||
val progressUpdateListener = { position: Long, bufferedPosition: Long ->
|
val progressUpdateListener = { position: Long, bufferedPosition: Long ->
|
||||||
val currentTime = position.formatDuration()
|
val currentTime = position.formatDuration()
|
||||||
val currentDuration = duration.formatDuration()
|
val currentDuration = duration.formatDuration()
|
||||||
@ -433,6 +450,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateAutoplayButton() {
|
||||||
|
_control_autoplay.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
|
||||||
|
_control_autoplay_fullscreen.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
|
||||||
|
}
|
||||||
|
|
||||||
private fun setSystemBrightness(brightness: Float) {
|
private fun setSystemBrightness(brightness: Float) {
|
||||||
Log.i(TAG, "setSystemBrightness $brightness")
|
Log.i(TAG, "setSystemBrightness $brightness")
|
||||||
if (android.provider.Settings.System.canWrite(context)) {
|
if (android.provider.Settings.System.canWrite(context)) {
|
||||||
|
@ -133,6 +133,20 @@
|
|||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:layout_marginBottom="18dp" />
|
android:layout_marginBottom="18dp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/button_autoplay"
|
||||||
|
android:layout_width="55dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:clickable="true"
|
||||||
|
app:srcCompat="@drawable/autoplay_24px"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/button_fullscreen"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/button_fullscreen"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/button_fullscreen"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingEnd="5dp"
|
||||||
|
android:scaleType="fitCenter"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_position"
|
android:id="@+id/text_position"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -147,6 +147,20 @@
|
|||||||
app:layout_constraintLeft_toRightOf="@id/layout_play_pause"
|
app:layout_constraintLeft_toRightOf="@id/layout_play_pause"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/button_autoplay"
|
||||||
|
android:layout_width="55dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:clickable="true"
|
||||||
|
app:srcCompat="@drawable/autoplay_24px"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/button_fullscreen"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/button_fullscreen"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/button_fullscreen"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingEnd="5dp"
|
||||||
|
android:scaleType="fitCenter"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/button_fullscreen"
|
android:id="@+id/button_fullscreen"
|
||||||
android:layout_width="55dp"
|
android:layout_width="55dp"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user