Compare commits
38 Commits
3e62c754df
...
5e44949094
Author | SHA1 | Date |
---|---|---|
|
5e44949094 | |
|
bb1e260a4c | |
|
68f2896031 | |
|
a75217ee09 | |
|
e8b4134956 | |
|
035fa5a2fa | |
|
8db80f2da9 | |
|
6d93da2136 | |
|
f455aa81a2 | |
|
1b53aec571 | |
|
6126525ebb | |
|
0563fe6890 | |
|
1329ae5e5d | |
|
7dbbd8d490 | |
|
c5896529e1 | |
|
ce35be9688 | |
|
cc8fab76b0 | |
|
3ef78b64ad | |
|
14b652d38e | |
|
662ef64696 | |
|
c2618669c8 | |
|
8a094adc4f | |
|
0cbf2abf5e | |
|
79127801c6 | |
|
7fb43b3f91 | |
|
3b235a8495 | |
|
03de8eba86 | |
|
bf7a7d69a2 | |
|
8e0a5ccc91 | |
|
bad5e6612a | |
|
6f86663a54 | |
|
fd8c4e726d | |
|
edbaceba0b | |
|
6c8183b12f | |
|
d60eb7e408 | |
|
41c6228af5 | |
|
d562e9199c | |
|
c1054c5ede |
|
@ -20,7 +20,8 @@ bin/
|
|||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
release/
|
||||
/release/
|
||||
/debug/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
|
@ -302,7 +303,7 @@ fabric.properties
|
|||
### AndroidStudio Patch ###
|
||||
|
||||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
app/debug/*
|
||||
app/release/*
|
||||
app/debug/
|
||||
app/release/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos,android,androidstudio,visualstudiocode,git,kotlin,java
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.gms.google-services'
|
||||
id 'com.google.android.gms.oss-licenses-plugin'
|
||||
|
||||
id 'kotlin-kapt'
|
||||
|
@ -8,6 +9,7 @@ plugins {
|
|||
id 'org.jlleitschuh.gradle.ktlint'
|
||||
|
||||
id 'io.objectbox'
|
||||
id("com.google.firebase.crashlytics")
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -18,7 +20,9 @@ android {
|
|||
enabled true
|
||||
}
|
||||
|
||||
buildFeatures.dataBinding = true
|
||||
buildFeatures {
|
||||
dataBinding true
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkDependencies true
|
||||
|
@ -43,13 +47,22 @@ android {
|
|||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
||||
buildConfigField 'String', 'BASE_URL', '"https://api.sodalive.net"'
|
||||
buildConfigField 'String', 'BOOTPAY_APP_ID', '"64c35be1d25985001dc50c87"'
|
||||
buildConfigField 'String', 'AGORA_APP_ID', '"e34e40046e9847baba3adfe2b8ffb4f6"'
|
||||
buildConfigField 'String', 'AGORA_APP_CERTIFICATE', '"15cadeea4ba94ff7b091c9a10f4bf4a6"'
|
||||
}
|
||||
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
||||
applicationIdSuffix '.debug'
|
||||
|
||||
buildConfigField 'String', 'BASE_URL', '"https://test-api.sodalive.net"'
|
||||
buildConfigField 'String', 'BOOTPAY_APP_ID', '"6242a7772701800023f68b2e"'
|
||||
buildConfigField 'String', 'AGORA_APP_ID', '"b96574e191a9430fa54c605528aa3ef7"'
|
||||
buildConfigField 'String', 'AGORA_APP_CERTIFICATE', '"ae18ade3afcf4086bd4397726eb0654c"'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
@ -91,7 +104,7 @@ dependencies {
|
|||
}
|
||||
|
||||
// Gson
|
||||
implementation "com.google.code.gson:gson:2.9.0"
|
||||
implementation "com.google.code.gson:gson:2.9.1"
|
||||
|
||||
// Network
|
||||
implementation "com.squareup.retrofit2:retrofit:2.9.0"
|
||||
|
@ -107,5 +120,32 @@ dependencies {
|
|||
// permission
|
||||
implementation "io.github.ParkSangGwon:tedpermission-normal:3.3.0"
|
||||
|
||||
implementation 'com.github.dhaval2404:imagepicker:2.1'
|
||||
implementation 'com.github.zhpanvip:bannerviewpager:3.5.7'
|
||||
|
||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1'
|
||||
|
||||
// Firebase
|
||||
implementation platform('com.google.firebase:firebase-bom:32.2.0')
|
||||
implementation 'com.google.firebase:firebase-dynamic-links-ktx'
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ktx'
|
||||
implementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
implementation 'com.google.firebase:firebase-messaging-ktx'
|
||||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
|
||||
// bootpay
|
||||
implementation "io.github.bootpay:android:4.3.4"
|
||||
|
||||
// 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"
|
||||
|
||||
// Glide
|
||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||
|
||||
implementation "com.michalsvec:single-row-calednar:1.0.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:2209417227252155460",
|
||||
"lastPropertyId": "8:7803281435927194929",
|
||||
"name": "PlaybackTracking",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:3889922602505997244",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:874896374244616380",
|
||||
"name": "contentId",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "3:305496269372931228",
|
||||
"name": "totalDuration",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"id": "4:1202262957765031780",
|
||||
"name": "startPosition",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"id": "5:1595250877919247629",
|
||||
"name": "isFree",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "6:4066577743967565922",
|
||||
"name": "isPreview",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "7:7482414752180672089",
|
||||
"name": "endPosition",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"id": "8:7803281435927194929",
|
||||
"name": "playDateTime",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "1:2209417227252155460",
|
||||
"lastIndexId": "0:0",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "758414412471",
|
||||
"project_id": "sodalive-test",
|
||||
"storage_bucket": "sodalive-test.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:758414412471:android:dcea9dff87fa125c7a5b32",
|
||||
"android_client_info": {
|
||||
"package_name": "kr.co.vividnext.sodalive.debug"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "758414412471-g35socquiplhaamhfl4e6bsta5blabi7.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAeNDVDY_r5afz97L1NPvQC6oFy5lPXHNI"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "758414412471-g35socquiplhaamhfl4e6bsta5blabi7.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
|
@ -2,7 +2,34 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
||||
|
||||
<application
|
||||
android:name=".app.SodaLiveApp"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
@ -10,7 +37,7 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SodaLive"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
@ -25,6 +52,43 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".main.MainActivity" />
|
||||
<activity android:name=".user.login.LoginActivity" />
|
||||
<activity android:name=".user.signup.SignUpActivity" />
|
||||
<activity android:name=".settings.terms.TermsActivity" />
|
||||
<activity android:name=".user.find_password.FindPasswordActivity" />
|
||||
<activity android:name=".mypage.can.status.CanStatusActivity" />
|
||||
<activity android:name=".mypage.can.charge.CanChargeActivity" />
|
||||
<activity android:name=".mypage.can.payment.CanPaymentActivity" />
|
||||
<activity android:name=".live.room.create.LiveRoomCreateActivity" />
|
||||
<activity android:name=".live.room.update.LiveRoomEditActivity" />
|
||||
<activity android:name=".live.reservation.complete.LiveReservationCompleteActivity" />
|
||||
<activity android:name=".live.room.LiveRoomActivity" />
|
||||
<activity android:name=".explorer.profile.UserProfileActivity" />
|
||||
<activity android:name=".explorer.profile.donation.UserProfileDonationAllViewActivity" />
|
||||
<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=".settings.SettingsActivity" />
|
||||
<activity android:name=".settings.signout.SignOutActivity" />
|
||||
<activity android:name=".settings.notice.NoticeActivity" />
|
||||
<activity android:name=".settings.notice.NoticeDetailActivity" />
|
||||
<activity android:name=".settings.event.EventActivity" />
|
||||
<activity android:name=".settings.event.EventDetailActivity" />
|
||||
<activity android:name=".settings.notification.NotificationSettingsActivity" />
|
||||
<activity android:name=".live.reservation_status.LiveReservationStatusActivity" />
|
||||
<activity android:name=".live.reservation_status.LiveReservationCancelActivity" />
|
||||
<activity android:name=".audio_content.AudioContentActivity" />
|
||||
<activity android:name=".audio_content.detail.AudioContentDetailActivity" />
|
||||
<activity android:name=".audio_content.modify.AudioContentModifyActivity" />
|
||||
<activity android:name=".audio_content.order.AudioContentOrderListActivity" />
|
||||
<activity android:name=".audio_content.upload.AudioContentUploadActivity" />
|
||||
<activity android:name=".following.FollowingCreatorActivity" />
|
||||
<activity android:name=".live.now.all.LiveNowAllActivity" />
|
||||
<activity android:name=".live.reservation.all.LiveReservationAllActivity" />
|
||||
<activity android:name=".mypage.service_center.ServiceCenterActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
|
@ -32,6 +96,27 @@
|
|||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight" />
|
||||
</application>
|
||||
|
||||
<service
|
||||
android:name=".common.SodaLiveService"
|
||||
android:stopWithTask="false" />
|
||||
|
||||
<service android:name=".audio_content.AudioContentPlayService" />
|
||||
|
||||
<!-- [START firebase_service] -->
|
||||
<service
|
||||
android:name=".fcm.SodaFirebaseMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<!-- [END firebase_service] -->
|
||||
|
||||
<!-- [START fcm_default_channel] -->
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
android:value="@string/default_notification_channel_id" />
|
||||
<!-- [END fcm_default_channel] -->
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
package kr.co.vividnext.sodalive.agora
|
||||
|
||||
import android.content.Context
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.agora.rtc2.Constants
|
||||
import io.agora.rtc2.IRtcEngineEventHandler
|
||||
import io.agora.rtc2.RtcEngine
|
||||
import io.agora.rtm.ErrorInfo
|
||||
import io.agora.rtm.ResultCallback
|
||||
import io.agora.rtm.RtmChannel
|
||||
import io.agora.rtm.RtmChannelListener
|
||||
import io.agora.rtm.RtmClient
|
||||
import io.agora.rtm.RtmClientListener
|
||||
import io.agora.rtm.SendMessageOptions
|
||||
import kr.co.vividnext.sodalive.BuildConfig
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomRequestType
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class Agora(
|
||||
private val context: Context,
|
||||
private val rtcEventHandler: IRtcEngineEventHandler,
|
||||
private val rtmClientListener: RtmClientListener
|
||||
) {
|
||||
// RTM client instance
|
||||
private var rtmClient: RtmClient? = null
|
||||
|
||||
// RTM channel instance
|
||||
private var rtmChannel: RtmChannel? = null
|
||||
|
||||
private var rtcEngine: RtcEngine? = null
|
||||
|
||||
init {
|
||||
initAgoraEngine()
|
||||
}
|
||||
|
||||
private fun initAgoraEngine() {
|
||||
try {
|
||||
rtcEngine = RtcEngine.create(
|
||||
context,
|
||||
BuildConfig.AGORA_APP_ID,
|
||||
rtcEventHandler
|
||||
)
|
||||
|
||||
rtcEngine!!.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING)
|
||||
rtcEngine!!.setAudioProfile(
|
||||
Constants.AUDIO_PROFILE_MUSIC_HIGH_QUALITY_STEREO,
|
||||
Constants.AUDIO_SCENARIO_GAME_STREAMING
|
||||
)
|
||||
rtcEngine!!.enableAudio()
|
||||
rtcEngine!!.enableAudioVolumeIndication(500, 3, true)
|
||||
|
||||
rtmClient = RtmClient.createInstance(
|
||||
context,
|
||||
BuildConfig.AGORA_APP_ID,
|
||||
rtmClientListener
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun deInitAgoraEngine() {
|
||||
if (rtcEngine != null) {
|
||||
rtcEngine!!.leaveChannel()
|
||||
|
||||
thread {
|
||||
RtcEngine.destroy()
|
||||
rtcEngine = null
|
||||
}
|
||||
}
|
||||
|
||||
rtmChannel?.leave(null)
|
||||
rtmChannel?.release()
|
||||
rtmClient?.logout(null)
|
||||
}
|
||||
|
||||
fun inputChat(message: String) {
|
||||
val rtmMessage = rtmClient!!.createMessage()
|
||||
rtmMessage.text = message
|
||||
|
||||
rtmChannel!!.sendMessage(
|
||||
rtmMessage,
|
||||
object : ResultCallback<Void?> {
|
||||
override fun onSuccess(p0: Void?) {
|
||||
Logger.e("sendMessage - onSuccess")
|
||||
}
|
||||
|
||||
override fun onFailure(p0: ErrorInfo) {
|
||||
Logger.e("sendMessage fail - ${p0.errorCode}")
|
||||
Logger.e("sendMessage fail - ${p0.errorDescription}")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun joinRtcChannel(uid: Int, rtcToken: String, channelName: String) {
|
||||
rtcEngine!!.joinChannel(
|
||||
rtcToken,
|
||||
channelName,
|
||||
"",
|
||||
uid
|
||||
)
|
||||
}
|
||||
|
||||
fun createRtmChannelAndLogin(
|
||||
uid: String,
|
||||
rtmToken: String,
|
||||
channelName: String,
|
||||
rtmChannelListener: RtmChannelListener,
|
||||
rtmChannelJoinSuccess: () -> Unit,
|
||||
rtmChannelJoinFail: () -> Unit
|
||||
) {
|
||||
rtmChannel = rtmClient!!.createChannel(channelName, rtmChannelListener)
|
||||
rtmClient!!.login(
|
||||
rtmToken,
|
||||
uid,
|
||||
object : ResultCallback<Void> {
|
||||
override fun onSuccess(p0: Void?) {
|
||||
rtmChannel!!.join(object : ResultCallback<Void> {
|
||||
override fun onSuccess(p0: Void?) {
|
||||
Logger.e("rtmChannel join - onSuccess")
|
||||
rtmChannelJoinSuccess()
|
||||
}
|
||||
|
||||
override fun onFailure(p0: ErrorInfo?) {
|
||||
rtmChannelJoinFail()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onFailure(p0: ErrorInfo?) {
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun sendRawMessageToGroup(
|
||||
rawMessage: ByteArray,
|
||||
onSuccess: (() -> Unit)? = null,
|
||||
onFailure: (() -> Unit)? = null
|
||||
) {
|
||||
val message = rtmClient!!.createMessage()
|
||||
message.rawMessage = rawMessage
|
||||
rtmChannel!!.sendMessage(
|
||||
message,
|
||||
object : ResultCallback<Void?> {
|
||||
override fun onSuccess(p0: Void?) {
|
||||
Logger.e("sendMessage - onSuccess")
|
||||
onSuccess?.invoke()
|
||||
}
|
||||
|
||||
override fun onFailure(p0: ErrorInfo) {
|
||||
Logger.e("sendMessage fail - ${p0.errorCode}")
|
||||
Logger.e("sendMessage fail - ${p0.errorDescription}")
|
||||
onFailure?.invoke()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setClientRole(role: Int) {
|
||||
rtcEngine!!.setClientRole(role)
|
||||
}
|
||||
|
||||
fun muteLocalAudioStream(muted: Boolean) {
|
||||
rtcEngine?.muteLocalAudioStream(muted)
|
||||
}
|
||||
|
||||
fun muteAllRemoteAudioStreams(mute: Boolean) {
|
||||
rtcEngine?.muteAllRemoteAudioStreams(mute)
|
||||
}
|
||||
|
||||
fun sendRawMessageToPeer(
|
||||
receiverUid: String,
|
||||
requestType: LiveRoomRequestType,
|
||||
onSuccess: () -> Unit
|
||||
) {
|
||||
val option = SendMessageOptions()
|
||||
|
||||
val message = rtmClient!!.createMessage()
|
||||
message.rawMessage = requestType.toString().toByteArray()
|
||||
|
||||
rtmClient!!.sendMessageToPeer(
|
||||
receiverUid,
|
||||
message,
|
||||
option,
|
||||
object : ResultCallback<Void?> {
|
||||
override fun onSuccess(aVoid: Void?) {
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
override fun onFailure(errorInfo: ErrorInfo) {
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun rtmChannelIsNull(): Boolean {
|
||||
return rtmChannel == null
|
||||
}
|
||||
|
||||
fun getConnectionState(): Int {
|
||||
return rtcEngine!!.connectionState
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package kr.co.vividnext.sodalive.app
|
||||
|
||||
import android.app.Application
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.orhanobut.logger.AndroidLogAdapter
|
||||
import com.orhanobut.logger.Logger
|
||||
import kr.co.vividnext.sodalive.BuildConfig
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.di.AppDI
|
||||
|
||||
class SodaLiveApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Logger.addLogAdapter(object : AndroidLogAdapter() {
|
||||
override fun isLoggable(priority: Int, tag: String?): Boolean {
|
||||
return BuildConfig.DEBUG && isDebuggable()
|
||||
}
|
||||
})
|
||||
|
||||
AppDI(applicationContext, BuildConfig.DEBUG && isDebuggable())
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
|
||||
SharedPreferenceManager.init(applicationContext)
|
||||
}
|
||||
|
||||
private fun isDebuggable(): Boolean {
|
||||
var debuggable = false
|
||||
|
||||
try {
|
||||
val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getApplicationInfo(
|
||||
packageName,
|
||||
PackageManager.ApplicationInfoFlags.of(0L)
|
||||
)
|
||||
} else {
|
||||
packageManager.getApplicationInfo(packageName, 0)
|
||||
}
|
||||
debuggable = 0 != appInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
/* debuggable variable will remain false */
|
||||
}
|
||||
|
||||
return debuggable
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.util.TimeZone
|
||||
|
||||
data class AddAllPlaybackTrackingRequest(
|
||||
@SerializedName("timezone") val timezone: String = TimeZone.getDefault().id,
|
||||
@SerializedName("trackingDataList") val trackingDataList: List<PlaybackTrackingData>
|
||||
)
|
||||
|
||||
data class PlaybackTrackingData(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("playDateTime") val playDateTime: String,
|
||||
@SerializedName("isPreview") val isPreview: Boolean,
|
||||
)
|
|
@ -0,0 +1,212 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
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.TextView
|
||||
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.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||
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.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentActivity : BaseActivity<ActivityAudioContentBinding>(
|
||||
ActivityAudioContentBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var audioContentAdapter: AudioContentAdapter
|
||||
|
||||
private var userId: Long = 0
|
||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
userId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
|
||||
activityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
viewModel.page = 1
|
||||
viewModel.getAudioContentList(userId = userId) { finish() }
|
||||
}
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (userId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentList(userId = userId) { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "콘텐츠 전체보기"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
audioContentAdapter = AudioContentAdapter {
|
||||
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
binding.rvAudioContent.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvAudioContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
audioContentAdapter.itemCount - 1 -> {
|
||||
outRect.bottom = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvAudioContent.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.getAudioContentList(userId = userId) { }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvAudioContent.adapter = audioContentAdapter
|
||||
|
||||
binding.tvSortNewest.setOnClickListener {
|
||||
viewModel.changeSort(AudioContentViewModel.Sort.NEWEST)
|
||||
}
|
||||
|
||||
binding.tvSortPriceLow.setOnClickListener {
|
||||
viewModel.changeSort(AudioContentViewModel.Sort.PRICE_LOW)
|
||||
}
|
||||
|
||||
binding.tvSortPriceHigh.setOnClickListener {
|
||||
viewModel.changeSort(AudioContentViewModel.Sort.PRICE_HIGH)
|
||||
}
|
||||
|
||||
if (userId == SharedPreferenceManager.userId) {
|
||||
binding.tvNewContent.visibility = View.VISIBLE
|
||||
binding.tvNewContent.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
applicationContext,
|
||||
AudioContentUploadActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.tvNewContent.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.audioContentListLiveData.observe(this) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
audioContentAdapter.items.clear()
|
||||
binding.rvAudioContent.scrollToPosition(0)
|
||||
}
|
||||
|
||||
binding.tvTotalCount.text = "${it.totalCount}"
|
||||
|
||||
audioContentAdapter.items.addAll(it.items)
|
||||
audioContentAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
viewModel.sort.observe(this) {
|
||||
deselectSort()
|
||||
selectSort(
|
||||
when (it) {
|
||||
AudioContentViewModel.Sort.PRICE_HIGH -> {
|
||||
binding.tvSortPriceHigh
|
||||
}
|
||||
|
||||
AudioContentViewModel.Sort.PRICE_LOW -> {
|
||||
binding.tvSortPriceLow
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.tvSortNewest
|
||||
}
|
||||
}
|
||||
)
|
||||
viewModel.getAudioContentList(userId = userId) { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun deselectSort() {
|
||||
val color = ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_88e2e2e2
|
||||
)
|
||||
|
||||
binding.tvSortNewest.setTextColor(color)
|
||||
binding.tvSortPriceLow.setTextColor(color)
|
||||
binding.tvSortPriceHigh.setTextColor(color)
|
||||
}
|
||||
|
||||
private fun selectSort(view: TextView) {
|
||||
view.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_e2e2e2
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
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.databinding.ItemAudioContentBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListItem
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AudioContentAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<GetAudioContentListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentListItem) {
|
||||
binding.ivCover.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvTheme.text = item.themeStr
|
||||
binding.tvDuration.text = item.duration
|
||||
binding.tvLikeCount.text = item.likeCount.moneyFormat()
|
||||
binding.tvCommentCount.text = item.commentCount.moneyFormat()
|
||||
|
||||
if (item.price < 1) {
|
||||
binding.tvPrice.text = "무료"
|
||||
binding.tvPrice.setCompoundDrawables(null, null, null, null)
|
||||
} else {
|
||||
binding.tvPrice.text = item.price.moneyFormat()
|
||||
binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds(
|
||||
R.drawable.ic_coin_w,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
binding.iv19.visibility = if (item.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.GetAudioContentCommentListResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.RegisterAudioContentCommentRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.GetAudioContentDetailResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem
|
||||
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.GetAudioContentThemeResponse
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListResponse
|
||||
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 AudioContentApi {
|
||||
@GET("/audio-content")
|
||||
fun getAudioContentList(
|
||||
@Query("creator-id") id: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Query("sort-type") sort: AudioContentViewModel.Sort,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentListResponse>>
|
||||
|
||||
@GET("/audio-content/theme")
|
||||
fun getAudioContentThemeList(
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetAudioContentThemeResponse>>>
|
||||
|
||||
@POST("/audio-content")
|
||||
@Multipart
|
||||
fun uploadAudioContent(
|
||||
@Part coverImage: MultipartBody.Part,
|
||||
@Part contentFile: MultipartBody.Part,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/audio-content/{id}")
|
||||
fun getAudioContentDetail(
|
||||
@Path("id") id: Long,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentDetailResponse>>
|
||||
|
||||
@POST("/order/audio-content")
|
||||
fun orderAudioContent(
|
||||
@Body request: OrderRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/order/audio-content")
|
||||
fun getAudioContentOrderList(
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentOrderListResponse>>
|
||||
|
||||
@POST("/audio-content/playback-tracking")
|
||||
fun addAllPlaybackTracking(
|
||||
@Body request: AddAllPlaybackTrackingRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@POST("/audio-content/comment")
|
||||
fun registerComment(
|
||||
@Body request: RegisterAudioContentCommentRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/audio-content/{id}/comment")
|
||||
fun getAudioContentCommentList(
|
||||
@Path("id") id: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentCommentListResponse>>
|
||||
|
||||
@GET("/audio-content/comment/{id}")
|
||||
fun getAudioContentCommentCommentList(
|
||||
@Path("id") id: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentCommentListResponse>>
|
||||
|
||||
@PUT("/audio-content/like")
|
||||
fun likeContent(
|
||||
@Body request: PutAudioContentLikeRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<PutAudioContentLikeResponse>>
|
||||
|
||||
@PUT("/audio-content")
|
||||
@Multipart
|
||||
fun modifyAudioContent(
|
||||
@Part coverImage: MultipartBody.Part?,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@DELETE("/audio-content/{id}")
|
||||
fun deleteAudioContent(
|
||||
@Path("id") id: Long,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/audio-content/main")
|
||||
fun getMain(
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentMainResponse>>
|
||||
|
||||
@GET("/audio-content/main/new")
|
||||
fun getNewContentOfTheme(
|
||||
@Query("theme") theme: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetAudioContentMainItem>>>
|
||||
|
||||
@POST("/audio-content/donation")
|
||||
fun donation(
|
||||
@Body request: AudioContentDonationRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.main.MainActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentPlayService :
|
||||
Service(),
|
||||
MediaPlayer.OnPreparedListener,
|
||||
MediaPlayer.OnCompletionListener {
|
||||
|
||||
private var playbackTrackingId: Long = 0
|
||||
private val playbackTrackingRepository: PlaybackTrackingRepository by inject()
|
||||
|
||||
private lateinit var mediaPlayer: MediaPlayer
|
||||
private var url: String? = null
|
||||
private var title: String? = null
|
||||
private var isFree: Boolean? = null
|
||||
private var isPreview: Boolean? = null
|
||||
private var nickname: String? = null
|
||||
private var contentId: Long? = null
|
||||
private var creatorId: Long? = null
|
||||
private var coverImageUrl: String? = null
|
||||
|
||||
private var isPlaying = false
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var changeMediaPlayerPositionRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val intent = Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, mediaPlayer.currentPosition)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
|
||||
}
|
||||
sendBroadcast(intent)
|
||||
handler.postDelayed(this, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.action) {
|
||||
MusicAction.INIT.name -> {
|
||||
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (this.contentId != null && this.contentId == contentId) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_CHANGE_UI,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_DURATION,
|
||||
mediaPlayer.duration
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PROGRESS,
|
||||
mediaPlayer.currentPosition
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
this.contentId != null &&
|
||||
title != null &&
|
||||
nickname != null &&
|
||||
coverImageUrl != null
|
||||
) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_TITLE,
|
||||
title
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_NICKNAME,
|
||||
nickname
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL,
|
||||
coverImageUrl
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
this@AudioContentPlayService.contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
false
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.PLAY.name -> {
|
||||
if (!isPlaying) {
|
||||
mediaPlayer.start()
|
||||
toggleIsPlaying()
|
||||
updateNotification()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.PAUSE.name -> {
|
||||
if (isPlaying) {
|
||||
mediaPlayer.pause()
|
||||
toggleIsPlaying()
|
||||
updateNotification()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.STOP.name -> {
|
||||
if (this::mediaPlayer.isInitialized) {
|
||||
mediaPlayer.stop()
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
toggleIsPlaying(false)
|
||||
onStopService()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.CONDITIONAL_STOP.name -> {
|
||||
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (
|
||||
this.contentId != null &&
|
||||
this.contentId == contentId &&
|
||||
this::mediaPlayer.isInitialized
|
||||
) {
|
||||
mediaPlayer.stop()
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
toggleIsPlaying(false)
|
||||
onStopService()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.PROGRESS.name -> {
|
||||
val progress = intent.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, 0)
|
||||
if (progress > 0) {
|
||||
if (contentId != null) saveNewPlaybackTracking(
|
||||
totalDuration = mediaPlayer.duration,
|
||||
progress = progress
|
||||
)
|
||||
mediaPlayer.seekTo(progress)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val contentId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (contentId != null && this.contentId == contentId) {
|
||||
if (isPlaying) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
MusicAction.PAUSE
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
MusicAction.PLAY
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
url = intent?.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_URL)
|
||||
title = intent?.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE)
|
||||
isFree = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_FREE,
|
||||
true
|
||||
)
|
||||
isPreview = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PREVIEW,
|
||||
true
|
||||
)
|
||||
nickname = intent?.getStringExtra(Constants.EXTRA_NICKNAME)
|
||||
coverImageUrl = intent?.getStringExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL
|
||||
)
|
||||
creatorId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID, 0)
|
||||
this.contentId = contentId
|
||||
|
||||
if (url != null) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_LOADING,
|
||||
true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (isPlaying) {
|
||||
mediaPlayer.stop()
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
|
||||
mediaPlayer.release()
|
||||
toggleIsPlaying()
|
||||
}
|
||||
|
||||
initMediaPlayer()
|
||||
mediaPlayer.setDataSource(url)
|
||||
mediaPlayer.prepareAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (this::mediaPlayer.isInitialized) {
|
||||
mediaPlayer.release()
|
||||
}
|
||||
|
||||
onStopService()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCompletion(mp: MediaPlayer?) {
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
|
||||
if (SharedPreferenceManager.isContentPlayLoop) {
|
||||
saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0)
|
||||
mediaPlayer.start()
|
||||
} else {
|
||||
toggleIsPlaying(false)
|
||||
mediaPlayer.release()
|
||||
onStopService()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleIsPlaying(isPlaying: Boolean? = null) {
|
||||
this.isPlaying = isPlaying ?: !this.isPlaying
|
||||
if (this.isPlaying) {
|
||||
handler.postDelayed(changeMediaPlayerPositionRunnable, 1000)
|
||||
} else {
|
||||
handler.removeCallbacks(changeMediaPlayerPositionRunnable)
|
||||
}
|
||||
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_CHANGE_UI,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
this@AudioContentPlayService.isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (isPlaying != null && !isPlaying) {
|
||||
resetAudioData()
|
||||
}
|
||||
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
this@AudioContentPlayService.isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
contentId != null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun resetAudioData() {
|
||||
url = null
|
||||
title = null
|
||||
nickname = null
|
||||
contentId = null
|
||||
}
|
||||
|
||||
private fun initMediaPlayer() {
|
||||
mediaPlayer = MediaPlayer()
|
||||
mediaPlayer.setOnPreparedListener(this)
|
||||
mediaPlayer.setOnCompletionListener(this)
|
||||
mediaPlayer.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPrepared(mp: MediaPlayer?) {
|
||||
saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0)
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
MusicAction.PLAY
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_DURATION,
|
||||
mediaPlayer.duration
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ALERT_PREVIEW,
|
||||
true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
false
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_TITLE,
|
||||
title
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_NICKNAME,
|
||||
nickname
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL,
|
||||
coverImageUrl
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateNotification() {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
|
||||
val channelId = "audio_content_play_channel"
|
||||
val notificationManager =
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
"콘텐츠 알림 채널",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val playPauseIcon =
|
||||
if (isPlaying) R.drawable.ic_noti_pause else R.drawable.ic_noti_play
|
||||
val playPauseAction =
|
||||
if (isPlaying) MusicAction.PAUSE.name else MusicAction.PLAY.name
|
||||
|
||||
Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.load(coverImageUrl)
|
||||
.into(object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
val notificationBuilder = NotificationCompat
|
||||
.Builder(this@AudioContentPlayService, channelId)
|
||||
.setSmallIcon(R.drawable.ic_noti)
|
||||
.setLargeIcon(resource)
|
||||
.setContentTitle(title ?: "오디오 콘텐츠")
|
||||
.setContentText(nickname ?: "")
|
||||
.setContentIntent(pendingIntent)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.addAction(
|
||||
NotificationCompat
|
||||
.Action
|
||||
.Builder(
|
||||
playPauseIcon,
|
||||
"Play_or_Pause",
|
||||
getServiceIntent(playPauseAction)
|
||||
).build()
|
||||
)
|
||||
.addAction(
|
||||
NotificationCompat
|
||||
.Action
|
||||
.Builder(
|
||||
R.drawable.ic_noti_stop,
|
||||
"Stop",
|
||||
getServiceIntent(MusicAction.STOP.name)
|
||||
).build()
|
||||
)
|
||||
|
||||
notificationBuilder.setStyle(
|
||||
androidx.media.app.NotificationCompat.MediaStyle()
|
||||
.setShowActionsInCompactView(0, 1)
|
||||
)
|
||||
|
||||
startForeground(1, notificationBuilder.build())
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getServiceIntent(action: String): PendingIntent {
|
||||
val intent = Intent(this, AudioContentPlayService::class.java)
|
||||
intent.action = action
|
||||
return PendingIntent.getService(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveNewPlaybackTracking(totalDuration: Int, progress: Int) {
|
||||
if (creatorId != SharedPreferenceManager.userId) {
|
||||
playbackTrackingId = playbackTrackingRepository.savePlaybackTracking(
|
||||
PlaybackTracking(
|
||||
contentId = contentId!!,
|
||||
totalDuration = totalDuration,
|
||||
startPosition = progress,
|
||||
isFree = isFree!!,
|
||||
isPreview = isPreview!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setEndPositionPlaybackTracking(progress: Int) {
|
||||
if (creatorId != SharedPreferenceManager.userId && playbackTrackingId > 0) {
|
||||
val playbackTracking = playbackTrackingRepository
|
||||
.getPlaybackTracking(playbackTrackingId)
|
||||
|
||||
if (playbackTracking != null) {
|
||||
playbackTracking.endPosition = progress
|
||||
playbackTrackingRepository.savePlaybackTracking(playbackTracking)
|
||||
}
|
||||
|
||||
playbackTrackingId = 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStopService() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
stopForeground(true)
|
||||
}
|
||||
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
enum class MusicAction {
|
||||
PLAY, PAUSE, STOP, PROGRESS, INIT, CONDITIONAL_STOP
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
|
||||
import kr.co.vividnext.sodalive.user.UserApi
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.util.TimeZone
|
||||
|
||||
class AudioContentRepository(
|
||||
private val api: AudioContentApi,
|
||||
private val userApi: UserApi
|
||||
) {
|
||||
fun getAudioContentList(
|
||||
id: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
sort: AudioContentViewModel.Sort,
|
||||
token: String
|
||||
) = api.getAudioContentList(
|
||||
id = id,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
sort = sort,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentThemeList(token: String) = api.getAudioContentThemeList(token)
|
||||
|
||||
fun uploadAudioContent(
|
||||
coverImage: MultipartBody.Part,
|
||||
contentFile: MultipartBody.Part,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
) = api.uploadAudioContent(
|
||||
coverImage = coverImage,
|
||||
contentFile = contentFile,
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun modifyAudioContent(
|
||||
coverImage: MultipartBody.Part? = null,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
) = api.modifyAudioContent(
|
||||
coverImage = coverImage,
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun deleteAudioContent(
|
||||
id: Long,
|
||||
token: String
|
||||
) = api.deleteAudioContent(
|
||||
id = id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentDetail(audioContentId: Long, token: String) = api.getAudioContentDetail(
|
||||
id = audioContentId,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun registerNotification(
|
||||
creatorId: Long,
|
||||
token: String
|
||||
) = userApi.creatorFollow(
|
||||
request = CreatorFollowRequestRequest(creatorId = creatorId),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun unRegisterNotification(
|
||||
creatorId: Long,
|
||||
token: String
|
||||
) = userApi.creatorUnFollow(
|
||||
request = CreatorFollowRequestRequest(creatorId = creatorId),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun orderContent(
|
||||
contentId: Long,
|
||||
orderType: OrderType,
|
||||
token: String
|
||||
) = api.orderAudioContent(
|
||||
request = OrderRequest(
|
||||
contentId = contentId,
|
||||
orderType = orderType,
|
||||
container = "aos"
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentOrderList(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getAudioContentOrderList(
|
||||
page = page - 1,
|
||||
size = size,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun addAllPlaybackTracking(
|
||||
request: AddAllPlaybackTrackingRequest,
|
||||
token: String
|
||||
) = api.addAllPlaybackTracking(request, authHeader = token)
|
||||
|
||||
fun likeContent(
|
||||
request: PutAudioContentLikeRequest,
|
||||
token: String
|
||||
) = api.likeContent(request, authHeader = token)
|
||||
|
||||
fun getMain(token: String) = api.getMain(authHeader = token)
|
||||
|
||||
fun getNewContentOfTheme(theme: String, token: String) = api.getNewContentOfTheme(
|
||||
theme = theme,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun donation(
|
||||
contentId: Long,
|
||||
can: Int,
|
||||
comment: String,
|
||||
token: String
|
||||
) = api.donation(
|
||||
request = AudioContentDonationRequest(
|
||||
contentId = contentId,
|
||||
donationCan = can,
|
||||
comment = comment
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.gson.annotations.SerializedName
|
||||
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.explorer.profile.GetAudioContentListResponse
|
||||
|
||||
class AudioContentViewModel(private val repository: AudioContentRepository) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _audioContentListLiveData = MutableLiveData<GetAudioContentListResponse>()
|
||||
val audioContentListLiveData: LiveData<GetAudioContentListResponse>
|
||||
get() = _audioContentListLiveData
|
||||
|
||||
private val _sort = MutableLiveData(Sort.NEWEST)
|
||||
val sort: LiveData<Sort>
|
||||
get() = _sort
|
||||
|
||||
enum class Sort {
|
||||
@SerializedName("NEWEST")
|
||||
NEWEST,
|
||||
|
||||
@SerializedName("PRICE_HIGH")
|
||||
PRICE_HIGH,
|
||||
|
||||
@SerializedName("PRICE_LOW")
|
||||
PRICE_LOW
|
||||
}
|
||||
|
||||
private var isLast = false
|
||||
var page = 1
|
||||
private val size = 10
|
||||
|
||||
fun getAudioContentList(userId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!_isLoading.value!! && !isLast) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentList(
|
||||
id = userId,
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}",
|
||||
sort = _sort.value!!
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_audioContentListLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeSort(sort: Sort) {
|
||||
page = 1
|
||||
isLast = false
|
||||
_sort.postValue(sort)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import io.objectbox.annotation.Entity
|
||||
import io.objectbox.annotation.Id
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Entity
|
||||
data class PlaybackTracking(
|
||||
@Id
|
||||
var id: Long = 0,
|
||||
var contentId: Long,
|
||||
var totalDuration: Int,
|
||||
var startPosition: Int,
|
||||
var isFree: Boolean,
|
||||
var isPreview: Boolean,
|
||||
var endPosition: Int? = null,
|
||||
var playDateTime: String = SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
Locale.getDefault()
|
||||
).format(Date())
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ObjectBox
|
||||
|
||||
class PlaybackTrackingRepository(private val objectBox: ObjectBox) {
|
||||
fun savePlaybackTracking(data: PlaybackTracking): Long {
|
||||
return objectBox.playbackTrackingBox.put(data)
|
||||
}
|
||||
|
||||
fun getPlaybackTracking(id: Long): PlaybackTracking? {
|
||||
val query = objectBox.playbackTrackingBox
|
||||
.query(PlaybackTracking_.id.equal(id))
|
||||
.build()
|
||||
|
||||
val playbackTracking = query.findFirst()
|
||||
query.close()
|
||||
return playbackTracking
|
||||
}
|
||||
|
||||
fun getAllPlaybackTracking(): List<PlaybackTracking> {
|
||||
return objectBox
|
||||
.playbackTrackingBox
|
||||
.all
|
||||
}
|
||||
|
||||
fun removeAllPlaybackTracking() {
|
||||
objectBox.playbackTrackingBox.removeAll()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AudioContentCommentAdapter(
|
||||
private val onItemClick: (GetAudioContentCommentListItem) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentCommentAdapter.ViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentCommentListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentCommentBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: GetAudioContentCommentListItem) {
|
||||
binding.ivCommentProfile.load(item.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
val tvCommentLayoutParams = binding.tvComment.layoutParams as LinearLayout.LayoutParams
|
||||
val coin = item.donationCoin
|
||||
if (coin > 0) {
|
||||
tvCommentLayoutParams.topMargin = 0
|
||||
binding.llDonationCoin.visibility = View.VISIBLE
|
||||
binding.tvDonationCoin.text = coin.moneyFormat()
|
||||
binding.llDonationCoin.setBackgroundResource(
|
||||
when {
|
||||
coin >= 100000 -> {
|
||||
R.drawable.bg_round_corner_10_7_973a3a
|
||||
}
|
||||
|
||||
coin >= 50000 -> {
|
||||
R.drawable.bg_round_corner_10_7_d85e37
|
||||
}
|
||||
|
||||
coin >= 10000 -> {
|
||||
R.drawable.bg_round_corner_10_7_d38c38
|
||||
}
|
||||
|
||||
coin >= 5000 -> {
|
||||
R.drawable.bg_round_corner_10_7_59548f
|
||||
}
|
||||
|
||||
coin >= 1000 -> {
|
||||
R.drawable.bg_round_corner_10_7_4d6aa4
|
||||
}
|
||||
|
||||
coin >= 500 -> {
|
||||
R.drawable.bg_round_corner_10_7_2d7390
|
||||
}
|
||||
|
||||
else -> {
|
||||
R.drawable.bg_round_corner_10_7_548f7d
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
tvCommentLayoutParams.topMargin = 13.3f.dpToPx().toInt()
|
||||
binding.llDonationCoin.visibility = View.GONE
|
||||
}
|
||||
binding.tvComment.layoutParams = tvCommentLayoutParams
|
||||
|
||||
binding.tvComment.text = item.comment
|
||||
binding.tvCommentDate.text = item.date
|
||||
binding.tvCommentNickname.text = item.nickname
|
||||
|
||||
binding.tvWriteReply.text = if (item.replyCount > 0) {
|
||||
"답글 ${item.replyCount}개"
|
||||
} else {
|
||||
"답글 쓰기"
|
||||
}
|
||||
|
||||
binding.tvWriteReply.setOnClickListener { onItemClick(item) }
|
||||
binding.root.setOnClickListener { onItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentCommentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentCommentBinding
|
||||
|
||||
class AudioContentCommentFragment(private val audioContentId: Long) : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: DialogAudioContentCommentBinding
|
||||
|
||||
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 = DialogAudioContentCommentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val commentListFragmentTag = "COMMENT_LIST_FRAGMENT"
|
||||
val commentListFragment = AudioContentCommentListFragment.newInstance(
|
||||
audioContentId = audioContentId
|
||||
)
|
||||
val fragmentTransaction = childFragmentManager.beginTransaction()
|
||||
fragmentTransaction.add(R.id.fl_container, commentListFragment, commentListFragmentTag)
|
||||
fragmentTransaction.addToBackStack(commentListFragmentTag)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
|
||||
fun hideCommentDialog() {
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
fun onClickComment(comment: GetAudioContentCommentListItem) {
|
||||
val commentReplyFragmentTag = "COMMENT_REPLY_FRAGMENT"
|
||||
val commentReplyFragment = AudioContentCommentReplyFragment.newInstance(
|
||||
audioContentId = audioContentId,
|
||||
comment = comment
|
||||
)
|
||||
val fragmentTransaction = childFragmentManager.beginTransaction()
|
||||
fragmentTransaction.add(R.id.fl_container, commentReplyFragment, commentReplyFragmentTag)
|
||||
fragmentTransaction.addToBackStack(commentReplyFragmentTag)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
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.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentCommentListBinding>(
|
||||
FragmentAudioContentCommentListBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentCommentListViewModel by inject()
|
||||
|
||||
private lateinit var imm: InputMethodManager
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AudioContentCommentAdapter
|
||||
|
||||
private var audioContentId: Long = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
viewModel.getCommentList(audioContentId = audioContentId) { hideDialog() }
|
||||
}
|
||||
|
||||
private fun hideDialog() {
|
||||
(parentFragment as AudioContentCommentFragment).hideCommentDialog()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
binding.ivClose.setOnClickListener { hideDialog() }
|
||||
|
||||
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.ivCommentSend.setOnClickListener {
|
||||
hideKeyboard()
|
||||
val comment = binding.etComment.text.toString()
|
||||
binding.etComment.setText("")
|
||||
viewModel.registerComment(audioContentId, comment)
|
||||
}
|
||||
|
||||
adapter = AudioContentCommentAdapter {
|
||||
(parentFragment as AudioContentCommentFragment).onClickComment(it)
|
||||
}
|
||||
|
||||
val recyclerView = binding.rvComment
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
activity,
|
||||
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()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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.getCommentList(audioContentId = audioContentId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.totalCommentCount.observe(viewLifecycleOwner) {
|
||||
binding.tvCommentCount.text = "$it"
|
||||
}
|
||||
|
||||
viewModel.commentList.observe(viewLifecycleOwner) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
adapter.items.clear()
|
||||
binding.rvComment.scrollToPosition(0)
|
||||
}
|
||||
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
imm.hideSoftInputFromWindow(view?.windowToken, 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(audioContentId: Long): AudioContentCommentListFragment {
|
||||
val args = Bundle()
|
||||
args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
|
||||
|
||||
val fragment = AudioContentCommentListFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
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
|
||||
|
||||
class AudioContentCommentListViewModel(
|
||||
private val repository: AudioContentCommentRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _commentList = MutableLiveData<List<GetAudioContentCommentListItem>>()
|
||||
val commentList: LiveData<List<GetAudioContentCommentListItem>>
|
||||
get() = _commentList
|
||||
|
||||
private var _totalCommentCount = MutableLiveData(0)
|
||||
val totalCommentCount: LiveData<Int>
|
||||
get() = _totalCommentCount
|
||||
|
||||
var page = 1
|
||||
private var isLast = false
|
||||
private val size = 10
|
||||
|
||||
fun getCommentList(audioContentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!_isLoading.value!! && !isLast) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentCommentList(
|
||||
audioContentId = audioContentId,
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_totalCommentCount.postValue(it.data.totalCount)
|
||||
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_commentList.postValue(it.data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerComment(contentId: Long, comment: String) {
|
||||
if (!_isLoading.value!!) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.registerComment(
|
||||
contentId = contentId,
|
||||
comment = comment,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
page = 1
|
||||
isLast = false
|
||||
getCommentList(contentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentReplyBinding
|
||||
|
||||
class AudioContentCommentReplyAdapter :
|
||||
RecyclerView.Adapter<AudioContentCommentReplyViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentCommentListItem>()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): AudioContentCommentReplyViewHolder {
|
||||
return if (viewType == 0) {
|
||||
AudioContentCommentReplyHeaderViewHolder(
|
||||
ItemAudioContentCommentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
AudioContentCommentReplyItemViewHolder(
|
||||
ItemAudioContentCommentReplyBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AudioContentCommentReplyViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return position
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AudioContentCommentReplyViewHolder(
|
||||
binding: ViewBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
abstract fun bind(item: GetAudioContentCommentListItem)
|
||||
}
|
||||
|
||||
class AudioContentCommentReplyHeaderViewHolder(
|
||||
private val binding: ItemAudioContentCommentBinding
|
||||
) : AudioContentCommentReplyViewHolder(binding) {
|
||||
|
||||
override fun bind(item: GetAudioContentCommentListItem) {
|
||||
binding.ivCommentProfile.load(item.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvComment.text = item.comment
|
||||
binding.tvCommentDate.text = item.date
|
||||
binding.tvCommentNickname.text = item.nickname
|
||||
|
||||
binding.tvWriteReply.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
class AudioContentCommentReplyItemViewHolder(
|
||||
private val binding: ItemAudioContentCommentReplyBinding
|
||||
) : AudioContentCommentReplyViewHolder(binding) {
|
||||
|
||||
override fun bind(item: GetAudioContentCommentListItem) {
|
||||
binding.ivCommentProfile.load(item.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvComment.text = item.comment
|
||||
binding.tvCommentDate.text = item.date
|
||||
binding.tvCommentNickname.text = item.nickname
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
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.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentReplyBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommentReplyBinding>(
|
||||
FragmentAudioContentCommentReplyBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentCommentReplyViewModel by inject()
|
||||
|
||||
private lateinit var imm: InputMethodManager
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AudioContentCommentReplyAdapter
|
||||
|
||||
private var originalComment: GetAudioContentCommentListItem? = null
|
||||
private var audioContentId: Long = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0
|
||||
originalComment = BundleCompat.getParcelable(
|
||||
requireArguments(),
|
||||
Constants.EXTRA_AUDIO_CONTENT_COMMENT,
|
||||
GetAudioContentCommentListItem::class.java
|
||||
)
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (originalComment == null) {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
viewModel.getCommentReplyList(commentId = originalComment!!.id) {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideDialog() {
|
||||
(parentFragment as AudioContentCommentFragment).hideCommentDialog()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
binding.root.setOnClickListener { }
|
||||
|
||||
binding.tvBack.setOnClickListener {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
binding.ivClose.setOnClickListener { hideDialog() }
|
||||
|
||||
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.ivCommentSend.setOnClickListener {
|
||||
hideKeyboard()
|
||||
val comment = binding.etComment.text.toString()
|
||||
binding.etComment.setText("")
|
||||
viewModel.registerComment(audioContentId, originalComment!!.id, comment)
|
||||
}
|
||||
|
||||
adapter = AudioContentCommentReplyAdapter().apply {
|
||||
items.add(originalComment!!)
|
||||
}
|
||||
|
||||
val recyclerView = binding.rvCommentReply
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
activity,
|
||||
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()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 12f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 12f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 12f.dpToPx().toInt()
|
||||
outRect.bottom = 12f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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.getCommentReplyList(originalComment!!.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.commentList.observe(viewLifecycleOwner) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
adapter.items.clear()
|
||||
binding.rvCommentReply.scrollToPosition(0)
|
||||
adapter.items.add(originalComment!!)
|
||||
}
|
||||
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
imm.hideSoftInputFromWindow(view?.windowToken, 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(
|
||||
audioContentId: Long,
|
||||
comment: GetAudioContentCommentListItem
|
||||
): AudioContentCommentReplyFragment {
|
||||
val args = Bundle()
|
||||
args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
|
||||
args.putParcelable(Constants.EXTRA_AUDIO_CONTENT_COMMENT, comment)
|
||||
|
||||
val fragment = AudioContentCommentReplyFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
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
|
||||
|
||||
class AudioContentCommentReplyViewModel(
|
||||
private val repository: AudioContentCommentRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _commentList = MutableLiveData<List<GetAudioContentCommentListItem>>()
|
||||
val commentList: LiveData<List<GetAudioContentCommentListItem>>
|
||||
get() = _commentList
|
||||
|
||||
var page = 1
|
||||
private var isLast = false
|
||||
private val size = 10
|
||||
|
||||
fun getCommentReplyList(commentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!_isLoading.value!! && !isLast) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentCommentReplyList(
|
||||
commentId = commentId,
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_commentList.postValue(it.data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerComment(contentId: Long, commentId: Long, comment: String) {
|
||||
if (!_isLoading.value!!) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.registerComment(
|
||||
contentId = contentId,
|
||||
comment = comment,
|
||||
parentId = commentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
page = 1
|
||||
isLast = false
|
||||
getCommentReplyList(commentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentApi
|
||||
import java.util.TimeZone
|
||||
|
||||
class AudioContentCommentRepository(private val api: AudioContentApi) {
|
||||
fun registerComment(
|
||||
contentId: Long,
|
||||
comment: String,
|
||||
parentId: Long? = null,
|
||||
token: String
|
||||
) = api.registerComment(
|
||||
request = RegisterAudioContentCommentRequest(
|
||||
comment = comment,
|
||||
contentId = contentId,
|
||||
parentId = parentId
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentCommentList(
|
||||
audioContentId: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getAudioContentCommentList(
|
||||
id = audioContentId,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentCommentReplyList(
|
||||
commentId: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getAudioContentCommentCommentList(
|
||||
id = commentId,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
data class GetAudioContentCommentListResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<GetAudioContentCommentListItem>
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class GetAudioContentCommentListItem(
|
||||
@SerializedName("id") val id: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileUrl") val profileUrl: String,
|
||||
@SerializedName("comment") val comment: String,
|
||||
@SerializedName("donationCoin") val donationCoin: Int,
|
||||
@SerializedName("date") val date: String,
|
||||
@SerializedName("replyCount") val replyCount: Int
|
||||
) : Parcelable
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RegisterAudioContentCommentRequest(
|
||||
@SerializedName("comment") val comment: String,
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("parentId") val parentId: Long?
|
||||
)
|
|
@ -0,0 +1,67 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentDeleteBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
class AudioContentDeleteDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
title: String,
|
||||
confirmButtonClick: () -> Unit
|
||||
) {
|
||||
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogAudioContentDeleteBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvTitle.text = "[$title]을 삭제하시겠습니까?"
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.setOnClickListener {
|
||||
if (dialogView.tvNotice.isSelected) {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"동의하셔야 삭제할 수 있습니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
dialogView.tvNotice.setOnClickListener {
|
||||
it.isSelected = !it.isSelected
|
||||
}
|
||||
}
|
||||
|
||||
fun show(width: Int) {
|
||||
alertDialog.show()
|
||||
|
||||
val lp = WindowManager.LayoutParams()
|
||||
lp.copyFrom(alertDialog.window?.attributes)
|
||||
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
|
||||
alertDialog.window?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,788 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.google.gson.Gson
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentFragment
|
||||
import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderConfirmDialog
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderFragment
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
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.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.Utils
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentDetailBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
|
||||
import kr.co.vividnext.sodalive.mypage.auth.Auth
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
||||
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBinding>(
|
||||
ActivityAudioContentDetailBinding::inflate
|
||||
) {
|
||||
private val viewModel: AudioContentDetailViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var creatorOtherContentAdapter: OtherContentAdapter
|
||||
private lateinit var sameThemeOtherContentAdapter: OtherContentAdapter
|
||||
|
||||
private var audioContentId: Long = 0
|
||||
private var isAlertPreview = false
|
||||
private val audioContentReceiver = AudioContentReceiver()
|
||||
|
||||
private var refresh = false
|
||||
set(value) {
|
||||
field = value
|
||||
setResult(RESULT_OK)
|
||||
}
|
||||
private var title = ""
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
binding.scrollView.scrollTo(0, 0)
|
||||
binding.sbProgress.progress = 0
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play)
|
||||
binding.tvTotalDuration.text = " / 00:00:00"
|
||||
binding.tvCurrentDuration.text = "00:00:00"
|
||||
binding.rlPreviewAlert.visibility = View.GONE
|
||||
|
||||
audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (audioContentId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val intentFilter = IntentFilter(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
registerReceiver(audioContentReceiver, intentFilter)
|
||||
|
||||
if (refresh) {
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
unregisterReceiver(audioContentReceiver)
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.tvBack.text = "콘텐츠 상세"
|
||||
binding.tvBack.setOnClickListener { finish() }
|
||||
binding.ivClosePreviewAlert.setOnClickListener { viewModel.toggleShowPreviewAlert() }
|
||||
binding.ivMenu.setOnClickListener {
|
||||
showOptionMenu(
|
||||
this,
|
||||
binding.ivMenu,
|
||||
)
|
||||
}
|
||||
|
||||
creatorOtherContentAdapter = OtherContentAdapter {
|
||||
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvCreatorOtherContent.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
viewModel.getAudioContentDetail(
|
||||
audioContentId = audioContentId
|
||||
) { finish() }
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
|
||||
binding.rvCreatorOtherContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
creatorOtherContentAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvCreatorOtherContent.adapter = creatorOtherContentAdapter
|
||||
|
||||
sameThemeOtherContentAdapter = OtherContentAdapter {
|
||||
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvThemeOtherContent.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvThemeOtherContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
creatorOtherContentAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvThemeOtherContent.adapter = sameThemeOtherContentAdapter
|
||||
|
||||
binding.sbProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(p0: SeekBar?) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {
|
||||
if (seekBar != null) {
|
||||
val intent = Intent(
|
||||
this@AudioContentDetailActivity,
|
||||
AudioContentPlayService::class.java
|
||||
)
|
||||
intent.action = AudioContentPlayService.MusicAction.PROGRESS.name
|
||||
intent.putExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, seekBar.progress)
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val layoutParams = binding.ivCover.layoutParams as RelativeLayout.LayoutParams
|
||||
layoutParams.width = (screenWidth - 13.3f.dpToPx()).toInt()
|
||||
layoutParams.height = (screenWidth - 13.3f.dpToPx()).toInt()
|
||||
binding.ivCover.layoutParams = layoutParams
|
||||
binding.ivPlayLoop.setOnClickListener { viewModel.togglePlayLoop() }
|
||||
binding.llDonation.setOnClickListener {
|
||||
val dialog = LiveRoomDonationDialog(
|
||||
this,
|
||||
LayoutInflater.from(this)
|
||||
) { can, message ->
|
||||
if (can <= 0) {
|
||||
showToast("1코인 이상 후원하실 수 있습니다.")
|
||||
} else if (message.isBlank()) {
|
||||
showToast("함께 보낼 메시지를 입력하세요.")
|
||||
} else {
|
||||
donation(can, message)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show(screenWidth)
|
||||
}
|
||||
}
|
||||
|
||||
private fun donation(coin: Int, message: String) {
|
||||
viewModel.donation(audioContentId, coin, message) {
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOptionMenu(context: Context, v: View) {
|
||||
val popup = PopupMenu(context, v)
|
||||
val inflater = popup.menuInflater
|
||||
|
||||
if (
|
||||
viewModel.audioContentLiveData.value!!.creator.creatorId ==
|
||||
SharedPreferenceManager.userId
|
||||
) {
|
||||
inflater.inflate(R.menu.audio_content_detail_creator_menu, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_modify -> {
|
||||
refresh = true
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentModifyActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
R.id.menu_delete -> {
|
||||
showDeleteDialog()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
} else {
|
||||
inflater.inflate(R.menu.audio_content_detail_user_menu, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_report -> {
|
||||
showReportDialog()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun showDeleteDialog() {
|
||||
AudioContentDeleteDialog(
|
||||
this,
|
||||
layoutInflater,
|
||||
this.title,
|
||||
confirmButtonClick = {
|
||||
viewModel.deleteAudioContent(audioContentId) {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showReportDialog() {
|
||||
AudioContentReportDialog(this, layoutInflater) {
|
||||
viewModel.report(
|
||||
type = ReportType.AUDIO_CONTENT,
|
||||
contentId = audioContentId,
|
||||
reason = it
|
||||
)
|
||||
}.show(screenWidth)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
|
||||
private fun bindData() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isExpandDetail.observe(this) {
|
||||
binding.tvDetail.maxLines = if (it) {
|
||||
Int.MAX_VALUE
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isShowPreviewAlert.observe(this) {
|
||||
binding.rlPreviewAlert.visibility = if (it) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.audioContentLiveData.observe(this) {
|
||||
refresh = false
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
action = AudioContentPlayService.MusicAction.INIT.name
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it.contentId)
|
||||
}
|
||||
)
|
||||
|
||||
title = it.title
|
||||
setupCreatorArea(it.creator)
|
||||
setupMosaicArea(it.isMosaic)
|
||||
setupPlayArea(it)
|
||||
setupInfoArea(it)
|
||||
setupPurchaseButton(it)
|
||||
setupCommentArea(it)
|
||||
setupCreatorOtherContentListArea(it.creatorOtherContentList)
|
||||
setupSameThemeOtherContentList(it.sameThemeOtherContentList)
|
||||
|
||||
isAlertPreview = it.creator.creatorId != SharedPreferenceManager.userId &&
|
||||
!it.existOrdered &&
|
||||
it.price > 0
|
||||
}
|
||||
|
||||
viewModel.isContentPlayLoopLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivPlayLoop.setImageResource(R.drawable.btn_player_repeat)
|
||||
} else {
|
||||
binding.ivPlayLoop.setImageResource(R.drawable.btn_player_repeat_done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setupSameThemeOtherContentList(
|
||||
sameThemeOtherContentList: List<OtherContentResponse>
|
||||
) {
|
||||
if (sameThemeOtherContentList.isEmpty()) {
|
||||
binding.rvThemeOtherContent.visibility = View.GONE
|
||||
binding.llThemeOtherContentPreparing.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.rvThemeOtherContent.visibility = View.VISIBLE
|
||||
binding.llThemeOtherContentPreparing.visibility = View.GONE
|
||||
|
||||
sameThemeOtherContentAdapter.items.clear()
|
||||
sameThemeOtherContentAdapter.items.addAll(sameThemeOtherContentList)
|
||||
sameThemeOtherContentAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setupCreatorOtherContentListArea(
|
||||
creatorOtherContentList: List<OtherContentResponse>
|
||||
) {
|
||||
if (creatorOtherContentList.isEmpty()) {
|
||||
binding.rvCreatorOtherContent.visibility = View.GONE
|
||||
binding.llCreatorOtherContentPreparing.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.rvCreatorOtherContent.visibility = View.VISIBLE
|
||||
binding.llCreatorOtherContentPreparing.visibility = View.GONE
|
||||
|
||||
creatorOtherContentAdapter.items.clear()
|
||||
creatorOtherContentAdapter.items.addAll(creatorOtherContentList)
|
||||
creatorOtherContentAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCommentArea(response: GetAudioContentDetailResponse) {
|
||||
if (response.isCommentAvailable) {
|
||||
binding.llDonation.visibility = View.VISIBLE
|
||||
binding.llComment.visibility = View.VISIBLE
|
||||
binding.tvCommentCount.text = "${response.commentCount}"
|
||||
|
||||
if (response.commentCount > 0) {
|
||||
binding.ivCommentProfile.load(response.commentList[0].profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
binding.tvCommentText.text = response.commentList[0].comment
|
||||
binding.tvCommentText.visibility = View.VISIBLE
|
||||
binding.rlInputComment.visibility = View.GONE
|
||||
|
||||
binding.llComment.setOnClickListener { showCommentBottomSheetDialog() }
|
||||
} else {
|
||||
binding.tvCommentText.visibility = View.GONE
|
||||
binding.rlInputComment.visibility = View.VISIBLE
|
||||
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.ivCommentSend.setOnClickListener {
|
||||
val comment = binding.etComment.text.toString()
|
||||
binding.etComment.setText("")
|
||||
viewModel.registerComment(audioContentId, comment)
|
||||
}
|
||||
|
||||
binding.llComment.setOnClickListener {}
|
||||
}
|
||||
} else {
|
||||
binding.llComment.visibility = View.GONE
|
||||
binding.llDonation.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCommentBottomSheetDialog() {
|
||||
val dialog = AudioContentCommentFragment(audioContentId = audioContentId)
|
||||
dialog.show(
|
||||
supportFragmentManager,
|
||||
dialog.tag
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupPurchaseButton(response: GetAudioContentDetailResponse) {
|
||||
if (
|
||||
response.price > 0 &&
|
||||
!response.existOrdered &&
|
||||
response.orderType == null &&
|
||||
response.creator.creatorId != SharedPreferenceManager.userId
|
||||
) {
|
||||
binding.llPurchase.visibility = View.VISIBLE
|
||||
binding.tvPrice.text = response.price.toString()
|
||||
|
||||
binding.llPurchase.setOnClickListener {
|
||||
showOrderDialog(audioContent = response)
|
||||
}
|
||||
} else {
|
||||
binding.llPurchase.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun setupPlayArea(response: GetAudioContentDetailResponse) {
|
||||
Glide
|
||||
.with(this)
|
||||
.load(response.coverImageUrl)
|
||||
.centerCrop()
|
||||
.placeholder(R.drawable.bg_black)
|
||||
.apply(RequestOptions().override((screenWidth - 13.3f.dpToPx()).toInt()))
|
||||
.into(binding.ivCover)
|
||||
|
||||
binding.ivPlayOrPause.setOnClickListener {
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL, response.coverImageUrl)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_URL, response.contentUrl)
|
||||
putExtra(Constants.EXTRA_NICKNAME, response.creator.nickname)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE, response.title)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, response.contentId)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID, response.creator.creatorId)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_FREE, response.price <= 0)
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PREVIEW,
|
||||
!response.existOrdered && response.price > 0
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
binding.tvTotalDuration.text = " / ${response.duration}"
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun setupInfoArea(response: GetAudioContentDetailResponse) {
|
||||
binding.tvTheme.text = response.themeStr
|
||||
binding.tv19.visibility = if (response.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
if (response.orderType != null && response.orderType == OrderType.KEEP) {
|
||||
binding.tvPurchased.visibility = View.VISIBLE
|
||||
binding.tvRental.visibility = View.GONE
|
||||
binding.tvRemainingTime.visibility = View.GONE
|
||||
} else if (response.orderType != null && response.orderType == OrderType.RENTAL) {
|
||||
binding.tvPurchased.visibility = View.GONE
|
||||
binding.tvRental.visibility = View.VISIBLE
|
||||
binding.tvRemainingTime.visibility = View.VISIBLE
|
||||
binding.tvRemainingTime.text = response.remainingTime
|
||||
} else {
|
||||
binding.tvPurchased.visibility = View.GONE
|
||||
binding.tvRental.visibility = View.GONE
|
||||
binding.tvRemainingTime.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.tvTitle.text = response.title
|
||||
binding.tvDetail.text = response.detail
|
||||
binding.tvDetail.setOnClickListener { viewModel.toggleExpandDetail() }
|
||||
|
||||
if (response.tag.isNotBlank()) {
|
||||
binding.tvTag.visibility = View.VISIBLE
|
||||
binding.tvTag.text = response.tag
|
||||
} else {
|
||||
binding.tvTag.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.ivLike.setImageResource(
|
||||
if (response.isLike) {
|
||||
R.drawable.ic_audio_content_heart_pressed
|
||||
} else {
|
||||
R.drawable.ic_audio_content_heart_normal
|
||||
}
|
||||
)
|
||||
|
||||
binding.tvLike.text = "${response.likeCount}"
|
||||
binding.llLike.setOnClickListener {
|
||||
viewModel.likeContent(contentId = audioContentId) {
|
||||
val likeCount = binding.tvLike.text.toString().toInt()
|
||||
if (it) {
|
||||
binding.tvLike.text = "${likeCount + 1}"
|
||||
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_pressed)
|
||||
} else {
|
||||
binding.tvLike.text = if (likeCount - 1 < 0) {
|
||||
"0"
|
||||
} else {
|
||||
"${likeCount - 1}"
|
||||
}
|
||||
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvShare.setOnClickListener {
|
||||
viewModel.shareAudioContent(
|
||||
audioContentId = audioContentId,
|
||||
contentImage = response.coverImageUrl,
|
||||
contentTitle = "${response.title} - ${response.creator.nickname}"
|
||||
) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, it)
|
||||
|
||||
val shareIntent = Intent.createChooser(intent, "오디오콘텐츠 공유")
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMosaicArea(isMosaic: Boolean) {
|
||||
if (isMosaic) {
|
||||
binding.alert19Bg.visibility = View.VISIBLE
|
||||
binding.llAlert19.visibility = View.VISIBLE
|
||||
|
||||
binding.tvAuth.setOnClickListener {
|
||||
Auth.auth(this, applicationContext) { data ->
|
||||
val bootpayResponse = Gson().fromJson(data, BootpayResponse::class.java)
|
||||
val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)
|
||||
runOnUiThread {
|
||||
viewModel.authVerify(audioContentId = audioContentId, request = request)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.alert19Bg.visibility = View.GONE
|
||||
binding.llAlert19.visibility = View.GONE
|
||||
binding.tvAuth.setOnClickListener {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreatorArea(creator: AudioContentCreator) {
|
||||
binding.rlProfile.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(applicationContext, UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, creator.creatorId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
binding.ivProfile.load(creator.profileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvProfileNickname.text = creator.nickname
|
||||
|
||||
if (creator.creatorId != SharedPreferenceManager.userId) {
|
||||
binding.ivFollow.visibility = View.VISIBLE
|
||||
|
||||
if (creator.isFollowing) {
|
||||
binding.ivFollow.setImageResource(R.drawable.btn_notification_selected)
|
||||
binding.ivFollow.setOnClickListener {
|
||||
viewModel.unRegisterNotification(
|
||||
contentId = audioContentId,
|
||||
creatorId = creator.creatorId
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.ivFollow.setImageResource(R.drawable.btn_notification)
|
||||
binding.ivFollow.setOnClickListener {
|
||||
viewModel.registerNotification(
|
||||
contentId = audioContentId,
|
||||
creatorId = creator.creatorId
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.ivFollow.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrderDialog(audioContent: GetAudioContentDetailResponse) {
|
||||
val dialog = AudioContentOrderFragment(
|
||||
price = audioContent.price,
|
||||
onClickKeep = { showOrderConfirmDialog(audioContent, OrderType.KEEP) },
|
||||
onClickRental = { showOrderConfirmDialog(audioContent, OrderType.RENTAL) }
|
||||
)
|
||||
|
||||
dialog.show(
|
||||
supportFragmentManager,
|
||||
dialog.tag
|
||||
)
|
||||
}
|
||||
|
||||
private fun showOrderConfirmDialog(
|
||||
audioContent: GetAudioContentDetailResponse,
|
||||
orderType: OrderType
|
||||
) {
|
||||
AudioContentOrderConfirmDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = audioContent.title,
|
||||
theme = audioContent.themeStr,
|
||||
coverImageUrl = audioContent.coverImageUrl,
|
||||
isAdult = audioContent.isAdult,
|
||||
profileImageUrl = audioContent.creator.profileImageUrl,
|
||||
nickname = audioContent.creator.nickname,
|
||||
duration = audioContent.duration,
|
||||
orderType = orderType,
|
||||
price = audioContent.price,
|
||||
confirmButtonClick = {
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
action = AudioContentPlayService.MusicAction.CONDITIONAL_STOP.name
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContent.contentId)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rlPreviewAlert.visibility = View.GONE
|
||||
|
||||
viewModel.order(
|
||||
contentId = audioContent.contentId,
|
||||
orderType = orderType
|
||||
)
|
||||
},
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
inner class AudioContentReceiver : BroadcastReceiver() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val nextAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent?.getSerializableExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
AudioContentPlayService.MusicAction::class.java
|
||||
)
|
||||
} else {
|
||||
intent?.getSerializableExtra(Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION)
|
||||
}
|
||||
|
||||
val duration = intent?.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_DURATION, 0)
|
||||
val progress = intent?.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, 0)
|
||||
val contentId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
val isPlaying = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYING, false)
|
||||
val changeUi = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_CHANGE_UI, false)
|
||||
val alertPreview = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ALERT_PREVIEW,
|
||||
false
|
||||
)
|
||||
val isLoading = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_LOADING,
|
||||
false
|
||||
)
|
||||
|
||||
viewModel.isLoading.value = isLoading ?: false
|
||||
|
||||
if (this@AudioContentDetailActivity.audioContentId == contentId) {
|
||||
runOnUiThread {
|
||||
if (changeUi != null && changeUi) {
|
||||
binding.ivPlayOrPause.setImageResource(
|
||||
if (isPlaying != null && isPlaying) {
|
||||
R.drawable.btn_audio_content_pause
|
||||
} else {
|
||||
R.drawable.btn_audio_content_play
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (duration != null && duration > 0) {
|
||||
binding.sbProgress.max = duration
|
||||
binding.tvTotalDuration.text = " / ${Utils.convertDurationToString(duration)}"
|
||||
}
|
||||
|
||||
if (progress != null && progress > 0) {
|
||||
binding.sbProgress.progress = progress
|
||||
binding.tvCurrentDuration.text = Utils.convertDurationToString(progress)
|
||||
}
|
||||
|
||||
if (alertPreview != null && alertPreview && isAlertPreview) {
|
||||
viewModel.toggleShowPreviewAlert()
|
||||
}
|
||||
|
||||
if (nextAction != null) {
|
||||
startService(
|
||||
Intent(
|
||||
this@AudioContentDetailActivity,
|
||||
AudioContentPlayService::class.java
|
||||
).apply {
|
||||
action = (nextAction as AudioContentPlayService.MusicAction).name
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,475 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.firebase.dynamiclinks.ShortDynamicLink
|
||||
import com.google.firebase.dynamiclinks.ktx.androidParameters
|
||||
import com.google.firebase.dynamiclinks.ktx.dynamicLinks
|
||||
import com.google.firebase.dynamiclinks.ktx.iosParameters
|
||||
import com.google.firebase.dynamiclinks.ktx.shortLinkAsync
|
||||
import com.google.firebase.dynamiclinks.ktx.socialMetaTagParameters
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthRepository
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
||||
import kr.co.vividnext.sodalive.report.ReportRepository
|
||||
import kr.co.vividnext.sodalive.report.ReportRequest
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
|
||||
class AudioContentDetailViewModel(
|
||||
private val repository: AudioContentRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val reportRepository: ReportRepository,
|
||||
private val commentRepository: AudioContentCommentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
var isLoading = MutableLiveData(false)
|
||||
private set
|
||||
|
||||
private var _audioContentLiveData = MutableLiveData<GetAudioContentDetailResponse>()
|
||||
val audioContentLiveData: LiveData<GetAudioContentDetailResponse>
|
||||
get() = _audioContentLiveData
|
||||
|
||||
private val _isExpandDetail = MutableLiveData(false)
|
||||
val isExpandDetail: LiveData<Boolean>
|
||||
get() = _isExpandDetail
|
||||
|
||||
private val _isShowPreviewAlert = MutableLiveData(false)
|
||||
val isShowPreviewAlert: LiveData<Boolean>
|
||||
get() = _isShowPreviewAlert
|
||||
|
||||
private val _isContentPlayLoopLiveData = MutableLiveData(
|
||||
SharedPreferenceManager.isContentPlayLoop
|
||||
)
|
||||
val isContentPlayLoopLiveData: LiveData<Boolean>
|
||||
get() = _isContentPlayLoopLiveData
|
||||
|
||||
fun getAudioContentDetail(audioContentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentDetail(
|
||||
audioContentId = audioContentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_audioContentLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun registerNotification(contentId: Long, creatorId: Long) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.registerNotification(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getAudioContentDetail(contentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun unRegisterNotification(contentId: Long, creatorId: Long) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.unRegisterNotification(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getAudioContentDetail(contentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun toggleExpandDetail() {
|
||||
_isExpandDetail.value = !_isExpandDetail.value!!
|
||||
}
|
||||
|
||||
fun toggleShowPreviewAlert() {
|
||||
_isShowPreviewAlert.value = !_isShowPreviewAlert.value!!
|
||||
}
|
||||
|
||||
fun order(contentId: Long, orderType: OrderType) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.orderContent(
|
||||
contentId = contentId,
|
||||
orderType = orderType,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getAudioContentDetail(audioContentId = contentId)
|
||||
_toastLiveData.postValue("구매가 완료되었습니다.")
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun authVerify(audioContentId: Long, request: AuthVerifyRequest) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
authRepository.verify(request, "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
getAudioContentDetail(audioContentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun registerComment(audioContentId: Long, comment: String) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
commentRepository.registerComment(
|
||||
contentId = audioContentId,
|
||||
comment = comment,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
getAudioContentDetail(audioContentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun likeContent(contentId: Long, onSuccess: (Boolean) -> Unit) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.likeContent(
|
||||
request = PutAudioContentLikeRequest(contentId = contentId),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
if (it.data != null) {
|
||||
onSuccess(it.data.like)
|
||||
} else {
|
||||
getAudioContentDetail(contentId)
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun shareAudioContent(
|
||||
audioContentId: Long,
|
||||
contentImage: String,
|
||||
contentTitle: String,
|
||||
onSuccess: (String) -> Unit
|
||||
) {
|
||||
isLoading.value = true
|
||||
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
|
||||
link = Uri.parse("https://yozm.day/?audio_content_id=$audioContentId")
|
||||
domainUriPrefix = "https://yozm.page.link"
|
||||
androidParameters { }
|
||||
iosParameters("kr.co.vividnext.yozm") {
|
||||
appStoreId = "1630284226"
|
||||
}
|
||||
socialMetaTagParameters {
|
||||
title = contentTitle
|
||||
description = "지금 요즘라이브에서 이 콘텐츠 감상하기"
|
||||
imageUrl = contentImage.toUri()
|
||||
}
|
||||
}.addOnSuccessListener {
|
||||
val uri = it.shortLink
|
||||
if (uri != null) {
|
||||
val message = uri.toString()
|
||||
onSuccess(message)
|
||||
} else {
|
||||
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
|
||||
}.addOnCompleteListener {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAudioContent(audioContentId: Long, onSuccess: () -> Unit) {
|
||||
isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.deleteAudioContent(
|
||||
audioContentId,
|
||||
token = "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(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun report(type: ReportType, contentId: Long, reason: String) {
|
||||
isLoading.value = true
|
||||
val request = ReportRequest(type = type, reason = reason, contentId = contentId)
|
||||
compositeDisposable.add(
|
||||
reportRepository.report(
|
||||
request = request,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"신고가 접수되었습니다."
|
||||
)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("신고가 접수되었습니다.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun togglePlayLoop() {
|
||||
val isPlayLoop = !SharedPreferenceManager.isContentPlayLoop
|
||||
SharedPreferenceManager.isContentPlayLoop = isPlayLoop
|
||||
_isContentPlayLoopLiveData.value = isPlayLoop
|
||||
}
|
||||
|
||||
fun donation(contentId: Long, can: Int, comment: String, onSuccess: () -> Unit) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.donation(
|
||||
contentId = contentId,
|
||||
can = can,
|
||||
comment = comment,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
SharedPreferenceManager.can -= can
|
||||
_toastLiveData.postValue(
|
||||
"${can.moneyFormat()}캔을 후원하였습니다."
|
||||
)
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import android.widget.RadioButton
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentReportBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentReportDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
confirmButtonClick: (String) -> Unit
|
||||
) {
|
||||
private val alertDialog: AlertDialog
|
||||
val dialogView = DialogAudioContentReportBinding.inflate(layoutInflater)
|
||||
var reason = ""
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.tvReport.setOnClickListener {
|
||||
if (reason.isNotBlank()) {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick(reason)
|
||||
} else {
|
||||
Toast.makeText(activity, "신고 이유를 선택하세요.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
dialogView.radioGroup.setOnCheckedChangeListener { radioGroup, checkedId ->
|
||||
val radioButton = radioGroup.findViewById<RadioButton>(checkedId)
|
||||
reason = radioButton.text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(width: Int) {
|
||||
alertDialog.show()
|
||||
|
||||
val lp = WindowManager.LayoutParams()
|
||||
lp.copyFrom(alertDialog.window?.attributes)
|
||||
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
|
||||
alertDialog.window?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.GetAudioContentCommentListItem
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
|
||||
data class GetAudioContentDetailResponse(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("detail") val detail: String,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("contentUrl") val contentUrl: String,
|
||||
@SerializedName("themeStr") val themeStr: String,
|
||||
@SerializedName("tag") val tag: String,
|
||||
@SerializedName("price") val price: Int,
|
||||
@SerializedName("duration") val duration: String,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isMosaic") val isMosaic: Boolean,
|
||||
@SerializedName("existOrdered") val existOrdered: Boolean,
|
||||
@SerializedName("orderType") val orderType: OrderType?,
|
||||
@SerializedName("remainingTime") val remainingTime: String?,
|
||||
@SerializedName("creatorOtherContentList")
|
||||
val creatorOtherContentList: List<OtherContentResponse>,
|
||||
@SerializedName("sameThemeOtherContentList")
|
||||
val sameThemeOtherContentList: List<OtherContentResponse>,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
|
||||
@SerializedName("isLike") val isLike: Boolean,
|
||||
@SerializedName("likeCount") val likeCount: Int,
|
||||
@SerializedName("commentList") val commentList: List<GetAudioContentCommentListItem>,
|
||||
@SerializedName("commentCount") val commentCount: Int,
|
||||
@SerializedName("creator") val creator: AudioContentCreator
|
||||
)
|
||||
|
||||
data class OtherContentResponse(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("coverUrl") val coverUrl: String,
|
||||
)
|
||||
|
||||
data class AudioContentCreator(
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileImageUrl") val profileImageUrl: String,
|
||||
@SerializedName("isFollowing") val isFollowing: Boolean
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
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.databinding.ItemAudioOtherContentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class OtherContentAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<OtherContentAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<OtherContentResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioOtherContentBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: OtherContentResponse) {
|
||||
binding.ivCover.load(item.coverUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(2.7f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioOtherContentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PutAudioContentLikeRequest(
|
||||
@SerializedName("contentId") val contentId: Long
|
||||
)
|
||||
|
||||
data class PutAudioContentLikeResponse(
|
||||
@SerializedName("like") val like: Boolean
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.donation
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AudioContentDonationRequest(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("donationCan") val donationCan: Int,
|
||||
@SerializedName("comment") val comment: String,
|
||||
@SerializedName("container") val container: String = "aos"
|
||||
)
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.zhpan.bannerview.BaseBannerAdapter
|
||||
import com.zhpan.bannerview.BaseViewHolder
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentMainBannerAdapter(
|
||||
private val itemWidth: Int,
|
||||
private val itemHeight: Int,
|
||||
private val onClick: (GetAudioContentBannerResponse) -> Unit
|
||||
) : BaseBannerAdapter<GetAudioContentBannerResponse>() {
|
||||
override fun bindData(
|
||||
holder: BaseViewHolder<GetAudioContentBannerResponse>,
|
||||
data: GetAudioContentBannerResponse,
|
||||
position: Int,
|
||||
pageSize: Int
|
||||
) {
|
||||
val ivBanner = holder.findViewById<ImageView>(R.id.iv_recommend_live)
|
||||
val layoutParams = ivBanner.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
layoutParams.width = itemWidth
|
||||
layoutParams.height = itemHeight
|
||||
|
||||
ivBanner.load(data.thumbnailImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
ivBanner.layoutParams = layoutParams
|
||||
ivBanner.setOnClickListener { onClick(data) }
|
||||
}
|
||||
|
||||
override fun getLayoutId(viewType: Int): Int {
|
||||
return R.layout.item_recommend_live
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainBinding
|
||||
|
||||
class AudioContentMainContentAdapter(
|
||||
private val onClickItem: (Long) -> Unit,
|
||||
private val onClickCreator: (Long) -> Unit,
|
||||
) : RecyclerView.Adapter<AudioContentMainItemViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetAudioContentMainItem>()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
) = AudioContentMainItemViewHolder(
|
||||
ItemAudioContentMainBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
),
|
||||
onClickItem = onClickItem,
|
||||
onClickCreator = onClickCreator
|
||||
)
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(holder: AudioContentMainItemViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetAudioContentMainItem>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainCurationBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentMainCurationAdapter(
|
||||
private val onClickItem: (Long) -> Unit,
|
||||
private val onClickCreator: (Long) -> Unit,
|
||||
) : RecyclerView.Adapter<AudioContentMainCurationAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetAudioContentCurationResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemAudioContentMainCurationBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentCurationResponse) {
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvDesc.text = item.description
|
||||
setAudioContentList(item.audioContents)
|
||||
}
|
||||
|
||||
private fun setAudioContentList(audioContents: List<GetAudioContentMainItem>) {
|
||||
val adapter = AudioContentMainContentAdapter(onClickItem, onClickCreator)
|
||||
|
||||
binding.rvCuration.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
if (binding.rvCuration.itemDecorationCount == 0) {
|
||||
binding.rvCuration.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
binding.rvCuration.adapter = adapter
|
||||
adapter.addItems(audioContents)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemAudioContentMainCurationBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetAudioContentCurationResponse>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,470 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.zhpan.bannerview.BaseBannerAdapter
|
||||
import com.zhpan.indicator.enums.IndicatorSlideMode
|
||||
import com.zhpan.indicator.enums.IndicatorStyle
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||
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.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
|
||||
import kr.co.vividnext.sodalive.settings.notification.MemberRole
|
||||
import org.koin.android.ext.android.inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
|
||||
FragmentAudioContentMainBinding::inflate
|
||||
) {
|
||||
private val viewModel: AudioContentMainViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var imm: InputMethodManager
|
||||
|
||||
private lateinit var newContentCreatorAdapter: AudioContentMainNewContentCreatorAdapter
|
||||
private lateinit var bannerAdapter: AudioContentMainBannerAdapter
|
||||
private lateinit var orderListAdapter: AudioContentMainContentAdapter
|
||||
private lateinit var newContentThemeAdapter: AudioContentMainNewContentThemeAdapter
|
||||
private lateinit var newContentAdapter: AudioContentMainContentAdapter
|
||||
private lateinit var curationAdapter: AudioContentMainCurationAdapter
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
|
||||
viewModel.getMain()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
if (SharedPreferenceManager.role == MemberRole.CREATOR.name) {
|
||||
binding.llUploadContent.visibility = View.VISIBLE
|
||||
binding.llUploadContent.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
requireActivity(),
|
||||
AudioContentUploadActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.llUploadContent.visibility = View.GONE
|
||||
}
|
||||
|
||||
setupNewContentCreator()
|
||||
setupBanner()
|
||||
setupOrderList()
|
||||
setupNewContentTheme()
|
||||
setupNewContent()
|
||||
setupCuration()
|
||||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
viewModel.getMain()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNewContentCreator() {
|
||||
newContentCreatorAdapter = AudioContentMainNewContentCreatorAdapter {
|
||||
val intent = Intent(requireContext(), UserProfileActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, it)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvNewContentCreator.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvNewContentCreator.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 10.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 10.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 10.7f.dpToPx().toInt()
|
||||
outRect.right = 10.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvNewContentCreator.adapter = newContentCreatorAdapter
|
||||
}
|
||||
|
||||
private fun setupBanner() {
|
||||
val layoutParams = binding
|
||||
.rvBanner
|
||||
.layoutParams as LinearLayout.LayoutParams
|
||||
|
||||
val pagerWidth = screenWidth.toDouble() - 26.7f.dpToPx()
|
||||
val pagerHeight = (pagerWidth * 0.53).roundToInt()
|
||||
layoutParams.width = pagerWidth.roundToInt()
|
||||
layoutParams.height = pagerHeight
|
||||
|
||||
bannerAdapter = AudioContentMainBannerAdapter(pagerWidth.roundToInt(), pagerHeight) {
|
||||
when (it.type) {
|
||||
AudioContentBannerType.EVENT -> {
|
||||
startActivity(
|
||||
Intent(requireContext(), EventDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_EVENT, it.eventItem!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AudioContentBannerType.CREATOR -> {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it.creatorId!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AudioContentBannerType.LINK -> {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.link!!)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding
|
||||
.rvBanner
|
||||
.layoutParams = layoutParams
|
||||
|
||||
binding.rvBanner.apply {
|
||||
adapter = bannerAdapter as BaseBannerAdapter<Any>
|
||||
|
||||
setLifecycleRegistry(lifecycle)
|
||||
setScrollDuration(1000)
|
||||
setInterval(4 * 1000)
|
||||
}.create()
|
||||
|
||||
binding
|
||||
.rvBanner
|
||||
.setIndicatorView(binding.indicatorBanner)
|
||||
.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
|
||||
.setIndicatorSlideMode(IndicatorSlideMode.SMOOTH)
|
||||
.setIndicatorVisibility(View.GONE)
|
||||
.setIndicatorSliderColor(
|
||||
ContextCompat.getColor(requireContext(), R.color.color_909090),
|
||||
ContextCompat.getColor(requireContext(), R.color.color_9970ff)
|
||||
)
|
||||
.setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt())
|
||||
.setIndicatorHeight(4f.dpToPx().toInt())
|
||||
}
|
||||
|
||||
private fun setupOrderList() {
|
||||
orderListAdapter = AudioContentMainContentAdapter(
|
||||
onClickItem = {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClickCreator = {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvMyStash.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvMyStash.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvMyStash.adapter = orderListAdapter
|
||||
binding.tvMyStashViewAll.setOnClickListener {
|
||||
startActivity(Intent(requireContext(), AudioContentOrderListActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNewContentTheme() {
|
||||
newContentThemeAdapter = AudioContentMainNewContentThemeAdapter {
|
||||
viewModel.getNewContentOfTheme(theme = it)
|
||||
}
|
||||
|
||||
binding.rvNewContentTheme.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvNewContentTheme.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 4f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 4f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 4f.dpToPx().toInt()
|
||||
outRect.right = 4f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvNewContentTheme.adapter = newContentThemeAdapter
|
||||
}
|
||||
|
||||
private fun setupNewContent() {
|
||||
newContentAdapter = AudioContentMainContentAdapter(
|
||||
onClickItem = {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClickCreator = {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvNewContent.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvNewContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvNewContent.adapter = newContentAdapter
|
||||
}
|
||||
|
||||
private fun setupCuration() {
|
||||
curationAdapter = AudioContentMainCurationAdapter(
|
||||
onClickItem = {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClickCreator = {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvCuration.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvCuration.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 40f.dpToPx().toInt()
|
||||
outRect.bottom = 20f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
curationAdapter.itemCount - 1 -> {
|
||||
outRect.top = 20f.dpToPx().toInt()
|
||||
outRect.bottom = 40f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 20f.dpToPx().toInt()
|
||||
outRect.bottom = 20f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
binding.rvCuration.adapter = curationAdapter
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.newContentUploadCreatorListLiveData.observe(viewLifecycleOwner) {
|
||||
newContentCreatorAdapter.addItems(it)
|
||||
binding.rvNewContentCreator.visibility = if (
|
||||
newContentCreatorAdapter.itemCount <= 0 && it.isEmpty()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.bannerLiveData.observe(viewLifecycleOwner) {
|
||||
if (bannerAdapter.itemCount <= 0 && it.isEmpty()) {
|
||||
binding.rvBanner.visibility = View.GONE
|
||||
binding.indicatorBanner.visibility = View.GONE
|
||||
} else {
|
||||
binding.rvBanner.visibility = View.VISIBLE
|
||||
binding.indicatorBanner.visibility = View.VISIBLE
|
||||
binding.rvBanner.refreshData(it)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.orderListLiveData.observe(viewLifecycleOwner) {
|
||||
orderListAdapter.addItems(it)
|
||||
binding.llMyStash.visibility = if (
|
||||
orderListAdapter.itemCount <= 0 && it.isEmpty()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.newContentListLiveData.observe(viewLifecycleOwner) {
|
||||
newContentAdapter.addItems(it)
|
||||
}
|
||||
|
||||
viewModel.themeListLiveData.observe(viewLifecycleOwner) {
|
||||
binding.llNewContent.visibility = View.VISIBLE
|
||||
newContentThemeAdapter.addItems(it)
|
||||
}
|
||||
|
||||
viewModel.curationListLiveData.observe(viewLifecycleOwner) {
|
||||
curationAdapter.addItems(it)
|
||||
binding.rvCuration.visibility = if (
|
||||
curationAdapter.itemCount <= 0 && it.isEmpty()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentMainItemViewHolder(
|
||||
private val binding: ItemAudioContentMainBinding,
|
||||
private val onClickItem: (Long) -> Unit,
|
||||
private val onClickCreator: (Long) -> Unit
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentMainItem) {
|
||||
binding.ivAudioContentCoverImage.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(2.7f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.ivAudioContentCreator.load(item.creatorProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvAudioContentTitle.text = item.title
|
||||
binding.tvAudioContentCreatorNickname.text = item.creatorNickname
|
||||
|
||||
binding.ivAudioContentCreator.setOnClickListener { onClickCreator(item.creatorId) }
|
||||
binding.iv19.visibility = if (item.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainNewContentCreatorBinding
|
||||
|
||||
class AudioContentMainNewContentCreatorAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentMainNewContentCreatorAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetNewContentUploadCreator>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentMainNewContentCreatorBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetNewContentUploadCreator) {
|
||||
binding.tvNewContentCreator.text = item.creatorNickname
|
||||
binding.ivNewContentCreator.load(item.creatorProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
binding.root.setOnClickListener { onClickItem(item.creatorId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentMainNewContentCreatorBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetNewContentUploadCreator>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainNewContentThemeBinding
|
||||
|
||||
class AudioContentMainNewContentThemeAdapter(
|
||||
private val onClickItem: (String) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentMainNewContentThemeAdapter.ViewHolder>() {
|
||||
|
||||
private val themeList = mutableListOf<String>()
|
||||
private var selectedTheme = ""
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemAudioContentMainNewContentThemeBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun bind(theme: String) {
|
||||
if (theme == selectedTheme || (selectedTheme == "" && theme == "전체")) {
|
||||
binding.tvTheme.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_9970ff))
|
||||
} else {
|
||||
binding.tvTheme.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_777777
|
||||
)
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_777777))
|
||||
}
|
||||
|
||||
binding.tvTheme.text = theme
|
||||
binding.root.setOnClickListener {
|
||||
onClickItem(theme)
|
||||
selectedTheme = theme
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(themeList: List<String>) {
|
||||
this.selectedTheme = ""
|
||||
this.themeList.clear()
|
||||
this.themeList.addAll(themeList)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemAudioContentMainNewContentThemeBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun getItemCount() = themeList.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(themeList[position])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentMainViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _newContentUploadCreatorListLiveData =
|
||||
MutableLiveData<List<GetNewContentUploadCreator>>()
|
||||
val newContentUploadCreatorListLiveData: LiveData<List<GetNewContentUploadCreator>>
|
||||
get() = _newContentUploadCreatorListLiveData
|
||||
|
||||
private var _newContentListLiveData = MutableLiveData<List<GetAudioContentMainItem>>()
|
||||
val newContentListLiveData: LiveData<List<GetAudioContentMainItem>>
|
||||
get() = _newContentListLiveData
|
||||
|
||||
private var _bannerLiveData = MutableLiveData<List<GetAudioContentBannerResponse>>()
|
||||
val bannerLiveData: LiveData<List<GetAudioContentBannerResponse>>
|
||||
get() = _bannerLiveData
|
||||
|
||||
private var _orderListLiveData = MutableLiveData<List<GetAudioContentMainItem>>()
|
||||
val orderListLiveData: LiveData<List<GetAudioContentMainItem>>
|
||||
get() = _orderListLiveData
|
||||
|
||||
private var _themeListLiveData = MutableLiveData<List<String>>()
|
||||
val themeListLiveData: LiveData<List<String>>
|
||||
get() = _themeListLiveData
|
||||
|
||||
private var _curationListLiveData = MutableLiveData<List<GetAudioContentCurationResponse>>()
|
||||
val curationListLiveData: LiveData<List<GetAudioContentCurationResponse>>
|
||||
get() = _curationListLiveData
|
||||
|
||||
fun getMain() {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getMain(token = "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
val data = it.data
|
||||
_newContentUploadCreatorListLiveData.value =
|
||||
data.newContentUploadCreatorList
|
||||
_newContentListLiveData.value = data.newContentList
|
||||
_orderListLiveData.value = data.orderList
|
||||
_bannerLiveData.value = data.bannerList
|
||||
_curationListLiveData.value = data.curationList
|
||||
|
||||
val themeList = listOf("전체").union(data.themeList).toList()
|
||||
_themeListLiveData.value = themeList
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getNewContentOfTheme(theme: String) {
|
||||
compositeDisposable.add(
|
||||
repository.getNewContentOfTheme(
|
||||
theme = if (theme == "전체") {
|
||||
""
|
||||
} else {
|
||||
theme
|
||||
},
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_newContentListLiveData.value = it.data!!
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||
|
||||
data class GetAudioContentMainResponse(
|
||||
@SerializedName("newContentUploadCreatorList")
|
||||
val newContentUploadCreatorList: List<GetNewContentUploadCreator>,
|
||||
@SerializedName("bannerList") val bannerList: List<GetAudioContentBannerResponse>,
|
||||
@SerializedName("orderList") val orderList: List<GetAudioContentMainItem>,
|
||||
@SerializedName("themeList") val themeList: List<String>,
|
||||
@SerializedName("newContentList") val newContentList: List<GetAudioContentMainItem>,
|
||||
@SerializedName("curationList") val curationList: List<GetAudioContentCurationResponse>
|
||||
)
|
||||
|
||||
data class GetNewContentUploadCreator(
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("creatorNickname") val creatorNickname: String,
|
||||
@SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String
|
||||
)
|
||||
|
||||
data class GetAudioContentMainItem(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String,
|
||||
@SerializedName("creatorNickname") val creatorNickname: String
|
||||
)
|
||||
|
||||
data class GetAudioContentCurationResponse(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("description") val description: String,
|
||||
@SerializedName("audioContents") val audioContents: List<GetAudioContentMainItem>
|
||||
)
|
||||
|
||||
data class GetAudioContentBannerResponse(
|
||||
@SerializedName("type") val type: AudioContentBannerType,
|
||||
@SerializedName("thumbnailImageUrl") val thumbnailImageUrl: String,
|
||||
@SerializedName("eventItem") val eventItem: EventItem?,
|
||||
@SerializedName("creatorId") val creatorId: Long?,
|
||||
@SerializedName("link") val link: String?
|
||||
)
|
||||
|
||||
enum class AudioContentBannerType {
|
||||
@SerializedName("EVENT")
|
||||
EVENT,
|
||||
|
||||
@SerializedName("CREATOR")
|
||||
CREATOR,
|
||||
|
||||
@SerializedName("LINK")
|
||||
LINK
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.modify
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.R
|
||||
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.common.RealPathUtil
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentModifyBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBinding>(
|
||||
ActivityAudioContentModifyBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentModifyViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
private val imageResult = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
val fileUri = data?.data
|
||||
|
||||
if (fileUri != null) {
|
||||
binding.ivCover.setPadding(0)
|
||||
binding.ivCover.background = null
|
||||
binding.ivCover.load(fileUri) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(13.3f.dpToPx()))
|
||||
}
|
||||
viewModel.coverImageUri = fileUri
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 파일입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (audioContentId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
checkPermissions()
|
||||
|
||||
viewModel.getRealPathFromURI = {
|
||||
RealPathUtil.getRealPath(applicationContext, it)
|
||||
}
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "콘텐츠 수정"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
binding.ivPhotoPicker.setOnClickListener {
|
||||
ImagePicker.with(this)
|
||||
.crop()
|
||||
.galleryOnly()
|
||||
.galleryMimeTypes( // Exclude gif images
|
||||
mimeTypes = arrayOf(
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg"
|
||||
)
|
||||
)
|
||||
.createIntent { imageResult.launch(it) }
|
||||
}
|
||||
|
||||
binding.llCommentNo.setOnClickListener { viewModel.setAvailableComment(false) }
|
||||
binding.llCommentYes.setOnClickListener { viewModel.setAvailableComment(true) }
|
||||
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
binding.tvModify.setOnClickListener {
|
||||
viewModel.modifyAudioContent { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
listOf(Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage(R.string.read_storage_permission_denied_message)
|
||||
.setPermissions(*permissions.toTypedArray())
|
||||
.check()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etTitle.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
viewModel.title = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etDetail.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
binding.tvNumberOfCharacters.text = "${it.length}자"
|
||||
viewModel.detail = 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()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAvailableCommentLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivCommentYes.visibility = View.VISIBLE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentNo.visibility = View.GONE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
} else {
|
||||
binding.ivCommentNo.visibility = View.VISIBLE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentYes.visibility = View.GONE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentYes
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734_9970ff)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAdultShowUiLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.llSetAdult.visibility = View.VISIBLE
|
||||
|
||||
binding.llAgeAll.setOnClickListener {
|
||||
viewModel.setAdult(false)
|
||||
}
|
||||
|
||||
binding.llAge19.setOnClickListener {
|
||||
viewModel.setAdult(true)
|
||||
}
|
||||
|
||||
viewModel.isAdultLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivAgeAll.visibility = View.GONE
|
||||
binding.llAgeAll.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734
|
||||
)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAge19.visibility = View.VISIBLE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.ivAge19.visibility = View.GONE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAgeAll.visibility = View.VISIBLE
|
||||
binding.llAgeAll.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_9970ff
|
||||
)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.llSetAdult.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.coverImageLiveData.observe(this) {
|
||||
binding.ivCover.setPadding(0)
|
||||
binding.ivCover.background = null
|
||||
binding.ivCover.load(it) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(13.3f.dpToPx()))
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.titleLiveData.observe(this) {
|
||||
binding.etTitle.setText(it)
|
||||
}
|
||||
|
||||
viewModel.detailLiveData.observe(this) {
|
||||
binding.etDetail.setText(it)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.modify
|
||||
|
||||
import android.net.Uri
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.BufferedSink
|
||||
import java.io.File
|
||||
|
||||
class AudioContentModifyViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private val _isAdultLiveData = MutableLiveData(false)
|
||||
val isAdultLiveData: LiveData<Boolean>
|
||||
get() = _isAdultLiveData
|
||||
|
||||
private val _isAvailableCommentLiveData = MutableLiveData(false)
|
||||
val isAvailableCommentLiveData: LiveData<Boolean>
|
||||
get() = _isAvailableCommentLiveData
|
||||
|
||||
private val _titleLiveData = MutableLiveData("")
|
||||
val titleLiveData: LiveData<String>
|
||||
get() = _titleLiveData
|
||||
|
||||
private val _detailLiveData = MutableLiveData("")
|
||||
val detailLiveData: LiveData<String>
|
||||
get() = _detailLiveData
|
||||
|
||||
private val _coverImageLiveData = MutableLiveData("")
|
||||
val coverImageLiveData: LiveData<String>
|
||||
get() = _coverImageLiveData
|
||||
|
||||
private val _isAdultShowUiLiveData = MutableLiveData(true)
|
||||
val isAdultShowUiLiveData: LiveData<Boolean>
|
||||
get() = _isAdultShowUiLiveData
|
||||
|
||||
lateinit var getRealPathFromURI: (Uri) -> String?
|
||||
|
||||
var contentId: Long = 0
|
||||
var title: String? = null
|
||||
var detail: String? = null
|
||||
var coverImageUri: Uri? = null
|
||||
|
||||
fun setAdult(isAdult: Boolean) {
|
||||
_isAdultLiveData.postValue(isAdult)
|
||||
}
|
||||
|
||||
fun setAvailableComment(isAvailableComment: Boolean) {
|
||||
_isAvailableCommentLiveData.postValue(isAvailableComment)
|
||||
}
|
||||
|
||||
fun getAudioContentDetail(audioContentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
this.contentId = audioContentId
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentDetail(
|
||||
audioContentId = audioContentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_titleLiveData.value = it.data.title
|
||||
_detailLiveData.value = it.data.detail
|
||||
_coverImageLiveData.value = it.data.coverImageUrl
|
||||
_isAvailableCommentLiveData.value = it.data.isCommentAvailable
|
||||
_isAdultLiveData.value = it.data.isAdult
|
||||
_isAdultShowUiLiveData.value = !it.data.isAdult
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun modifyAudioContent(onSuccess: () -> Unit) {
|
||||
if (!_isLoading.value!! && contentId > 0 && validateData()) {
|
||||
_isLoading.value = true
|
||||
|
||||
val request = ModifyAudioContentRequest(
|
||||
contentId = contentId,
|
||||
title = title,
|
||||
detail = detail,
|
||||
isAdult = _isAdultLiveData.value!!,
|
||||
isCommentAvailable = _isAvailableCommentLiveData.value!!
|
||||
)
|
||||
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val coverImage = if (coverImageUri != null) {
|
||||
val file = File(getRealPathFromURI(coverImageUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"coverImage",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "image/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.modifyAudioContent(
|
||||
coverImage = coverImage,
|
||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
_toastLiveData.postValue("수정되었습니다.")
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
_isLoading.postValue(false)
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateData(): Boolean {
|
||||
if (title != null && title!!.isBlank()) {
|
||||
_toastLiveData.postValue("제목을 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (detail != null && (detail!!.isBlank() || detail!!.length < 5)) {
|
||||
_toastLiveData.postValue("내용을 5자 이상 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.modify
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ModifyAudioContentRequest(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("title") val title: String?,
|
||||
@SerializedName("detail") val detail: String?,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean
|
||||
)
|
|
@ -0,0 +1,100 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentOrderConfirmBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kotlin.math.ceil
|
||||
|
||||
class AudioContentOrderConfirmDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
title: String,
|
||||
theme: String,
|
||||
coverImageUrl: String,
|
||||
isAdult: Boolean,
|
||||
profileImageUrl: String,
|
||||
nickname: String,
|
||||
duration: String,
|
||||
orderType: OrderType,
|
||||
price: Int,
|
||||
confirmButtonClick: () -> Unit,
|
||||
) {
|
||||
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogAudioContentOrderConfirmBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvTitle.text = title
|
||||
dialogView.tvTheme.text = theme
|
||||
dialogView.tvProfileNickname.text = nickname
|
||||
|
||||
dialogView.ivCover.load(coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(4f))
|
||||
}
|
||||
|
||||
dialogView.ivProfile.load(profileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
dialogView.tvDuration.text = duration
|
||||
dialogView.tvPrice.text = if (orderType == OrderType.RENTAL) {
|
||||
"${ceil(price * 0.7).toInt()}"
|
||||
} else {
|
||||
"$price"
|
||||
}
|
||||
|
||||
dialogView.iv19.visibility = if (isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) {
|
||||
"콘텐츠를 대여하시겠습니까?\n아래 코인이 차감됩니다."
|
||||
} else {
|
||||
"콘텐츠를 소장하시겠습니까?\n아래 코인이 차감됩니다."
|
||||
}
|
||||
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(width: Int) {
|
||||
alertDialog.show()
|
||||
|
||||
val lp = WindowManager.LayoutParams()
|
||||
lp.copyFrom(alertDialog.window?.attributes)
|
||||
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
|
||||
alertDialog.window?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentOrderBinding
|
||||
import kotlin.math.ceil
|
||||
|
||||
class AudioContentOrderFragment(
|
||||
private val price: Int,
|
||||
private val onClickRental: () -> Unit,
|
||||
private val onClickKeep: () -> Unit
|
||||
) : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: FragmentAudioContentOrderBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentAudioContentOrderBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.tvKeep.text = "$price"
|
||||
binding.tvRental.text = "${ceil(price * 0.7).toInt()}"
|
||||
|
||||
binding.llKeep.setOnClickListener {
|
||||
onClickKeep()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.llRental.setOnClickListener {
|
||||
onClickRental()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
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.ActivityAudioContentOrderListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentOrderListActivity : BaseActivity<ActivityAudioContentOrderListBinding>(
|
||||
ActivityAudioContentOrderListBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentOrderListViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AudioContentOrderListAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentOrderList { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "구매목록"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
adapter = AudioContentOrderListAdapter {
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply { putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) }
|
||||
)
|
||||
}
|
||||
|
||||
binding.rvOrderList.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvOrderList.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvOrderList.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.orderList.observe(this) {
|
||||
if (viewModel.page == 2) {
|
||||
adapter.items.clear()
|
||||
}
|
||||
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
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.databinding.ItemAudioContentOrderListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AudioContentOrderListAdapter(
|
||||
private val onItemClick: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentOrderListAdapter.ViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentOrderListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentOrderListBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentOrderListItem) {
|
||||
binding.ivCover.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvTheme.text = item.themeStr
|
||||
binding.tvDuration.text = item.duration
|
||||
binding.tvLikeCount.text = item.likeCount.moneyFormat()
|
||||
binding.tvCommentCount.text = item.commentCount.moneyFormat()
|
||||
|
||||
if (item.orderType == OrderType.RENTAL) {
|
||||
binding.tvRental.visibility = View.VISIBLE
|
||||
binding.tvPurchased.visibility = View.GONE
|
||||
} else {
|
||||
binding.tvPurchased.visibility = View.VISIBLE
|
||||
binding.tvRental.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onItemClick(item.contentId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentOrderListBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentOrderListViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _orderList = MutableLiveData<List<GetAudioContentOrderListItem>>()
|
||||
val orderList: LiveData<List<GetAudioContentOrderListItem>>
|
||||
get() = _orderList
|
||||
|
||||
private var isLast = false
|
||||
var page = 1
|
||||
private val size = 10
|
||||
|
||||
fun getAudioContentOrderList(onFailure: (() -> Unit)? = null) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentOrderList(
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_orderList.postValue(it.data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetAudioContentOrderListResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<GetAudioContentOrderListItem>
|
||||
)
|
||||
|
||||
data class GetAudioContentOrderListItem(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("themeStr") val themeStr: String,
|
||||
@SerializedName("duration") val duration: String?,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("orderType") val orderType: OrderType,
|
||||
@SerializedName("likeCount") val likeCount: Int,
|
||||
@SerializedName("commentCount") val commentCount: Int,
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderRequest(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("orderType") val orderType: OrderType,
|
||||
@SerializedName("container") val container: String
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
enum class OrderType {
|
||||
@SerializedName("RENTAL")
|
||||
RENTAL,
|
||||
|
||||
@SerializedName("KEEP")
|
||||
KEEP
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeFragment
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.RealPathUtil
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentUploadBinding
|
||||
import kr.co.vividnext.sodalive.dialog.LiveDialog
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
|
||||
class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBinding>(
|
||||
ActivityAudioContentUploadBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentUploadViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
private val themeFragment: AudioContentThemeFragment by lazy {
|
||||
AudioContentThemeFragment(
|
||||
getSelectedTheme = { viewModel.theme },
|
||||
onItemClick = {
|
||||
binding.ivTheme.load(it.image) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
|
||||
binding.ivTheme.visibility = View.VISIBLE
|
||||
}
|
||||
binding.tvTheme.text = it.theme
|
||||
viewModel.theme = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val imageResult = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
val fileUri = data?.data
|
||||
|
||||
if (fileUri != null) {
|
||||
binding.ivCover.background = null
|
||||
binding.ivCover.load(fileUri) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(13.3f.dpToPx()))
|
||||
}
|
||||
viewModel.coverImageUri = fileUri
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 파일입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private val selectAudioActivityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
val fileUri = data?.data
|
||||
if (fileUri != null) {
|
||||
binding.tvSelectContent.text = getFileName(fileUri)
|
||||
viewModel.contentUri = fileUri
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 파일입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
binding.tvSelectContent.text = "파일 선택"
|
||||
viewModel.contentUri = null
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
checkPermissions()
|
||||
|
||||
viewModel.getRealPathFromURI = {
|
||||
RealPathUtil.getRealPath(applicationContext, it)
|
||||
}
|
||||
|
||||
bindData()
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
binding.toolbar.tvBack.text = "콘텐츠 등록"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
binding.llTheme.setOnClickListener {
|
||||
if (themeFragment.isAdded) return@setOnClickListener
|
||||
|
||||
themeFragment.show(supportFragmentManager, themeFragment.tag)
|
||||
}
|
||||
|
||||
binding.ivPhotoPicker.setOnClickListener {
|
||||
ImagePicker.with(this)
|
||||
.crop()
|
||||
.galleryOnly()
|
||||
.galleryMimeTypes( // Exclude gif images
|
||||
mimeTypes = arrayOf(
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg"
|
||||
)
|
||||
)
|
||||
.createIntent { imageResult.launch(it) }
|
||||
}
|
||||
|
||||
binding.tvSelectContent.setOnClickListener {
|
||||
val intent = Intent().apply {
|
||||
type = "audio/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
}
|
||||
selectAudioActivityResultLauncher.launch(
|
||||
Intent.createChooser(
|
||||
intent,
|
||||
"Select Audio"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
binding.llSetAdult.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.llSetAdult.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.llPricePaid.setOnClickListener { viewModel.setPriceFree(false) }
|
||||
binding.llPriceFree.setOnClickListener { viewModel.setPriceFree(true) }
|
||||
binding.llCommentNo.setOnClickListener { viewModel.setAvailableComment(false) }
|
||||
binding.llCommentYes.setOnClickListener { viewModel.setAvailableComment(true) }
|
||||
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
binding.tvUpload.setOnClickListener {
|
||||
viewModel.uploadAudioContent {
|
||||
LiveDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = "콘텐츠 업로드",
|
||||
desc = "등록한 콘텐츠가 업로드 중입니다.\n" +
|
||||
"콘텐츠 등록이 완료되면 알림을 보내드립니다.\n" +
|
||||
"이 페이지를 나가도 콘텐츠는 자동으로 등록됩니다.",
|
||||
confirmButtonTitle = "확인",
|
||||
confirmButtonClick = { finish() },
|
||||
).show(screenWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
listOf(Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage(R.string.read_storage_permission_denied_message)
|
||||
.setPermissions(*permissions.toTypedArray())
|
||||
.check()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etTitle.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
viewModel.title = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etDetail.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
binding.tvNumberOfCharacters.text = "${it.length}자"
|
||||
viewModel.detail = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etTag.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
viewModel.tags = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etSetPrice.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
val price = it.toString().toIntOrNull()
|
||||
if (price != null) {
|
||||
viewModel.price = price.toInt()
|
||||
} else {
|
||||
viewModel.price = 0
|
||||
if (it.isNotBlank()) {
|
||||
binding.etSetPrice.setText(it.substring(0, it.length - 1))
|
||||
binding.etSetPrice.setSelection(it.length - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isPriceFreeLiveData.observe(this) {
|
||||
if (it) {
|
||||
viewModel.price = 0
|
||||
binding.etSetPrice.setText("0")
|
||||
binding.llSetPrice.visibility = View.GONE
|
||||
|
||||
binding.ivPriceFree.visibility = View.VISIBLE
|
||||
binding.tvPriceFree.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llPriceFree.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivPricePaid.visibility = View.GONE
|
||||
binding.tvPricePaid.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llPricePaid.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
} else {
|
||||
binding.llSetPrice.visibility = View.VISIBLE
|
||||
|
||||
binding.ivPricePaid.visibility = View.VISIBLE
|
||||
binding.tvPricePaid.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llPricePaid.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivPriceFree.visibility = View.GONE
|
||||
binding.tvPriceFree.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llPriceFree.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAvailableCommentLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivCommentYes.visibility = View.VISIBLE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentNo.visibility = View.GONE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
} else {
|
||||
binding.ivCommentNo.visibility = View.VISIBLE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentYes.visibility = View.GONE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentYes
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734_9970ff)
|
||||
}
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
binding.llAgeAll.setOnClickListener {
|
||||
viewModel.setAdult(false)
|
||||
}
|
||||
|
||||
binding.llAge19.setOnClickListener {
|
||||
viewModel.setAdult(true)
|
||||
}
|
||||
|
||||
viewModel.isAdultLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivAgeAll.visibility = View.GONE
|
||||
binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAge19.visibility = View.VISIBLE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.ivAge19.visibility = View.GONE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAgeAll.visibility = View.VISIBLE
|
||||
binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileName(uri: Uri): String? {
|
||||
val scheme = uri.scheme
|
||||
var fileName: String? = null
|
||||
|
||||
if (scheme == "content") {
|
||||
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (nameIndex != -1 && cursor.moveToFirst()) {
|
||||
fileName = cursor.getString(nameIndex)
|
||||
}
|
||||
}
|
||||
} else if (scheme == "file") {
|
||||
val file = File(uri.path ?: "")
|
||||
fileName = file.name
|
||||
}
|
||||
|
||||
return fileName
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload
|
||||
|
||||
import android.net.Uri
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.GetAudioContentThemeResponse
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.BufferedSink
|
||||
import java.io.File
|
||||
|
||||
class AudioContentUploadViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private val _isAdultLiveData = MutableLiveData(false)
|
||||
val isAdultLiveData: LiveData<Boolean>
|
||||
get() = _isAdultLiveData
|
||||
|
||||
private val _isAvailableCommentLiveData = MutableLiveData(true)
|
||||
val isAvailableCommentLiveData: LiveData<Boolean>
|
||||
get() = _isAvailableCommentLiveData
|
||||
|
||||
private val _isPriceFreeLiveData = MutableLiveData(true)
|
||||
val isPriceFreeLiveData: LiveData<Boolean>
|
||||
get() = _isPriceFreeLiveData
|
||||
|
||||
lateinit var getRealPathFromURI: (Uri) -> String?
|
||||
|
||||
var title = ""
|
||||
var detail = ""
|
||||
var tags = ""
|
||||
var price = 0
|
||||
var theme: GetAudioContentThemeResponse? = null
|
||||
var coverImageUri: Uri? = null
|
||||
var contentUri: Uri? = null
|
||||
|
||||
fun setAdult(isAdult: Boolean) {
|
||||
_isAdultLiveData.postValue(isAdult)
|
||||
}
|
||||
|
||||
fun setAvailableComment(isAvailableComment: Boolean) {
|
||||
_isAvailableCommentLiveData.postValue(isAvailableComment)
|
||||
}
|
||||
|
||||
fun setPriceFree(isPriceFree: Boolean) {
|
||||
_isPriceFreeLiveData.postValue(isPriceFree)
|
||||
}
|
||||
|
||||
fun uploadAudioContent(onSuccess: () -> Unit) {
|
||||
if (!_isLoading.value!! && validateData()) {
|
||||
_isLoading.postValue(true)
|
||||
|
||||
val request = CreateAudioContentRequest(
|
||||
title = title,
|
||||
detail = detail,
|
||||
tags = tags,
|
||||
price = price,
|
||||
themeId = theme!!.id,
|
||||
isAdult = _isAdultLiveData.value!!,
|
||||
isCommentAvailable = _isAvailableCommentLiveData.value!!
|
||||
)
|
||||
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val coverImage = if (coverImageUri != null) {
|
||||
val file = File(getRealPathFromURI(coverImageUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"coverImage",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "image/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val contentFile = if (contentUri != null) {
|
||||
val file = File(getRealPathFromURI(contentUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"contentFile",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "audio/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(512)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (coverImage == null) {
|
||||
_toastLiveData.postValue("커버이미지를 선택해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
if (contentFile == null) {
|
||||
_toastLiveData.postValue("오디오 콘텐츠를 선택해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.uploadAudioContent(
|
||||
coverImage = coverImage,
|
||||
contentFile = contentFile,
|
||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
_isLoading.postValue(false)
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateData(): Boolean {
|
||||
if (title.isBlank()) {
|
||||
_toastLiveData.postValue("제목을 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (detail.isBlank() || detail.length < 5) {
|
||||
_toastLiveData.postValue("내용을 5자 이상 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (theme == null) {
|
||||
_toastLiveData.postValue("테마를 선택해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (coverImageUri == null) {
|
||||
_toastLiveData.postValue("커버이미지를 선택해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (contentUri == null) {
|
||||
_toastLiveData.postValue("오디오 콘텐츠를 선택해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isPriceFreeLiveData.value!! && price < 10) {
|
||||
_toastLiveData.postValue("콘텐츠의 최소금액은 10코인 입니다.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateAudioContentRequest(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("detail") val detail: String,
|
||||
@SerializedName("tags") val tags: String,
|
||||
@SerializedName("price") val price: Int,
|
||||
@SerializedName("themeId") val themeId: Long,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
|
||||
)
|
|
@ -0,0 +1,64 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentThemeBinding
|
||||
|
||||
class AudioContentThemeAdapter(
|
||||
private val selectedTheme: GetAudioContentThemeResponse?,
|
||||
private val onItemClick: (GetAudioContentThemeResponse) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentThemeAdapter.ViewHolder>() {
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemAudioContentThemeBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
private var isChecked = false
|
||||
|
||||
fun bind(item: GetAudioContentThemeResponse) {
|
||||
if (selectedTheme == item) {
|
||||
binding.ivThemeChecked.visibility = View.VISIBLE
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_9970ff))
|
||||
isChecked = true
|
||||
} else {
|
||||
binding.ivThemeChecked.visibility = View.GONE
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_bbbbbb))
|
||||
isChecked = false
|
||||
}
|
||||
|
||||
binding.ivTheme.load(item.image) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvTheme.text = item.theme
|
||||
|
||||
binding.root.setOnClickListener { onItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
val items = mutableSetOf<GetAudioContentThemeResponse>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemAudioContentThemeBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentThemeFragment(
|
||||
private val getSelectedTheme: () -> GetAudioContentThemeResponse?,
|
||||
private val onItemClick: (GetAudioContentThemeResponse) -> Unit
|
||||
) : BottomSheetDialogFragment() {
|
||||
|
||||
private val viewModel: AudioContentThemeViewModel by inject()
|
||||
|
||||
private lateinit var adapter: AudioContentThemeAdapter
|
||||
|
||||
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 = inflater.inflate(R.layout.fragment_audio_content_theme, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
view.findViewById<ImageView>(R.id.iv_close).setOnClickListener {
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
setupAdapter(view)
|
||||
bindData()
|
||||
|
||||
viewModel.getThemes()
|
||||
}
|
||||
|
||||
private fun setupAdapter(view: View) {
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.rv_themes)
|
||||
adapter = AudioContentThemeAdapter(getSelectedTheme()) {
|
||||
onItemClick(it)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = GridLayoutManager(requireContext(), 4)
|
||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
})
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.themeLiveData.observe(viewLifecycleOwner) {
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentThemeViewModel(private val repository: AudioContentRepository) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private val _themeLiveData = MutableLiveData<List<GetAudioContentThemeResponse>>()
|
||||
val themeLiveData: LiveData<List<GetAudioContentThemeResponse>>
|
||||
get() = _themeLiveData
|
||||
|
||||
fun getThemes() {
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentThemeList(token = "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_themeLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetAudioContentThemeResponse(
|
||||
@SerializedName("id") val id: Long,
|
||||
@SerializedName("theme") val theme: String,
|
||||
@SerializedName("image") val image: String
|
||||
)
|
|
@ -0,0 +1,74 @@
|
|||
package kr.co.vividnext.sodalive.base
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogSodaBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
open class SodaDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
title: String,
|
||||
desc: String,
|
||||
confirmButtonTitle: String,
|
||||
confirmButtonClick: () -> Unit,
|
||||
cancelButtonTitle: String = "",
|
||||
cancelButtonClick: (() -> Unit)? = null,
|
||||
) {
|
||||
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogSodaBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvTitle.text = title
|
||||
dialogView.tvDesc.text = desc
|
||||
|
||||
dialogView.tvCancel.text = cancelButtonTitle
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
cancelButtonClick?.let { it() }
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.text = confirmButtonTitle
|
||||
dialogView.tvConfirm.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick()
|
||||
}
|
||||
|
||||
dialogView.tvCancel.visibility = if (cancelButtonTitle.isNotBlank()) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.visibility = if (confirmButtonTitle.isNotBlank()) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun show(width: Int) {
|
||||
alertDialog.show()
|
||||
|
||||
val lp = WindowManager.LayoutParams()
|
||||
lp.copyFrom(alertDialog.window?.attributes)
|
||||
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
|
||||
alertDialog.window?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import retrofit2.Retrofit
|
||||
|
||||
class ApiBuilder {
|
||||
fun <T> build(retrofit: Retrofit, service: Class<T>): T {
|
||||
return retrofit.create(service)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ApiResponse<T>(
|
||||
@SerializedName("success") val success: Boolean,
|
||||
@SerializedName("data") val data: T? = null,
|
||||
@SerializedName("message") val message: String? = null,
|
||||
@SerializedName("errorProperty") val errorProperty: String? = null
|
||||
)
|
|
@ -0,0 +1,55 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
object Constants {
|
||||
const val PREF_CAN = "pref_can"
|
||||
const val PREF_TOKEN = "pref_token"
|
||||
const val PREF_EMAIL = "pref_email"
|
||||
const val PREF_USER_ID = "pref_user_id"
|
||||
const val PREF_IS_ADULT = "pref_is_adult"
|
||||
const val PREF_NICKNAME = "pref_nickname"
|
||||
const val PREF_USER_ROLE = "pref_user_role"
|
||||
const val PREF_PUSH_TOKEN = "pref_push_token"
|
||||
const val PREF_PROFILE_IMAGE = "pref_profile_image"
|
||||
const val PREF_IS_CONTENT_PLAY_LOOP = "pref_is_content_play_loop"
|
||||
const val PREF_IS_FOLLOWED_CREATOR_LIVE = "pref_is_followed_creator_live"
|
||||
const val PREF_NOT_SHOWING_EVENT_POPUP_ID = "pref_not_showing_event_popup_id"
|
||||
|
||||
const val EXTRA_CAN = "extra_can"
|
||||
const val EXTRA_DATA = "extra_data"
|
||||
const val EXTRA_TERMS = "extra_terms"
|
||||
const val EXTRA_EVENT = "extra_event"
|
||||
const val EXTRA_NOTICE = "extra_notice"
|
||||
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"
|
||||
|
||||
const val EXTRA_AUDIO_CONTENT_ID = "audio_content_id"
|
||||
const val EXTRA_AUDIO_CONTENT_URL = "audio_content_url"
|
||||
const val EXTRA_AUDIO_CONTENT_TITLE = "audio_content_title"
|
||||
const val EXTRA_AUDIO_CONTENT_FREE = "audio_content_is_free"
|
||||
const val EXTRA_AUDIO_CONTENT_PREVIEW = "audio_content_is_preview"
|
||||
const val EXTRA_AUDIO_CONTENT_PLAYING = "audio_content_is_playing"
|
||||
const val EXTRA_AUDIO_CONTENT_SHOWING = "audio_content_is_showing"
|
||||
const val EXTRA_AUDIO_CONTENT_CHANGE_UI = "audio_content_change_ui"
|
||||
const val EXTRA_AUDIO_CONTENT_PROGRESS = "audio_content_progress"
|
||||
const val EXTRA_AUDIO_CONTENT_DURATION = "audio_content_duration"
|
||||
const val EXTRA_AUDIO_CONTENT_COMMENT = "audio_content_comment"
|
||||
const val EXTRA_AUDIO_CONTENT_LOADING = "audio_content_loading"
|
||||
const val EXTRA_AUDIO_CONTENT_CREATOR_ID = "audio_content_creator_id"
|
||||
const val EXTRA_AUDIO_CONTENT_NEXT_ACTION = "audio_content_next_action"
|
||||
const val EXTRA_AUDIO_CONTENT_ALERT_PREVIEW = "audio_content_alert_preview"
|
||||
const val EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL = "audio_content_cover_image_url"
|
||||
|
||||
const val LIVE_SERVICE_NOTIFICATION_ID: Int = 2
|
||||
const val ACTION_AUDIO_CONTENT_RECEIVER = "soda_live_action_content_receiver"
|
||||
const val ACTION_MAIN_AUDIO_CONTENT_RECEIVER = "soda_live_action_main_content_receiver"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextPaint
|
||||
import android.text.style.MetricAffectingSpan
|
||||
|
||||
class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() {
|
||||
override fun updateDrawState(tp: TextPaint) {
|
||||
tp.typeface = typeface
|
||||
}
|
||||
|
||||
override fun updateMeasureState(textPaint: TextPaint) {
|
||||
textPaint.typeface = typeface
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.AnimationDrawable
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogLoadingBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class LoadingDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater
|
||||
) {
|
||||
private val alertDialog: AlertDialog
|
||||
private val dialogView = DialogLoadingBinding.inflate(layoutInflater)
|
||||
private val animationDrawable: AnimationDrawable
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
animationDrawable = dialogView.tvLoading.compoundDrawables[1] as AnimationDrawable
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
}
|
||||
|
||||
fun show(width: Int, message: String = "") {
|
||||
alertDialog.show()
|
||||
animationDrawable.start()
|
||||
dialogView.tvLoading.text = message
|
||||
|
||||
val lp = WindowManager.LayoutParams()
|
||||
lp.copyFrom(alertDialog.window?.attributes)
|
||||
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
|
||||
alertDialog.window?.attributes = lp
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
animationDrawable.stop()
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.content.Context
|
||||
import io.objectbox.BoxStore
|
||||
import kr.co.vividnext.sodalive.audio_content.MyObjectBox
|
||||
import kr.co.vividnext.sodalive.audio_content.PlaybackTracking
|
||||
|
||||
class ObjectBox(context: Context) {
|
||||
private var store: BoxStore = MyObjectBox.builder()
|
||||
.androidContext(context.applicationContext)
|
||||
.build()
|
||||
|
||||
val playbackTrackingBox = store.boxFor(PlaybackTracking::class.java)
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import android.text.TextUtils
|
||||
|
||||
object RealPathUtil {
|
||||
fun getRealPath(context: Context, fileUri: Uri): String? {
|
||||
return getRealPathFromURIAPI19(context, fileUri) // SDK > 19 (Android 4.4) and up
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||
* Framework Documents, as well as the _data field for the MediaStore and
|
||||
* other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @author Niks
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun getRealPathFromURIAPI19(context: Context, uri: Uri): String? {
|
||||
// DocumentProvider
|
||||
if (DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
val docId = DocumentsContract.getDocumentId(uri)
|
||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val type = split[0]
|
||||
|
||||
if ("primary".equals(type, ignoreCase = true)) {
|
||||
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
|
||||
}
|
||||
} else if (isDownloadsDocument(uri)) {
|
||||
var cursor: Cursor? = null
|
||||
try {
|
||||
cursor = context.contentResolver.query(
|
||||
uri,
|
||||
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
cursor!!.moveToNext()
|
||||
val fileName = cursor.getString(0)
|
||||
val path = Environment.getExternalStorageDirectory()
|
||||
.toString() + "/Download/" + fileName
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
return path
|
||||
}
|
||||
} finally {
|
||||
cursor?.close()
|
||||
}
|
||||
val id = DocumentsContract.getDocumentId(uri)
|
||||
if (id.startsWith("raw:")) {
|
||||
return id.replaceFirst("raw:".toRegex(), "")
|
||||
}
|
||||
val contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads"),
|
||||
java.lang.Long.valueOf(id)
|
||||
)
|
||||
|
||||
return getDataColumn(context, contentUri, null, null)
|
||||
} else if (isMediaDocument(uri)) {
|
||||
val docId = DocumentsContract.getDocumentId(uri)
|
||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val type = split[0]
|
||||
|
||||
var contentUri: Uri? = null
|
||||
when (type) {
|
||||
"image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
"video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
"audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
|
||||
val selection = "_id=?"
|
||||
val selectionArgs = arrayOf(split[1])
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs)
|
||||
} // MediaProvider
|
||||
// DownloadsProvider
|
||||
} else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
|
||||
|
||||
// Return the remote address
|
||||
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(
|
||||
context,
|
||||
uri,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} else if ("file".equals(uri.scheme!!, ignoreCase = true)) {
|
||||
return uri.path
|
||||
} // File
|
||||
// MediaStore (and general)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
* @author Niks
|
||||
*/
|
||||
private fun getDataColumn(
|
||||
context: Context,
|
||||
uri: Uri?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?
|
||||
): String? {
|
||||
|
||||
var cursor: Cursor? = null
|
||||
val column = "_data"
|
||||
val projection = arrayOf(column)
|
||||
|
||||
try {
|
||||
cursor =
|
||||
context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val index = cursor.getColumnIndexOrThrow(column)
|
||||
return cursor.getString(index)
|
||||
}
|
||||
} finally {
|
||||
cursor?.close()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
private fun isExternalStorageDocument(uri: Uri): Boolean {
|
||||
return "com.android.externalstorage.documents" == uri.authority
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
*/
|
||||
private fun isDownloadsDocument(uri: Uri): Boolean {
|
||||
return "com.android.providers.downloads.documents" == uri.authority
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
*/
|
||||
private fun isMediaDocument(uri: Uri): Boolean {
|
||||
return "com.android.providers.media.documents" == uri.authority
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is Google Photos.
|
||||
*/
|
||||
private fun isGooglePhotosUri(uri: Uri): Boolean {
|
||||
return "com.google.android.apps.photos.content" == uri.authority
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import kr.co.vividnext.sodalive.settings.notification.MemberRole
|
||||
|
||||
object SharedPreferenceManager {
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
fun init(context: Context) {
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
sharedPreferences.edit { it.clear() }
|
||||
}
|
||||
|
||||
private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
|
||||
val editor = this.edit()
|
||||
operation(editor)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private operator fun SharedPreferences.set(key: String, value: Any?) {
|
||||
when (value) {
|
||||
is String? -> edit { it.putString(key, value) }
|
||||
is Int -> edit { it.putInt(key, value) }
|
||||
is Boolean -> edit { it.putBoolean(key, value) }
|
||||
is Float -> edit { it.putFloat(key, value) }
|
||||
is Long -> edit { it.putLong(key, value) }
|
||||
else -> throw UnsupportedOperationException("Error")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private operator fun <T> SharedPreferences.get(key: String, defaultValue: T? = null): T {
|
||||
return when (defaultValue) {
|
||||
is String, null -> getString(key, defaultValue as? String) as T
|
||||
is Int -> getInt(key, defaultValue as? Int ?: -1) as T
|
||||
is Boolean -> getBoolean(key, defaultValue as? Boolean ?: false) as T
|
||||
is Float -> getFloat(key, defaultValue as? Float ?: -1f) as T
|
||||
is Long -> getLong(key, defaultValue as? Long ?: -1) as T
|
||||
else -> throw UnsupportedOperationException("Error")
|
||||
}
|
||||
}
|
||||
|
||||
var token: String
|
||||
get() = sharedPreferences[Constants.PREF_TOKEN, ""]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_TOKEN] = value
|
||||
}
|
||||
|
||||
var userId: Long
|
||||
get() = sharedPreferences[Constants.PREF_USER_ID, 0]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_USER_ID] = value
|
||||
}
|
||||
|
||||
var nickname: String
|
||||
get() = sharedPreferences[Constants.PREF_NICKNAME, ""]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_NICKNAME] = value
|
||||
}
|
||||
|
||||
var email: String
|
||||
get() = sharedPreferences[Constants.PREF_EMAIL, ""]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_EMAIL] = value
|
||||
}
|
||||
|
||||
var profileImage: String
|
||||
get() = sharedPreferences[Constants.PREF_PROFILE_IMAGE, ""]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_PROFILE_IMAGE] = value
|
||||
}
|
||||
|
||||
var can: Int
|
||||
get() = sharedPreferences[Constants.PREF_CAN, 0]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_CAN] = value
|
||||
}
|
||||
|
||||
var role: String
|
||||
get() = sharedPreferences[Constants.PREF_USER_ROLE, MemberRole.USER.name]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_USER_ROLE] = value
|
||||
}
|
||||
|
||||
var isAuth: Boolean
|
||||
get() = sharedPreferences[Constants.PREF_IS_ADULT, false]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_IS_ADULT] = value
|
||||
}
|
||||
|
||||
var pushToken: String
|
||||
get() = sharedPreferences[Constants.PREF_PUSH_TOKEN, ""]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_PUSH_TOKEN] = value
|
||||
}
|
||||
|
||||
var isFollowedCreatorLive: Boolean
|
||||
get() = sharedPreferences[Constants.PREF_IS_FOLLOWED_CREATOR_LIVE, false]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_IS_FOLLOWED_CREATOR_LIVE] = value
|
||||
}
|
||||
|
||||
var isContentPlayLoop: Boolean
|
||||
get() = sharedPreferences[Constants.PREF_IS_CONTENT_PLAY_LOOP, false]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_IS_CONTENT_PLAY_LOOP] = value
|
||||
}
|
||||
|
||||
var notShowingEventPopupId: Long
|
||||
get() = sharedPreferences[Constants.PREF_NOT_SHOWING_EVENT_POPUP_ID, 0]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_NOT_SHOWING_EVENT_POPUP_ID] = value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.live.LiveViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class SodaLiveService : Service() {
|
||||
|
||||
private val liveViewModel: LiveViewModel by inject()
|
||||
|
||||
var roomId: Long = 0
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val content = intent?.getStringExtra("content") ?: "라이브 진행중"
|
||||
roomId = intent?.getLongExtra("roomId", 0) ?: 0L
|
||||
updateNotification(content)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun updateNotification(content: String) {
|
||||
startForeground(Constants.LIVE_SERVICE_NOTIFICATION_ID, createNotification(content))
|
||||
}
|
||||
|
||||
private fun createNotification(content: String): Notification {
|
||||
val notificationChannelId = "soda_live_service_foreground_service_channel"
|
||||
val notificationManager =
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
notificationChannelId,
|
||||
getString(R.string.app_name),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val intent = Intent(this, LiveRoomActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
|
||||
.setSmallIcon(R.drawable.ic_noti)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(content)
|
||||
.setOngoing(true)
|
||||
.setSilent(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
|
||||
return notificationBuilder.build()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
liveViewModel.quitRoom(roomId) { }
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun stopService(context: Context) {
|
||||
val intent = Intent(context, SodaLiveService::class.java)
|
||||
context.stopService(intent)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
object Utils {
|
||||
fun convertDurationToString(duration: Int): String {
|
||||
val durationSeconds = duration / 1000
|
||||
val hours = (durationSeconds / 3600)
|
||||
val minutes = ((durationSeconds % 3600) / 60)
|
||||
val seconds = (durationSeconds % 60)
|
||||
|
||||
return "%02d:%02d:%02d".format(hours, minutes, seconds)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
package kr.co.vividnext.sodalive.di
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.GsonBuilder
|
||||
import kr.co.vividnext.sodalive.BuildConfig
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentApi
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentListViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentReplyViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel
|
||||
import kr.co.vividnext.sodalive.common.ApiBuilder
|
||||
import kr.co.vividnext.sodalive.common.ObjectBox
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerApi
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerRepository
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileViewModel
|
||||
import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel
|
||||
import kr.co.vividnext.sodalive.following.FollowingCreatorRepository
|
||||
import kr.co.vividnext.sodalive.following.FollowingCreatorViewModel
|
||||
import kr.co.vividnext.sodalive.live.LiveApi
|
||||
import kr.co.vividnext.sodalive.live.LiveRepository
|
||||
import kr.co.vividnext.sodalive.live.LiveViewModel
|
||||
import kr.co.vividnext.sodalive.live.recommend.LiveRecommendApi
|
||||
import kr.co.vividnext.sodalive.live.recommend.LiveRecommendRepository
|
||||
import kr.co.vividnext.sodalive.live.reservation_status.LiveReservationStatusViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
|
||||
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.TextMessageDetailViewModel
|
||||
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
|
||||
import kr.co.vividnext.sodalive.mypage.can.CanApi
|
||||
import kr.co.vividnext.sodalive.mypage.can.CanRepository
|
||||
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeViewModel
|
||||
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentViewModel
|
||||
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusViewModel
|
||||
import kr.co.vividnext.sodalive.mypage.service_center.FaqApi
|
||||
import kr.co.vividnext.sodalive.mypage.service_center.FaqRepository
|
||||
import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel
|
||||
import kr.co.vividnext.sodalive.network.TokenAuthenticator
|
||||
import kr.co.vividnext.sodalive.report.ReportApi
|
||||
import kr.co.vividnext.sodalive.report.ReportRepository
|
||||
import kr.co.vividnext.sodalive.settings.SettingsViewModel
|
||||
import kr.co.vividnext.sodalive.settings.event.EventApi
|
||||
import kr.co.vividnext.sodalive.settings.event.EventRepository
|
||||
import kr.co.vividnext.sodalive.settings.event.EventViewModel
|
||||
import kr.co.vividnext.sodalive.settings.notice.NoticeApi
|
||||
import kr.co.vividnext.sodalive.settings.notice.NoticeRepository
|
||||
import kr.co.vividnext.sodalive.settings.notice.NoticeViewModel
|
||||
import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsViewModel
|
||||
import kr.co.vividnext.sodalive.settings.signout.SignOutViewModel
|
||||
import kr.co.vividnext.sodalive.settings.terms.TermsApi
|
||||
import kr.co.vividnext.sodalive.settings.terms.TermsRepository
|
||||
import kr.co.vividnext.sodalive.settings.terms.TermsViewModel
|
||||
import kr.co.vividnext.sodalive.user.UserApi
|
||||
import kr.co.vividnext.sodalive.user.UserRepository
|
||||
import kr.co.vividnext.sodalive.user.find_password.FindPasswordViewModel
|
||||
import kr.co.vividnext.sodalive.user.login.LoginViewModel
|
||||
import kr.co.vividnext.sodalive.user.signup.SignUpViewModel
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.dsl.module
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
class AppDI(private val context: Context, isDebugMode: Boolean) {
|
||||
private val baseUrl = BuildConfig.BASE_URL
|
||||
|
||||
private val otherModule = module {
|
||||
single { GsonBuilder().create() }
|
||||
single { ObjectBox(get()) }
|
||||
}
|
||||
|
||||
private val networkModule = module {
|
||||
single {
|
||||
val logging = HttpLoggingInterceptor()
|
||||
|
||||
if (isDebugMode) {
|
||||
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
} else {
|
||||
logging.setLevel(HttpLoggingInterceptor.Level.NONE)
|
||||
}
|
||||
|
||||
OkHttpClient().newBuilder()
|
||||
.addInterceptor(logging)
|
||||
.authenticator(TokenAuthenticator(get()))
|
||||
.build()
|
||||
}
|
||||
|
||||
single {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.client(get())
|
||||
.build()
|
||||
}
|
||||
|
||||
single { ApiBuilder().build(get(), CanApi::class.java) }
|
||||
single { ApiBuilder().build(get(), AuthApi::class.java) }
|
||||
single { ApiBuilder().build(get(), UserApi::class.java) }
|
||||
single { ApiBuilder().build(get(), LiveApi::class.java) }
|
||||
single { ApiBuilder().build(get(), TermsApi::class.java) }
|
||||
single { ApiBuilder().build(get(), EventApi::class.java) }
|
||||
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) }
|
||||
single { ApiBuilder().build(get(), NoticeApi::class.java) }
|
||||
single { ApiBuilder().build(get(), AudioContentApi::class.java) }
|
||||
single { ApiBuilder().build(get(), FaqApi::class.java) }
|
||||
}
|
||||
|
||||
private val viewModelModule = module {
|
||||
viewModel { LoginViewModel(get()) }
|
||||
viewModel { SignUpViewModel(get()) }
|
||||
viewModel { TermsViewModel(get()) }
|
||||
viewModel { FindPasswordViewModel(get()) }
|
||||
viewModel { MainViewModel(get(), get(), get(), get()) }
|
||||
viewModel { LiveViewModel(get(), get(), get()) }
|
||||
viewModel { MyPageViewModel(get(), get()) }
|
||||
viewModel { CanStatusViewModel(get()) }
|
||||
viewModel { CanChargeViewModel(get()) }
|
||||
viewModel { CanPaymentViewModel(get()) }
|
||||
viewModel { LiveRoomDetailViewModel(get()) }
|
||||
viewModel { LiveRoomCreateViewModel(get()) }
|
||||
viewModel { LiveTagViewModel(get()) }
|
||||
viewModel { LiveRoomEditViewModel(get()) }
|
||||
viewModel { LiveRoomViewModel(get(), get(), get()) }
|
||||
viewModel { LiveRoomDonationMessageViewModel(get()) }
|
||||
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()) }
|
||||
viewModel { SignOutViewModel(get()) }
|
||||
viewModel { NoticeViewModel(get()) }
|
||||
viewModel { EventViewModel(get()) }
|
||||
viewModel { NotificationSettingsViewModel(get()) }
|
||||
viewModel { SettingsViewModel(get()) }
|
||||
viewModel { TextMessageDetailViewModel(get()) }
|
||||
viewModel { LiveReservationStatusViewModel(get()) }
|
||||
viewModel { AudioContentMainViewModel(get()) }
|
||||
viewModel { AudioContentViewModel(get()) }
|
||||
viewModel { AudioContentOrderListViewModel(get()) }
|
||||
viewModel { AudioContentUploadViewModel(get()) }
|
||||
viewModel { AudioContentModifyViewModel(get()) }
|
||||
viewModel { AudioContentThemeViewModel(get()) }
|
||||
viewModel { AudioContentDetailViewModel(get(), get(), get(), get()) }
|
||||
viewModel { AudioContentCommentListViewModel(get()) }
|
||||
viewModel { AudioContentCommentReplyViewModel(get()) }
|
||||
viewModel { FollowingCreatorViewModel(get()) }
|
||||
viewModel { ServiceCenterViewModel(get()) }
|
||||
}
|
||||
|
||||
private val repositoryModule = module {
|
||||
factory { UserRepository(get()) }
|
||||
factory { TermsRepository(get()) }
|
||||
factory { LiveRepository(get(), get()) }
|
||||
factory { EventRepository(get()) }
|
||||
factory { LiveRecommendRepository(get()) }
|
||||
factory { AuthRepository(get()) }
|
||||
factory { CanRepository(get()) }
|
||||
factory { LiveTagRepository(get()) }
|
||||
factory { ReportRepository(get()) }
|
||||
factory { ExplorerRepository(get()) }
|
||||
factory { MessageRepository(get()) }
|
||||
factory { NoticeRepository(get()) }
|
||||
factory { AudioContentRepository(get(), get()) }
|
||||
factory { AudioContentCommentRepository(get()) }
|
||||
factory { PlaybackTrackingRepository(get()) }
|
||||
factory { FollowingCreatorRepository(get(), get()) }
|
||||
factory { FaqRepository(get()) }
|
||||
}
|
||||
|
||||
private val moduleList = listOf(
|
||||
networkModule,
|
||||
viewModelModule,
|
||||
repositoryModule,
|
||||
otherModule
|
||||
)
|
||||
|
||||
init {
|
||||
startKoin {
|
||||
androidContext(context)
|
||||
modules(moduleList)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package kr.co.vividnext.sodalive.dialog
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogLiveBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
open class LiveDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
title: String,
|
||||
desc: String,
|
||||
confirmButtonTitle: String,
|
||||
confirmButtonClick: () -> Unit,
|
||||
cancelButtonTitle: String = "",
|
||||
cancelButtonClick: (() -> Unit)? = null,
|
||||
) {
|
||||
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogLiveBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvTitle.text = title
|
||||
dialogView.tvDesc.text = desc
|
||||
|
||||
dialogView.tvCancel.text = cancelButtonTitle
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
cancelButtonClick?.let { it() }
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.text = confirmButtonTitle
|
||||
dialogView.tvConfirm.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick()
|
||||
}
|
||||
|
||||
dialogView.tvCancel.visibility = if (cancelButtonTitle.isNotBlank()) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.visibility = if (confirmButtonTitle.isNotBlank()) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun show(width: Int) {
|
||||
alertDialog.show()
|
||||
|
||||
val lp = WindowManager.LayoutParams()
|
||||
lp.copyFrom(alertDialog.window?.attributes)
|
||||
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
|
||||
alertDialog.window?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.text.SpannableString
|
||||
import android.text.Spanned
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.databinding.ItemExplorerBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class ExplorerAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<ExplorerAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetExplorerSectionResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemExplorerBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetExplorerSectionResponse) {
|
||||
setTitle(item)
|
||||
setCreatorList(item)
|
||||
}
|
||||
|
||||
private fun setTitle(item: GetExplorerSectionResponse) {
|
||||
binding.tvTitle.text = if (
|
||||
!item.coloredTitle.isNullOrBlank() &&
|
||||
!item.color.isNullOrBlank()
|
||||
) {
|
||||
val spStr = SpannableString(item.title)
|
||||
|
||||
try {
|
||||
spStr.setSpan(
|
||||
ForegroundColorSpan(
|
||||
Color.parseColor("#${item.color}")
|
||||
),
|
||||
item.title.indexOf(item.coloredTitle),
|
||||
item.title.indexOf(item.coloredTitle) + item.coloredTitle.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
|
||||
spStr
|
||||
} catch (e: IllegalArgumentException) {
|
||||
item.title
|
||||
}
|
||||
} else {
|
||||
item.title
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCreatorList(item: GetExplorerSectionResponse) {
|
||||
val adapter = ExplorerSectionAdapter(onClickItem = onClickItem)
|
||||
|
||||
binding.rvExplorerSection.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvExplorerSection.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvExplorerSection.adapter = adapter
|
||||
adapter.addItems(item.creators)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemExplorerBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetExplorerSectionResponse>) {
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetCreatorProfileResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.cheers.PostWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface ExplorerApi {
|
||||
@GET("/explorer")
|
||||
fun getExplorer(
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetExplorerResponse>>
|
||||
|
||||
@GET("/explorer/search/channel")
|
||||
fun searchChannel(
|
||||
@Query("channel") channel: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetRoomDetailUser>>>
|
||||
|
||||
@GET("/explorer/profile/{id}")
|
||||
fun getCreatorProfile(
|
||||
@Path("id") id: Long,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetCreatorProfileResponse>>
|
||||
|
||||
@POST("/explorer/profile/cheers")
|
||||
fun writeCheers(
|
||||
@Body request: PostWriteCheersRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@PUT("/explorer/profile/cheers")
|
||||
fun modifyCheers(
|
||||
@Body request: PutModifyCheersRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@POST("/explorer/profile/notice")
|
||||
fun writeCreatorNotice(
|
||||
@Body request: PostCreatorNoticeRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/explorer/profile/{id}/follower-list")
|
||||
fun getFollowerList(
|
||||
@Path("id") userId: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetFollowerListResponse>>
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
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.BaseFragment
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
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.SelectMessageRecipientAdapter
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ExplorerFragment : BaseFragment<FragmentExplorerBinding>(
|
||||
FragmentExplorerBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: ExplorerViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: ExplorerAdapter
|
||||
private lateinit var imm: InputMethodManager
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private lateinit var searchChannelAdapter: SelectMessageRecipientAdapter
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
|
||||
viewModel.getExplorer()
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
handler.postDelayed({
|
||||
imm.hideSoftInputFromWindow(
|
||||
requireActivity().window.decorView.applicationWindowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
adapter = ExplorerAdapter {
|
||||
val intent = Intent(requireContext(), UserProfileActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, it)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvExplorer.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvExplorer.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 0
|
||||
outRect.bottom = 30f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 30f.dpToPx().toInt()
|
||||
outRect.bottom = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 30f.dpToPx().toInt()
|
||||
outRect.bottom = 30f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvExplorer.adapter = adapter
|
||||
setupSearchChannelView()
|
||||
}
|
||||
|
||||
private fun setupSearchChannelView() {
|
||||
searchChannelAdapter = SelectMessageRecipientAdapter {
|
||||
hideKeyboard()
|
||||
val intent = Intent(requireContext(), UserProfileActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, it.id)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvSearchChannel.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvSearchChannel.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()
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvSearchChannel.adapter = searchChannelAdapter
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etSearchChannel.textChanges().skip(1)
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe {
|
||||
binding.ivX.visibility = if (it.length > 1) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
if (it.length >= 2) {
|
||||
viewModel.searchChannel(it.toString())
|
||||
binding.rvSearchChannel.visibility = View.VISIBLE
|
||||
binding.rvExplorer.visibility = View.GONE
|
||||
} else {
|
||||
binding.rvSearchChannel.visibility = View.GONE
|
||||
binding.rvExplorer.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
binding.tvResultX.visibility = View.GONE
|
||||
}
|
||||
)
|
||||
|
||||
binding.ivX.setOnClickListener {
|
||||
hideKeyboard()
|
||||
binding.etSearchChannel.setText("")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.responseLiveData.observe(viewLifecycleOwner) {
|
||||
adapter.addItems(it.sections)
|
||||
}
|
||||
|
||||
viewModel.searchChannelLiveData.observe(viewLifecycleOwner) {
|
||||
searchChannelAdapter.items.clear()
|
||||
if (it.isNotEmpty()) {
|
||||
searchChannelAdapter.items.addAll(it)
|
||||
binding.rvSearchChannel.visibility = View.VISIBLE
|
||||
binding.tvResultX.visibility = View.GONE
|
||||
} else {
|
||||
binding.rvSearchChannel.visibility = View.GONE
|
||||
binding.tvResultX.visibility = View.VISIBLE
|
||||
}
|
||||
searchChannelAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.cheers.PostWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
|
||||
import java.util.TimeZone
|
||||
|
||||
class ExplorerRepository(
|
||||
private val api: ExplorerApi
|
||||
) {
|
||||
fun getExplorer(token: String) = api.getExplorer(authHeader = token)
|
||||
|
||||
fun searchChannel(channel: String, token: String) = api.searchChannel(
|
||||
channel = channel,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getCreatorProfile(id: Long, token: String) = api.getCreatorProfile(
|
||||
id = id,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun writeCheers(
|
||||
parentCheersId: Long?,
|
||||
creatorId: Long,
|
||||
content: String,
|
||||
token: String
|
||||
) = api.writeCheers(
|
||||
request = PostWriteCheersRequest(
|
||||
parentId = parentCheersId,
|
||||
creatorId = creatorId,
|
||||
content = content
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun modifyCheers(
|
||||
cheersId: Long,
|
||||
content: String,
|
||||
token: String
|
||||
) = api.modifyCheers(
|
||||
request = PutModifyCheersRequest(
|
||||
cheersId = cheersId,
|
||||
content = content
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun writeCreatorNotice(notice: String, token: String) = api.writeCreatorNotice(
|
||||
request = PostCreatorNoticeRequest(notice),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getFollowerList(
|
||||
userId: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getFollowerList(
|
||||
userId = userId,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemExplorerSectionBinding
|
||||
|
||||
class ExplorerSectionAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<ExplorerSectionAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetExplorerSectionCreatorResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemExplorerSectionBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetExplorerSectionCreatorResponse) {
|
||||
binding.tvNickname.text = item.nickname
|
||||
binding.tvTags.text = item.tags
|
||||
|
||||
binding.ivProfile.load(item.profileImageUrl) {
|
||||
transformations(CircleCropTransformation())
|
||||
placeholder(R.drawable.ic_logo)
|
||||
crossfade(true)
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onClickItem(item.id) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemExplorerSectionBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetExplorerSectionCreatorResponse>) {
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
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.room.detail.GetRoomDetailUser
|
||||
|
||||
class ExplorerViewModel(private val repository: ExplorerRepository) : BaseViewModel() {
|
||||
|
||||
private val _responseLiveData = MutableLiveData<GetExplorerResponse>()
|
||||
val responseLiveData: LiveData<GetExplorerResponse>
|
||||
get() = _responseLiveData
|
||||
|
||||
private val _searchChannelLiveData = MutableLiveData<List<GetRoomDetailUser>>()
|
||||
val searchChannelLiveData: LiveData<List<GetRoomDetailUser>>
|
||||
get() = _searchChannelLiveData
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
fun searchChannel(channel: String) {
|
||||
compositeDisposable.add(
|
||||
repository.searchChannel(
|
||||
channel = channel,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_searchChannelLiveData.value = it.data!!
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getExplorer() {
|
||||
if (!_isLoading.value!!) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.getExplorer(token = "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_responseLiveData.value = it.data!!
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetExplorerResponse(
|
||||
@SerializedName("sections") val sections: List<GetExplorerSectionResponse>
|
||||
)
|
||||
|
||||
data class GetExplorerSectionResponse(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("coloredTitle") val coloredTitle: String?,
|
||||
@SerializedName("color") val color: String?,
|
||||
@SerializedName("creators") val creators: List<GetExplorerSectionCreatorResponse>
|
||||
)
|
||||
|
||||
data class GetExplorerSectionCreatorResponse(
|
||||
@SerializedName("id") val id: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("tags") val tags: String,
|
||||
@SerializedName("profileImageUrl") val profileImageUrl: String
|
||||
)
|
|
@ -0,0 +1,73 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import com.orhanobut.logger.Logger
|
||||
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.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorNoticeWriteBinding
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerRepository
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class CreatorNoticeWriteActivity : BaseActivity<ActivityCreatorNoticeWriteBinding>(
|
||||
ActivityCreatorNoticeWriteBinding::inflate
|
||||
) {
|
||||
|
||||
private val repository: ExplorerRepository by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "공지사항 쓰기"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
val notice = intent.getStringExtra("notice")
|
||||
binding.etContent.setText(notice)
|
||||
|
||||
binding.tvSave.setOnClickListener {
|
||||
loadingDialog.show(screenWidth)
|
||||
|
||||
val writtenNotice = binding.etContent.text.toString()
|
||||
compositeDisposable.add(
|
||||
repository.writeCreatorNotice(
|
||||
notice = writtenNotice,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
loadingDialog.dismiss()
|
||||
|
||||
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
message,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
if (it.success) {
|
||||
val dataIntent = Intent()
|
||||
dataIntent.putExtra("notice", writtenNotice)
|
||||
setResult(RESULT_OK, dataIntent)
|
||||
finish()
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingDialog.dismiss()
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetCheersResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("cheers") val cheers: List<GetCheersResponseItem>
|
||||
)
|
||||
|
||||
data class GetCheersResponseItem(
|
||||
@SerializedName("cheersId") val cheersId: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileUrl") val profileUrl: String,
|
||||
@SerializedName("content") val content: String,
|
||||
@SerializedName("date") val date: String,
|
||||
@SerializedName("replyList") val replyList: List<GetCheersResponseItem>
|
||||
)
|
|
@ -0,0 +1,95 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetCreatorProfileResponse(
|
||||
@SerializedName("creator")
|
||||
val creator: CreatorResponse,
|
||||
@SerializedName("userDonationRanking")
|
||||
val userDonationRanking: List<UserDonationRankingResponse>,
|
||||
@SerializedName("similarCreatorList")
|
||||
val similarCreatorList: List<SimilarCreatorResponse>,
|
||||
@SerializedName("liveRoomList")
|
||||
val liveRoomList: List<LiveRoomResponse>,
|
||||
@SerializedName("audioContentList")
|
||||
val audioContentList: List<GetAudioContentListItem>,
|
||||
@SerializedName("notice")
|
||||
val notice: String,
|
||||
@SerializedName("cheers")
|
||||
val cheers: GetCheersResponse,
|
||||
@SerializedName("activitySummary")
|
||||
val activitySummary: GetCreatorActivitySummary,
|
||||
@SerializedName("isBlock")
|
||||
val isBlock: Boolean
|
||||
)
|
||||
|
||||
data class CreatorResponse(
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("profileUrl") val profileUrl: String,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("tags") val tags: List<String>,
|
||||
@SerializedName("introduce") val introduce: String = "",
|
||||
@SerializedName("instagramUrl") val instagramUrl: String? = null,
|
||||
@SerializedName("youtubeUrl") val youtubeUrl: String? = null,
|
||||
@SerializedName("websiteUrl") val websiteUrl: String? = null,
|
||||
@SerializedName("blogUrl") val blogUrl: String? = null,
|
||||
@SerializedName("isAvailableChat") val isAvailableChat: Boolean = true,
|
||||
@SerializedName("isNotification") val isNotification: Boolean,
|
||||
@SerializedName("notificationRecipientCount") val notificationRecipientCount: Int
|
||||
)
|
||||
|
||||
data class UserDonationRankingResponse(
|
||||
@SerializedName("userId") val userId: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileImage") val profileImage: String,
|
||||
@SerializedName("donationCoin") val donationCoin: Int
|
||||
)
|
||||
|
||||
data class SimilarCreatorResponse(
|
||||
@SerializedName("userId") val userId: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileImage") val profileImage: String,
|
||||
@SerializedName("tags") val tags: List<String>
|
||||
)
|
||||
|
||||
data class LiveRoomResponse(
|
||||
@SerializedName("roomId") val roomId: Long,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("content") val content: String,
|
||||
@SerializedName("isPaid") val isPaid: Boolean,
|
||||
@SerializedName("beginDateTime") val beginDateTime: String,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("price") val price: Int,
|
||||
@SerializedName("channelName") val channelName: String?,
|
||||
@SerializedName("managerNickname") val managerNickname: String,
|
||||
@SerializedName("isReservation") val isReservation: Boolean,
|
||||
@SerializedName("isActive") val isActive: Boolean,
|
||||
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean
|
||||
)
|
||||
|
||||
data class GetAudioContentListResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<GetAudioContentListItem>
|
||||
)
|
||||
|
||||
data class GetAudioContentListItem(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("price") val price: Int,
|
||||
@SerializedName("themeStr") val themeStr: String,
|
||||
@SerializedName("duration") val duration: String?,
|
||||
@SerializedName("likeCount") val likeCount: Int,
|
||||
@SerializedName("commentCount") val commentCount: Int,
|
||||
@SerializedName("isAdult") val isAdult: Boolean
|
||||
)
|
||||
|
||||
data class GetCreatorActivitySummary(
|
||||
@SerializedName("liveCount") val liveCount: Int,
|
||||
@SerializedName("liveTime") val liveTime: Int,
|
||||
@SerializedName("liveContributorCount") val liveContributorCount: Int,
|
||||
@SerializedName("contentCount") val contentCount: Int
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MemberBlockRequest(@SerializedName("blockMemberId") val blockMemberId: Long)
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PostCreatorNoticeRequest(
|
||||
@SerializedName("notice")
|
||||
val notice: String
|
||||
)
|
|
@ -0,0 +1,773 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.webkit.URLUtil
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
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.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityUserProfileBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter
|
||||
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter
|
||||
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListActivity
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import kr.co.vividnext.sodalive.live.LiveViewModel
|
||||
import kr.co.vividnext.sodalive.live.reservation.complete.LiveReservationCompleteActivity
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
|
||||
import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog
|
||||
import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog
|
||||
import kr.co.vividnext.sodalive.report.CheersReportDialog
|
||||
import kr.co.vividnext.sodalive.report.ProfileReportDialog
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
import kr.co.vividnext.sodalive.report.UserReportDialog
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
|
||||
ActivityUserProfileBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: UserProfileViewModel by inject()
|
||||
private val liveViewModel: LiveViewModel by inject()
|
||||
|
||||
private lateinit var imm: InputMethodManager
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var liveAdapter: UserProfileLiveAdapter
|
||||
private lateinit var donationAdapter: UserProfileDonationAdapter
|
||||
private lateinit var similarCreatorAdapter: UserProfileSimilarCreatorAdapter
|
||||
private lateinit var cheersAdapter: UserProfileCheersAdapter
|
||||
|
||||
private lateinit var noticeWriteLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var userId: Long = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
userId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
imm = getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
noticeWriteLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
val writtenNotice = it.data?.getStringExtra("notice")
|
||||
binding.tvNotice.text = writtenNotice?.ifBlank {
|
||||
"공지사항이 없습니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
bindData()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.getCreatorProfile(userId) { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.tvBack.text = "채널"
|
||||
binding.tvBack.setOnClickListener { finish() }
|
||||
binding.ivMenu.setOnClickListener {
|
||||
showOptionMenu(
|
||||
this,
|
||||
binding.ivMenu,
|
||||
)
|
||||
}
|
||||
binding.layoutUserProfile.ivShare.setOnClickListener {
|
||||
viewModel.shareChannel(userId = userId) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, it)
|
||||
|
||||
val shareIntent = Intent.createChooser(intent, "채널 공유")
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
|
||||
setupLiveView()
|
||||
setupDonationView()
|
||||
setupSimilarCreatorView()
|
||||
setupFanTalkView()
|
||||
}
|
||||
|
||||
private fun hideKeyboard(onAfterExecute: () -> Unit) {
|
||||
handler.postDelayed({
|
||||
imm.hideSoftInputFromWindow(
|
||||
window.decorView.applicationWindowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
onAfterExecute()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
private fun showOptionMenu(context: Context, v: View) {
|
||||
val popup = PopupMenu(context, v)
|
||||
val inflater = popup.menuInflater
|
||||
|
||||
if (viewModel.creatorProfileLiveData.value!!.isBlock) {
|
||||
inflater.inflate(R.menu.user_profile_option_menu_2, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_user_block -> {
|
||||
viewModel.userUnBlock(userId)
|
||||
}
|
||||
|
||||
R.id.menu_user_report -> {
|
||||
showUserReportDialog()
|
||||
}
|
||||
|
||||
R.id.menu_profile_report -> {
|
||||
showProfileReportDialog()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
} else {
|
||||
inflater.inflate(R.menu.user_profile_option_menu, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_user_block -> {
|
||||
showUserBlockDialog()
|
||||
}
|
||||
|
||||
R.id.menu_user_report -> {
|
||||
showUserReportDialog()
|
||||
}
|
||||
|
||||
R.id.menu_profile_report -> {
|
||||
showProfileReportDialog()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun showUserBlockDialog() {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
dialog.setTitle("사용자 차단")
|
||||
dialog.setMessage(
|
||||
"${binding.layoutUserProfile.tvNickname.text}님을 차단하시겠습니까?\n\n" +
|
||||
"사용자를 차단하면 사용자는 아래 기능이 제한됩니다.\n" +
|
||||
"- 내가 개설한 라이브 입장 불가\n" +
|
||||
"- 나에게 메시지 보내기 불가\n" +
|
||||
"- 내 채널의 팬Talk 작성불가"
|
||||
)
|
||||
dialog.setPositiveButton("차단") { _, _ ->
|
||||
viewModel.userBlock(userId)
|
||||
}
|
||||
dialog.setNegativeButton("취소") { _, _ -> }
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun showUserReportDialog() {
|
||||
val dialog = UserReportDialog(this, layoutInflater) {
|
||||
viewModel.report(
|
||||
type = ReportType.USER,
|
||||
userId = userId,
|
||||
reason = it
|
||||
)
|
||||
}
|
||||
|
||||
dialog.show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showProfileReportDialog() {
|
||||
val dialog = ProfileReportDialog(this, layoutInflater) {
|
||||
viewModel.report(
|
||||
type = ReportType.PROFILE,
|
||||
userId = userId
|
||||
)
|
||||
}
|
||||
|
||||
dialog.show(screenWidth)
|
||||
}
|
||||
|
||||
private fun setupLiveView() {
|
||||
val recyclerView = binding.layoutUserProfileLive.rvLive
|
||||
liveAdapter = UserProfileLiveAdapter(
|
||||
onClickParticipant = { enterLiveRoom(roomId = it.roomId) },
|
||||
onClickReservation = { reservationRoom(roomId = it.roomId) }
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
liveAdapter.itemCount - 1 -> {
|
||||
outRect.bottom = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = liveAdapter
|
||||
}
|
||||
|
||||
private fun setupDonationView() {
|
||||
binding.layoutUserProfileDonation.tvAll.setOnClickListener {
|
||||
val intent = Intent(applicationContext, UserProfileDonationAllViewActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, userId)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
val recyclerView = binding.layoutUserProfileDonation.rvDonation
|
||||
donationAdapter = UserProfileDonationAdapter()
|
||||
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
donationAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = donationAdapter
|
||||
}
|
||||
|
||||
private fun setupSimilarCreatorView() {
|
||||
val recyclerView = binding.layoutUserProfileSimilarCreator.rvSimilarCreator
|
||||
similarCreatorAdapter = UserProfileSimilarCreatorAdapter {
|
||||
val intent = Intent(applicationContext, UserProfileActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, it.userId)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 0
|
||||
outRect.bottom = 10f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
similarCreatorAdapter.itemCount - 1 -> {
|
||||
outRect.top = 10f.dpToPx().toInt()
|
||||
outRect.bottom = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 10f.dpToPx().toInt()
|
||||
outRect.bottom = 10f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = similarCreatorAdapter
|
||||
}
|
||||
|
||||
private fun setupFanTalkView() {
|
||||
binding.layoutUserProfileFanTalk.tvAll.setOnClickListener {
|
||||
val intent = Intent(
|
||||
applicationContext,
|
||||
UserProfileFantalkAllViewActivity::class.java
|
||||
)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, userId)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
setupCheersView()
|
||||
}
|
||||
|
||||
private fun setupCheersView() {
|
||||
binding.layoutUserProfileFanTalk.ivSend.setOnClickListener {
|
||||
hideKeyboard {
|
||||
viewModel.writeCheers(
|
||||
creatorId = userId,
|
||||
cheersContent = binding.layoutUserProfileFanTalk.etCheer.text.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val rvCheers = binding.layoutUserProfileFanTalk.rvCheers
|
||||
cheersAdapter = UserProfileCheersAdapter(
|
||||
userId = userId,
|
||||
enterReply = { cheersId, content ->
|
||||
hideKeyboard {
|
||||
viewModel.writeCheers(
|
||||
parentCheersId = cheersId,
|
||||
creatorId = userId,
|
||||
cheersContent = content
|
||||
)
|
||||
}
|
||||
},
|
||||
modifyReply = { cheersId, content ->
|
||||
hideKeyboard {
|
||||
viewModel.modifyCheers(
|
||||
cheersId = cheersId,
|
||||
creatorId = userId,
|
||||
cheersContent = content
|
||||
)
|
||||
}
|
||||
},
|
||||
onClickReport = { showCheersReportPopup(it) }
|
||||
)
|
||||
|
||||
rvCheers.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
rvCheers.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.bottom = 0
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 0
|
||||
}
|
||||
|
||||
cheersAdapter.itemCount - 1 -> {
|
||||
outRect.top = 10.dpToPx().toInt()
|
||||
outRect.bottom = 10.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 10.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
rvCheers.adapter = cheersAdapter
|
||||
}
|
||||
|
||||
private fun showCheersReportPopup(cheersId: Long) {
|
||||
val dialog = CheersReportDialog(this, layoutInflater) {
|
||||
if (it.isBlank()) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"신고 이유를 선택해 주세요.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
viewModel.cheersReport(cheersId, reason = it)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show(screenWidth)
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
liveViewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
liveViewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.creatorProfileLiveData.observe(this) {
|
||||
setCheers(it.cheers)
|
||||
setCreatorProfile(it.creator)
|
||||
setCreatorNotice(it.notice, it.creator.creatorId)
|
||||
setLiveRoomList(it.liveRoomList)
|
||||
setSimilarCreatorList(it.similarCreatorList)
|
||||
setUserDonationRanking(it.userDonationRanking)
|
||||
setActivitySummary(it.activitySummary)
|
||||
}
|
||||
|
||||
viewModel.isExpandNotice.observe(this) {
|
||||
if (it) {
|
||||
binding.tvNotice.maxLines = Int.MAX_VALUE
|
||||
} else {
|
||||
binding.tvNotice.maxLines = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setActivitySummary(activitySummary: GetCreatorActivitySummary) {
|
||||
binding.tvLiveCount.text = activitySummary.liveCount.moneyFormat()
|
||||
binding.tvLiveContributorCount.text = activitySummary.liveContributorCount.moneyFormat()
|
||||
binding.tvLiveTime.text = activitySummary.liveTime.moneyFormat()
|
||||
binding.tvContentCount.text = activitySummary.contentCount.moneyFormat()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setCheers(cheers: GetCheersResponse) {
|
||||
binding.layoutUserProfileFanTalk.etCheer.setText("")
|
||||
cheersAdapter.items.clear()
|
||||
binding.layoutUserProfileFanTalk.tvCheersCount.text = cheers.totalCount.toString()
|
||||
cheersAdapter.items.addAll(cheers.cheers)
|
||||
cheersAdapter.notifyDataSetChanged()
|
||||
|
||||
if (cheersAdapter.itemCount <= 0) {
|
||||
binding.layoutUserProfileFanTalk.rvCheers.visibility = View.GONE
|
||||
binding.layoutUserProfileFanTalk.tvNoCheers.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.layoutUserProfileFanTalk.rvCheers.visibility = View.VISIBLE
|
||||
binding.layoutUserProfileFanTalk.tvNoCheers.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun setCreatorProfile(creator: CreatorResponse) {
|
||||
val layoutUserProfile = binding.layoutUserProfile
|
||||
|
||||
if (creator.creatorId == SharedPreferenceManager.userId) {
|
||||
layoutUserProfile.tvFollowerList.visibility = View.VISIBLE
|
||||
layoutUserProfile.llNotification.visibility = View.GONE
|
||||
|
||||
layoutUserProfile.tvFollowerList.setOnClickListener {
|
||||
val intent = Intent(applicationContext, UserFollowerListActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, creator.creatorId)
|
||||
startActivity(intent)
|
||||
}
|
||||
} else {
|
||||
layoutUserProfile.llNotification.visibility = View.VISIBLE
|
||||
layoutUserProfile.tvFollowerList.visibility = View.GONE
|
||||
}
|
||||
|
||||
layoutUserProfile.ivProfile.load(creator.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvBack.text = "${creator.nickname}님의 채널"
|
||||
layoutUserProfile.tvNickname.text = creator.nickname
|
||||
layoutUserProfile.tvTags.text = creator.tags.joinToString(" ") { "#$it" }
|
||||
|
||||
if (creator.websiteUrl.isNullOrBlank() || !URLUtil.isValidUrl(creator.websiteUrl)) {
|
||||
layoutUserProfile.ivWebsite.visibility = View.GONE
|
||||
} else {
|
||||
layoutUserProfile.ivWebsite.visibility = View.VISIBLE
|
||||
layoutUserProfile.ivWebsite.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(creator.websiteUrl)))
|
||||
}
|
||||
}
|
||||
|
||||
if (creator.blogUrl.isNullOrBlank() || !URLUtil.isValidUrl(creator.blogUrl)) {
|
||||
layoutUserProfile.ivBlog.visibility = View.GONE
|
||||
} else {
|
||||
layoutUserProfile.ivBlog.visibility = View.VISIBLE
|
||||
layoutUserProfile.ivBlog.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(creator.blogUrl)))
|
||||
}
|
||||
}
|
||||
|
||||
if (creator.instagramUrl.isNullOrBlank() || !URLUtil.isValidUrl(creator.instagramUrl)) {
|
||||
layoutUserProfile.ivInstagram.visibility = View.GONE
|
||||
} else {
|
||||
layoutUserProfile.ivInstagram.visibility = View.VISIBLE
|
||||
layoutUserProfile.ivInstagram.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(creator.instagramUrl)))
|
||||
}
|
||||
}
|
||||
|
||||
if (creator.youtubeUrl.isNullOrBlank() || !URLUtil.isValidUrl(creator.youtubeUrl)) {
|
||||
layoutUserProfile.ivYoutube.visibility = View.GONE
|
||||
} else {
|
||||
layoutUserProfile.ivYoutube.visibility = View.VISIBLE
|
||||
layoutUserProfile.ivYoutube.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(creator.youtubeUrl)))
|
||||
}
|
||||
}
|
||||
|
||||
if (creator.isNotification) {
|
||||
layoutUserProfile.ivNotification.setImageResource(R.drawable.btn_notification_selected)
|
||||
layoutUserProfile.ivNotification.setOnClickListener {
|
||||
viewModel.unFollow(creator.creatorId)
|
||||
}
|
||||
} else {
|
||||
layoutUserProfile.ivNotification.setImageResource(R.drawable.btn_notification)
|
||||
layoutUserProfile.ivNotification.setOnClickListener {
|
||||
viewModel.follow(creator.creatorId)
|
||||
}
|
||||
}
|
||||
|
||||
layoutUserProfile
|
||||
.tvNotificationCount
|
||||
.text = "팔로워 ${creator.notificationRecipientCount.moneyFormat()}명"
|
||||
|
||||
val introduce = creator.introduce.ifBlank {
|
||||
"채널 소개내용이 없습니다."
|
||||
}
|
||||
|
||||
binding.layoutUserProfileIntroduce.tvIntroduce.text = introduce
|
||||
}
|
||||
|
||||
private fun setCreatorNotice(notice: String, creatorId: Long) {
|
||||
binding.tvNotice.text = notice.ifBlank {
|
||||
"공지사항이 없습니다."
|
||||
}
|
||||
|
||||
binding.rlNotice.setOnClickListener {
|
||||
if (creatorId == SharedPreferenceManager.userId) {
|
||||
val intent = Intent(applicationContext, CreatorNoticeWriteActivity::class.java)
|
||||
intent.putExtra("notice", notice)
|
||||
noticeWriteLauncher.launch(intent)
|
||||
} else {
|
||||
viewModel.toggleExpandNotice()
|
||||
}
|
||||
}
|
||||
|
||||
binding.ivWrite.visibility = if (creatorId == SharedPreferenceManager.userId) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setLiveRoomList(liveRoomList: List<LiveRoomResponse>) {
|
||||
if (liveRoomList.isEmpty()) {
|
||||
binding.layoutUserProfileLive.root.visibility = View.GONE
|
||||
} else {
|
||||
binding.layoutUserProfileLive.root.visibility = View.VISIBLE
|
||||
liveAdapter.items.clear()
|
||||
liveAdapter.items.addAll(liveRoomList)
|
||||
liveAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setSimilarCreatorList(similarCreatorList: List<SimilarCreatorResponse>) {
|
||||
if (similarCreatorList.isEmpty()) {
|
||||
binding.llUserProfileSimilarCreator.visibility = View.GONE
|
||||
} else {
|
||||
binding.llUserProfileSimilarCreator.visibility = View.VISIBLE
|
||||
similarCreatorAdapter.items.clear()
|
||||
similarCreatorAdapter.items.addAll(similarCreatorList)
|
||||
similarCreatorAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setUserDonationRanking(userDonationRanking: List<UserDonationRankingResponse>) {
|
||||
if (userDonationRanking.isEmpty()) {
|
||||
binding.llUserProfileDonation.visibility = View.GONE
|
||||
} else {
|
||||
binding.llUserProfileDonation.visibility = View.VISIBLE
|
||||
donationAdapter.items.clear()
|
||||
donationAdapter.items.addAll(userDonationRanking)
|
||||
donationAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reservationRoom(roomId: Long) {
|
||||
liveViewModel.getRoomDetail(roomId) {
|
||||
if (it.manager.id == SharedPreferenceManager.userId) {
|
||||
showToast("내가 만든 라이브는 예약할 수 없습니다.")
|
||||
} else {
|
||||
if (it.isPrivateRoom) {
|
||||
LiveRoomPasswordDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
can = if (it.isPaid) 0 else it.price,
|
||||
confirmButtonClick = { password ->
|
||||
handler.postDelayed({
|
||||
processLiveReservation(roomId, password)
|
||||
}, 300)
|
||||
}
|
||||
).show(screenWidth)
|
||||
} else {
|
||||
if (it.price == 0 || it.isPaid) {
|
||||
processLiveReservation(roomId)
|
||||
} else {
|
||||
LivePaymentDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = "${it.price.moneyFormat()}캔으로 예약",
|
||||
desc = "'${it.title}' 라이브에 참여하기 위해 결제합니다.",
|
||||
confirmButtonTitle = "예약하기",
|
||||
confirmButtonClick = { processLiveReservation(roomId) },
|
||||
cancelButtonTitle = "취소",
|
||||
cancelButtonClick = {}
|
||||
).show(screenWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processLiveReservation(roomId: Long, password: String? = null) {
|
||||
liveViewModel.reservationRoom(roomId, password) {
|
||||
val intent = Intent(
|
||||
applicationContext,
|
||||
LiveReservationCompleteActivity::class.java
|
||||
)
|
||||
intent.putExtra(Constants.EXTRA_LIVE_RESERVATION_RESPONSE, it)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enterLiveRoom(roomId: Long) {
|
||||
val onEnterRoomSuccess = {
|
||||
runOnUiThread {
|
||||
val intent = Intent(applicationContext, LiveRoomActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_ROOM_ID, roomId)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
liveViewModel.getRoomDetail(roomId) {
|
||||
if (it.channelName != null) {
|
||||
if (it.manager.id == SharedPreferenceManager.userId) {
|
||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||
} else if (it.price == 0 || it.isPaid) {
|
||||
if (it.isPrivateRoom) {
|
||||
LiveRoomPasswordDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
can = 0,
|
||||
confirmButtonClick = { password ->
|
||||
liveViewModel.enterRoom(
|
||||
roomId = roomId,
|
||||
onSuccess = onEnterRoomSuccess,
|
||||
password = password
|
||||
)
|
||||
}
|
||||
).show(screenWidth)
|
||||
} else {
|
||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||
}
|
||||
} else {
|
||||
if (it.isPrivateRoom) {
|
||||
LiveRoomPasswordDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
can = it.price,
|
||||
confirmButtonClick = { password ->
|
||||
liveViewModel.enterRoom(
|
||||
roomId = roomId,
|
||||
onSuccess = onEnterRoomSuccess,
|
||||
password = password
|
||||
)
|
||||
}
|
||||
).show(screenWidth)
|
||||
} else {
|
||||
LivePaymentDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = "${it.price.moneyFormat()}캔으로 입장",
|
||||
desc = "'${it.title}' 라이브에 참여하기 위해 결제합니다.",
|
||||
confirmButtonTitle = "결제 후 입장",
|
||||
confirmButtonClick = {
|
||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||
},
|
||||
cancelButtonTitle = "취소",
|
||||
cancelButtonClick = {}
|
||||
).show(screenWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemUserProfileLiveBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class UserProfileLiveAdapter(
|
||||
private val onClickParticipant: (LiveRoomResponse) -> Unit,
|
||||
private val onClickReservation: (LiveRoomResponse) -> Unit
|
||||
) : RecyclerView.Adapter<UserProfileLiveAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<LiveRoomResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemUserProfileLiveBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(item: LiveRoomResponse) {
|
||||
binding.ivCover.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(4.7f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.iv19.visibility = if (item.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
binding.tvDate.text = item.beginDateTime
|
||||
binding.tvNickname.text = item.managerNickname
|
||||
binding.tvTitle.text = item.title
|
||||
|
||||
if (item.isActive && !item.channelName.isNullOrBlank()) {
|
||||
binding.bgCover.visibility = View.GONE
|
||||
binding.tvStatus.text = "LIVE"
|
||||
binding.tvStatus.setTextColor(ContextCompat.getColor(context, R.color.color_ff5c49))
|
||||
binding.tvStatus.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_3_3_transparent_ff5c49
|
||||
)
|
||||
|
||||
binding.tvParticipate.text = if (!item.isPaid && item.price > 0) {
|
||||
"${item.price}캔으로 지금 참여하기"
|
||||
} else {
|
||||
"지금 참여하기"
|
||||
}
|
||||
|
||||
binding.tvParticipate.setOnClickListener { onClickParticipant(item) }
|
||||
|
||||
binding.tvParticipate.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
R.color.white
|
||||
)
|
||||
)
|
||||
binding.tvParticipate.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_5_3_dd4500
|
||||
)
|
||||
} else if (item.isActive && item.channelName.isNullOrBlank()) {
|
||||
binding.bgCover.visibility = View.GONE
|
||||
binding.tvStatus.text = "예정"
|
||||
binding.tvStatus.setTextColor(ContextCompat.getColor(context, R.color.color_fdca2f))
|
||||
binding.tvStatus.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_3_3_transparent_fdca2f
|
||||
)
|
||||
|
||||
if (item.isReservation) {
|
||||
binding.tvParticipate.text = "예약완료"
|
||||
binding.tvParticipate.setTextColor(
|
||||
ContextCompat.getColor(context, R.color.color_777777)
|
||||
)
|
||||
binding.tvParticipate.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_5_3_525252
|
||||
)
|
||||
} else {
|
||||
binding.tvParticipate.text = if (item.price > 0) {
|
||||
"${item.price}캔으로 예약하기"
|
||||
} else {
|
||||
"예약하기"
|
||||
}
|
||||
|
||||
binding.tvParticipate.setOnClickListener { onClickReservation(item) }
|
||||
|
||||
binding.tvParticipate.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
R.color.black
|
||||
)
|
||||
)
|
||||
binding.tvParticipate.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_5_3_fdca2f
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.bgCover.visibility = View.VISIBLE
|
||||
binding.tvStatus.text = "종료"
|
||||
binding.tvStatus.setTextColor(ContextCompat.getColor(context, R.color.color_777777))
|
||||
binding.tvStatus.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_3_3_transparent_777777
|
||||
)
|
||||
|
||||
binding.tvParticipate.text = "다시듣기를 지원하지 않습니다"
|
||||
|
||||
binding.tvParticipate.setTextColor(
|
||||
ContextCompat.getColor(context, R.color.color_777777)
|
||||
)
|
||||
binding.tvParticipate.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_5_3_525252
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemUserProfileLiveBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemUserProfileSimilarCreatorBinding
|
||||
|
||||
class UserProfileSimilarCreatorAdapter(
|
||||
private val onClickItem: (SimilarCreatorResponse) -> Unit
|
||||
) : RecyclerView.Adapter<UserProfileSimilarCreatorAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<SimilarCreatorResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemUserProfileSimilarCreatorBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: SimilarCreatorResponse) {
|
||||
binding.ivProfile.load(item.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvNickname.text = item.nickname
|
||||
binding.tvTags.text = item.tags.joinToString(" ") { "#$it" }
|
||||
binding.root.setOnClickListener { onClickItem(item) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemUserProfileSimilarCreatorBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.firebase.dynamiclinks.ShortDynamicLink
|
||||
import com.google.firebase.dynamiclinks.ktx.androidParameters
|
||||
import com.google.firebase.dynamiclinks.ktx.dynamicLinks
|
||||
import com.google.firebase.dynamiclinks.ktx.iosParameters
|
||||
import com.google.firebase.dynamiclinks.ktx.shortLinkAsync
|
||||
import com.google.firebase.ktx.Firebase
|
||||
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.explorer.ExplorerRepository
|
||||
import kr.co.vividnext.sodalive.report.ReportRepository
|
||||
import kr.co.vividnext.sodalive.report.ReportRequest
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
import kr.co.vividnext.sodalive.user.UserRepository
|
||||
|
||||
class UserProfileViewModel(
|
||||
private val repository: ExplorerRepository,
|
||||
private val reportRepository: ReportRepository,
|
||||
private val userRepository: UserRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _creatorProfileLiveData = MutableLiveData<GetCreatorProfileResponse>()
|
||||
val creatorProfileLiveData: LiveData<GetCreatorProfileResponse>
|
||||
get() = _creatorProfileLiveData
|
||||
|
||||
private val _isExpandNotice = MutableLiveData(false)
|
||||
val isExpandNotice: LiveData<Boolean>
|
||||
get() = _isExpandNotice
|
||||
|
||||
private var creatorNickname = ""
|
||||
|
||||
fun cheersReport(cheersId: Long, reason: String) {
|
||||
_isLoading.value = true
|
||||
|
||||
val request = ReportRequest(ReportType.CHEERS, reason, cheersId = cheersId)
|
||||
compositeDisposable.add(
|
||||
reportRepository.report(
|
||||
request = request,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"신고가 접수되었습니다."
|
||||
)
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("신고가 접수되었습니다.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun report(type: ReportType, userId: Long, reason: String = "프로필 신고") {
|
||||
_isLoading.value = true
|
||||
|
||||
val request = ReportRequest(type, reason, reportedMemberId = userId)
|
||||
compositeDisposable.add(
|
||||
reportRepository.report(
|
||||
request = request,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"신고가 접수되었습니다."
|
||||
)
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("신고가 접수되었습니다.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getCreatorProfile(userId: Long, onFailure: (() -> Unit)? = null) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getCreatorProfile(
|
||||
id = userId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
val data = it.data
|
||||
_creatorProfileLiveData.postValue(data!!)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun follow(creatorId: Long) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
userRepository.creatorFollow(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getCreatorProfile(creatorId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun unFollow(creatorId: Long) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
userRepository.creatorUnFollow(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getCreatorProfile(creatorId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun toggleExpandNotice() {
|
||||
_isExpandNotice.value = !isExpandNotice.value!!
|
||||
}
|
||||
|
||||
fun writeCheers(parentCheersId: Long? = null, creatorId: Long, cheersContent: String) {
|
||||
if (cheersContent.isBlank()) {
|
||||
_toastLiveData.postValue("내용을 입력하세요")
|
||||
return
|
||||
}
|
||||
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.writeCheers(
|
||||
parentCheersId = parentCheersId,
|
||||
creatorId = creatorId,
|
||||
content = cheersContent,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
getCreatorProfile(creatorId)
|
||||
} else {
|
||||
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
_toastLiveData.postValue(message)
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun modifyCheers(cheersId: Long, creatorId: Long, cheersContent: String) {
|
||||
if (cheersContent.isBlank()) {
|
||||
_toastLiveData.postValue("내용을 입력하세요")
|
||||
return
|
||||
}
|
||||
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.modifyCheers(
|
||||
cheersId = cheersId,
|
||||
content = cheersContent,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
getCreatorProfile(creatorId)
|
||||
} else {
|
||||
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
_toastLiveData.postValue(message)
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun shareChannel(userId: Long, onSuccess: (String) -> Unit) {
|
||||
_isLoading.value = true
|
||||
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
|
||||
link = Uri.parse("https://yozm.day/?channel_id=$userId")
|
||||
domainUriPrefix = "https://yozm.page.link"
|
||||
androidParameters { }
|
||||
iosParameters("kr.co.vividnext.yozm") {
|
||||
appStoreId = "1630284226"
|
||||
}
|
||||
}.addOnSuccessListener {
|
||||
val uri = it.shortLink
|
||||
if (uri != null) {
|
||||
onSuccess("요즘라이브 ${creatorNickname}님의 채널입니다.\n$uri")
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
|
||||
}.addOnCompleteListener {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun userBlock(userId: Long) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
userRepository.memberBlock(
|
||||
userId = userId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
getCreatorProfile(userId)
|
||||
_toastLiveData.postValue("차단하였습니다.")
|
||||
} else {
|
||||
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
_toastLiveData.postValue(message)
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun userUnBlock(userId: Long) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
userRepository.memberUnBlock(
|
||||
userId = userId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
getCreatorProfile(userId)
|
||||
_toastLiveData.postValue("차단이 해제 되었습니다.")
|
||||
} else {
|
||||
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
_toastLiveData.postValue(message)
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.cheers
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PostWriteCheersRequest(
|
||||
@SerializedName("parentId") val parentId: Long? = null,
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("content") val content: String
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.cheers
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PutModifyCheersRequest(
|
||||
@SerializedName("cheersId") val cheersId: Long,
|
||||
@SerializedName("content") val content: String
|
||||
)
|
|
@ -0,0 +1,126 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.cheers
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
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.ItemUserProfileCheersBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponseItem
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class UserProfileCheersAdapter(
|
||||
private val userId: Long,
|
||||
private val enterReply: (Long, String) -> Unit,
|
||||
private val modifyReply: (Long, String) -> Unit,
|
||||
private val onClickReport: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<UserProfileCheersAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<GetCheersResponseItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemUserProfileCheersBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(cheers: GetCheersResponseItem) {
|
||||
binding.tvWriteReply.visibility = View.GONE
|
||||
binding.llCheerReply.visibility = View.GONE
|
||||
binding.rlCheerReply.visibility = View.GONE
|
||||
|
||||
binding.ivProfile.load(cheers.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(16.7f.dpToPx()))
|
||||
}
|
||||
binding.tvContent.text = cheers.content
|
||||
binding.tvNickname.text = cheers.nickname
|
||||
binding.tvDate.text = cheers.date
|
||||
|
||||
binding.ivMenu.setOnClickListener {
|
||||
showOptionMenu(
|
||||
context,
|
||||
binding.ivMenu,
|
||||
cheersId = cheers.cheersId
|
||||
)
|
||||
}
|
||||
|
||||
if (cheers.replyList.isNotEmpty()) {
|
||||
binding.tvWriteReply.visibility = View.GONE
|
||||
binding.llCheerReply.visibility = View.VISIBLE
|
||||
val reply = cheers.replyList[0]
|
||||
binding.tvReply.text = reply.content
|
||||
binding.tvReplyDate.text = reply.date
|
||||
if (userId == SharedPreferenceManager.userId) {
|
||||
binding.tvModifyReply.visibility = View.VISIBLE
|
||||
binding.tvModifyReply.setOnClickListener {
|
||||
binding.etCheerReply.setText(binding.tvReply.text)
|
||||
binding.rlCheerReply.visibility = View.VISIBLE
|
||||
binding.tvSend.setOnClickListener {
|
||||
val content = binding.etCheerReply.text.toString()
|
||||
modifyReply(reply.cheersId, content)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.tvModifyReply.visibility = View.GONE
|
||||
}
|
||||
} else {
|
||||
if (userId == SharedPreferenceManager.userId) {
|
||||
binding.tvWriteReply.visibility = View.VISIBLE
|
||||
binding.tvWriteReply.setOnClickListener {
|
||||
binding.tvWriteReply.visibility = View.GONE
|
||||
binding.rlCheerReply.visibility = View.VISIBLE
|
||||
binding.tvSend.setOnClickListener {
|
||||
val content = binding.etCheerReply.text.toString()
|
||||
enterReply(cheers.cheersId, content)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.tvWriteReply.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvReply.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
parent.context,
|
||||
ItemUserProfileCheersBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
|
||||
private fun showOptionMenu(context: Context, v: View, cheersId: Long) {
|
||||
val popup = PopupMenu(context, v)
|
||||
val inflater = popup.menuInflater
|
||||
inflater.inflate(R.menu.review_option_menu, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_review_report -> {
|
||||
onClickReport(cheersId)
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
popup.show()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.donation
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemUserProfileDonationBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserDonationRankingResponse
|
||||
|
||||
class UserProfileDonationAdapter : RecyclerView.Adapter<UserProfileDonationAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<UserDonationRankingResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemUserProfileDonationBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: UserDonationRankingResponse, position: Int) {
|
||||
binding.tvNickname.text = item.nickname
|
||||
|
||||
binding.ivProfile.load(item.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
when (position) {
|
||||
0 -> {
|
||||
binding.ivBg.setImageResource(R.drawable.bg_circle_ffdc00_ffb600)
|
||||
binding.ivBg.visibility = View.VISIBLE
|
||||
|
||||
binding.ivCrown.setImageResource(R.drawable.ic_crown_1)
|
||||
binding.ivCrown.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
1 -> {
|
||||
binding.ivBg.setImageResource(R.drawable.bg_circle_ffffff_9f9f9f)
|
||||
binding.ivBg.visibility = View.VISIBLE
|
||||
|
||||
binding.ivCrown.setImageResource(R.drawable.ic_crown_2)
|
||||
binding.ivCrown.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
2 -> {
|
||||
binding.ivBg.setImageResource(R.drawable.bg_circle_e6a77a_c67e4a)
|
||||
binding.ivBg.visibility = View.VISIBLE
|
||||
|
||||
binding.ivCrown.setImageResource(R.drawable.ic_crown_3)
|
||||
binding.ivCrown.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.ivBg.setImageResource(0)
|
||||
binding.ivBg.visibility = View.GONE
|
||||
binding.ivCrown.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemUserProfileDonationBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position], position)
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.donation
|
||||
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityUserProfileLiveAllBinding
|
||||
|
||||
class UserProfileDonationAllViewActivity : BaseActivity<ActivityUserProfileLiveAllBinding>(
|
||||
ActivityUserProfileLiveAllBinding::inflate
|
||||
) {
|
||||
override fun setupView() {}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.fantalk
|
||||
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityUserProfileFantalkAllBinding
|
||||
|
||||
class UserProfileFantalkAllViewActivity : BaseActivity<ActivityUserProfileFantalkAllBinding>(
|
||||
ActivityUserProfileFantalkAllBinding::inflate
|
||||
) {
|
||||
override fun setupView() {}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.follow
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetFollowerListResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<GetFollowerListResponseItem>
|
||||
)
|
||||
|
||||
data class GetFollowerListResponseItem(
|
||||
@SerializedName("userId") val userId: Long,
|
||||
@SerializedName("profileImage") val profileImage: String,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("isFollow") val isFollow: Boolean?
|
||||
)
|
|
@ -0,0 +1,116 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.follow
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.ActivityUserFollowerListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class UserFollowerListActivity : BaseActivity<ActivityUserFollowerListBinding>(
|
||||
ActivityUserFollowerListBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: UserFollowerListViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: UserFollowerListAdapter
|
||||
|
||||
private var userId: Long = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
userId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
|
||||
|
||||
if (userId <= 0) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"잘못된 요청입니다.\n다시 시도해 주세요.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindData()
|
||||
viewModel.getFollowerList()
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "팔로워 리스트"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
adapter = UserFollowerListAdapter(
|
||||
onClickRegisterNotification = { viewModel.registerNotification(it) },
|
||||
onClickUnRegisterNotification = { viewModel.unRegisterNotification(it) }
|
||||
)
|
||||
|
||||
binding.rvFollowerList.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvFollowerList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val layoutManager = recyclerView.layoutManager as? LinearLayoutManager
|
||||
if (
|
||||
layoutManager != null &&
|
||||
layoutManager.findLastCompletelyVisibleItemPosition() == adapter.itemCount - 1
|
||||
) {
|
||||
viewModel.getFollowerList()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvFollowerList.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.left = 20f.dpToPx().toInt()
|
||||
outRect.right = 20f.dpToPx().toInt()
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvFollowerList.adapter = adapter
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
viewModel.userId = userId
|
||||
viewModel.followerListItemsLiveData.observe(this) {
|
||||
adapter.addAll(it)
|
||||
}
|
||||
|
||||
viewModel.totalCountLiveData.observe(this) {
|
||||
binding.tvCount.text = it.moneyFormat()
|
||||
}
|
||||
|
||||
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,75 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.follow
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemFollowerListBinding
|
||||
|
||||
class UserFollowerListAdapter(
|
||||
private val onClickRegisterNotification: (Long) -> Unit,
|
||||
private val onClickUnRegisterNotification: (Long) -> Unit,
|
||||
) : RecyclerView.Adapter<UserFollowerListAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetFollowerListResponseItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemFollowerListBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetFollowerListResponseItem) {
|
||||
binding.tvNickname.text = item.nickname
|
||||
binding.ivProfile.load(item.profileImage) {
|
||||
transformations(CircleCropTransformation())
|
||||
placeholder(R.drawable.ic_logo)
|
||||
crossfade(true)
|
||||
}
|
||||
|
||||
if (item.isFollow != null) {
|
||||
binding.ivNotification.visibility = View.VISIBLE
|
||||
if (item.isFollow) {
|
||||
binding.ivNotification.setImageResource(R.drawable.btn_notification_selected)
|
||||
binding.ivNotification.setOnClickListener {
|
||||
onClickUnRegisterNotification(item.userId)
|
||||
clear()
|
||||
}
|
||||
} else {
|
||||
binding.ivNotification.setImageResource(R.drawable.btn_notification)
|
||||
binding.ivNotification.setOnClickListener {
|
||||
onClickRegisterNotification(item.userId)
|
||||
clear()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.ivNotification.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addAll(items: List<GetFollowerListResponseItem>) {
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
this.items.clear()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemFollowerListBinding.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,156 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile.follow
|
||||
|
||||
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.explorer.ExplorerRepository
|
||||
import kr.co.vividnext.sodalive.user.UserRepository
|
||||
|
||||
class UserFollowerListViewModel(
|
||||
private val repository: ExplorerRepository,
|
||||
private val userRepository: UserRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _totalCountLiveData = MutableLiveData(0)
|
||||
val totalCountLiveData: LiveData<Int>
|
||||
get() = _totalCountLiveData
|
||||
|
||||
private var _followerListItemsLiveData = MutableLiveData<List<GetFollowerListResponseItem>>()
|
||||
val followerListItemsLiveData: LiveData<List<GetFollowerListResponseItem>>
|
||||
get() = _followerListItemsLiveData
|
||||
|
||||
var userId: Long = 0
|
||||
var page = 1
|
||||
private var isLast = false
|
||||
private val pageSize = 10
|
||||
|
||||
fun getFollowerList() {
|
||||
if (!isLast && !_isLoading.value!!) {
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.getFollowerList(
|
||||
userId,
|
||||
page,
|
||||
pageSize,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success && it.data != null) {
|
||||
val data = it.data
|
||||
_totalCountLiveData.value = data.totalCount
|
||||
|
||||
if (data.items.isEmpty()) {
|
||||
isLast = true
|
||||
} else {
|
||||
page += 1
|
||||
_followerListItemsLiveData.value = data.items
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerNotification(creatorId: Long) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
userRepository.creatorFollow(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success && it.data != null) {
|
||||
page = 1
|
||||
isLast = false
|
||||
getFollowerList()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun unRegisterNotification(creatorId: Long) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
userRepository.creatorUnFollow(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success && it.data != null) {
|
||||
page = 1
|
||||
isLast = false
|
||||
getFollowerList()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue