메시지 페이지 추가
|
@ -139,4 +139,7 @@ dependencies {
|
|||
// agora
|
||||
implementation "io.agora.rtc:voice-sdk:4.1.0-1"
|
||||
implementation 'io.agora.rtm:rtm-sdk:1.5.3'
|
||||
|
||||
// sound visualizer
|
||||
implementation "com.gauravk.audiovisualizer:audiovisualizer:0.9.2"
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
<activity android:name=".explorer.profile.fantalk.UserProfileFantalkAllViewActivity" />
|
||||
<activity android:name=".explorer.profile.CreatorNoticeWriteActivity" />
|
||||
<activity android:name=".explorer.profile.follow.UserFollowerListActivity" />
|
||||
<activity android:name=".message.text.TextMessageWriteActivity" />
|
||||
<activity android:name=".message.text.TextMessageDetailActivity" />
|
||||
<activity android:name=".message.SelectMessageRecipientActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
|
|
|
@ -17,10 +17,14 @@ object Constants {
|
|||
const val EXTRA_TERMS = "extra_terms"
|
||||
const val EXTRA_ROOM_ID = "extra_room_id"
|
||||
const val EXTRA_USER_ID = "extra_user_id"
|
||||
const val EXTRA_NICKNAME = "extra_nickname"
|
||||
const val EXTRA_MESSAGE_ID = "extra_message_id"
|
||||
const val EXTRA_ROOM_DETAIL = "extra_room_detail"
|
||||
const val EXTRA_MESSAGE_BOX = "extra_message_box"
|
||||
const val EXTRA_TEXT_MESSAGE = "extra_text_message"
|
||||
const val EXTRA_LIVE_TIME_NOW = "extra_live_time_now"
|
||||
const val EXTRA_PREV_LIVE_ROOM = "extra_prev_live_room"
|
||||
const val EXTRA_SELECT_RECIPIENT = "extra_select_recipient"
|
||||
const val EXTRA_ROOM_CHANNEL_NAME = "extra_room_channel_name"
|
||||
const val EXTRA_LIVE_RESERVATION_RESPONSE = "extra_live_reservation_response"
|
||||
|
||||
|
|
|
@ -22,6 +22,13 @@ import kr.co.vividnext.sodalive.live.room.tag.LiveTagRepository
|
|||
import kr.co.vividnext.sodalive.live.room.tag.LiveTagViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditViewModel
|
||||
import kr.co.vividnext.sodalive.main.MainViewModel
|
||||
import kr.co.vividnext.sodalive.message.MessageApi
|
||||
import kr.co.vividnext.sodalive.message.MessageRepository
|
||||
import kr.co.vividnext.sodalive.message.SelectMessageRecipientViewModel
|
||||
import kr.co.vividnext.sodalive.message.text.TextMessageViewModel
|
||||
import kr.co.vividnext.sodalive.message.text.TextMessageWriteViewModel
|
||||
import kr.co.vividnext.sodalive.message.voice.VoiceMessageViewModel
|
||||
import kr.co.vividnext.sodalive.message.voice.VoiceMessageWriteViewModel
|
||||
import kr.co.vividnext.sodalive.mypage.MyPageViewModel
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthApi
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthRepository
|
||||
|
@ -94,6 +101,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||
single { ApiBuilder().build(get(), ReportApi::class.java) }
|
||||
single { ApiBuilder().build(get(), LiveRecommendApi::class.java) }
|
||||
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
|
||||
single { ApiBuilder().build(get(), MessageApi::class.java) }
|
||||
}
|
||||
|
||||
private val viewModelModule = module {
|
||||
|
@ -116,6 +124,11 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||
viewModel { ExplorerViewModel(get()) }
|
||||
viewModel { UserProfileViewModel(get(), get(), get()) }
|
||||
viewModel { UserFollowerListViewModel(get(), get()) }
|
||||
viewModel { TextMessageViewModel(get()) }
|
||||
viewModel { TextMessageWriteViewModel(get()) }
|
||||
viewModel { VoiceMessageViewModel(get()) }
|
||||
viewModel { VoiceMessageWriteViewModel(get()) }
|
||||
viewModel { SelectMessageRecipientViewModel(get(), get()) }
|
||||
}
|
||||
|
||||
private val repositoryModule = module {
|
||||
|
@ -129,6 +142,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||
factory { LiveTagRepository(get()) }
|
||||
factory { ReportRepository(get()) }
|
||||
factory { ExplorerRepository(get()) }
|
||||
factory { MessageRepository(get()) }
|
||||
}
|
||||
|
||||
private val moduleList = listOf(
|
||||
|
|
|
@ -21,7 +21,7 @@ import kr.co.vividnext.sodalive.common.LoadingDialog
|
|||
import kr.co.vividnext.sodalive.databinding.FragmentExplorerBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.message.MessageSelectRecipientAdapter
|
||||
import kr.co.vividnext.sodalive.message.SelectMessageRecipientAdapter
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -36,7 +36,7 @@ class ExplorerFragment : BaseFragment<FragmentExplorerBinding>(
|
|||
private lateinit var imm: InputMethodManager
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private lateinit var searchChannelAdapter: MessageSelectRecipientAdapter
|
||||
private lateinit var searchChannelAdapter: SelectMessageRecipientAdapter
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -106,7 +106,7 @@ class ExplorerFragment : BaseFragment<FragmentExplorerBinding>(
|
|||
}
|
||||
|
||||
private fun setupSearchChannelView() {
|
||||
searchChannelAdapter = MessageSelectRecipientAdapter {
|
||||
searchChannelAdapter = SelectMessageRecipientAdapter {
|
||||
hideKeyboard()
|
||||
val intent = Intent(requireContext(), UserProfileActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, it.id)
|
||||
|
|
|
@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.live.room.StartLiveRequest
|
|||
import kr.co.vividnext.sodalive.live.room.create.CreateLiveRoomResponse
|
||||
import kr.co.vividnext.sodalive.live.room.create.GetRecentRoomInfoResponse
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
|
||||
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationStatusResponse
|
||||
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationTotalResponse
|
||||
|
@ -182,4 +183,9 @@ interface LiveApi {
|
|||
@Path("id") id: Long,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetLiveRoomDonationStatusResponse>>
|
||||
|
||||
@GET("/live/room/recent_visit_room/users")
|
||||
fun recentVisitRoomUsers(
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetRoomDetailUser>>>
|
||||
}
|
||||
|
|
|
@ -208,4 +208,6 @@ class LiveRepository(
|
|||
roomId: Long,
|
||||
token: String
|
||||
) = api.donationStatus(roomId, authHeader = token)
|
||||
|
||||
fun recentVisitRoomUsers(token: String) = api.recentVisitRoomUsers(authHeader = token)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
data class GetVoiceMessageResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<VoiceMessageItem>
|
||||
) {
|
||||
data class VoiceMessageItem(
|
||||
@SerializedName("messageId") val messageId: Long,
|
||||
@SerializedName("senderId") val senderId: Long,
|
||||
@SerializedName("senderNickname") val senderNickname: String,
|
||||
@SerializedName("senderProfileImageUrl") val senderProfileImageUrl: String,
|
||||
@SerializedName("recipientNickname") val recipientNickname: String,
|
||||
@SerializedName("recipientProfileImageUrl") val recipientProfileImageUrl: String,
|
||||
@SerializedName("voiceMessageUrl") val voiceMessageUrl: String,
|
||||
@SerializedName("date") val date: String,
|
||||
@SerializedName("isKept") val isKept: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
data class GetTextMessageResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<TextMessageItem>
|
||||
) {
|
||||
@Parcelize
|
||||
data class TextMessageItem(
|
||||
@SerializedName("messageId") val messageId: Long,
|
||||
@SerializedName("senderId") val senderId: Long,
|
||||
@SerializedName("senderNickname") val senderNickname: String,
|
||||
@SerializedName("senderProfileImageUrl") val senderProfileImageUrl: String,
|
||||
@SerializedName("recipientNickname") val recipientNickname: String,
|
||||
@SerializedName("recipientProfileImageUrl") val recipientProfileImageUrl: String,
|
||||
@SerializedName("textMessage") val textMessage: String,
|
||||
@SerializedName("date") val date: String,
|
||||
@SerializedName("isKept") val isKept: Boolean
|
||||
) : Parcelable
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface MessageApi {
|
||||
@POST("/message/send/text")
|
||||
fun sendTextMessage(
|
||||
@Body request: SendTextMessageRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@POST("/message/send/voice")
|
||||
@Multipart
|
||||
fun sendVoiceMessage(
|
||||
@Part voiceFile: MultipartBody.Part,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/message/sent/text")
|
||||
fun getSentTextMessage(
|
||||
@Query("timezone") timezone: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetTextMessageResponse>>
|
||||
|
||||
@GET("/message/received/text")
|
||||
fun getReceivedTextMessage(
|
||||
@Query("timezone") timezone: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetTextMessageResponse>>
|
||||
|
||||
@GET("/message/keep/text")
|
||||
fun getKeepTextMessage(
|
||||
@Query("timezone") timezone: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetTextMessageResponse>>
|
||||
|
||||
@GET("/message/sent/voice")
|
||||
fun getSentVoiceMessage(
|
||||
@Query("timezone") timezone: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetVoiceMessageResponse>>
|
||||
|
||||
@GET("/message/received/voice")
|
||||
fun getReceivedVoiceMessage(
|
||||
@Query("timezone") timezone: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetVoiceMessageResponse>>
|
||||
|
||||
@GET("/message/keep/voice")
|
||||
fun getKeepVoiceMessage(
|
||||
@Query("timezone") timezone: String,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetVoiceMessageResponse>>
|
||||
|
||||
@DELETE("/message/{messageId}")
|
||||
fun deleteMessage(
|
||||
@Path("messageId") messageId: Long,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@PUT("/message/keep/text/{id}")
|
||||
fun keepTextMessage(
|
||||
@Path("id") id: Long,
|
||||
@Body container: String = "aos",
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@PUT("/message/keep/voice/{id}")
|
||||
fun keepVoiceMessage(
|
||||
@Path("id") id: Long,
|
||||
@Body container: String = "aos",
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
enum class MessageBox {
|
||||
@SerializedName("SENT")
|
||||
SENT,
|
||||
|
||||
@SerializedName("RECEIVE")
|
||||
RECEIVE,
|
||||
|
||||
@SerializedName("KEEP")
|
||||
KEEP
|
||||
}
|
|
@ -1,7 +1,65 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentMessageBinding
|
||||
import kr.co.vividnext.sodalive.message.text.TextMessageFragment
|
||||
import kr.co.vividnext.sodalive.message.voice.VoiceMessageFragment
|
||||
|
||||
class MessageFragment : BaseFragment<FragmentMessageBinding>(FragmentMessageBinding::inflate) {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupView()
|
||||
changeFragment("message")
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
val tabs = binding.tabs
|
||||
tabs.addTab(tabs.newTab().setText("문자").setTag("message"))
|
||||
tabs.addTab(tabs.newTab().setText("음성").setTag("voice"))
|
||||
|
||||
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
val tag = tab.tag as String
|
||||
changeFragment(tag)
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||
}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun changeFragment(tag: String) {
|
||||
val fragmentManager = childFragmentManager
|
||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||
|
||||
val currentFragment = fragmentManager.primaryNavigationFragment
|
||||
if (currentFragment != null) {
|
||||
fragmentTransaction.hide(currentFragment)
|
||||
}
|
||||
|
||||
var fragment = fragmentManager.findFragmentByTag(tag)
|
||||
if (fragment == null) {
|
||||
fragment = if (tag == "message") {
|
||||
TextMessageFragment()
|
||||
} else {
|
||||
VoiceMessageFragment()
|
||||
}
|
||||
|
||||
fragmentTransaction.add(R.id.container, fragment, tag)
|
||||
} else {
|
||||
fragmentTransaction.show(fragment)
|
||||
}
|
||||
|
||||
fragmentTransaction.setPrimaryNavigationFragment(fragment)
|
||||
fragmentTransaction.setReorderingAllowed(true)
|
||||
fragmentTransaction.commitNow()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.util.TimeZone
|
||||
|
||||
class MessageRepository(private val api: MessageApi) {
|
||||
fun sendTextMessage(request: SendTextMessageRequest, token: String): Single<ApiResponse<Any>> {
|
||||
return api.sendTextMessage(request, authHeader = token)
|
||||
}
|
||||
|
||||
fun sendVoiceMessage(
|
||||
voiceFile: MultipartBody.Part,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
): Single<ApiResponse<Any>> {
|
||||
return api.sendVoiceMessage(
|
||||
voiceFile,
|
||||
request,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun getSentTextMessage(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
): Single<ApiResponse<GetTextMessageResponse>> {
|
||||
return api.getSentTextMessage(
|
||||
TimeZone.getDefault().id,
|
||||
page,
|
||||
size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun getReceivedTextMessage(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
): Single<ApiResponse<GetTextMessageResponse>> {
|
||||
return api.getReceivedTextMessage(
|
||||
TimeZone.getDefault().id,
|
||||
page,
|
||||
size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun keepTextMessage(messageId: Long, token: String): Single<ApiResponse<Any>> {
|
||||
return api.keepTextMessage(
|
||||
messageId,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun getKeepTextMessage(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
): Single<ApiResponse<GetTextMessageResponse>> {
|
||||
return api.getKeepTextMessage(
|
||||
TimeZone.getDefault().id,
|
||||
page,
|
||||
size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun getSentVoiceMessage(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
): Single<ApiResponse<GetVoiceMessageResponse>> {
|
||||
return api.getSentVoiceMessage(
|
||||
TimeZone.getDefault().id,
|
||||
page,
|
||||
size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun getReceivedVoiceMessage(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
): Single<ApiResponse<GetVoiceMessageResponse>> {
|
||||
return api.getReceivedVoiceMessage(
|
||||
TimeZone.getDefault().id,
|
||||
page,
|
||||
size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun getKeepVoiceMessage(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
): Single<ApiResponse<GetVoiceMessageResponse>> {
|
||||
return api.getKeepVoiceMessage(
|
||||
TimeZone.getDefault().id,
|
||||
page,
|
||||
size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteMessage(
|
||||
messageId: Long,
|
||||
token: String
|
||||
): Single<ApiResponse<Any>> {
|
||||
return api.deleteMessage(messageId, authHeader = token)
|
||||
}
|
||||
|
||||
fun keepVoiceMessage(messageId: Long, token: String): Single<ApiResponse<Any>> {
|
||||
return api.keepVoiceMessage(
|
||||
messageId,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.databinding.ActivitySelectMessageRecipientBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SelectMessageRecipientActivity : BaseActivity<ActivitySelectMessageRecipientBinding>(
|
||||
ActivitySelectMessageRecipientBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: SelectMessageRecipientViewModel by inject()
|
||||
|
||||
private lateinit var adapter: SelectMessageRecipientAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindData()
|
||||
viewModel.searchUser("")
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
binding.toolbar.tvBack.text = "받는 사람 검색"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
val recyclerView = binding.rvRecipient
|
||||
|
||||
adapter = SelectMessageRecipientAdapter {
|
||||
val intent = Intent()
|
||||
intent.putExtra(Constants.EXTRA_SELECT_RECIPIENT, it)
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
recyclerView.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()
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etSearchNickname.textChanges().skip(1)
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe {
|
||||
viewModel.searchUser(it.toString())
|
||||
}
|
||||
)
|
||||
|
||||
viewModel.searchUserLiveData.observe(this) {
|
||||
adapter.items.clear()
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,9 +10,9 @@ import kr.co.vividnext.sodalive.databinding.ItemSelectRecipientBinding
|
|||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
|
||||
class MessageSelectRecipientAdapter(
|
||||
class SelectMessageRecipientAdapter(
|
||||
private val onClickItem: (GetRoomDetailUser) -> Unit
|
||||
) : RecyclerView.Adapter<MessageSelectRecipientAdapter.ViewHolder>() {
|
||||
) : RecyclerView.Adapter<SelectMessageRecipientAdapter.ViewHolder>() {
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemSelectRecipientBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
|
@ -0,0 +1,65 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.live.LiveRepository
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.user.UserRepository
|
||||
|
||||
class SelectMessageRecipientViewModel(
|
||||
private val liveRepository: LiveRepository,
|
||||
private val userRepository: UserRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _searchUserLiveData =
|
||||
MutableLiveData<List<GetRoomDetailUser>>()
|
||||
val searchUserLiveData: LiveData<List<GetRoomDetailUser>>
|
||||
get() = _searchUserLiveData
|
||||
|
||||
fun searchUser(nickname: String) {
|
||||
if (nickname.length > 1) {
|
||||
compositeDisposable.add(
|
||||
userRepository.searchUser(nickname, "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_searchUserLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
_searchUserLiveData.postValue(emptyList())
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_searchUserLiveData.postValue(emptyList())
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
compositeDisposable.add(
|
||||
liveRepository.recentVisitRoomUsers("Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_searchUserLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
_searchUserLiveData.postValue(emptyList())
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_searchUserLiveData.postValue(emptyList())
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package kr.co.vividnext.sodalive.message
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SendVoiceMessageRequest(
|
||||
@SerializedName("recipientId") val recipientId: Long,
|
||||
@SerializedName("container") val container: String = "aos"
|
||||
)
|
||||
|
||||
data class SendTextMessageRequest(
|
||||
@SerializedName("recipientId") val recipientId: Long,
|
||||
@SerializedName("textMessage") val textMessage: String,
|
||||
@SerializedName("container") val container: String = "aos"
|
||||
)
|
|
@ -0,0 +1,63 @@
|
|||
package kr.co.vividnext.sodalive.message.text
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ItemTextMessageBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.message.GetTextMessageResponse
|
||||
|
||||
class TextMessageAdapter(
|
||||
private val onItemClick: (GetTextMessageResponse.TextMessageItem) -> Unit
|
||||
) : RecyclerView.Adapter<TextMessageAdapter.ViewHolder>() {
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemTextMessageBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: GetTextMessageResponse.TextMessageItem) {
|
||||
if (SharedPreferenceManager.nickname == item.recipientNickname) {
|
||||
binding.ivProfile.load(item.senderProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvNickname.text = item.senderNickname
|
||||
} else {
|
||||
binding.ivProfile.load(item.recipientProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvNickname.text = item.recipientNickname
|
||||
}
|
||||
|
||||
binding.tvDate.text = item.date
|
||||
binding.tvMessage.text = item.textMessage
|
||||
|
||||
binding.root.setOnClickListener { onItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
val items = mutableListOf<GetTextMessageResponse.TextMessageItem>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemTextMessageBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.message.text
|
||||
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityTextMessageDetailBinding
|
||||
|
||||
class TextMessageDetailActivity : BaseActivity<ActivityTextMessageDetailBinding>(
|
||||
ActivityTextMessageDetailBinding::inflate
|
||||
) {
|
||||
override fun setupView() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
package kr.co.vividnext.sodalive.message.text
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentTextMessageBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.message.MessageBox
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class TextMessageFragment : BaseFragment<FragmentTextMessageBinding>(
|
||||
FragmentTextMessageBinding::inflate
|
||||
) {
|
||||
private val viewModel: TextMessageViewModel by inject()
|
||||
|
||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var adapter: TextMessageAdapter
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
viewModel.page = 1
|
||||
viewModel.getMessages()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
|
||||
viewModel.getMessages()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
|
||||
binding.tvSent.setOnClickListener {
|
||||
viewModel.selectMessageBox(MessageBox.SENT)
|
||||
}
|
||||
|
||||
binding.tvReceive.setOnClickListener {
|
||||
viewModel.selectMessageBox(MessageBox.RECEIVE)
|
||||
}
|
||||
|
||||
binding.tvKeep.setOnClickListener {
|
||||
viewModel.selectMessageBox(MessageBox.KEEP)
|
||||
}
|
||||
|
||||
binding.ivWrite.setOnClickListener {
|
||||
val intent = Intent(requireActivity(), TextMessageWriteActivity::class.java)
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
val recyclerView = binding.rvMessage
|
||||
adapter = TextMessageAdapter {
|
||||
val intent = Intent(requireActivity(), TextMessageDetailActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_TEXT_MESSAGE, it)
|
||||
intent.putExtra(
|
||||
Constants.EXTRA_MESSAGE_BOX,
|
||||
viewModel.messageBoxLiveData.value!!.name
|
||||
)
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
requireContext(),
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
recyclerView.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()
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||
.findLastCompletelyVisibleItemPosition()
|
||||
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||
|
||||
// 스크롤이 끝에 도달했는지 확인
|
||||
if (!recyclerView.canScrollVertically(1) &&
|
||||
lastVisibleItemPosition == itemTotalCount
|
||||
) {
|
||||
viewModel.getMessages(viewModel.messageBoxLiveData.value!!)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "문자 메시지를 불러오고 있습니다.")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.getMessagesLiveData.observe(viewLifecycleOwner) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
adapter.items.clear()
|
||||
}
|
||||
|
||||
if (adapter.items.size == 0 && it.isEmpty()) {
|
||||
binding.rvMessage.visibility = View.GONE
|
||||
binding.llNoItems.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.rvMessage.visibility = View.VISIBLE
|
||||
binding.llNoItems.visibility = View.GONE
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.messageBoxLiveData.observe(viewLifecycleOwner) {
|
||||
binding.tvSent.setBackgroundResource(R.drawable.bg_round_corner_16_7_transparent_777777)
|
||||
binding.tvSent.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_777777
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvReceive.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_777777
|
||||
)
|
||||
binding.tvReceive.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_777777
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvKeep.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_777777
|
||||
)
|
||||
binding.tvKeep.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_777777
|
||||
)
|
||||
)
|
||||
|
||||
when (it) {
|
||||
MessageBox.SENT -> {
|
||||
binding.tvSent.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvSent.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.RECEIVE -> {
|
||||
binding.tvReceive.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvReceive.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.KEEP -> {
|
||||
binding.tvKeep.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvKeep.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package kr.co.vividnext.sodalive.message.text
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.message.GetTextMessageResponse
|
||||
import kr.co.vividnext.sodalive.message.MessageBox
|
||||
import kr.co.vividnext.sodalive.message.MessageRepository
|
||||
|
||||
class TextMessageViewModel(private val repository: MessageRepository) : BaseViewModel() {
|
||||
private val _messageBoxLiveData = MutableLiveData(MessageBox.RECEIVE)
|
||||
val messageBoxLiveData: LiveData<MessageBox>
|
||||
get() = _messageBoxLiveData
|
||||
|
||||
private val _getMessagesLiveData =
|
||||
MutableLiveData<List<GetTextMessageResponse.TextMessageItem>>()
|
||||
val getMessagesLiveData: LiveData<List<GetTextMessageResponse.TextMessageItem>>
|
||||
get() = _getMessagesLiveData
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
var page = 1
|
||||
var pageSize = 10
|
||||
private var totalCount = 0
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
fun selectMessageBox(messageBox: MessageBox) {
|
||||
if (messageBox != _messageBoxLiveData.value!!) {
|
||||
page = 1
|
||||
_messageBoxLiveData.postValue(messageBox)
|
||||
getMessages(messageBox)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessages(messageBox: MessageBox = _messageBoxLiveData.value!!) {
|
||||
if (!_isLoading.value!! && (page - 1 == 0 || totalCount > page * pageSize)) {
|
||||
_isLoading.postValue(true)
|
||||
|
||||
val messageBoxObservable = when (messageBox) {
|
||||
MessageBox.SENT -> {
|
||||
repository.getSentTextMessage(
|
||||
page - 1,
|
||||
pageSize,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.RECEIVE -> {
|
||||
repository.getReceivedTextMessage(
|
||||
page - 1,
|
||||
pageSize,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.KEEP -> {
|
||||
repository.getKeepTextMessage(
|
||||
page - 1,
|
||||
pageSize,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
messageBoxObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
totalCount = it.data.totalCount
|
||||
_getMessagesLiveData.postValue(it.data.items)
|
||||
|
||||
page += 1
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
_isLoading.postValue(false)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package kr.co.vividnext.sodalive.message.text
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityTextMessageWriteBinding
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.message.SelectMessageRecipientActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class TextMessageWriteActivity : BaseActivity<ActivityTextMessageWriteBinding>(
|
||||
ActivityTextMessageWriteBinding::inflate
|
||||
) {
|
||||
private val viewModel: TextMessageWriteViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
activityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
if (it.data != null) {
|
||||
val recipient = IntentCompat.getParcelableExtra(
|
||||
it.data!!,
|
||||
Constants.EXTRA_SELECT_RECIPIENT,
|
||||
GetRoomDetailUser::class.java
|
||||
)
|
||||
|
||||
if (recipient != null) {
|
||||
binding.tvRecipientNickname.text = recipient.nickname
|
||||
viewModel.recipientId = recipient.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindData()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
val replySenderNickname = intent.getStringExtra(Constants.EXTRA_NICKNAME)
|
||||
val replySenderId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
|
||||
|
||||
if (replySenderId > 0 && replySenderNickname != null) {
|
||||
binding.ivSelectRecipient.visibility = View.GONE
|
||||
binding.tvRecipientNickname.text = replySenderNickname
|
||||
viewModel.recipientId = replySenderId
|
||||
|
||||
binding.tvTitle.text = "메시지 보내기"
|
||||
}
|
||||
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
|
||||
binding.tvSend.setOnClickListener {
|
||||
viewModel.write {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"메시지 전송이 완료되었습니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
binding.ivSelectRecipient.setOnClickListener {
|
||||
val intent = Intent(applicationContext, SelectMessageRecipientActivity::class.java)
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etMessage.textChanges().skip(1)
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe {
|
||||
viewModel.textMessage = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package kr.co.vividnext.sodalive.message.text
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.message.MessageRepository
|
||||
import kr.co.vividnext.sodalive.message.SendTextMessageRequest
|
||||
|
||||
class TextMessageWriteViewModel(private val repository: MessageRepository) : BaseViewModel() {
|
||||
var textMessage = ""
|
||||
var recipientId: Long = 0
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
fun write(onSuccess: () -> Unit) {
|
||||
if (recipientId <= 0) {
|
||||
_toastLiveData.postValue("받는 사람을 선택해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
if (textMessage.isBlank()) {
|
||||
_toastLiveData.postValue("메시지를 입력하세요.")
|
||||
return
|
||||
}
|
||||
|
||||
val request = SendTextMessageRequest(recipientId = recipientId, textMessage = textMessage)
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.sendTextMessage(
|
||||
request = request,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success) {
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
_isLoading.postValue(false)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package kr.co.vividnext.sodalive.message.voice
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ItemVoiceMessageBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.message.GetVoiceMessageResponse
|
||||
import kr.co.vividnext.sodalive.message.MessageBox
|
||||
|
||||
class VoiceMessageAdapter(
|
||||
private val onPlay: (GetVoiceMessageResponse.VoiceMessageItem) -> Int,
|
||||
private val onStop: () -> Unit,
|
||||
private val onStartSeekbar: (SeekBar, TextView) -> Unit,
|
||||
private val onItemDelete: (Long) -> Unit,
|
||||
private val onItemKeep: (Long, Boolean) -> Unit,
|
||||
private val onItemReply: (Long, String, String) -> Unit
|
||||
) : RecyclerView.Adapter<VoiceMessageAdapter.ViewHolder>() {
|
||||
|
||||
private var openPlayerItemPosition = -1
|
||||
private var isPlaying = false
|
||||
|
||||
private var messageBox = MessageBox.RECEIVE
|
||||
|
||||
fun setMessageBox(messageBox: MessageBox) {
|
||||
this.messageBox = messageBox
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemVoiceMessageBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun bind(item: GetVoiceMessageResponse.VoiceMessageItem, position: Int) {
|
||||
if (openPlayerItemPosition == position) {
|
||||
binding.llPlayer.visibility = View.VISIBLE
|
||||
if (isPlaying) {
|
||||
onStartSeekbar(binding.seekbar, binding.tvTotalDuration)
|
||||
}
|
||||
} else {
|
||||
binding.llPlayer.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
binding.ivPlayOrStop.setImageResource(R.drawable.btn_bar_stop)
|
||||
} else {
|
||||
binding.ivPlayOrStop.setImageResource(R.drawable.btn_bar_play)
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.nickname == item.recipientNickname) {
|
||||
binding.ivProfile.load(item.senderProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvNickname.text = item.senderNickname
|
||||
} else {
|
||||
binding.ivProfile.load(item.recipientProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvNickname.text = item.recipientNickname
|
||||
}
|
||||
|
||||
binding.tvDate.text = item.date
|
||||
binding.root.setOnClickListener {
|
||||
if (isPlaying) {
|
||||
onStop()
|
||||
isPlaying = false
|
||||
}
|
||||
|
||||
openPlayerItemPosition = if (
|
||||
openPlayerItemPosition == position &&
|
||||
binding.llPlayer.visibility == View.VISIBLE
|
||||
) {
|
||||
-1
|
||||
} else {
|
||||
position
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
binding.llPlayer.setOnClickListener {}
|
||||
binding.seekbar.isEnabled = false
|
||||
|
||||
binding.ivPlayOrStop.setOnClickListener {
|
||||
if (isPlaying) {
|
||||
isPlaying = false
|
||||
onStop()
|
||||
binding.ivPlayOrStop.setImageResource(R.drawable.btn_bar_play)
|
||||
} else {
|
||||
val totalDuration = onPlay(item).toLong()
|
||||
if (totalDuration > 0) {
|
||||
isPlaying = true
|
||||
onStartSeekbar(binding.seekbar, binding.tvTotalDuration)
|
||||
binding.ivPlayOrStop.setImageResource(R.drawable.btn_bar_stop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messageBox == MessageBox.RECEIVE) {
|
||||
binding.ivKeep.visibility = View.VISIBLE
|
||||
binding.ivReply.visibility = View.VISIBLE
|
||||
binding.ivDelete.visibility = View.GONE
|
||||
|
||||
binding.ivKeep.setOnClickListener { onItemKeep(item.messageId, item.isKept) }
|
||||
binding.ivReply.setOnClickListener {
|
||||
onItemReply(
|
||||
item.senderId,
|
||||
item.senderNickname,
|
||||
item.senderProfileImageUrl
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.ivKeep.visibility = View.GONE
|
||||
binding.ivReply.visibility = View.GONE
|
||||
binding.ivDelete.visibility = View.VISIBLE
|
||||
|
||||
binding.ivDelete.setOnClickListener { onItemDelete(item.messageId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val items = mutableListOf<GetVoiceMessageResponse.VoiceMessageItem>()
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemVoiceMessageBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position], position)
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun voicePlayComplete() {
|
||||
isPlaying = false
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
package kr.co.vividnext.sodalive.message.voice
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Rect
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.orhanobut.logger.Logger
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentVoiceMessageBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.message.MessageBox
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class VoiceMessageFragment : BaseFragment<FragmentVoiceMessageBinding>(
|
||||
FragmentVoiceMessageBinding::inflate
|
||||
) {
|
||||
private val viewModel: VoiceMessageViewModel by inject()
|
||||
|
||||
private lateinit var adapter: VoiceMessageAdapter
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var timerTask: TimerTask? = null
|
||||
private var timer: Timer? = null
|
||||
|
||||
override fun onDestroy() {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
|
||||
viewModel.getMessages()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
|
||||
binding.tvSent.setOnClickListener {
|
||||
adapter.setMessageBox(MessageBox.SENT)
|
||||
viewModel.selectMessageBox(
|
||||
MessageBox.SENT
|
||||
)
|
||||
}
|
||||
|
||||
binding.tvReceive.setOnClickListener {
|
||||
adapter.setMessageBox(MessageBox.RECEIVE)
|
||||
viewModel.selectMessageBox(
|
||||
MessageBox.RECEIVE
|
||||
)
|
||||
}
|
||||
|
||||
binding.tvKeep.setOnClickListener {
|
||||
adapter.setMessageBox(MessageBox.KEEP)
|
||||
viewModel.selectMessageBox(
|
||||
MessageBox.KEEP
|
||||
)
|
||||
}
|
||||
|
||||
binding.ivWrite.setOnClickListener {
|
||||
stopVoiceMessage()
|
||||
val voiceWriteFragment = VoiceMessageWriteFragment(
|
||||
onSendSuccess = {
|
||||
viewModel.page = 1
|
||||
viewModel.getMessages()
|
||||
}
|
||||
)
|
||||
voiceWriteFragment.show(childFragmentManager, voiceWriteFragment.tag)
|
||||
}
|
||||
|
||||
val recyclerView = binding.rvMessage
|
||||
adapter = VoiceMessageAdapter(
|
||||
onPlay = { playVoiceMessage(it.voiceMessageUrl) },
|
||||
onStop = { stopVoiceMessage() },
|
||||
onStartSeekbar = { seekbar, textView ->
|
||||
startSeekbar(seekbar, textView)
|
||||
},
|
||||
onItemDelete = {
|
||||
viewModel.deleteMessage(it) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"메시지가 삭제되었습니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
viewModel.page = 1
|
||||
viewModel.getMessages()
|
||||
}
|
||||
},
|
||||
onItemKeep = { messageId, isKept ->
|
||||
if (isKept) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"이미 보관된 메시지 입니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
return@VoiceMessageAdapter
|
||||
}
|
||||
|
||||
viewModel.keepVoiceMessage(messageId)
|
||||
},
|
||||
onItemReply = { senderId, senderNickname, senderProfileUrl ->
|
||||
stopVoiceMessage()
|
||||
val voiceWriteFragment = VoiceMessageWriteFragment(
|
||||
onSendSuccess = {
|
||||
viewModel.page = 1
|
||||
viewModel.getMessages()
|
||||
},
|
||||
senderId = senderId,
|
||||
senderNickname = senderNickname,
|
||||
senderProfileUrl = senderProfileUrl
|
||||
)
|
||||
voiceWriteFragment.show(childFragmentManager, voiceWriteFragment.tag)
|
||||
}
|
||||
)
|
||||
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
requireContext(),
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
recyclerView.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()
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||
.findLastCompletelyVisibleItemPosition()
|
||||
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||
|
||||
// 스크롤이 끝에 도달했는지 확인
|
||||
if (!recyclerView.canScrollVertically(1) &&
|
||||
lastVisibleItemPosition == itemTotalCount
|
||||
) {
|
||||
viewModel.getMessages(viewModel.messageBoxLiveData.value!!)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun startSeekbar(seekbar: SeekBar, textView: TextView) {
|
||||
if (timer != null) {
|
||||
timer!!.cancel()
|
||||
timerTask = null
|
||||
timer = null
|
||||
}
|
||||
|
||||
if (mediaPlayer != null) {
|
||||
val duration = mediaPlayer?.duration!!
|
||||
seekbar.max = duration
|
||||
var seconds = TimeUnit.MILLISECONDS.toSeconds(duration.toLong())
|
||||
val minutes = seconds / 60
|
||||
seconds %= 60
|
||||
textView.text = String.format("%02d:%02d", minutes, seconds)
|
||||
|
||||
timerTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
handler.post {
|
||||
seekbar.progress = mediaPlayer?.currentPosition ?: 0
|
||||
Logger.e("test")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer = Timer()
|
||||
timer!!.scheduleAtFixedRate(timerTask, 0, 100)
|
||||
}
|
||||
}
|
||||
|
||||
private fun playVoiceMessage(voiceMessageUrl: String): Int {
|
||||
if (mediaPlayer == null) {
|
||||
mediaPlayer = MediaPlayer()
|
||||
mediaPlayer!!.reset()
|
||||
|
||||
mediaPlayer!!.setOnCompletionListener {
|
||||
stopVoiceMessage()
|
||||
}
|
||||
|
||||
mediaPlayer!!.setOnPreparedListener {
|
||||
it.start()
|
||||
}
|
||||
|
||||
try {
|
||||
mediaPlayer!!.setDataSource(voiceMessageUrl)
|
||||
mediaPlayer!!.prepare()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
|
||||
return 0
|
||||
}
|
||||
|
||||
return mediaPlayer!!.duration
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun stopVoiceMessage() {
|
||||
if (timer != null) {
|
||||
timer!!.cancel()
|
||||
timerTask = null
|
||||
timer = null
|
||||
}
|
||||
|
||||
if (mediaPlayer != null) {
|
||||
adapter.voicePlayComplete()
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "음성 메시지를 불러오고 있습니다.")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.getMessagesLiveData.observe(viewLifecycleOwner) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
adapter.items.clear()
|
||||
}
|
||||
|
||||
if (adapter.items.size == 0 && it.isEmpty()) {
|
||||
binding.rvMessage.visibility = View.GONE
|
||||
binding.llNoItems.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.rvMessage.visibility = View.VISIBLE
|
||||
binding.llNoItems.visibility = View.GONE
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.messageBoxLiveData.observe(viewLifecycleOwner) {
|
||||
binding.tvSent.setBackgroundResource(R.drawable.bg_round_corner_16_7_transparent_777777)
|
||||
binding.tvSent.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_777777
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvReceive.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_777777
|
||||
)
|
||||
binding.tvReceive.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_777777
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvKeep.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_777777
|
||||
)
|
||||
binding.tvKeep.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_777777
|
||||
)
|
||||
)
|
||||
|
||||
when (it) {
|
||||
MessageBox.SENT -> {
|
||||
binding.tvSent.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvSent.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.RECEIVE -> {
|
||||
binding.tvReceive.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvReceive.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.KEEP -> {
|
||||
binding.tvKeep.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvKeep.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package kr.co.vividnext.sodalive.message.voice
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.message.GetVoiceMessageResponse
|
||||
import kr.co.vividnext.sodalive.message.MessageBox
|
||||
import kr.co.vividnext.sodalive.message.MessageRepository
|
||||
|
||||
class VoiceMessageViewModel(private val repository: MessageRepository) : BaseViewModel() {
|
||||
private val _messageBoxLiveData = MutableLiveData(MessageBox.RECEIVE)
|
||||
val messageBoxLiveData: LiveData<MessageBox>
|
||||
get() = _messageBoxLiveData
|
||||
|
||||
private val _getMessagesLiveData =
|
||||
MutableLiveData<List<GetVoiceMessageResponse.VoiceMessageItem>>()
|
||||
val getMessagesLiveData: LiveData<List<GetVoiceMessageResponse.VoiceMessageItem>>
|
||||
get() = _getMessagesLiveData
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
var page = 1
|
||||
var pageSize = 10
|
||||
private var totalCount = 0
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
fun selectMessageBox(messageBox: MessageBox) {
|
||||
if (messageBox != _messageBoxLiveData.value!!) {
|
||||
page = 1
|
||||
_messageBoxLiveData.postValue(messageBox)
|
||||
getMessages(messageBox)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessages(messageBox: MessageBox = _messageBoxLiveData.value!!) {
|
||||
if (!_isLoading.value!! && (page - 1 == 0 || totalCount > page * pageSize)) {
|
||||
_isLoading.postValue(true)
|
||||
val messageBoxObservable = when (messageBox) {
|
||||
MessageBox.SENT -> {
|
||||
repository.getSentVoiceMessage(
|
||||
page - 1,
|
||||
pageSize,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.RECEIVE -> {
|
||||
repository.getReceivedVoiceMessage(
|
||||
page - 1,
|
||||
pageSize,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
}
|
||||
|
||||
MessageBox.KEEP -> {
|
||||
repository.getKeepVoiceMessage(
|
||||
page - 1,
|
||||
pageSize,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
messageBoxObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
totalCount = it.data.totalCount
|
||||
_getMessagesLiveData.postValue(it.data.items)
|
||||
|
||||
page += 1
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
_isLoading.postValue(false)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteMessage(messageId: Long, onSuccess: () -> Unit) {
|
||||
if (messageId <= 0) {
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.deleteMessage(
|
||||
messageId = messageId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun keepVoiceMessage(messageId: Long) {
|
||||
if (messageId <= 0) {
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.keepVoiceMessage(
|
||||
messageId = messageId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success) {
|
||||
_toastLiveData.postValue(
|
||||
"보관되었습니다."
|
||||
)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
_isLoading.value = false
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,431 @@
|
|||
package kr.co.vividnext.sodalive.message.voice
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.media.MediaPlayer
|
||||
import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentVoiceMessageWriteBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.message.SelectMessageRecipientActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class VoiceMessageWriteFragment(
|
||||
private val onSendSuccess: () -> Unit,
|
||||
private val senderId: Long? = null,
|
||||
private val senderNickname: String? = null,
|
||||
private val senderProfileUrl: String? = null
|
||||
) : BottomSheetDialogFragment() {
|
||||
|
||||
private val viewModel: VoiceMessageWriteViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var binding: FragmentVoiceMessageWriteBinding
|
||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
private var mediaRecorder: MediaRecorder? = null
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var fileNameMedia = ""
|
||||
|
||||
private var second = -1
|
||||
private var minute = 0
|
||||
private var hour = 0
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
activityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
if (it.data != null) {
|
||||
val recipient = IntentCompat.getParcelableExtra(
|
||||
it.data!!,
|
||||
Constants.EXTRA_SELECT_RECIPIENT,
|
||||
GetRoomDetailUser::class.java
|
||||
)
|
||||
|
||||
if (recipient != null) {
|
||||
setReceiver(
|
||||
userId = recipient.id,
|
||||
nickname = recipient.nickname,
|
||||
profileUrl = recipient.profileImageUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
dismiss()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage("오디오 녹음 권한을 거부하시면 음성 속닥을 이용하실 수 없습니다.")
|
||||
.setPermissions(Manifest.permission.RECORD_AUDIO)
|
||||
.check()
|
||||
}
|
||||
|
||||
private fun setReceiver(userId: Long, nickname: String, profileUrl: String) {
|
||||
binding.ivProfile.load(profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
|
||||
}
|
||||
binding.tvNickname.text = nickname
|
||||
binding.tvNickname.typeface = ResourcesCompat.getFont(
|
||||
requireContext(),
|
||||
R.font.gmarket_sans_bold
|
||||
)
|
||||
binding.tvNickname.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.ivPlus.visibility = View.GONE
|
||||
viewModel.recipientId = userId
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val d = it as BottomSheetDialog
|
||||
val bottomSheet = d.findViewById<FrameLayout>(
|
||||
com.google.android.material.R.id.design_bottom_sheet
|
||||
)
|
||||
if (bottomSheet != null) {
|
||||
BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentVoiceMessageWriteBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
|
||||
bindData()
|
||||
|
||||
binding.ivClose.setOnClickListener { dismiss() }
|
||||
|
||||
if (senderId != null && senderProfileUrl != null && senderNickname != null) {
|
||||
setReceiver(
|
||||
userId = senderId,
|
||||
nickname = senderNickname,
|
||||
profileUrl = senderProfileUrl
|
||||
)
|
||||
} else {
|
||||
binding.rlSelectRecipient.setOnClickListener {
|
||||
val intent = Intent(requireContext(), SelectMessageRecipientActivity::class.java)
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
binding.ivRecordStart.setOnClickListener {
|
||||
if (viewModel.recipientId <= 0) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"받는 사람을 선택해 주세요.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
fileNameMedia =
|
||||
"${requireActivity().filesDir.path}/socdoc_${System.currentTimeMillis()}.mp3"
|
||||
|
||||
val fileMedia = File(fileNameMedia)
|
||||
if (!fileMedia.exists()) {
|
||||
try {
|
||||
fileMedia.createNewFile()
|
||||
|
||||
startRecording()
|
||||
} catch (e: IOException) {
|
||||
Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.ivRecordStop.setOnClickListener {
|
||||
stopRecording()
|
||||
}
|
||||
|
||||
binding.ivRecordPlay.setOnClickListener {
|
||||
startPlaying()
|
||||
}
|
||||
|
||||
binding.ivRecordPause.setOnClickListener {
|
||||
stopPlaying()
|
||||
}
|
||||
|
||||
binding.tvDelete.setOnClickListener {
|
||||
if (fileNameMedia.isNotBlank()) {
|
||||
val fileMedia = File(fileNameMedia)
|
||||
if (fileMedia.exists()) {
|
||||
fileMedia.delete()
|
||||
}
|
||||
fileNameMedia = ""
|
||||
}
|
||||
|
||||
binding.ivRecordStart.visibility = View.VISIBLE
|
||||
binding.llRetryOrSend.visibility = View.GONE
|
||||
binding.rlRecordPlay.visibility = View.GONE
|
||||
binding.soundVisualizer.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.tvRetryRecord.setOnClickListener {
|
||||
if (fileNameMedia.isNotBlank()) {
|
||||
val fileMedia = File(fileNameMedia)
|
||||
if (fileMedia.exists()) {
|
||||
fileMedia.delete()
|
||||
}
|
||||
fileNameMedia = ""
|
||||
}
|
||||
|
||||
binding.ivRecordStart.visibility = View.VISIBLE
|
||||
binding.llRetryOrSend.visibility = View.GONE
|
||||
binding.rlRecordPlay.visibility = View.GONE
|
||||
binding.soundVisualizer.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.tvSendMessage.setOnClickListener {
|
||||
viewModel.write(File(fileNameMedia)) {
|
||||
dismiss()
|
||||
onSendSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(resources.displayMetrics.widthPixels)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
|
||||
if (mediaRecorder != null) {
|
||||
// stop recording and free up resources
|
||||
mediaRecorder!!.stop()
|
||||
mediaRecorder!!.reset()
|
||||
mediaRecorder!!.release()
|
||||
|
||||
mediaRecorder = null
|
||||
}
|
||||
|
||||
if (fileNameMedia.isNotBlank()) {
|
||||
val fileMedia = File(fileNameMedia)
|
||||
if (fileMedia.exists()) {
|
||||
fileMedia.delete()
|
||||
}
|
||||
|
||||
fileNameMedia = ""
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun startRecording() {
|
||||
if (mediaRecorder == null) {
|
||||
// safety check, don't start a new recording if one is already going
|
||||
mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
MediaRecorder(requireContext())
|
||||
} else {
|
||||
MediaRecorder()
|
||||
}
|
||||
mediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
mediaRecorder!!.setOutputFile(fileNameMedia)
|
||||
mediaRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||
mediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
|
||||
try {
|
||||
mediaRecorder!!.prepare()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
mediaRecorder!!.start()
|
||||
binding.ivRecordStart.visibility = View.GONE
|
||||
binding.ivRecordStop.visibility = View.VISIBLE
|
||||
|
||||
startCountDownTimer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopRecording() {
|
||||
if (mediaRecorder != null) {
|
||||
// stop recording and free up resources
|
||||
mediaRecorder!!.stop()
|
||||
mediaRecorder!!.reset()
|
||||
mediaRecorder!!.release()
|
||||
|
||||
mediaRecorder = null
|
||||
|
||||
binding.ivRecordStop.visibility = View.GONE
|
||||
binding.rlRecordPlay.visibility = View.VISIBLE
|
||||
binding.llRetryOrSend.visibility = View.VISIBLE
|
||||
|
||||
stopCountDownTimer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPlaying() {
|
||||
if (mediaPlayer == null) {
|
||||
mediaPlayer = MediaPlayer()
|
||||
mediaPlayer!!.reset()
|
||||
|
||||
mediaPlayer!!.setOnCompletionListener {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
binding.tvDelete.visibility = View.VISIBLE
|
||||
binding.ivRecordPlay.visibility = View.VISIBLE
|
||||
binding.llRetryOrSend.visibility = View.VISIBLE
|
||||
binding.ivRecordPause.visibility = View.GONE
|
||||
binding.soundVisualizer.visibility = View.GONE
|
||||
|
||||
stopCountDownTimer()
|
||||
}
|
||||
|
||||
mediaPlayer!!.setOnPreparedListener {
|
||||
binding.soundVisualizer.visibility = View.VISIBLE
|
||||
binding.soundVisualizer.setAudioSessionId(mediaPlayer!!.audioSessionId)
|
||||
it.start()
|
||||
|
||||
startCountDownTimer()
|
||||
}
|
||||
|
||||
try {
|
||||
mediaPlayer!!.setDataSource(fileNameMedia)
|
||||
mediaPlayer!!.prepare()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
binding.tvDelete.visibility = View.GONE
|
||||
binding.ivRecordPlay.visibility = View.GONE
|
||||
binding.llRetryOrSend.visibility = View.GONE
|
||||
binding.ivRecordPause.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopPlaying() {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
|
||||
binding.tvDelete.visibility = View.VISIBLE
|
||||
binding.ivRecordPlay.visibility = View.VISIBLE
|
||||
binding.llRetryOrSend.visibility = View.VISIBLE
|
||||
binding.ivRecordPause.visibility = View.GONE
|
||||
binding.soundVisualizer.visibility = View.GONE
|
||||
|
||||
stopCountDownTimer()
|
||||
}
|
||||
|
||||
private fun startCountDownTimer() {
|
||||
countDownTimer = object : CountDownTimer(Long.MAX_VALUE, 1000) {
|
||||
override fun onTick(p0: Long) {
|
||||
second += 1
|
||||
binding.tvTimer.text = recordingTime()
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
}
|
||||
}
|
||||
|
||||
countDownTimer!!.start()
|
||||
}
|
||||
|
||||
private fun recordingTime(): String {
|
||||
if (second == 60) {
|
||||
minute += 1
|
||||
second = 0
|
||||
}
|
||||
|
||||
if (minute == 60) {
|
||||
hour += 1
|
||||
minute = 0
|
||||
}
|
||||
|
||||
return String.format("%02d:%02d:%02d", hour, minute, second)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun stopCountDownTimer() {
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer!!.cancel()
|
||||
countDownTimer = null
|
||||
}
|
||||
|
||||
binding.tvTimer.text = "00:00:00"
|
||||
second = -1
|
||||
minute = 0
|
||||
hour = 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package kr.co.vividnext.sodalive.message.voice
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.gson.Gson
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.message.MessageRepository
|
||||
import kr.co.vividnext.sodalive.message.SendVoiceMessageRequest
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.File
|
||||
|
||||
class VoiceMessageWriteViewModel(private val repository: MessageRepository) : BaseViewModel() {
|
||||
var recipientId: Long = 0
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
fun write(file: File, onSuccess: () -> Unit) {
|
||||
if (recipientId <= 0) {
|
||||
_toastLiveData.postValue("받는 사람을 선택해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
val request = SendVoiceMessageRequest(recipientId)
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val recordedFile = MultipartBody.Part.createFormData(
|
||||
"voiceMessageFile",
|
||||
file.name,
|
||||
file.asRequestBody("audio/mpeg".toMediaType())
|
||||
)
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.sendVoiceMessage(
|
||||
recordedFile,
|
||||
requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success) {
|
||||
_toastLiveData.postValue("메시지 전송이 완료되었습니다.")
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
_isLoading.postValue(false)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.user
|
|||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.MemberBlockRequest
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest
|
||||
import kr.co.vividnext.sodalive.mypage.MyPageResponse
|
||||
import kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponse
|
||||
|
@ -81,4 +82,10 @@ interface UserApi {
|
|||
request: Any,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/member/search")
|
||||
fun searchUser(
|
||||
@Query("nickname") nickname: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetRoomDetailUser>>>
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.user
|
|||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.MemberBlockRequest
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest
|
||||
import kr.co.vividnext.sodalive.mypage.MyPageResponse
|
||||
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
|
||||
|
@ -62,4 +63,11 @@ class UserRepository(private val userApi: UserApi) {
|
|||
request = CreatorFollowRequestRequest(creatorId = creatorId),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun searchUser(
|
||||
nickname: String,
|
||||
token: String
|
||||
): Single<ApiResponse<List<GetRoomDetailUser>>> {
|
||||
return userApi.searchUser(nickname, authHeader = token)
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 513 B |
After Width: | Height: | Size: 294 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 571 B |
After Width: | Height: | Size: 742 B |
After Width: | Height: | Size: 694 B |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 443 B |
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_1b1b1b" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_1b1b1b" />
|
||||
</shape>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="16.7dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_777777" />
|
||||
</shape>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_339970ff" />
|
||||
<corners android:radius="6.7dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_339970ff" />
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<corners android:radius="6.7dp" />
|
||||
<solid android:color="@color/color_4dd8d8d8" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="6.7dp" />
|
||||
<solid android:color="@color/color_4dd8d8d8" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="6.7dp" />
|
||||
<solid android:color="@color/color_9970ff" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,37 @@
|
|||
<?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="@color/black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/detail_toolbar" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_search_nickname"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/bg_round_corner_10_232323"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="검색"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textWebEditText"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textColorHint="@color/color_eeeeee"
|
||||
android:textCursorDrawable="@drawable/edit_text_cursor"
|
||||
android:textSize="13.3sp"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_recipient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="20dp" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,125 @@
|
|||
<?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="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/detail_toolbar" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_profile"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_10_1b1b1b"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="12.7dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_profile"
|
||||
android:layout_width="26.7dp"
|
||||
android:layout_height="26.7dp"
|
||||
android:layout_marginEnd="13.3dp"
|
||||
android:contentDescription="@null"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="18.3sp"
|
||||
tools:text="이재형 대표님" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16.7dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/ll_profile"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ll_profile"
|
||||
tools:text="2021년 7월 14일 수요일 12:00" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16.7dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@drawable/bg_round_corner_10_222222"
|
||||
android:paddingHorizontal="26.7dp"
|
||||
android:paddingVertical="13.3dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/ll_buttons"
|
||||
app:layout_constraintEnd_toEndOf="@+id/ll_profile"
|
||||
app:layout_constraintStart_toStartOf="@+id/ll_profile"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_date">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="15sp" />
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_buttons"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48.7dp"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginBottom="26.7dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_reply"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_9970ff"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="답장"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="14.7sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_keep"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="6.7dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_1f1734"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="보관"
|
||||
android:textColor="@color/color_9970ff"
|
||||
android:textSize="14.7sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_delete"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_1f1734"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="삭제"
|
||||
android:textColor="@color/color_9970ff"
|
||||
android:textSize="14.7sp" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout 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="@color/black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:text="새로운 메시지"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="18.3sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="13.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="취소"
|
||||
android:textColor="@color/color_9970ff"
|
||||
android:textSize="16.7sp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_recipient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_below="@+id/toolbar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_recipient_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="받는 사람"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="16.7sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_recipient_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:layout_toEndOf="@+id/tv_recipient_title"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="16.7sp"
|
||||
tools:ignore="RelativeOverlap"
|
||||
tools:text="재민" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_select_recipient"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="13.3dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/btn_plus_round" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@color/color_909090" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/rl_recipient"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_10_232323_9970ff">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@null"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:gravity="top"
|
||||
android:hint="내용을 입력해 주세요"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textMultiLine"
|
||||
android:minHeight="239dp"
|
||||
android:padding="20dp"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textColorHint="@color/color_777777"
|
||||
android:textCursorDrawable="@drawable/edit_text_cursor"
|
||||
android:textSize="13.3sp"
|
||||
tools:ignore="LabelFor" />
|
||||
</ScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_send"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48.7dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginBottom="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_6_7_9970ff"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="메시지 보내기"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="14.7sp" />
|
||||
</RelativeLayout>
|
|
@ -1,16 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:background="@color/black"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="13.3dp"
|
||||
android:text="메시지"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="18.3sp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
app:tabIndicatorColor="@color/color_9970ff"
|
||||
app:tabIndicatorFullWidth="true"
|
||||
app:tabIndicatorHeight="1.3dp"
|
||||
app:tabSelectedTextColor="@color/color_eeeeee"
|
||||
app:tabTextAppearance="@style/tabText"
|
||||
app:tabTextColor="@color/color_777777" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/color_88909090" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_notice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="※ 보관하지 않은 받은 메시지는 3일 후, 자동 삭제됩니다."
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_filter"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_notice">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_receive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="10.7dp"
|
||||
android:text="받은 메시지"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_sent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="6.7dp"
|
||||
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="10.7dp"
|
||||
android:text="보낸 메시지"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_keep"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="10.7dp"
|
||||
android:text="보관함"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_no_items"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_4_7_2b2635"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ll_filter">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_no_item" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4sp"
|
||||
android:text="메시지가 없습니다.\n친구들과 소통해보세요!"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="10.7sp"
|
||||
tools:ignore="SmallSp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:padding="13.3dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ll_filter" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_write"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16.7dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:background="@drawable/bg_round_corner_33_3_9970ff"
|
||||
android:contentDescription="@null"
|
||||
android:padding="13.3dp"
|
||||
android:src="@drawable/ic_make_message"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,126 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_notice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="※ 보관하지 않은 받은 메시지는 3일 후, 자동 삭제됩니다."
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_filter"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_notice">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_receive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="10.7dp"
|
||||
android:text="받은 메시지"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_sent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="6.7dp"
|
||||
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="10.7dp"
|
||||
android:text="보낸 메시지"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_keep"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="10.7dp"
|
||||
android:text="보관함"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_no_items"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="13.3dp"
|
||||
android:background="@drawable/bg_round_corner_4_7_2b2635"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ll_filter">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_no_item" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4sp"
|
||||
android:text="메시지가 없습니다.\n친구들과 소통해보세요!"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="10.7sp"
|
||||
tools:ignore="SmallSp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:padding="13.3dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ll_filter" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_write"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16.7dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:background="@drawable/bg_round_corner_33_3_9970ff"
|
||||
android:contentDescription="@null"
|
||||
android:padding="13.3dp"
|
||||
android:src="@drawable/ic_make_voice"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,218 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView 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="match_parent"
|
||||
tools:background="@color/color_222222">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:paddingHorizontal="26.7dp"
|
||||
android:paddingTop="26.7dp"
|
||||
android:text="음성메시지"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18.3sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:paddingHorizontal="26.7dp"
|
||||
android:paddingTop="26.7dp"
|
||||
android:src="@drawable/ic_close_white"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_select_recipient"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="26.7dp"
|
||||
android:background="@drawable/bg_round_corner_6_7_339970ff"
|
||||
android:orientation="horizontal"
|
||||
android:padding="13.3dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/iv_close">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_profile"
|
||||
android:layout_width="46.7dp"
|
||||
android:layout_height="46.7dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/img_thumb_default" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_toStartOf="@+id/iv_plus"
|
||||
android:layout_toEndOf="@+id/iv_profile"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="TO."
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="13.3sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:fontFamily="@font/gmarket_sans_light"
|
||||
android:text="받는 사람"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="16.7sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_plus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/btn_plus_round" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="81dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/rl_select_recipient">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_timer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/gmarket_sans_light"
|
||||
android:text="00:00:00"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="33.3sp" />
|
||||
|
||||
<com.gauravk.audiovisualizer.visualizer.WaveVisualizer
|
||||
android:id="@+id/sound_visualizer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:visibility="gone"
|
||||
app:avColor="@color/av_deep_orange"
|
||||
app:avDensity="0.8"
|
||||
app:avSpeed="normal"
|
||||
app:avType="fill" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_record_start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="52.3dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_record" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_record_stop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="52.3dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_record_stop"
|
||||
android:visibility="gone" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_record_play"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_record_play"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="90dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_record_play" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_record_pause"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="90dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_record_pause"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_toEndOf="@+id/iv_record_play"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="삭제"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="15.3sp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_retry_or_send"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="26.7dp"
|
||||
android:layout_marginBottom="13.3dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_retry_record"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_10_339970ff_9970ff"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="다시 녹음"
|
||||
android:textColor="@color/color_9970ff"
|
||||
android:textSize="18.3sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_send_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:layout_weight="2"
|
||||
android:background="@drawable/bg_round_corner_10_9970ff"
|
||||
android:fontFamily="@font/gmarket_sans_bold"
|
||||
android:gravity="center"
|
||||
android:text="메시지 보내기"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18.3sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -0,0 +1,65 @@
|
|||
<?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:background="@color/black">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_profile"
|
||||
android:layout_width="46.7dp"
|
||||
android:layout_height="46.7dp"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:layout_marginEnd="35.3dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tv_date"
|
||||
app:layout_constraintStart_toEndOf="@+id/iv_profile"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="13.3sp"
|
||||
tools:text="dlksjfei" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/gmarket_sans_light"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="12sp"
|
||||
tools:text="마지막 메세지 내용. 네 감사합니다. 그럼 다음에 또 연락드릴께요~!" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:fontFamily="@font/gmarket_sans_light"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/color_525252"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="8월 04일" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,142 @@
|
|||
<?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:background="@color/black">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_profile"
|
||||
android:layout_width="46.7dp"
|
||||
android:layout_height="46.7dp"
|
||||
android:contentDescription="@null"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_nickname"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:layout_marginEnd="35.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="13.3sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tv_date"
|
||||
app:layout_constraintStart_toEndOf="@+id/iv_profile"
|
||||
app:layout_constraintTop_toTopOf="@+id/iv_profile"
|
||||
tools:text="dlksjfei" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:fontFamily="@font/gmarket_sans_light"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/color_525252"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/iv_profile"
|
||||
tools:text="8월 04일" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_player"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/bg_round_corner_6_7_339970ff"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="20dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/iv_profile">
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressDrawable="@drawable/voice_message_player_seekbar"
|
||||
android:thumb="@null" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6.7dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="00:00"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="10.7sp"
|
||||
tools:ignore="SmallSp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_total_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="13.3dp"
|
||||
android:fontFamily="@font/gmarket_sans_medium"
|
||||
android:text="00:00"
|
||||
android:textColor="@color/color_bbbbbb"
|
||||
android:textSize="10.7sp"
|
||||
tools:ignore="SmallSp" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24.3dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_keep"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_save"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_play_or_stop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/btn_bar_play" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="13.3dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_reply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="13.3dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_mic_paint"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -68,4 +68,5 @@
|
|||
<color name="color_ffb600">#FFB600</color>
|
||||
<color name="color_99000000">#99000000</color>
|
||||
<color name="color_4c9970ff">#4C9970FF</color>
|
||||
<color name="color_4dd8d8d8">#4DD8D8D8</color>
|
||||
</resources>
|
||||
|
|
|
@ -9,6 +9,7 @@ dependencyResolutionManagement {
|
|||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
|