mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-04 15:19:48 +00:00
Proper implementation for replies/likes/dislikes in the comment tab.
This commit is contained in:
parent
c806ff2e33
commit
3387c727d1
5 changed files with 113 additions and 69 deletions
|
@ -33,6 +33,7 @@ import com.futo.polycentric.core.PublicKey
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
import java.util.IdentityHashMap
|
||||||
|
|
||||||
class CommentsFragment : MainFragment() {
|
class CommentsFragment : MainFragment() {
|
||||||
override val isMainView : Boolean = true
|
override val isMainView : Boolean = true
|
||||||
|
@ -84,6 +85,7 @@ class CommentsFragment : MainFragment() {
|
||||||
private var _loading = false;
|
private var _loading = false;
|
||||||
private val _repliesOverlay: RepliesOverlay;
|
private val _repliesOverlay: RepliesOverlay;
|
||||||
private var _repliesAnimator: ViewPropertyAnimator? = null;
|
private var _repliesAnimator: ViewPropertyAnimator? = null;
|
||||||
|
private val _cache: IdentityHashMap<IPlatformComment, StatePolycentric.LikesDislikesReplies> = IdentityHashMap()
|
||||||
|
|
||||||
private val _taskLoadComments = if(!isInEditMode) TaskHandler<PublicKey, List<IPlatformComment>>(
|
private val _taskLoadComments = if(!isInEditMode) TaskHandler<PublicKey, List<IPlatformComment>>(
|
||||||
StateApp.instance.scopeGetter, { StatePolycentric.instance.getSystemComments(context, it) })
|
StateApp.instance.scopeGetter, { StatePolycentric.instance.getSystemComments(context, it) })
|
||||||
|
@ -111,7 +113,7 @@ class CommentsFragment : MainFragment() {
|
||||||
childCountGetter = { _comments.size },
|
childCountGetter = { _comments.size },
|
||||||
childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_comments[position]); },
|
childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_comments[position]); },
|
||||||
childViewHolderFactory = { viewGroup, _ ->
|
childViewHolderFactory = { viewGroup, _ ->
|
||||||
val holder = CommentWithReferenceViewHolder(viewGroup);
|
val holder = CommentWithReferenceViewHolder(viewGroup, _cache);
|
||||||
holder.onDelete.subscribe(::onDelete);
|
holder.onDelete.subscribe(::onDelete);
|
||||||
holder.onRepliesClick.subscribe(::onRepliesClick);
|
holder.onRepliesClick.subscribe(::onRepliesClick);
|
||||||
return@InsertedViewAdapterWithLoader holder;
|
return@InsertedViewAdapterWithLoader holder;
|
||||||
|
@ -202,15 +204,21 @@ class CommentsFragment : MainFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c is PolycentricPlatformComment) {
|
if (c is PolycentricPlatformComment) {
|
||||||
var parentComment: PolycentricPlatformComment = c;
|
|
||||||
_repliesOverlay.load(false, metadata, c.contextUrl, c.reference, c,
|
_repliesOverlay.load(false, metadata, c.contextUrl, c.reference, c,
|
||||||
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
|
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
|
||||||
{
|
{ newComment ->
|
||||||
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
|
synchronized(_cache) {
|
||||||
val index = _comments.indexOf(c);
|
_cache.remove(c)
|
||||||
_comments[index] = newComment;
|
}
|
||||||
_adapterComments.notifyItemChanged(_adapterComments.childToParentPosition(index));
|
|
||||||
parentComment = newComment;
|
val newCommentIndex = if (_spinnerSortBy.selectedItemPosition == 0) {
|
||||||
|
_comments.indexOfFirst { it.date!! < newComment.date!! }.takeIf { it != -1 } ?: _comments.size
|
||||||
|
} else {
|
||||||
|
_comments.indexOfFirst { it.date!! > newComment.date!! }.takeIf { it != -1 } ?: _comments.size
|
||||||
|
}
|
||||||
|
|
||||||
|
_comments.add(newCommentIndex, newComment)
|
||||||
|
_adapterComments.notifyItemInserted(_adapterComments.childToParentPosition(newCommentIndex))
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_repliesOverlay.load(true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
|
_repliesOverlay.load(true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
|
||||||
|
|
|
@ -11,17 +11,12 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||||
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
|
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
|
||||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder
|
|
||||||
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
|
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
|
||||||
import com.futo.platformplayer.api.media.structures.DedupContentPager
|
import com.futo.platformplayer.api.media.structures.DedupContentPager
|
||||||
import com.futo.platformplayer.api.media.structures.EmptyPager
|
import com.futo.platformplayer.api.media.structures.EmptyPager
|
||||||
import com.futo.platformplayer.api.media.structures.IAsyncPager
|
import com.futo.platformplayer.api.media.structures.IAsyncPager
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.api.media.structures.MultiChronoContentPager
|
import com.futo.platformplayer.api.media.structures.MultiChronoContentPager
|
||||||
import com.futo.platformplayer.api.media.structures.PlaceholderPager
|
|
||||||
import com.futo.platformplayer.api.media.structures.RefreshChronoContentPager
|
|
||||||
import com.futo.platformplayer.api.media.structures.RefreshDedupContentPager
|
|
||||||
import com.futo.platformplayer.api.media.structures.RefreshDistributionContentPager
|
|
||||||
import com.futo.platformplayer.awaitFirstDeferred
|
import com.futo.platformplayer.awaitFirstDeferred
|
||||||
import com.futo.platformplayer.dp
|
import com.futo.platformplayer.dp
|
||||||
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
|
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
|
||||||
|
@ -247,11 +242,16 @@ class StatePolycentric {
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLiveComment(contextUrl: String, reference: Protocol.Reference): PolycentricPlatformComment {
|
data class LikesDislikesReplies(
|
||||||
|
var likes: Long,
|
||||||
|
var dislikes: Long,
|
||||||
|
var replyCount: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun getLikesDislikesReplies(reference: Protocol.Reference): LikesDislikesReplies {
|
||||||
val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null,
|
val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null,
|
||||||
Protocol.QueryReferencesRequestEvents.newBuilder()
|
null,
|
||||||
.setFromType(ContentType.POST.value)
|
listOf(
|
||||||
.addAllCountLwwElementReferences(arrayListOf(
|
|
||||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
|
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
|
||||||
.setFromType(ContentType.OPINION.value)
|
.setFromType(ContentType.OPINION.value)
|
||||||
.setValue(ByteString.copyFrom(Opinion.like.data))
|
.setValue(ByteString.copyFrom(Opinion.like.data))
|
||||||
|
@ -260,15 +260,18 @@ class StatePolycentric {
|
||||||
.setFromType(ContentType.OPINION.value)
|
.setFromType(ContentType.OPINION.value)
|
||||||
.setValue(ByteString.copyFrom(Opinion.dislike.data))
|
.setValue(ByteString.copyFrom(Opinion.dislike.data))
|
||||||
.build()
|
.build()
|
||||||
))
|
),
|
||||||
.addCountReferences(
|
listOf(
|
||||||
Protocol.QueryReferencesRequestCountReferences.newBuilder()
|
Protocol.QueryReferencesRequestCountReferences.newBuilder()
|
||||||
.setFromType(ContentType.POST.value)
|
.setFromType(ContentType.POST.value)
|
||||||
.build())
|
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return mapQueryReferences(contextUrl, response).first()
|
val likes = response.countsList[0];
|
||||||
|
val dislikes = response.countsList[1];
|
||||||
|
val replyCount = response.countsList[2];
|
||||||
|
return LikesDislikesReplies(likes, dislikes, replyCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager<IPlatformComment> {
|
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager<IPlatformComment> {
|
||||||
|
@ -347,7 +350,6 @@ class StatePolycentric {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val post = Protocol.Post.parseFrom(ev.content);
|
val post = Protocol.Post.parseFrom(ev.content);
|
||||||
val id = ev.system.toProto().key.toByteArray().toBase64();
|
|
||||||
val likes = it.countsList[0];
|
val likes = it.countsList[0];
|
||||||
val dislikes = it.countsList[1];
|
val dislikes = it.countsList[1];
|
||||||
val replies = it.countsList[2];
|
val replies = it.countsList[2];
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.futo.platformplayer.views.adapters
|
package com.futo.platformplayer.views.adapters
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -22,6 +23,8 @@ import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
|
||||||
import com.futo.polycentric.core.Opinion
|
import com.futo.polycentric.core.Opinion
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import userpackage.Protocol
|
||||||
|
import java.util.IdentityHashMap
|
||||||
|
|
||||||
class CommentWithReferenceViewHolder : ViewHolder {
|
class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
private val _creatorThumbnail: CreatorThumbnail;
|
private val _creatorThumbnail: CreatorThumbnail;
|
||||||
|
@ -32,14 +35,18 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
private val _pillRatingLikesDislikes: PillRatingLikesDislikes;
|
private val _pillRatingLikesDislikes: PillRatingLikesDislikes;
|
||||||
private val _layoutComment: ConstraintLayout;
|
private val _layoutComment: ConstraintLayout;
|
||||||
private val _buttonDelete: FrameLayout;
|
private val _buttonDelete: FrameLayout;
|
||||||
|
private val _cache: IdentityHashMap<IPlatformComment, StatePolycentric.LikesDislikesReplies>;
|
||||||
|
private var _likesDislikesReplies: StatePolycentric.LikesDislikesReplies? = null;
|
||||||
|
|
||||||
private val _taskGetLiveComment = TaskHandler<PolycentricPlatformComment, PolycentricPlatformComment>(StateApp.instance.scopeGetter, { StatePolycentric.instance.getLiveComment(it.contextUrl, it.reference) })
|
private val _taskGetLiveComment = TaskHandler(StateApp.instance.scopeGetter, ::getLikesDislikesReplies)
|
||||||
.success {
|
.success {
|
||||||
bind(it, true);
|
_likesDislikesReplies = it
|
||||||
|
updateLikesDislikesReplies()
|
||||||
}
|
}
|
||||||
.exception<Throwable> {
|
.exception<Throwable> {
|
||||||
Logger.w(TAG, "Failed to get live comment.", it);
|
Logger.w(TAG, "Failed to get live comment.", it);
|
||||||
//TODO: Show error
|
//TODO: Show error
|
||||||
|
hideLikesDislikesReplies()
|
||||||
}
|
}
|
||||||
|
|
||||||
var onRepliesClick = Event1<IPlatformComment>();
|
var onRepliesClick = Event1<IPlatformComment>();
|
||||||
|
@ -47,7 +54,7 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
var comment: IPlatformComment? = null
|
var comment: IPlatformComment? = null
|
||||||
private set;
|
private set;
|
||||||
|
|
||||||
constructor(viewGroup: ViewGroup) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_comment_with_reference, viewGroup, false)) {
|
constructor(viewGroup: ViewGroup, cache: IdentityHashMap<IPlatformComment, StatePolycentric.LikesDislikesReplies>) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_comment_with_reference, viewGroup, false)) {
|
||||||
_layoutComment = itemView.findViewById(R.id.layout_comment);
|
_layoutComment = itemView.findViewById(R.id.layout_comment);
|
||||||
_creatorThumbnail = itemView.findViewById(R.id.image_thumbnail);
|
_creatorThumbnail = itemView.findViewById(R.id.image_thumbnail);
|
||||||
_textAuthor = itemView.findViewById(R.id.text_author);
|
_textAuthor = itemView.findViewById(R.id.text_author);
|
||||||
|
@ -56,6 +63,7 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
_buttonReplies = itemView.findViewById(R.id.button_replies);
|
_buttonReplies = itemView.findViewById(R.id.button_replies);
|
||||||
_pillRatingLikesDislikes = itemView.findViewById(R.id.rating);
|
_pillRatingLikesDislikes = itemView.findViewById(R.id.rating);
|
||||||
_buttonDelete = itemView.findViewById(R.id.button_delete)
|
_buttonDelete = itemView.findViewById(R.id.button_delete)
|
||||||
|
_cache = cache
|
||||||
|
|
||||||
_pillRatingLikesDislikes.onLikeDislikeUpdated.subscribe { args ->
|
_pillRatingLikesDislikes.onLikeDislikeUpdated.subscribe { args ->
|
||||||
val c = comment
|
val c = comment
|
||||||
|
@ -99,7 +107,18 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
|
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(comment: IPlatformComment, live: Boolean = false) {
|
private suspend fun getLikesDislikesReplies(c: PolycentricPlatformComment): StatePolycentric.LikesDislikesReplies {
|
||||||
|
val likesDislikesReplies = StatePolycentric.instance.getLikesDislikesReplies(c.reference)
|
||||||
|
synchronized(_cache) {
|
||||||
|
_cache[c] = likesDislikesReplies
|
||||||
|
}
|
||||||
|
return likesDislikesReplies
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(comment: IPlatformComment) {
|
||||||
|
Log.i(TAG, "bind")
|
||||||
|
|
||||||
|
_likesDislikesReplies = null;
|
||||||
_taskGetLiveComment.cancel()
|
_taskGetLiveComment.cancel()
|
||||||
|
|
||||||
_creatorThumbnail.setThumbnail(comment.author.thumbnail, false);
|
_creatorThumbnail.setThumbnail(comment.author.thumbnail, false);
|
||||||
|
@ -123,42 +142,53 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
|
|
||||||
_textBody.text = comment.message.fixHtmlLinks();
|
_textBody.text = comment.message.fixHtmlLinks();
|
||||||
|
|
||||||
if (comment is PolycentricPlatformComment) {
|
this.comment = comment;
|
||||||
if (live) {
|
updateLikesDislikesReplies();
|
||||||
val hasLiked = StatePolycentric.instance.hasLiked(comment.reference);
|
|
||||||
val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference);
|
|
||||||
_pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked);
|
|
||||||
} else {
|
|
||||||
_pillRatingLikesDislikes.setLoading(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (live) {
|
private fun updateLikesDislikesReplies() {
|
||||||
|
Log.i(TAG, "updateLikesDislikesReplies")
|
||||||
|
|
||||||
|
val c = comment ?: return
|
||||||
|
if (c is PolycentricPlatformComment) {
|
||||||
|
if (_likesDislikesReplies == null) {
|
||||||
|
Log.i(TAG, "updateLikesDislikesReplies retrieving from cache")
|
||||||
|
|
||||||
|
synchronized(_cache) {
|
||||||
|
_likesDislikesReplies = _cache[c]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val likesDislikesReplies = _likesDislikesReplies
|
||||||
|
if (likesDislikesReplies != null) {
|
||||||
|
Log.i(TAG, "updateLikesDislikesReplies set")
|
||||||
|
|
||||||
|
val hasLiked = StatePolycentric.instance.hasLiked(c.reference);
|
||||||
|
val hasDisliked = StatePolycentric.instance.hasDisliked(c.reference);
|
||||||
|
_pillRatingLikesDislikes.setRating(RatingLikeDislikes(likesDislikesReplies.likes, likesDislikesReplies.dislikes), hasLiked, hasDisliked);
|
||||||
|
|
||||||
_buttonReplies.setLoading(false)
|
_buttonReplies.setLoading(false)
|
||||||
|
|
||||||
val replies = comment.replyCount ?: 0;
|
val replies = likesDislikesReplies.replyCount ?: 0;
|
||||||
if (replies > 0) {
|
|
||||||
_buttonReplies.visibility = View.VISIBLE;
|
_buttonReplies.visibility = View.VISIBLE;
|
||||||
_buttonReplies.text.text = "$replies " + itemView.context.getString(R.string.replies);
|
_buttonReplies.text.text = "$replies " + itemView.context.getString(R.string.replies);
|
||||||
} else {
|
} else {
|
||||||
_buttonReplies.visibility = View.GONE;
|
Log.i(TAG, "updateLikesDislikesReplies to load")
|
||||||
|
|
||||||
|
_pillRatingLikesDislikes.setLoading(true)
|
||||||
|
_buttonReplies.setLoading(true)
|
||||||
|
_taskGetLiveComment.run(c)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_buttonReplies.setLoading(true)
|
hideLikesDislikesReplies()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false) {
|
private fun hideLikesDislikesReplies() {
|
||||||
//Restore from cached
|
|
||||||
} else {
|
|
||||||
//_taskGetLiveComment.run(comment)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_pillRatingLikesDislikes.visibility = View.GONE
|
_pillRatingLikesDislikes.visibility = View.GONE
|
||||||
_buttonReplies.visibility = View.GONE
|
_buttonReplies.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
this.comment = comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "CommentWithReferenceViewHolder";
|
private const val TAG = "CommentWithReferenceViewHolder";
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,8 @@ class PillRatingLikesDislikes : LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRating(rating: RatingLikeDislikes, hasLiked: Boolean = false, hasDisliked: Boolean = false) {
|
fun setRating(rating: RatingLikeDislikes, hasLiked: Boolean = false, hasDisliked: Boolean = false) {
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
_textLikes.text = rating.likes.toHumanNumber();
|
_textLikes.text = rating.likes.toHumanNumber();
|
||||||
_textDislikes.text = rating.dislikes.toHumanNumber();
|
_textDislikes.text = rating.dislikes.toHumanNumber();
|
||||||
_textLikes.visibility = View.VISIBLE;
|
_textLikes.visibility = View.VISIBLE;
|
||||||
|
@ -172,6 +174,8 @@ class PillRatingLikesDislikes : LinearLayout {
|
||||||
updateColors();
|
updateColors();
|
||||||
}
|
}
|
||||||
fun setRating(rating: RatingLikes, hasLiked: Boolean = false) {
|
fun setRating(rating: RatingLikes, hasLiked: Boolean = false) {
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
_textLikes.text = rating.likes.toHumanNumber();
|
_textLikes.text = rating.likes.toHumanNumber();
|
||||||
_textLikes.visibility = View.VISIBLE;
|
_textLikes.visibility = View.VISIBLE;
|
||||||
_textDislikes.visibility = View.GONE;
|
_textDislikes.visibility = View.GONE;
|
||||||
|
|
|
@ -61,6 +61,12 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<com.futo.platformplayer.views.overlays.RepliesOverlay
|
||||||
|
android:id="@+id/replies_overlay"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/layout_not_logged_in"
|
<LinearLayout android:id="@+id/layout_not_logged_in"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -81,12 +87,6 @@
|
||||||
android:paddingEnd="28dp"
|
android:paddingEnd="28dp"
|
||||||
android:layout_marginBottom="20dp"/>
|
android:layout_marginBottom="20dp"/>
|
||||||
|
|
||||||
<com.futo.platformplayer.views.overlays.RepliesOverlay
|
|
||||||
android:id="@+id/replies_overlay"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/button_login"
|
android:id="@+id/button_login"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue