From 3136b47838031e9829b48d1a9e6c60b6604e4a96 Mon Sep 17 00:00:00 2001 From: klaus <klaus@vividnext.co.kr> Date: Tue, 12 Nov 2024 00:32:21 +0900 Subject: [PATCH] =?UTF-8?q?=ED=95=98=ED=8A=B8=20=EB=9E=AD=ED=82=B9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/live/LiveApi.kt | 7 + .../vividnext/sodalive/live/LiveRepository.kt | 5 + .../sodalive/live/room/LiveRoomActivity.kt | 8 + .../sodalive/live/room/LiveRoomViewModel.kt | 29 ++++ .../room/like/GetLiveRoomHeartListResponse.kt | 18 +++ .../room/like/LiveRoomHeartRankingAdapter.kt | 144 ++++++++++++++++++ .../room/like/LiveRoomHeartRankingDialog.kt | 85 +++++++++++ .../layout/dialog_live_room_heart_ranking.xml | 120 +++++++++++++++ .../layout/item_live_room_heart_ranking.xml | 107 +++++++++++++ 9 files changed, 523 insertions(+) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/live/room/like/GetLiveRoomHeartListResponse.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingDialog.kt create mode 100644 app/src/main/res/layout/dialog_live_room_heart_ranking.xml create mode 100644 app/src/main/res/layout/item_live_room_heart_ranking.xml diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt index ef93d9b..4f1cfd4 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt @@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest +import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListResponse import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartTotalResponse import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse @@ -226,4 +227,10 @@ interface LiveApi { @Path("id") id: Long, @Header("Authorization") authHeader: String ): Single<ApiResponse<GetLiveRoomHeartTotalResponse>> + + @GET("/live/room/{id}/heart-list") + fun heartStatus( + @Path("id") id: Long, + @Header("Authorization") authHeader: String + ): Single<ApiResponse<GetLiveRoomHeartListResponse>> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt index 5feab7c..2acea0a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt @@ -256,4 +256,9 @@ class LiveRepository( roomId, authHeader = token ) + + fun heartStatus(roomId: Long, token: String) = api.heartStatus( + roomId, + authHeader = token + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt index 62091cc..6004f3f 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt @@ -87,6 +87,7 @@ import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewMo import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRankingDialog import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse +import kr.co.vividnext.sodalive.live.room.like.LiveRoomHeartRankingDialog import kr.co.vividnext.sodalive.live.room.profile.LiveRoomProfileDialog import kr.co.vividnext.sodalive.live.room.profile.LiveRoomProfileListAdapter import kr.co.vividnext.sodalive.live.room.profile.LiveRoomUserProfileDialog @@ -538,6 +539,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB roomId = roomId ).show() } + binding.llHeart.setOnClickListener { + LiveRoomHeartRankingDialog( + activity = this, + layoutInflater = layoutInflater, + getHeartRanking = { viewModel.heartStatus(roomId, it) } + ).show() + } setupChatAdapter() setupSpeakerListAdapter() diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt index c4e50e8..7312568 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt @@ -21,6 +21,7 @@ import kr.co.vividnext.sodalive.live.LiveRepository import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationStatusResponse import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse +import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListResponse import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse import kr.co.vividnext.sodalive.live.room.update.EditLiveRoomInfoRequest @@ -716,6 +717,34 @@ class LiveRoomViewModel( ) } + fun heartStatus(roomId: Long, onSuccess: (GetLiveRoomHeartListResponse) -> Unit) { + _isLoading.value = true + compositeDisposable.add( + repository.heartStatus(roomId, token = "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success && it.data != null) { + onSuccess(it.data) + } else { + _toastLiveData.postValue( + "하트 현황을 가져오지 못했습니다\n다시 시도해 주세요." + ) + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue( + "하트 현황을 가져오지 못했습니다\n다시 시도해 주세요." + ) + } + ) + ) + } + fun getUserRank(userId: Long): Int { // 방장 -> -2 // 스탭 -> -3 diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/GetLiveRoomHeartListResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/GetLiveRoomHeartListResponse.kt new file mode 100644 index 0000000..884a168 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/GetLiveRoomHeartListResponse.kt @@ -0,0 +1,18 @@ +package kr.co.vividnext.sodalive.live.room.like + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class GetLiveRoomHeartListResponse( + @SerializedName("heartList") val heartList: List<GetLiveRoomHeartListItem>, + @SerializedName("totalCount") val totalCount: Int, + @SerializedName("totalHeart") val totalHeart: Int +) + +@Keep +data class GetLiveRoomHeartListItem( + @SerializedName("profileImage") val profileImage: String, + @SerializedName("nickname") val nickname: String, + @SerializedName("heart") val heart: Int +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingAdapter.kt new file mode 100644 index 0000000..3788b2b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingAdapter.kt @@ -0,0 +1,144 @@ +package kr.co.vividnext.sodalive.live.room.like + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.ItemLiveRoomHeartRankingBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.extensions.moneyFormat + +class LiveRoomHeartRankingAdapter : + RecyclerView.Adapter<LiveRoomHeartRankingAdapter.ViewHolder>() { + val items = mutableListOf<GetLiveRoomHeartListItem>() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + parent.context, + ItemLiveRoomHeartRankingBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items[position], position) + } + + override fun getItemCount() = items.count() + + inner class ViewHolder( + private val context: Context, + private val binding: ItemLiveRoomHeartRankingBinding + ) : RecyclerView.ViewHolder(binding.root) { + @SuppressLint("SetTextI18n") + fun bind(item: GetLiveRoomHeartListItem, position: Int) { + binding.tvRank.text = "${position + 1}" + binding.tvNickname.text = item.nickname + binding.tvHeart.text = item.heart.moneyFormat() + binding.ivProfile.load(item.profileImage) { + crossfade(true) + placeholder(R.drawable.ic_place_holder) + transformations(CircleCropTransformation()) + } + + val lp = binding.rlHeartRanking.layoutParams as FrameLayout.LayoutParams + + when (position) { + 0 -> { + binding.ivBg.setImageResource(R.drawable.bg_circle_ffdc00_ffb600) + binding.ivBg.visibility = View.VISIBLE + + binding.ivCrown.setImageResource(R.drawable.ic_crown_1) + binding.ivCrown.visibility = View.VISIBLE + binding.root.setBackgroundResource( + if (items.size == 1) { + R.drawable.bg_round_corner_4_7_13181b + } else { + R.drawable.bg_top_round_corner_4_7_13181b + } + ) + + lp.setMargins( + 0, + 20.dpToPx().toInt(), + 0, + if (items.size == 1) { + 20.dpToPx().toInt() + } else { + 13.3f.dpToPx().toInt() + } + ) + binding.rlHeartRanking.layoutParams = lp + } + + 1 -> { + binding.ivBg.setImageResource(R.drawable.bg_circle_ffffff_9f9f9f) + binding.ivBg.visibility = View.VISIBLE + + binding.ivCrown.setImageResource(R.drawable.ic_crown_2) + binding.ivCrown.visibility = View.VISIBLE + + if (items.size == 2) { + binding.root.setBackgroundResource( + R.drawable.bg_bottom_round_corner_4_7_13181b + ) + } else { + binding.root.setBackgroundColor( + ContextCompat.getColor(context, R.color.color_13181b) + ) + } + + lp.setMargins( + 0, + 0, + 0, + if (items.size == 2) { + 20.dpToPx().toInt() + } else { + 13.3f.dpToPx().toInt() + } + ) + binding.rlHeartRanking.layoutParams = lp + } + + 2 -> { + binding.ivBg.setImageResource(R.drawable.bg_circle_e6a77a_c67e4a) + binding.ivBg.visibility = View.VISIBLE + + binding.ivCrown.setImageResource(R.drawable.ic_crown_3) + binding.ivCrown.visibility = View.VISIBLE + binding.root.setBackgroundResource( + R.drawable.bg_bottom_round_corner_4_7_13181b + ) + + lp.setMargins( + 0, + 0, + 0, + 20.dpToPx().toInt() + ) + binding.rlHeartRanking.layoutParams = lp + } + + else -> { + binding.ivBg.setImageResource(0) + binding.ivBg.visibility = View.GONE + binding.ivCrown.visibility = View.GONE + binding.root.setBackgroundResource(0) + binding.root.background = null + + lp.setMargins(0, 0, 0, 0) + binding.rlHeartRanking.layoutParams = lp + } + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingDialog.kt new file mode 100644 index 0000000..0fd3a76 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/like/LiveRoomHeartRankingDialog.kt @@ -0,0 +1,85 @@ +package kr.co.vividnext.sodalive.live.room.like + +import android.annotation.SuppressLint +import android.graphics.Rect +import android.view.LayoutInflater +import android.view.View +import androidx.fragment.app.FragmentActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetDialog +import kr.co.vividnext.sodalive.databinding.DialogLiveRoomHeartRankingBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.extensions.moneyFormat + +@SuppressLint("NotifyDataSetChanged") +class LiveRoomHeartRankingDialog( + private val activity: FragmentActivity, + layoutInflater: LayoutInflater, + getHeartRanking: ((GetLiveRoomHeartListResponse) -> Unit) -> Unit +) { + private val bottomSheetDialog: BottomSheetDialog = BottomSheetDialog(activity) + private val dialogView = DialogLiveRoomHeartRankingBinding.inflate(layoutInflater) + private val adapter = LiveRoomHeartRankingAdapter() + + init { + bottomSheetDialog.setContentView(dialogView.root) + bottomSheetDialog.setCancelable(false) + + dialogView.ivClose.setOnClickListener { bottomSheetDialog.dismiss() } + setupRecyclerView() + + getHeartRanking { + adapter.items.clear() + adapter.items.addAll(it.heartList) + adapter.notifyDataSetChanged() + dialogView.tvTotalHeart.text = it.totalHeart.moneyFormat() + dialogView.tvTotalCount.text = it.totalCount.moneyFormat() + } + } + + private fun setupRecyclerView() { + dialogView.rvHeartRanking.layoutManager = LinearLayoutManager( + activity, + LinearLayoutManager.VERTICAL, + false + ) + + dialogView.rvHeartRanking.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + outRect.left = 13.3f.dpToPx().toInt() + outRect.right = 13.3f.dpToPx().toInt() + + when (parent.getChildAdapterPosition(view)) { + 0, 1, 2 -> { + outRect.top = 0 + outRect.bottom = 0 + } + + 3 -> { + outRect.top = 20.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + + else -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + } + } + }) + + dialogView.rvHeartRanking.adapter = adapter + } + + fun show() { + bottomSheetDialog.show() + } +} diff --git a/app/src/main/res/layout/dialog_live_room_heart_ranking.xml b/app/src/main/res/layout/dialog_live_room_heart_ranking.xml new file mode 100644 index 0000000..ea8288c --- /dev/null +++ b/app/src/main/res/layout/dialog_live_room_heart_ranking.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/bg_top_round_corner_16_7_222222" + android:orientation="vertical"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="20dp" + android:layout_marginTop="20dp" + android:fontFamily="@font/gmarket_sans_bold" + android:text="현재 라이브 하트랭킹" + android:textColor="@color/color_eeeeee" + android:textSize="16.7sp" /> + + <ImageView + android:id="@+id/iv_close" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:contentDescription="@null" + android:paddingHorizontal="20dp" + android:paddingVertical="18.7dp" + android:src="@drawable/ic_close_white" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/rl_total_can" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="7.7dp" + android:background="@drawable/bg_round_corner_8_13181b" + android:paddingHorizontal="18.7dp" + android:paddingVertical="10.7dp"> + + <TextView + android:id="@+id/tv_total_heart_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_bold" + android:text="합계" + android:textColor="@color/color_d2d2d2" + android:textSize="13.3sp" /> + + <TextView + android:id="@+id/tv_total_heart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="2.7dp" + android:layout_toStartOf="@+id/tv_total_can_unit" + android:layout_toEndOf="@+id/tv_total_heart_title" + android:fontFamily="@font/gmarket_sans_medium" + android:gravity="end" + android:textColor="@color/color_80d8ff" + android:textSize="14sp" + tools:text="1,999,999" /> + + <TextView + android:id="@+id/tv_total_can_unit" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:fontFamily="@font/gmarket_sans_medium" + android:text="하트" + android:textColor="@color/color_bbbbbb" + android:textSize="10.7sp" + tools:ignore="SmallSp" /> + </RelativeLayout> + + <LinearLayout + android:id="@+id/ll_total_count" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="13.3dp" + android:layout_marginBottom="8dp" + android:gravity="center_vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_medium" + android:text="전체" + android:textColor="@color/color_eeeeee" + android:textSize="14.7sp" /> + + <TextView + android:id="@+id/tv_total_count" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="6.7dp" + android:fontFamily="@font/gmarket_sans_medium" + android:textColor="@color/color_80d8ff" + android:textSize="12sp" + tools:text="56" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_medium" + android:text=" 명" + android:textColor="@color/color_777777" + android:textSize="12sp" /> + </LinearLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/rv_heart_ranking" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="none" /> +</LinearLayout> diff --git a/app/src/main/res/layout/item_live_room_heart_ranking.xml b/app/src/main/res/layout/item_live_room_heart_ranking.xml new file mode 100644 index 0000000..1ec0d49 --- /dev/null +++ b/app/src/main/res/layout/item_live_room_heart_ranking.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:background="@color/black"> + + <RelativeLayout + android:id="@+id/rl_heart_ranking" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:background="@color/black" + tools:ignore="UselessParent"> + + <TextView + android:id="@+id/tv_rank" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="13.3dp" + android:ellipsize="end" + android:fontFamily="@font/gmarket_sans_bold" + android:maxLength="10" + android:textColor="@color/color_eeeeee" + android:textSize="13.3sp" + tools:text="1" /> + + <RelativeLayout + android:id="@+id/rl_profile" + android:layout_width="65dp" + android:layout_height="65dp" + android:layout_centerVertical="true" + android:layout_marginStart="13.3dp" + android:layout_toEndOf="@+id/tv_rank"> + + <ImageView + android:id="@+id/iv_bg" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@null" /> + + <ImageView + android:id="@+id/iv_profile" + android:layout_width="60dp" + android:layout_height="60dp" + android:layout_centerInParent="true" + android:contentDescription="@null" + tools:src="@mipmap/ic_launcher" /> + + <ImageView + android:id="@+id/iv_crown" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:contentDescription="@null" /> + </RelativeLayout> + + <TextView + android:id="@+id/tv_nickname" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="13.3dp" + android:layout_toEndOf="@+id/rl_profile" + android:ellipsize="end" + android:fontFamily="@font/gmarket_sans_medium" + android:lines="1" + android:maxLength="12" + android:textColor="@color/color_eeeeee" + android:textSize="13.3sp" + tools:text="Remix" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:layout_marginHorizontal="13.3dp" + android:layout_toEndOf="@+id/tv_nickname" + android:gravity="end|center_vertical" + android:orientation="horizontal"> + + <TextView + android:id="@+id/tv_heart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="4dp" + android:fontFamily="@font/gmarket_sans_medium" + android:gravity="end" + android:textColor="@color/color_3bb9f1" + android:textSize="13.3sp" + tools:text="1000" /> + + <TextView + android:id="@+id/tv_donation_can_unit" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_medium" + android:gravity="end" + android:text="하트" + android:textColor="@color/color_eeeeee" + android:textSize="10.7sp" + tools:ignore="SmallSp" /> + </LinearLayout> + </RelativeLayout> +</FrameLayout>