Compare commits

55 Commits

Author SHA1 Message Date
d791147886 @SerializedName 추가 2023-09-09 01:55:14 +09:00
dc028f297d versionName: 1.0.2, versionCode 3 2023-09-09 01:52:52 +09:00
e13eb3c404 응원글 - 수정 기능 추가 2023-09-07 16:35:19 +09:00
b6359f9b8d 콘텐츠 댓글의 답글 - 수정/삭제 기능 추가 2023-09-07 13:37:19 +09:00
5522672195 콘텐츠 댓글 수정 기능 추가 2023-09-07 13:06:35 +09:00
e9370e02be 콘텐츠 댓글 삭제 기능 추가 2023-09-06 13:59:50 +09:00
4f54545081 알림 아이콘 변경 2023-09-06 11:27:26 +09:00
8a4ad41212 응원글 삭제 기능 추가 2023-09-05 12:46:53 +09:00
e371fd2ac2 콘텐츠 구매 목록 - 콘텐츠 크리에이터 닉네임 표시 2023-09-04 22:48:54 +09:00
ad7a5b36f1 푸시, 딥링크 - 라이브 탭으로 이동하지 않아도 실행되도록 수정 2023-08-31 20:04:33 +09:00
9eb136379c 코인 -> 캔 2023-08-29 23:39:10 +09:00
735f353f31 콘텐츠 대여가격 60%로 변경 2023-08-29 23:29:33 +09:00
a422eb08c0 응원 전체보기 추가 2023-08-29 17:31:04 +09:00
d1b97aafd2 후원랭킹 전체보기 추가 2023-08-29 11:24:06 +09:00
110fcab710 콘텐츠 등록 - 5캔(500원) 부터 등록되도록 수정, 대여가격 안내 60%로 수정 2023-08-28 18:19:04 +09:00
91af371523 라이브 방 - 공지사항 수정 가능 2023-08-28 18:10:04 +09:00
2ee4c3e9ad 푸시 메시지 - 채널 id 추가 2023-08-28 17:56:28 +09:00
96c72c4fa0 라이브 후원 다이얼로그 - 키보드가 올라왔을 때 화면이 전체적으로 위로 이동하여 보이지 않는 부분이 없어지도록 수정 2023-08-25 21:56:03 +09:00
0a56ef1227 라이브 방 - 스피커 최대 10 -> 5명으로 수정 2023-08-21 05:37:49 +09:00
6a06a46713 메시지 탭 보이도록 수정 2023-08-21 04:41:44 +09:00
69f2cbd4a2 본인인증을 한 유저만 메시지 탭이 보이도록 수정 2023-08-21 04:20:09 +09:00
f0e841ffbe 라이브 상세 - 제목 왼쪽에 19금 표시 추가 2023-08-21 04:08:37 +09:00
b8d11d2276 로딩뷰 변경 2023-08-21 04:00:11 +09:00
28d5f3a6f6 라이브 메인 - 이벤트 배너 터치이벤트 추가 2023-08-21 01:35:05 +09:00
8e135e893e 라이브 방 - 입장메시지 방장만 보이도록 수정 2023-08-21 01:32:01 +09:00
01512abaf0 라이브 방 - 19금 방인 경우 제목 왼쪽에 19 표시 2023-08-20 23:44:41 +09:00
bb7476f640 마이페이지 탭 - 구매내역 추가 2023-08-20 23:37:17 +09:00
875d8361f3 이용약관/개인정보처리방침 링크 수정 2023-08-20 20:57:52 +09:00
36ffa4fa58 메인 하단 탭 - 콘텐츠, 라이브 순서 변경 2023-08-20 20:46:29 +09:00
3b868294b0 19금 표시 제거 2023-08-20 17:46:16 +09:00
508eff2cd0 크리에이터 채널 - 콘텐츠 영역 추가 2023-08-20 03:46:13 +09:00
9bc625b7a0 socdoc -> message 로 변경 2023-08-19 22:54:38 +09:00
98d895f91f 사용방법 블로그 URL 수정 2023-08-19 22:53:10 +09:00
bf75835c69 ProfileResponse - Coin 을 Can으로 변경 2023-08-19 16:25:15 +09:00
4ac272cd9d 콘텐츠 메인 - SerializeName 변경 audioContents -> contents 2023-08-18 23:14:56 +09:00
a5abb39059 이미지 로딩 라이브러리 변경 2023-08-18 21:51:38 +09:00
f7299cc0df mediaPlayer 를 사용하는 곳은 항상 초기화 되어 있는지 확인 하고 로직이 실행되도록 수정 2023-08-18 21:06:53 +09:00
a4cdbebeb4 본인이 만든 라이브의 경우 팔로잉 버튼, 선물하기 버튼이 보이지 않도록 수정 2023-08-18 20:56:26 +09:00
2e48dad913 라이브 방 상세 - 예약자/참여자 리스트 제거, 채널보기 버튼 액션 추가 2023-08-18 19:51:35 +09:00
f6d6f1de8d 라이브 방 - 키보드가 올라오면 화면이 전체적으로 위로 올라가도록 수정 2023-08-18 19:41:21 +09:00
be7c7d0682 프로필 수정 페이지 추가 2023-08-18 19:39:04 +09:00
9adadaf572 공유링크 수정 2023-08-18 16:54:55 +09:00
ca30e6949f 앱 런처 아이콘 변경 2023-08-18 16:43:35 +09:00
9fa6ccb786 온보딩 튜토리얼 추가 2023-08-18 16:41:36 +09:00
3d96fab0f6 placeholder 이미지 변경 2023-08-17 23:59:14 +09:00
0524615ee4 소다라이브 사용방법 배너 마이페이지로 이동 2023-08-17 23:54:47 +09:00
e165813545 캔 아이콘 변경 2023-08-17 23:41:16 +09:00
7aaac27d24 고객센터 카톡 URL 변경 2023-08-17 14:03:56 +09:00
66006853bd AGP 7.3.1 로 변경 2023-08-17 13:53:48 +09:00
79a241f436 proguard rule 추가 2023-08-16 19:09:52 +09:00
0e24eab7b3 firebase version 32.2.2 로 변경 2023-08-16 18:21:03 +09:00
ea79ffcc02 add real service firebase config file 2023-08-16 18:11:47 +09:00
90655c692d manager -> creator 2023-08-15 00:11:52 +09:00
7e9bcb6c38 사용하지 않는 필드 제거 2023-08-14 22:56:57 +09:00
999f90ae80 coin -> can,
코인 -> 캔

으로 변경
2023-08-14 11:15:21 +09:00
205 changed files with 5278 additions and 1230 deletions

View File

@@ -9,7 +9,7 @@ plugins {
id 'org.jlleitschuh.gradle.ktlint' id 'org.jlleitschuh.gradle.ktlint'
id 'io.objectbox' id 'io.objectbox'
id("com.google.firebase.crashlytics") id 'com.google.firebase.crashlytics'
} }
android { android {
@@ -39,8 +39,8 @@ android {
applicationId "kr.co.vividnext.sodalive" applicationId "kr.co.vividnext.sodalive"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 1 versionCode 3
versionName "1.0.0" versionName "1.0.2"
} }
buildTypes { buildTypes {
@@ -98,13 +98,13 @@ dependencies {
implementation "io.insert-koin:koin-android:3.1.3" implementation "io.insert-koin:koin-android:3.1.3"
// Preference // Preference
implementation("androidx.preference:preference-ktx:1.2.0") { implementation("androidx.preference:preference-ktx:1.2.1") {
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel'
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'
} }
// Gson // Gson
implementation "com.google.code.gson:gson:2.9.1" implementation "com.google.code.gson:gson:2.10.1"
// Network // Network
implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:retrofit:2.9.0"
@@ -113,8 +113,8 @@ dependencies {
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3" implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
// RxJava3 // RxJava3
implementation "io.reactivex.rxjava3:rxjava:3.1.3" implementation "io.reactivex.rxjava3:rxjava:3.1.6"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0" implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
// permission // permission
@@ -126,7 +126,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1'
// Firebase // Firebase
implementation platform('com.google.firebase:firebase-bom:32.2.0') implementation platform('com.google.firebase:firebase-bom:32.2.2')
implementation 'com.google.firebase:firebase-dynamic-links-ktx' implementation 'com.google.firebase:firebase-dynamic-links-ktx'
implementation 'com.google.firebase:firebase-crashlytics-ktx' implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx' implementation 'com.google.firebase:firebase-analytics-ktx'

117
app/proguard-rules.pro vendored
View File

@@ -21,38 +21,76 @@
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
##---------------Begin: proguard configuration for Gson ---------- ### Gson ProGuard and R8 rules which are relevant for all users
# Gson uses generic type information stored in a class file when working with fields. Proguard ### This file is automatically recognized by ProGuard and R8, see https://developer.android.com/build/shrink-code#configuration-files
# removes such information by default, so configure it to keep all of it. ###
### IMPORTANT:
### - These rules are additive; don't include anything here which is not specific to Gson (such as completely
### disabling obfuscation for all classes); the user would be unable to disable that then
### - These rules are not complete; users will most likely have to add additional rules for their specific
### classes, for example to disable obfuscation for certain fields or to keep no-args constructors
###
# Keep generic signatures; needed for correct type resolution
-keepattributes Signature -keepattributes Signature
# For using GSON @Expose annotation # Keep Gson annotations
-keepattributes *Annotation* # Note: Cannot perform finer selection here to only cover Gson annotations, see also https://stackoverflow.com/q/47515093
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson ### The following rules are needed for R8 in "full mode" which only adheres to `-keepattribtues` if
-keep class com.google.gson.examples.android.model.** { <fields>; } ### the corresponding class or field is matches by a `-keep` rule as well, see
### https://r8.googlesource.com/r8/+/refs/heads/main/compatibility-faq.md#r8-full-mode
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, # Keep class TypeToken (respectively its generic signature)
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) -keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null # Keep any (anonymous) classes extending TypeToken
-keep,allowobfuscation class * extends com.google.gson.reflect.TypeToken
# Keep classes with @JsonAdapter annotation
-keep,allowobfuscation,allowoptimization @com.google.gson.annotations.JsonAdapter class *
# Keep fields with @SerializedName annotation, but allow obfuscation of their names
-keepclassmembers,allowobfuscation class * { -keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>; @com.google.gson.annotations.SerializedName <fields>;
} }
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. # Keep fields with any other Gson annotation
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken # Also allow obfuscation, assuming that users will additionally use @SerializedName or
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken # other means to preserve the field names
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.Expose <fields>;
@com.google.gson.annotations.JsonAdapter <fields>;
@com.google.gson.annotations.Since <fields>;
@com.google.gson.annotations.Until <fields>;
}
##---------------End: proguard configuration for Gson ---------- # Keep no-args constructor of classes which can be used with @JsonAdapter
# By default their no-args constructor is invoked to create an adapter instance
-keepclassmembers class * extends com.google.gson.TypeAdapter {
<init>();
}
-keepclassmembers class * implements com.google.gson.TypeAdapterFactory {
<init>();
}
-keepclassmembers class * implements com.google.gson.JsonSerializer {
<init>();
}
-keepclassmembers class * implements com.google.gson.JsonDeserializer {
<init>();
}
# If a class is used in some way by the application, and has fields annotated with @SerializedName
# and a no-args constructor, keep those fields and the constructor
# Based on https://issuetracker.google.com/issues/150189783#comment11
# See also https://github.com/google/gson/pull/2420#discussion_r1241813541 for a more detailed explanation
-if class *
-keepclasseswithmembers,allowobfuscation,allowoptimization class <1> {
<init>();
@com.google.gson.annotations.SerializedName <fields>;
}
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule { -keep class * extends com.bumptech.glide.module.AppGlideModule {
@@ -112,30 +150,59 @@
@retrofit2.http.* <methods>; @retrofit2.http.* <methods>;
} }
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information. # Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.** -dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. # Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit -dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy # With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this. # and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; } -if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1> -keep,allowobfuscation interface <1>
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). # Keep inherited services.
-keep,allowobfuscation,allowshrinking interface retrofit2.Call -if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation,allowshrinking class retrofit2.Response -keep,allowobfuscation interface * extends <1>
# With R8 full mode generic signatures are stripped for classes that are not # With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument # kept. Suspend functions are wrapped in continuations where the type argument
# is used. # is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keepattributes Signature
-keep class kotlin.coroutines.Continuation
# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# Keep generic signature of RxJava3 (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking class io.reactivex.rxjava3.core.Flowable
-keep,allowobfuscation,allowshrinking class io.reactivex.rxjava3.core.Maybe
-keep,allowobfuscation,allowshrinking class io.reactivex.rxjava3.core.Observable
-keep,allowobfuscation,allowshrinking class io.reactivex.rxjava3.core.Single
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
-keep,allowoptimization,allowshrinking,allowobfuscation enum <3>
-keep,allowoptimization,allowshrinking,allowobfuscation interface <3>
-dontwarn java.nio.file.Files -dontwarn java.nio.file.Files
-dontwarn java.nio.file.Path -dontwarn java.nio.file.Path
-dontwarn java.nio.file.OpenOption -dontwarn java.nio.file.OpenOption
-dontwarn com.google.devtools.build.android.desugar.runtime.ThrowableExtension
-keep class io.agora.**{*;} -keep class io.agora.**{*;}
-dontwarn org.codehaus.mojo.** -dontwarn org.codehaus.mojo.**
@@ -152,3 +219,5 @@
-keep class androidx.recyclerview.widget.**{*;} -keep class androidx.recyclerview.widget.**{*;}
-keep class androidx.viewpager2.widget.**{*;} -keep class androidx.viewpager2.widget.**{*;}
-keep class kr.co.bootpay.core.** { *; }

View File

@@ -50,6 +50,17 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="sodalive.page.link" />
<data android:host="sodalive.net" />
</intent-filter>
</activity> </activity>
<activity android:name=".main.MainActivity" /> <activity android:name=".main.MainActivity" />
<activity android:name=".user.login.LoginActivity" /> <activity android:name=".user.login.LoginActivity" />
@@ -62,7 +73,9 @@
<activity android:name=".live.room.create.LiveRoomCreateActivity" /> <activity android:name=".live.room.create.LiveRoomCreateActivity" />
<activity android:name=".live.room.update.LiveRoomEditActivity" /> <activity android:name=".live.room.update.LiveRoomEditActivity" />
<activity android:name=".live.reservation.complete.LiveReservationCompleteActivity" /> <activity android:name=".live.reservation.complete.LiveReservationCompleteActivity" />
<activity android:name=".live.room.LiveRoomActivity" /> <activity
android:name=".live.room.LiveRoomActivity"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan" />
<activity android:name=".explorer.profile.UserProfileActivity" /> <activity android:name=".explorer.profile.UserProfileActivity" />
<activity android:name=".explorer.profile.donation.UserProfileDonationAllViewActivity" /> <activity android:name=".explorer.profile.donation.UserProfileDonationAllViewActivity" />
<activity android:name=".explorer.profile.fantalk.UserProfileFantalkAllViewActivity" /> <activity android:name=".explorer.profile.fantalk.UserProfileFantalkAllViewActivity" />
@@ -89,6 +102,10 @@
<activity android:name=".live.now.all.LiveNowAllActivity" /> <activity android:name=".live.now.all.LiveNowAllActivity" />
<activity android:name=".live.reservation.all.LiveReservationAllActivity" /> <activity android:name=".live.reservation.all.LiveReservationAllActivity" />
<activity android:name=".mypage.service_center.ServiceCenterActivity" /> <activity android:name=".mypage.service_center.ServiceCenterActivity" />
<activity android:name=".onboarding.OnBoardingActivity" />
<activity android:name=".mypage.profile.ProfileUpdateActivity" />
<activity android:name=".mypage.profile.nickname.NicknameUpdateActivity" />
<activity android:name=".mypage.profile.password.ModifyPasswordActivity" />
<activity <activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity" android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"

View File

@@ -1,7 +1,6 @@
package kr.co.vividnext.sodalive.audio_content package kr.co.vividnext.sodalive.audio_content
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
@@ -40,19 +39,13 @@ class AudioContentAdapter(
} else { } else {
binding.tvPrice.text = item.price.moneyFormat() binding.tvPrice.text = item.price.moneyFormat()
binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds( binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_coin_w, R.drawable.ic_can,
0, 0,
0, 0,
0 0
) )
} }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
binding.root.setOnClickListener { onClickItem(item.contentId) } binding.root.setOnClickListener { onClickItem(item.contentId) }
} }
} }

View File

@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.audio_content
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.audio_content.comment.GetAudioContentCommentListResponse import kr.co.vividnext.sodalive.audio_content.comment.GetAudioContentCommentListResponse
import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
import kr.co.vividnext.sodalive.audio_content.comment.RegisterAudioContentCommentRequest 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.GetAudioContentDetailResponse
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest
@@ -137,4 +138,10 @@ interface AudioContentApi {
@Body request: AudioContentDonationRequest, @Body request: AudioContentDonationRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<Any>>
@PUT("/audio-content/comment")
fun modifyComment(
@Body request: ModifyCommentRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
} }

View File

@@ -146,7 +146,7 @@ class AudioContentPlayService :
} }
MusicAction.PLAY.name -> { MusicAction.PLAY.name -> {
if (!isPlaying) { if (!isPlaying && this::mediaPlayer.isInitialized) {
mediaPlayer.start() mediaPlayer.start()
toggleIsPlaying() toggleIsPlaying()
updateNotification() updateNotification()
@@ -154,7 +154,7 @@ class AudioContentPlayService :
} }
MusicAction.PAUSE.name -> { MusicAction.PAUSE.name -> {
if (isPlaying) { if (isPlaying && this::mediaPlayer.isInitialized) {
mediaPlayer.pause() mediaPlayer.pause()
toggleIsPlaying() toggleIsPlaying()
updateNotification() updateNotification()
@@ -186,11 +186,14 @@ class AudioContentPlayService :
MusicAction.PROGRESS.name -> { MusicAction.PROGRESS.name -> {
val progress = intent.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, 0) val progress = intent.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, 0)
if (progress > 0) { if (progress > 0 && this::mediaPlayer.isInitialized) {
if (contentId != null) saveNewPlaybackTracking( if (contentId != null) {
saveNewPlaybackTracking(
totalDuration = mediaPlayer.duration, totalDuration = mediaPlayer.duration,
progress = progress progress = progress
) )
}
mediaPlayer.seekTo(progress) mediaPlayer.seekTo(progress)
} }
} }
@@ -258,7 +261,7 @@ class AudioContentPlayService :
} }
) )
if (isPlaying) { if (isPlaying && this::mediaPlayer.isInitialized) {
mediaPlayer.stop() mediaPlayer.stop()
setEndPositionPlaybackTracking(mediaPlayer.currentPosition) setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
@@ -290,12 +293,12 @@ class AudioContentPlayService :
return null return null
} }
override fun onCompletion(mp: MediaPlayer?) { override fun onCompletion(mp: MediaPlayer) {
setEndPositionPlaybackTracking(mediaPlayer.currentPosition) setEndPositionPlaybackTracking(mp.currentPosition)
if (SharedPreferenceManager.isContentPlayLoop) { if (SharedPreferenceManager.isContentPlayLoop) {
saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0) saveNewPlaybackTracking(totalDuration = mp.duration, progress = 0)
mediaPlayer.start() mp.start()
} else { } else {
toggleIsPlaying(false) toggleIsPlaying(false)
mediaPlayer.release() mediaPlayer.release()
@@ -370,6 +373,7 @@ class AudioContentPlayService :
} }
override fun onPrepared(mp: MediaPlayer?) { override fun onPrepared(mp: MediaPlayer?) {
if (this::mediaPlayer.isInitialized) {
saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0) saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0)
sendBroadcast( sendBroadcast(
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER) Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
@@ -426,6 +430,7 @@ class AudioContentPlayService :
} }
) )
} }
}
private fun updateNotification() { private fun updateNotification() {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
@@ -463,7 +468,7 @@ class AudioContentPlayService :
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) { override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
val notificationBuilder = NotificationCompat val notificationBuilder = NotificationCompat
.Builder(this@AudioContentPlayService, channelId) .Builder(this@AudioContentPlayService, channelId)
.setSmallIcon(R.drawable.ic_noti) .setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(resource) .setLargeIcon(resource)
.setContentTitle(title ?: "오디오 콘텐츠") .setContentTitle(title ?: "오디오 콘텐츠")
.setContentText(nickname ?: "") .setContentText(nickname ?: "")

View File

@@ -1,24 +1,31 @@
package kr.co.vividnext.sodalive.audio_content.comment package kr.co.vividnext.sodalive.audio_content.comment
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.extensions.moneyFormat
class AudioContentCommentAdapter( class AudioContentCommentAdapter(
private val creatorId: Long,
private val modifyComment: (Long, String) -> Unit,
private val onClickDelete: (Long) -> Unit,
private val onItemClick: (GetAudioContentCommentListItem) -> Unit private val onItemClick: (GetAudioContentCommentListItem) -> Unit
) : RecyclerView.Adapter<AudioContentCommentAdapter.ViewHolder>() { ) : RecyclerView.Adapter<AudioContentCommentAdapter.ViewHolder>() {
var items = mutableSetOf<GetAudioContentCommentListItem>() var items = mutableSetOf<GetAudioContentCommentListItem>()
inner class ViewHolder( inner class ViewHolder(
private val context: Context,
private val binding: ItemAudioContentCommentBinding private val binding: ItemAudioContentCommentBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
@@ -30,34 +37,34 @@ class AudioContentCommentAdapter(
} }
val tvCommentLayoutParams = binding.tvComment.layoutParams as LinearLayout.LayoutParams val tvCommentLayoutParams = binding.tvComment.layoutParams as LinearLayout.LayoutParams
val coin = item.donationCoin val can = item.donationCan
if (coin > 0) { if (can > 0) {
tvCommentLayoutParams.topMargin = 0 tvCommentLayoutParams.topMargin = 0
binding.llDonationCoin.visibility = View.VISIBLE binding.llDonationCan.visibility = View.VISIBLE
binding.tvDonationCoin.text = coin.moneyFormat() binding.tvDonationCan.text = can.moneyFormat()
binding.llDonationCoin.setBackgroundResource( binding.llDonationCan.setBackgroundResource(
when { when {
coin >= 100000 -> { can >= 100000 -> {
R.drawable.bg_round_corner_10_7_973a3a R.drawable.bg_round_corner_10_7_973a3a
} }
coin >= 50000 -> { can >= 50000 -> {
R.drawable.bg_round_corner_10_7_d85e37 R.drawable.bg_round_corner_10_7_d85e37
} }
coin >= 10000 -> { can >= 10000 -> {
R.drawable.bg_round_corner_10_7_d38c38 R.drawable.bg_round_corner_10_7_d38c38
} }
coin >= 5000 -> { can >= 5000 -> {
R.drawable.bg_round_corner_10_7_59548f R.drawable.bg_round_corner_10_7_59548f
} }
coin >= 1000 -> { can >= 1000 -> {
R.drawable.bg_round_corner_10_7_4d6aa4 R.drawable.bg_round_corner_10_7_4d6aa4
} }
coin >= 500 -> { can >= 500 -> {
R.drawable.bg_round_corner_10_7_2d7390 R.drawable.bg_round_corner_10_7_2d7390
} }
@@ -68,7 +75,7 @@ class AudioContentCommentAdapter(
) )
} else { } else {
tvCommentLayoutParams.topMargin = 13.3f.dpToPx().toInt() tvCommentLayoutParams.topMargin = 13.3f.dpToPx().toInt()
binding.llDonationCoin.visibility = View.GONE binding.llDonationCan.visibility = View.GONE
} }
binding.tvComment.layoutParams = tvCommentLayoutParams binding.tvComment.layoutParams = tvCommentLayoutParams
@@ -82,12 +89,42 @@ class AudioContentCommentAdapter(
"답글 쓰기" "답글 쓰기"
} }
if (
item.writerId == SharedPreferenceManager.userId ||
creatorId == SharedPreferenceManager.userId
) {
binding.etCommentModify.setText(item.comment)
binding.ivMenu.visibility = View.VISIBLE
binding.ivMenu.setOnClickListener {
showOptionMenu(
context,
binding.ivMenu,
commentId = item.id,
writerId = item.writerId,
creatorId = creatorId,
onClickModify = {
binding.rlCommentModify.visibility = View.VISIBLE
binding.tvComment.visibility = View.GONE
}
)
}
binding.tvModify.setOnClickListener {
binding.rlCommentModify.visibility = View.GONE
binding.tvComment.visibility = View.VISIBLE
modifyComment(item.id, binding.etCommentModify.text.toString())
}
} else {
binding.ivMenu.visibility = View.GONE
}
binding.tvWriteReply.setOnClickListener { onItemClick(item) } binding.tvWriteReply.setOnClickListener { onItemClick(item) }
binding.root.setOnClickListener { onItemClick(item) } binding.root.setOnClickListener { onItemClick(item) }
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
parent.context,
ItemAudioContentCommentBinding.inflate( ItemAudioContentCommentBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
@@ -100,4 +137,38 @@ class AudioContentCommentAdapter(
} }
override fun getItemCount() = items.size override fun getItemCount() = items.size
private fun showOptionMenu(
context: Context,
v: View,
commentId: Long,
writerId: Long,
creatorId: Long,
onClickModify: () -> Unit
) {
val popup = PopupMenu(context, v)
val inflater = popup.menuInflater
if (writerId == SharedPreferenceManager.userId) {
inflater.inflate(R.menu.content_comment_option_menu, popup.menu)
} else if (creatorId == SharedPreferenceManager.userId) {
inflater.inflate(R.menu.content_comment_option_menu2, popup.menu)
}
popup.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_review_modify -> {
onClickModify()
}
R.id.menu_review_delete -> {
onClickDelete(commentId)
}
}
true
}
popup.show()
}
} }

View File

@@ -12,7 +12,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.DialogAudioContentCommentBinding import kr.co.vividnext.sodalive.databinding.DialogAudioContentCommentBinding
class AudioContentCommentFragment(private val audioContentId: Long) : BottomSheetDialogFragment() { class AudioContentCommentFragment(
private val creatorId: Long,
private val audioContentId: Long
) : BottomSheetDialogFragment() {
private lateinit var binding: DialogAudioContentCommentBinding private lateinit var binding: DialogAudioContentCommentBinding
@@ -46,6 +49,7 @@ class AudioContentCommentFragment(private val audioContentId: Long) : BottomShee
val commentListFragmentTag = "COMMENT_LIST_FRAGMENT" val commentListFragmentTag = "COMMENT_LIST_FRAGMENT"
val commentListFragment = AudioContentCommentListFragment.newInstance( val commentListFragment = AudioContentCommentListFragment.newInstance(
creatorId = creatorId,
audioContentId = audioContentId audioContentId = audioContentId
) )
val fragmentTransaction = childFragmentManager.beginTransaction() val fragmentTransaction = childFragmentManager.beginTransaction()
@@ -61,6 +65,7 @@ class AudioContentCommentFragment(private val audioContentId: Long) : BottomShee
fun onClickComment(comment: GetAudioContentCommentListItem) { fun onClickComment(comment: GetAudioContentCommentListItem) {
val commentReplyFragmentTag = "COMMENT_REPLY_FRAGMENT" val commentReplyFragmentTag = "COMMENT_REPLY_FRAGMENT"
val commentReplyFragment = AudioContentCommentReplyFragment.newInstance( val commentReplyFragment = AudioContentCommentReplyFragment.newInstance(
creatorId = creatorId,
audioContentId = audioContentId, audioContentId = audioContentId,
comment = comment comment = comment
) )

View File

@@ -15,6 +15,7 @@ import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
@@ -32,6 +33,7 @@ class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentComment
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var adapter: AudioContentCommentAdapter private lateinit var adapter: AudioContentCommentAdapter
private var creatorId: Long = 0
private var audioContentId: Long = 0 private var audioContentId: Long = 0
override fun onCreateView( override fun onCreateView(
@@ -39,6 +41,7 @@ class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentComment
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
creatorId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID) ?: 0
audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0 audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }
@@ -63,7 +66,7 @@ class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentComment
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) { binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
@@ -74,9 +77,38 @@ class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentComment
viewModel.registerComment(audioContentId, comment) viewModel.registerComment(audioContentId, comment)
} }
adapter = AudioContentCommentAdapter { adapter = AudioContentCommentAdapter(
creatorId = creatorId,
modifyComment = { commentId, comment ->
hideKeyboard()
viewModel.modifyComment(
commentId = commentId,
audioContentId = audioContentId,
comment = comment
)
},
onClickDelete = {
SodaDialog(
activity = requireActivity(),
layoutInflater = layoutInflater,
title = "댓글 삭제",
desc = "삭제하시겠습니까?",
confirmButtonTitle = "삭제",
confirmButtonClick = {
viewModel.modifyComment(
commentId = it,
audioContentId = audioContentId,
isActive = false
)
},
cancelButtonTitle = "취소",
cancelButtonClick = {}
).show(screenWidth)
},
onItemClick = {
(parentFragment as AudioContentCommentFragment).onClickComment(it) (parentFragment as AudioContentCommentFragment).onClickComment(it)
} }
)
val recyclerView = binding.rvComment val recyclerView = binding.rvComment
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
@@ -170,8 +202,9 @@ class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentComment
} }
companion object { companion object {
fun newInstance(audioContentId: Long): AudioContentCommentListFragment { fun newInstance(creatorId: Long, audioContentId: Long): AudioContentCommentListFragment {
val args = Bundle() val args = Bundle()
args.putLong(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID, creatorId)
args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId) args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
val fragment = AudioContentCommentListFragment() val fragment = AudioContentCommentListFragment()

View File

@@ -48,11 +48,12 @@ class AudioContentCommentListViewModel(
if (it.success && it.data != null) { if (it.success && it.data != null) {
_totalCommentCount.postValue(it.data.totalCount) _totalCommentCount.postValue(it.data.totalCount)
if (it.data.items.isNotEmpty()) {
page += 1 page += 1
if (it.data.items.isNotEmpty()) {
_commentList.postValue(it.data.items) _commentList.postValue(it.data.items)
} else { } else {
isLast = true isLast = true
_commentList.postValue(listOf())
} }
} else { } else {
if (it.message != null) { if (it.message != null) {
@@ -122,4 +123,61 @@ class AudioContentCommentListViewModel(
) )
) )
} }
fun modifyComment(
commentId: Long,
audioContentId: Long,
comment: String? = null,
isActive: Boolean? = null
) {
if (comment == null && isActive == null) {
_toastLiveData.postValue("변경사항이 없습니다.")
return
}
if (comment != null && comment.isBlank()) {
_toastLiveData.postValue("내용을 입력하세요")
return
}
_isLoading.value = true
val request = ModifyCommentRequest(commentId = commentId)
if (comment != null) {
request.comment = comment
}
if (isActive != null) {
request.isActive = isActive
}
compositeDisposable.add(
repository.modifyComment(
request = request,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
page = 1
isLast = false
getCommentList(audioContentId)
} else {
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
_toastLiveData.postValue(message)
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
} }

View File

@@ -1,18 +1,27 @@
package kr.co.vividnext.sodalive.audio_content.comment package kr.co.vividnext.sodalive.audio_content.comment
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentReplyBinding import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentReplyBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
class AudioContentCommentReplyAdapter : class AudioContentCommentReplyAdapter(
RecyclerView.Adapter<AudioContentCommentReplyViewHolder>() { private val creatorId: Long,
private val modifyComment: (Long, String) -> Unit,
private val onClickDelete: (Long) -> Unit
) : RecyclerView.Adapter<AudioContentCommentReplyViewHolder>() {
var items = mutableSetOf<GetAudioContentCommentListItem>() var items = mutableSetOf<GetAudioContentCommentListItem>()
@@ -22,19 +31,25 @@ class AudioContentCommentReplyAdapter :
): AudioContentCommentReplyViewHolder { ): AudioContentCommentReplyViewHolder {
return if (viewType == 0) { return if (viewType == 0) {
AudioContentCommentReplyHeaderViewHolder( AudioContentCommentReplyHeaderViewHolder(
ItemAudioContentCommentBinding.inflate( binding = ItemAudioContentCommentBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
) ),
) )
} else { } else {
AudioContentCommentReplyItemViewHolder( AudioContentCommentReplyItemViewHolder(
context = parent.context,
creatorId = creatorId,
ItemAudioContentCommentReplyBinding.inflate( ItemAudioContentCommentReplyBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
) ),
showOptionMenu = { context, view, commentId, writerId, creatorId, onClickModify ->
showOptionMenu(context, view, commentId, writerId, creatorId, onClickModify)
},
modifyComment = modifyComment
) )
} }
} }
@@ -48,6 +63,40 @@ class AudioContentCommentReplyAdapter :
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return position return position
} }
private fun showOptionMenu(
context: Context,
v: View,
commentId: Long,
writerId: Long,
creatorId: Long,
onClickModify: () -> Unit
) {
val popup = PopupMenu(context, v)
val inflater = popup.menuInflater
if (writerId == SharedPreferenceManager.userId) {
inflater.inflate(R.menu.content_comment_option_menu, popup.menu)
} else if (creatorId == SharedPreferenceManager.userId) {
inflater.inflate(R.menu.content_comment_option_menu2, popup.menu)
}
popup.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_review_modify -> {
onClickModify()
}
R.id.menu_review_delete -> {
onClickDelete(commentId)
}
}
true
}
popup.show()
}
} }
abstract class AudioContentCommentReplyViewHolder( abstract class AudioContentCommentReplyViewHolder(
@@ -63,31 +112,109 @@ class AudioContentCommentReplyHeaderViewHolder(
override fun bind(item: GetAudioContentCommentListItem) { override fun bind(item: GetAudioContentCommentListItem) {
binding.ivCommentProfile.load(item.profileUrl) { binding.ivCommentProfile.load(item.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
val tvCommentLayoutParams = binding.tvComment.layoutParams as LinearLayout.LayoutParams
val can = item.donationCan
if (can > 0) {
tvCommentLayoutParams.topMargin = 0
binding.llDonationCan.visibility = View.VISIBLE
binding.tvDonationCan.text = can.moneyFormat()
binding.llDonationCan.setBackgroundResource(
when {
can >= 100000 -> {
R.drawable.bg_round_corner_10_7_973a3a
}
can >= 50000 -> {
R.drawable.bg_round_corner_10_7_d85e37
}
can >= 10000 -> {
R.drawable.bg_round_corner_10_7_d38c38
}
can >= 5000 -> {
R.drawable.bg_round_corner_10_7_59548f
}
can >= 1000 -> {
R.drawable.bg_round_corner_10_7_4d6aa4
}
can >= 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.llDonationCan.visibility = View.GONE
}
binding.tvComment.layoutParams = tvCommentLayoutParams
binding.tvComment.text = item.comment binding.tvComment.text = item.comment
binding.tvCommentDate.text = item.date binding.tvCommentDate.text = item.date
binding.tvCommentNickname.text = item.nickname binding.tvCommentNickname.text = item.nickname
binding.tvWriteReply.visibility = View.GONE binding.tvWriteReply.visibility = View.GONE
binding.ivMenu.visibility = View.GONE
} }
} }
class AudioContentCommentReplyItemViewHolder( class AudioContentCommentReplyItemViewHolder(
private val binding: ItemAudioContentCommentReplyBinding private val context: Context,
private val creatorId: Long,
private val binding: ItemAudioContentCommentReplyBinding,
private val showOptionMenu: (
Context, View, Long, Long, Long, onClickModify: () -> Unit
) -> Unit,
private val modifyComment: (Long, String) -> Unit
) : AudioContentCommentReplyViewHolder(binding) { ) : AudioContentCommentReplyViewHolder(binding) {
override fun bind(item: GetAudioContentCommentListItem) { override fun bind(item: GetAudioContentCommentListItem) {
binding.ivCommentProfile.load(item.profileUrl) { binding.ivCommentProfile.load(item.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
binding.tvComment.text = item.comment binding.tvComment.text = item.comment
binding.tvCommentDate.text = item.date binding.tvCommentDate.text = item.date
binding.tvCommentNickname.text = item.nickname binding.tvCommentNickname.text = item.nickname
if (
item.writerId == SharedPreferenceManager.userId ||
creatorId == SharedPreferenceManager.userId
) {
binding.etCommentModify.setText(item.comment)
binding.ivMenu.visibility = View.VISIBLE
binding.ivMenu.setOnClickListener {
showOptionMenu(
context,
binding.ivMenu,
item.id,
item.writerId,
creatorId
) {
binding.rlCommentModify.visibility = View.VISIBLE
binding.tvComment.visibility = View.GONE
}
}
binding.tvModify.setOnClickListener {
binding.rlCommentModify.visibility = View.GONE
binding.tvComment.visibility = View.VISIBLE
modifyComment(item.id, binding.etCommentModify.text.toString())
}
} else {
binding.ivMenu.visibility = View.GONE
}
} }
} }

View File

@@ -16,6 +16,7 @@ import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
@@ -34,6 +35,8 @@ class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommen
private lateinit var adapter: AudioContentCommentReplyAdapter private lateinit var adapter: AudioContentCommentReplyAdapter
private var originalComment: GetAudioContentCommentListItem? = null private var originalComment: GetAudioContentCommentListItem? = null
private var creatorId: Long = 0
private var audioContentId: Long = 0 private var audioContentId: Long = 0
override fun onCreateView( override fun onCreateView(
@@ -41,6 +44,7 @@ class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommen
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
creatorId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID) ?: 0
audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0 audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0
originalComment = BundleCompat.getParcelable( originalComment = BundleCompat.getParcelable(
requireArguments(), requireArguments(),
@@ -83,7 +87,7 @@ class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommen
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) { binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
@@ -94,7 +98,35 @@ class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommen
viewModel.registerComment(audioContentId, originalComment!!.id, comment) viewModel.registerComment(audioContentId, originalComment!!.id, comment)
} }
adapter = AudioContentCommentReplyAdapter().apply { adapter = AudioContentCommentReplyAdapter(
creatorId = creatorId,
modifyComment = { commentId, comment ->
hideKeyboard()
viewModel.modifyComment(
commentId = commentId,
parentCommentId = originalComment!!.id,
comment = comment
)
},
onClickDelete = {
SodaDialog(
activity = requireActivity(),
layoutInflater = layoutInflater,
title = "댓글 삭제",
desc = "삭제하시겠습니까?",
confirmButtonTitle = "삭제",
confirmButtonClick = {
viewModel.modifyComment(
commentId = it,
parentCommentId = originalComment!!.id,
isActive = false
)
},
cancelButtonTitle = "취소",
cancelButtonClick = {}
).show(screenWidth)
}
).apply {
items.add(originalComment!!) items.add(originalComment!!)
} }
@@ -189,11 +221,13 @@ class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommen
companion object { companion object {
fun newInstance( fun newInstance(
creatorId: Long,
audioContentId: Long, audioContentId: Long,
comment: GetAudioContentCommentListItem comment: GetAudioContentCommentListItem
): AudioContentCommentReplyFragment { ): AudioContentCommentReplyFragment {
val args = Bundle() val args = Bundle()
args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId) args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
args.putLong(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID, creatorId)
args.putParcelable(Constants.EXTRA_AUDIO_CONTENT_COMMENT, comment) args.putParcelable(Constants.EXTRA_AUDIO_CONTENT_COMMENT, comment)
val fragment = AudioContentCommentReplyFragment() val fragment = AudioContentCommentReplyFragment()

View File

@@ -42,11 +42,12 @@ class AudioContentCommentReplyViewModel(
.subscribe( .subscribe(
{ {
if (it.success && it.data != null) { if (it.success && it.data != null) {
if (it.data.items.isNotEmpty()) {
page += 1 page += 1
if (it.data.items.isNotEmpty()) {
_commentList.postValue(it.data.items) _commentList.postValue(it.data.items)
} else { } else {
isLast = true isLast = true
_commentList.postValue(listOf())
} }
} else { } else {
if (it.message != null) { if (it.message != null) {
@@ -117,4 +118,61 @@ class AudioContentCommentReplyViewModel(
) )
) )
} }
fun modifyComment(
commentId: Long,
parentCommentId: Long,
comment: String? = null,
isActive: Boolean? = null
) {
if (comment == null && isActive == null) {
_toastLiveData.postValue("변경사항이 없습니다.")
return
}
if (comment != null && comment.isBlank()) {
_toastLiveData.postValue("내용을 입력하세요")
return
}
_isLoading.value = true
val request = ModifyCommentRequest(commentId = commentId)
if (comment != null) {
request.comment = comment
}
if (isActive != null) {
request.isActive = isActive
}
compositeDisposable.add(
repository.modifyComment(
request = request,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
page = 1
isLast = false
getCommentReplyList(parentCommentId)
} else {
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
_toastLiveData.postValue(message)
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
} }

View File

@@ -43,4 +43,9 @@ class AudioContentCommentRepository(private val api: AudioContentApi) {
timezone = TimeZone.getDefault().id, timezone = TimeZone.getDefault().id,
authHeader = token authHeader = token
) )
fun modifyComment(request: ModifyCommentRequest, token: String) = api.modifyComment(
request = request,
authHeader = token
)
} }

View File

@@ -12,10 +12,11 @@ data class GetAudioContentCommentListResponse(
@Parcelize @Parcelize
data class GetAudioContentCommentListItem( data class GetAudioContentCommentListItem(
@SerializedName("id") val id: Long, @SerializedName("id") val id: Long,
@SerializedName("writerId") val writerId: Long,
@SerializedName("nickname") val nickname: String, @SerializedName("nickname") val nickname: String,
@SerializedName("profileUrl") val profileUrl: String, @SerializedName("profileUrl") val profileUrl: String,
@SerializedName("comment") val comment: String, @SerializedName("comment") val comment: String,
@SerializedName("donationCoin") val donationCoin: Int, @SerializedName("donationCan") val donationCan: Int,
@SerializedName("date") val date: String, @SerializedName("date") val date: String,
@SerializedName("replyCount") val replyCount: Int @SerializedName("replyCount") val replyCount: Int
) : Parcelable ) : Parcelable

View File

@@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.audio_content.comment
import com.google.gson.annotations.SerializedName
data class ModifyCommentRequest(
@SerializedName("commentId") val commentId: Long,
@SerializedName("comment") var comment: String? = null,
@SerializedName("isActive") var isActive: Boolean? = null
)

View File

@@ -56,6 +56,8 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
private var isAlertPreview = false private var isAlertPreview = false
private val audioContentReceiver = AudioContentReceiver() private val audioContentReceiver = AudioContentReceiver()
private var creatorId: Long = 0
private var refresh = false private var refresh = false
set(value) { set(value) {
field = value field = value
@@ -248,7 +250,7 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
LayoutInflater.from(this) LayoutInflater.from(this)
) { can, message -> ) { can, message ->
if (can <= 0) { if (can <= 0) {
showToast("1코인 이상 후원하실 수 있습니다.") showToast("1 이상 후원하실 수 있습니다.")
} else if (message.isBlank()) { } else if (message.isBlank()) {
showToast("함께 보낼 메시지를 입력하세요.") showToast("함께 보낼 메시지를 입력하세요.")
} else { } else {
@@ -260,8 +262,8 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
} }
} }
private fun donation(coin: Int, message: String) { private fun donation(can: Int, message: String) {
viewModel.donation(audioContentId, coin, message) { viewModel.donation(audioContentId, can, message) {
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() } viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
} }
} }
@@ -474,7 +476,10 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
} }
private fun showCommentBottomSheetDialog() { private fun showCommentBottomSheetDialog() {
val dialog = AudioContentCommentFragment(audioContentId = audioContentId) val dialog = AudioContentCommentFragment(
creatorId = creatorId,
audioContentId = audioContentId
)
dialog.show( dialog.show(
supportFragmentManager, supportFragmentManager,
dialog.tag dialog.tag
@@ -629,6 +634,7 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
} }
private fun setupCreatorArea(creator: AudioContentCreator) { private fun setupCreatorArea(creator: AudioContentCreator) {
this.creatorId = creator.creatorId
binding.rlProfile.setOnClickListener { binding.rlProfile.setOnClickListener {
startActivity( startActivity(
Intent(applicationContext, UserProfileActivity::class.java).apply { Intent(applicationContext, UserProfileActivity::class.java).apply {
@@ -693,7 +699,6 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
title = audioContent.title, title = audioContent.title,
theme = audioContent.themeStr, theme = audioContent.themeStr,
coverImageUrl = audioContent.coverImageUrl, coverImageUrl = audioContent.coverImageUrl,
isAdult = audioContent.isAdult,
profileImageUrl = audioContent.creator.profileImageUrl, profileImageUrl = audioContent.creator.profileImageUrl,
nickname = audioContent.creator.nickname, nickname = audioContent.creator.nickname,
duration = audioContent.duration, duration = audioContent.duration,

View File

@@ -332,15 +332,15 @@ class AudioContentDetailViewModel(
) { ) {
isLoading.value = true isLoading.value = true
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) { Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
link = Uri.parse("https://yozm.day/?audio_content_id=$audioContentId") link = Uri.parse("https://sodalive.net/?audio_content_id=$audioContentId")
domainUriPrefix = "https://yozm.page.link" domainUriPrefix = "https://sodalive.page.link"
androidParameters { } androidParameters { }
iosParameters("kr.co.vividnext.yozm") { iosParameters("kr.co.vividnext.sodalive") {
appStoreId = "1630284226" appStoreId = "6461721697"
} }
socialMetaTagParameters { socialMetaTagParameters {
title = contentTitle title = contentTitle
description = "지금 요즘라이브에서 이 콘텐츠 감상하기" description = "지금 소다라이브에서 이 콘텐츠 감상하기"
imageUrl = contentImage.toUri() imageUrl = contentImage.toUri()
} }
}.addOnSuccessListener { }.addOnSuccessListener {

View File

@@ -1,15 +1,19 @@
package kr.co.vividnext.sodalive.audio_content.main package kr.co.vividnext.sodalive.audio_content.main
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import coil.load import com.bumptech.glide.Glide
import coil.transform.RoundedCornersTransformation import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.zhpan.bannerview.BaseBannerAdapter import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.bannerview.BaseViewHolder import com.zhpan.bannerview.BaseViewHolder
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.extensions.dpToPx
class AudioContentMainBannerAdapter( class AudioContentMainBannerAdapter(
private val context: Context,
private val itemWidth: Int, private val itemWidth: Int,
private val itemHeight: Int, private val itemHeight: Int,
private val onClick: (GetAudioContentBannerResponse) -> Unit private val onClick: (GetAudioContentBannerResponse) -> Unit
@@ -26,12 +30,20 @@ class AudioContentMainBannerAdapter(
layoutParams.width = itemWidth layoutParams.width = itemWidth
layoutParams.height = itemHeight layoutParams.height = itemHeight
ivBanner.load(data.thumbnailImageUrl) { Glide
crossfade(true) .with(context)
placeholder(R.drawable.ic_logo) .asBitmap()
transformations(RoundedCornersTransformation(5.3f.dpToPx())) .load(data.thumbnailImageUrl)
} .into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
ivBanner.setImageBitmap(resource)
ivBanner.layoutParams = layoutParams ivBanner.layoutParams = layoutParams
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
ivBanner.setOnClickListener { onClick(data) } ivBanner.setOnClickListener { onClick(data) }
} }

View File

@@ -142,7 +142,11 @@ class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
layoutParams.width = pagerWidth.roundToInt() layoutParams.width = pagerWidth.roundToInt()
layoutParams.height = pagerHeight layoutParams.height = pagerHeight
bannerAdapter = AudioContentMainBannerAdapter(pagerWidth.roundToInt(), pagerHeight) { bannerAdapter = AudioContentMainBannerAdapter(
requireContext(),
pagerWidth.roundToInt(),
pagerHeight
) {
when (it.type) { when (it.type) {
AudioContentBannerType.EVENT -> { AudioContentBannerType.EVENT -> {
startActivity( startActivity(

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.audio_content.main package kr.co.vividnext.sodalive.audio_content.main
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
@@ -17,13 +16,13 @@ class AudioContentMainItemViewHolder(
fun bind(item: GetAudioContentMainItem) { fun bind(item: GetAudioContentMainItem) {
binding.ivAudioContentCoverImage.load(item.coverImageUrl) { binding.ivAudioContentCoverImage.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(2.7f.dpToPx())) transformations(RoundedCornersTransformation(2.7f.dpToPx()))
} }
binding.ivAudioContentCreator.load(item.creatorProfileImageUrl) { binding.ivAudioContentCreator.load(item.creatorProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
@@ -31,11 +30,6 @@ class AudioContentMainItemViewHolder(
binding.tvAudioContentCreatorNickname.text = item.creatorNickname binding.tvAudioContentCreatorNickname.text = item.creatorNickname
binding.ivAudioContentCreator.setOnClickListener { onClickCreator(item.creatorId) } binding.ivAudioContentCreator.setOnClickListener { onClickCreator(item.creatorId) }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
binding.root.setOnClickListener { onClickItem(item.contentId) } binding.root.setOnClickListener { onClickItem(item.contentId) }
} }
} }

View File

@@ -22,7 +22,7 @@ class AudioContentMainNewContentCreatorAdapter(
binding.tvNewContentCreator.text = item.creatorNickname binding.tvNewContentCreator.text = item.creatorNickname
binding.ivNewContentCreator.load(item.creatorProfileImageUrl) { binding.ivNewContentCreator.load(item.creatorProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
binding.root.setOnClickListener { onClickItem(item.creatorId) } binding.root.setOnClickListener { onClickItem(item.creatorId) }

View File

@@ -32,7 +32,7 @@ data class GetAudioContentMainItem(
data class GetAudioContentCurationResponse( data class GetAudioContentCurationResponse(
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("description") val description: String, @SerializedName("description") val description: String,
@SerializedName("audioContents") val audioContents: List<GetAudioContentMainItem> @SerializedName("contents") val audioContents: List<GetAudioContentMainItem>
) )
data class GetAudioContentBannerResponse( data class GetAudioContentBannerResponse(

View File

@@ -4,7 +4,6 @@ import android.app.Activity
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import coil.load import coil.load
@@ -21,7 +20,6 @@ class AudioContentOrderConfirmDialog(
title: String, title: String,
theme: String, theme: String,
coverImageUrl: String, coverImageUrl: String,
isAdult: Boolean,
profileImageUrl: String, profileImageUrl: String,
nickname: String, nickname: String,
duration: String, duration: String,
@@ -48,33 +46,27 @@ class AudioContentOrderConfirmDialog(
dialogView.ivCover.load(coverImageUrl) { dialogView.ivCover.load(coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4f)) transformations(RoundedCornersTransformation(4f))
} }
dialogView.ivProfile.load(profileImageUrl) { dialogView.ivProfile.load(profileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
dialogView.tvDuration.text = duration dialogView.tvDuration.text = duration
dialogView.tvPrice.text = if (orderType == OrderType.RENTAL) { dialogView.tvPrice.text = if (orderType == OrderType.RENTAL) {
"${ceil(price * 0.7).toInt()}" "${ceil(price * 0.6).toInt()}"
} else { } else {
"$price" "$price"
} }
dialogView.iv19.visibility = if (isAdult) {
View.VISIBLE
} else {
View.GONE
}
dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) { dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) {
"콘텐츠를 대여하시겠습니까?\n아래 코인이 차감됩니다." "콘텐츠를 대여하시겠습니까?\n아래 이 차감됩니다."
} else { } else {
"콘텐츠를 소장하시겠습니까?\n아래 코인이 차감됩니다." "콘텐츠를 소장하시겠습니까?\n아래 이 차감됩니다."
} }
dialogView.tvCancel.setOnClickListener { dialogView.tvCancel.setOnClickListener {

View File

@@ -29,7 +29,7 @@ class AudioContentOrderFragment(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.tvKeep.text = "$price" binding.tvKeep.text = "$price"
binding.tvRental.text = "${ceil(price * 0.7).toInt()}" binding.tvRental.text = "${ceil(price * 0.6).toInt()}"
binding.llKeep.setOnClickListener { binding.llKeep.setOnClickListener {
onClickKeep() onClickKeep()

View File

@@ -23,13 +23,14 @@ class AudioContentOrderListAdapter(
fun bind(item: GetAudioContentOrderListItem) { fun bind(item: GetAudioContentOrderListItem) {
binding.ivCover.load(item.coverImageUrl) { binding.ivCover.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(5.3f.dpToPx())) transformations(RoundedCornersTransformation(5.3f.dpToPx()))
} }
binding.tvTitle.text = item.title binding.tvTitle.text = item.title
binding.tvTheme.text = item.themeStr binding.tvTheme.text = item.themeStr
binding.tvDuration.text = item.duration binding.tvDuration.text = item.duration
binding.tvCreatorNickname.text = item.creatorNickname
binding.tvLikeCount.text = item.likeCount.moneyFormat() binding.tvLikeCount.text = item.likeCount.moneyFormat()
binding.tvCommentCount.text = item.commentCount.moneyFormat() binding.tvCommentCount.text = item.commentCount.moneyFormat()

View File

@@ -10,6 +10,7 @@ data class GetAudioContentOrderListResponse(
data class GetAudioContentOrderListItem( data class GetAudioContentOrderListItem(
@SerializedName("contentId") val contentId: Long, @SerializedName("contentId") val contentId: Long,
@SerializedName("coverImageUrl") val coverImageUrl: String, @SerializedName("coverImageUrl") val coverImageUrl: String,
@SerializedName("creatorNickname") val creatorNickname: String,
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("themeStr") val themeStr: String, @SerializedName("themeStr") val themeStr: String,
@SerializedName("duration") val duration: String?, @SerializedName("duration") val duration: String?,

View File

@@ -70,7 +70,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
binding.ivCover.background = null binding.ivCover.background = null
binding.ivCover.load(fileUri) { binding.ivCover.load(fileUri) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }
viewModel.coverImageUri = fileUri viewModel.coverImageUri = fileUri

View File

@@ -211,8 +211,8 @@ class AudioContentUploadViewModel(
return false return false
} }
if (!isPriceFreeLiveData.value!! && price < 10) { if (!isPriceFreeLiveData.value!! && price < 5) {
_toastLiveData.postValue("콘텐츠의 최소금액은 10코인 입니다.") _toastLiveData.postValue("콘텐츠의 최소금액은 5캔 입니다.")
return false return false
} }

View File

@@ -35,7 +35,7 @@ class AudioContentThemeAdapter(
binding.ivTheme.load(item.image) { binding.ivTheme.load(item.image) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }

View File

@@ -13,6 +13,7 @@ object Constants {
const val PREF_IS_CONTENT_PLAY_LOOP = "pref_is_content_play_loop" 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_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 PREF_NOT_SHOWING_EVENT_POPUP_ID = "pref_not_showing_event_popup_id"
const val PREF_IS_VIEWED_ON_BOARDING_TUTORIAL = "pref_is_viewed_on_boarding_tutorial"
const val EXTRA_CAN = "extra_can" const val EXTRA_CAN = "extra_can"
const val EXTRA_DATA = "extra_data" const val EXTRA_DATA = "extra_data"

View File

@@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.common
import android.app.Activity import android.app.Activity
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.WindowManager import android.view.WindowManager
@@ -16,14 +15,11 @@ class LoadingDialog(
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
private val dialogView = DialogLoadingBinding.inflate(layoutInflater) private val dialogView = DialogLoadingBinding.inflate(layoutInflater)
private val animationDrawable: AnimationDrawable
init { init {
val dialogBuilder = AlertDialog.Builder(activity) val dialogBuilder = AlertDialog.Builder(activity)
dialogBuilder.setView(dialogView.root) dialogBuilder.setView(dialogView.root)
animationDrawable = dialogView.tvLoading.compoundDrawables[1] as AnimationDrawable
alertDialog = dialogBuilder.create() alertDialog = dialogBuilder.create()
alertDialog.setCancelable(false) alertDialog.setCancelable(false)
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
@@ -31,7 +27,6 @@ class LoadingDialog(
fun show(width: Int, message: String = "") { fun show(width: Int, message: String = "") {
alertDialog.show() alertDialog.show()
animationDrawable.start()
dialogView.tvLoading.text = message dialogView.tvLoading.text = message
val lp = WindowManager.LayoutParams() val lp = WindowManager.LayoutParams()
@@ -43,7 +38,6 @@ class LoadingDialog(
} }
fun dismiss() { fun dismiss() {
animationDrawable.stop()
alertDialog.dismiss() alertDialog.dismiss()
} }
} }

View File

@@ -116,4 +116,10 @@ object SharedPreferenceManager {
set(value) { set(value) {
sharedPreferences[Constants.PREF_NOT_SHOWING_EVENT_POPUP_ID] = value sharedPreferences[Constants.PREF_NOT_SHOWING_EVENT_POPUP_ID] = value
} }
var isViewedOnboardingTutorial: Boolean
get() = sharedPreferences[Constants.PREF_IS_VIEWED_ON_BOARDING_TUTORIAL, false]
set(value) {
sharedPreferences[Constants.PREF_IS_VIEWED_ON_BOARDING_TUTORIAL] = value
}
} }

View File

@@ -62,7 +62,7 @@ class SodaLiveService : Service() {
) )
val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId) val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
.setSmallIcon(R.drawable.ic_noti) .setSmallIcon(R.drawable.ic_notification)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setContentText(content) .setContentText(content)
.setOngoing(true) .setOngoing(true)

View File

@@ -22,6 +22,8 @@ import kr.co.vividnext.sodalive.explorer.ExplorerApi
import kr.co.vividnext.sodalive.explorer.ExplorerRepository import kr.co.vividnext.sodalive.explorer.ExplorerRepository
import kr.co.vividnext.sodalive.explorer.ExplorerViewModel import kr.co.vividnext.sodalive.explorer.ExplorerViewModel
import kr.co.vividnext.sodalive.explorer.profile.UserProfileViewModel import kr.co.vividnext.sodalive.explorer.profile.UserProfileViewModel
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewModel
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewModel
import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel
import kr.co.vividnext.sodalive.following.FollowingCreatorRepository import kr.co.vividnext.sodalive.following.FollowingCreatorRepository
import kr.co.vividnext.sodalive.following.FollowingCreatorViewModel import kr.co.vividnext.sodalive.following.FollowingCreatorViewModel
@@ -55,6 +57,11 @@ import kr.co.vividnext.sodalive.mypage.can.CanRepository
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeViewModel 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.payment.CanPaymentViewModel
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusViewModel import kr.co.vividnext.sodalive.mypage.can.status.CanStatusViewModel
import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateViewModel
import kr.co.vividnext.sodalive.mypage.profile.nickname.NicknameUpdateViewModel
import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagApi
import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagRepository
import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagViewModel
import kr.co.vividnext.sodalive.mypage.service_center.FaqApi 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.FaqRepository
import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel
@@ -134,6 +141,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
single { ApiBuilder().build(get(), NoticeApi::class.java) } single { ApiBuilder().build(get(), NoticeApi::class.java) }
single { ApiBuilder().build(get(), AudioContentApi::class.java) } single { ApiBuilder().build(get(), AudioContentApi::class.java) }
single { ApiBuilder().build(get(), FaqApi::class.java) } single { ApiBuilder().build(get(), FaqApi::class.java) }
single { ApiBuilder().build(get(), MemberTagApi::class.java) }
} }
private val viewModelModule = module { private val viewModelModule = module {
@@ -179,6 +187,10 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { AudioContentCommentReplyViewModel(get()) } viewModel { AudioContentCommentReplyViewModel(get()) }
viewModel { FollowingCreatorViewModel(get()) } viewModel { FollowingCreatorViewModel(get()) }
viewModel { ServiceCenterViewModel(get()) } viewModel { ServiceCenterViewModel(get()) }
viewModel { ProfileUpdateViewModel(get()) }
viewModel { NicknameUpdateViewModel(get()) }
viewModel { MemberTagViewModel(get()) }
viewModel { UserProfileDonationAllViewModel(get()) }
} }
private val repositoryModule = module { private val repositoryModule = module {
@@ -199,6 +211,8 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { PlaybackTrackingRepository(get()) } factory { PlaybackTrackingRepository(get()) }
factory { FollowingCreatorRepository(get(), get()) } factory { FollowingCreatorRepository(get(), get()) }
factory { FaqRepository(get()) } factory { FaqRepository(get()) }
factory { MemberTagRepository(get()) }
factory { UserProfileFantalkAllViewModel(get(), get()) }
} }
private val moduleList = listOf( private val moduleList = listOf(

View File

@@ -1,11 +1,14 @@
package kr.co.vividnext.sodalive.explorer package kr.co.vividnext.sodalive.explorer
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse
import kr.co.vividnext.sodalive.explorer.profile.GetCreatorProfileResponse import kr.co.vividnext.sodalive.explorer.profile.GetCreatorProfileResponse
import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest 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.PostWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse
import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
import retrofit2.http.Body import retrofit2.http.Body
@@ -35,6 +38,23 @@ interface ExplorerApi {
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<GetCreatorProfileResponse>> ): Single<ApiResponse<GetCreatorProfileResponse>>
@GET("/explorer/profile/{id}/donation-rank")
fun getCreatorProfileDonationRanking(
@Path("id") id: Long,
@Query("page") page: Int,
@Query("size") size: Int,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetDonationAllResponse>>
@GET("/explorer/profile/{id}/cheers")
fun getCreatorProfileCheers(
@Path("id") creatorId: Long,
@Query("page") page: Int,
@Query("size") size: Int,
@Query("timezone") timezone: String,
@Header("Authorization") authHeader: String
): Flowable<ApiResponse<GetCheersResponse>>
@POST("/explorer/profile/cheers") @POST("/explorer/profile/cheers")
fun writeCheers( fun writeCheers(
@Body request: PostWriteCheersRequest, @Body request: PostWriteCheersRequest,

View File

@@ -1,8 +1,13 @@
package kr.co.vividnext.sodalive.explorer package kr.co.vividnext.sodalive.explorer
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse
import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest 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.PostWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse
import java.util.TimeZone import java.util.TimeZone
class ExplorerRepository( class ExplorerRepository(
@@ -21,6 +26,21 @@ class ExplorerRepository(
authHeader = token authHeader = token
) )
fun getCreatorProfileCheers(
creatorId: Long,
page: Int,
size: Int,
token: String
): Flowable<ApiResponse<GetCheersResponse>> {
return api.getCreatorProfileCheers(
creatorId = creatorId,
page = page - 1,
size = size,
timezone = TimeZone.getDefault().id,
authHeader = token
)
}
fun writeCheers( fun writeCheers(
parentCheersId: Long?, parentCheersId: Long?,
creatorId: Long, creatorId: Long,
@@ -36,14 +56,10 @@ class ExplorerRepository(
) )
fun modifyCheers( fun modifyCheers(
cheersId: Long, request: PutModifyCheersRequest,
content: String,
token: String token: String
) = api.modifyCheers( ) = api.modifyCheers(
request = PutModifyCheersRequest( request = request,
cheersId = cheersId,
content = content
),
authHeader = token authHeader = token
) )
@@ -63,4 +79,18 @@ class ExplorerRepository(
size = size, size = size,
authHeader = token authHeader = token
) )
fun getCreatorProfileDonationRanking(
id: Long,
page: Int,
size: Int,
token: String
): Single<ApiResponse<GetDonationAllResponse>> {
return api.getCreatorProfileDonationRanking(
id = id,
page = page - 1,
size = size,
authHeader = token
)
}
} }

View File

@@ -24,7 +24,7 @@ class ExplorerSectionAdapter(
binding.ivProfile.load(item.profileImageUrl) { binding.ivProfile.load(item.profileImageUrl) {
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
crossfade(true) crossfade(true)
} }

View File

@@ -9,6 +9,7 @@ data class GetCheersResponse(
data class GetCheersResponseItem( data class GetCheersResponseItem(
@SerializedName("cheersId") val cheersId: Long, @SerializedName("cheersId") val cheersId: Long,
@SerializedName("memberId") val memberId: Long,
@SerializedName("nickname") val nickname: String, @SerializedName("nickname") val nickname: String,
@SerializedName("profileUrl") val profileUrl: String, @SerializedName("profileUrl") val profileUrl: String,
@SerializedName("content") val content: String, @SerializedName("content") val content: String,

View File

@@ -11,8 +11,8 @@ data class GetCreatorProfileResponse(
val similarCreatorList: List<SimilarCreatorResponse>, val similarCreatorList: List<SimilarCreatorResponse>,
@SerializedName("liveRoomList") @SerializedName("liveRoomList")
val liveRoomList: List<LiveRoomResponse>, val liveRoomList: List<LiveRoomResponse>,
@SerializedName("audioContentList") @SerializedName("contentList")
val audioContentList: List<GetAudioContentListItem>, val contentList: List<GetAudioContentListItem>,
@SerializedName("notice") @SerializedName("notice")
val notice: String, val notice: String,
@SerializedName("cheers") @SerializedName("cheers")
@@ -42,7 +42,7 @@ data class UserDonationRankingResponse(
@SerializedName("userId") val userId: Long, @SerializedName("userId") val userId: Long,
@SerializedName("nickname") val nickname: String, @SerializedName("nickname") val nickname: String,
@SerializedName("profileImage") val profileImage: String, @SerializedName("profileImage") val profileImage: String,
@SerializedName("donationCoin") val donationCoin: Int @SerializedName("donationCan") val donationCan: Int
) )
data class SimilarCreatorResponse( data class SimilarCreatorResponse(

View File

@@ -23,7 +23,12 @@ import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentActivity
import kr.co.vividnext.sodalive.audio_content.AudioContentAdapter
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.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
@@ -56,6 +61,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
private lateinit var imm: InputMethodManager private lateinit var imm: InputMethodManager
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var liveAdapter: UserProfileLiveAdapter private lateinit var liveAdapter: UserProfileLiveAdapter
private lateinit var audioContentAdapter: AudioContentAdapter
private lateinit var donationAdapter: UserProfileDonationAdapter private lateinit var donationAdapter: UserProfileDonationAdapter
private lateinit var similarCreatorAdapter: UserProfileSimilarCreatorAdapter private lateinit var similarCreatorAdapter: UserProfileSimilarCreatorAdapter
private lateinit var cheersAdapter: UserProfileCheersAdapter private lateinit var cheersAdapter: UserProfileCheersAdapter
@@ -118,6 +124,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
setupDonationView() setupDonationView()
setupSimilarCreatorView() setupSimilarCreatorView()
setupFanTalkView() setupFanTalkView()
setupAudioContentListView()
} }
private fun hideKeyboard(onAfterExecute: () -> Unit) { private fun hideKeyboard(onAfterExecute: () -> Unit) {
@@ -392,7 +399,34 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
) )
} }
}, },
onClickReport = { showCheersReportPopup(it) } modifyCheers = { cheersId, content ->
hideKeyboard {
viewModel.modifyCheers(
cheersId = cheersId,
creatorId = userId,
cheersContent = content,
)
}
},
onClickReport = { showCheersReportPopup(it) },
onClickDelete = {
SodaDialog(
activity = this@UserProfileActivity,
layoutInflater = layoutInflater,
title = "응원글 삭제",
desc = "삭제하시겠습니까?",
confirmButtonTitle = "삭제",
confirmButtonClick = {
viewModel.modifyCheers(
cheersId = it,
creatorId = userId,
isActive = false
)
},
cancelButtonTitle = "취소",
cancelButtonClick = {}
).show(screenWidth)
}
) )
rvCheers.layoutManager = LinearLayoutManager( rvCheers.layoutManager = LinearLayoutManager(
@@ -432,6 +466,52 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
rvCheers.adapter = cheersAdapter rvCheers.adapter = cheersAdapter
} }
private fun setupAudioContentListView() {
binding.layoutUserProfileAudioContent.tvAll.setOnClickListener {
val intent = Intent(applicationContext, AudioContentActivity::class.java)
intent.putExtra(Constants.EXTRA_USER_ID, userId)
startActivity(intent)
}
val recyclerView = binding.layoutUserProfileAudioContent.rvAudioContent
audioContentAdapter = AudioContentAdapter {
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
.apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
}
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)) {
audioContentAdapter.itemCount - 1 -> {
outRect.bottom = 0
}
else -> {
outRect.bottom = 13.3f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = audioContentAdapter
}
private fun showCheersReportPopup(cheersId: Long) { private fun showCheersReportPopup(cheersId: Long) {
val dialog = CheersReportDialog(this, layoutInflater) { val dialog = CheersReportDialog(this, layoutInflater) {
if (it.isBlank()) { if (it.isBlank()) {
@@ -477,6 +557,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
setCheers(it.cheers) setCheers(it.cheers)
setCreatorProfile(it.creator) setCreatorProfile(it.creator)
setCreatorNotice(it.notice, it.creator.creatorId) setCreatorNotice(it.notice, it.creator.creatorId)
setAudioContentList(it.contentList)
setLiveRoomList(it.liveRoomList) setLiveRoomList(it.liveRoomList)
setSimilarCreatorList(it.similarCreatorList) setSimilarCreatorList(it.similarCreatorList)
setUserDonationRanking(it.userDonationRanking) setUserDonationRanking(it.userDonationRanking)
@@ -536,7 +617,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
layoutUserProfile.ivProfile.load(creator.profileUrl) { layoutUserProfile.ivProfile.load(creator.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
@@ -625,6 +706,36 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
} }
} }
@SuppressLint("NotifyDataSetChanged")
private fun setAudioContentList(audioContentList: List<GetAudioContentListItem>) {
binding.layoutUserProfileAudioContent.root.visibility =
if (userId == SharedPreferenceManager.userId || audioContentList.isNotEmpty()) {
View.VISIBLE
} else {
View.GONE
}
if (userId == SharedPreferenceManager.userId) {
binding.layoutUserProfileAudioContent.tvTitle.text = "내 콘텐츠"
binding.layoutUserProfileAudioContent.tvNewContent.setOnClickListener {
startActivity(
Intent(
applicationContext,
AudioContentUploadActivity::class.java
)
)
}
binding.layoutUserProfileAudioContent.tvNewContent.visibility = View.VISIBLE
} else {
binding.layoutUserProfileAudioContent.tvTitle.text = "콘텐츠"
binding.layoutUserProfileAudioContent.tvNewContent.visibility = View.GONE
}
audioContentAdapter.items.clear()
audioContentAdapter.items.addAll(audioContentList)
audioContentAdapter.notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun setLiveRoomList(liveRoomList: List<LiveRoomResponse>) { private fun setLiveRoomList(liveRoomList: List<LiveRoomResponse>) {
if (liveRoomList.isEmpty()) { if (liveRoomList.isEmpty()) {

View File

@@ -28,16 +28,10 @@ class UserProfileLiveAdapter(
fun bind(item: LiveRoomResponse) { fun bind(item: LiveRoomResponse) {
binding.ivCover.load(item.coverImageUrl) { binding.ivCover.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4.7f.dpToPx())) transformations(RoundedCornersTransformation(4.7f.dpToPx()))
} }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
binding.tvDate.text = item.beginDateTime binding.tvDate.text = item.beginDateTime
binding.tvNickname.text = item.managerNickname binding.tvNickname.text = item.managerNickname
binding.tvTitle.text = item.title binding.tvTitle.text = item.title

View File

@@ -20,7 +20,7 @@ class UserProfileSimilarCreatorAdapter(
fun bind(item: SimilarCreatorResponse) { fun bind(item: SimilarCreatorResponse) {
binding.ivProfile.load(item.profileImage) { binding.ivProfile.load(item.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }

View File

@@ -15,6 +15,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.explorer.ExplorerRepository import kr.co.vividnext.sodalive.explorer.ExplorerRepository
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
import kr.co.vividnext.sodalive.report.ReportRepository import kr.co.vividnext.sodalive.report.ReportRepository
import kr.co.vividnext.sodalive.report.ReportRequest import kr.co.vividnext.sodalive.report.ReportRequest
import kr.co.vividnext.sodalive.report.ReportType import kr.co.vividnext.sodalive.report.ReportType
@@ -256,18 +257,37 @@ class UserProfileViewModel(
) )
} }
fun modifyCheers(cheersId: Long, creatorId: Long, cheersContent: String) { fun modifyCheers(
if (cheersContent.isBlank()) { cheersId: Long,
creatorId: Long,
cheersContent: String? = null,
isActive: Boolean? = null
) {
if (cheersContent == null && isActive == null) {
_toastLiveData.postValue("변경사항이 없습니다.")
return
}
if (cheersContent != null && cheersContent.isBlank()) {
_toastLiveData.postValue("내용을 입력하세요") _toastLiveData.postValue("내용을 입력하세요")
return return
} }
_isLoading.value = true _isLoading.value = true
val request = PutModifyCheersRequest(cheersId = cheersId)
if (cheersContent != null) {
request.content = cheersContent
}
if (isActive != null) {
request.isActive = isActive
}
compositeDisposable.add( compositeDisposable.add(
repository.modifyCheers( repository.modifyCheers(
cheersId = cheersId, request = request,
content = cheersContent,
token = "Bearer ${SharedPreferenceManager.token}" token = "Bearer ${SharedPreferenceManager.token}"
) )
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -295,16 +315,16 @@ class UserProfileViewModel(
fun shareChannel(userId: Long, onSuccess: (String) -> Unit) { fun shareChannel(userId: Long, onSuccess: (String) -> Unit) {
_isLoading.value = true _isLoading.value = true
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) { Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
link = Uri.parse("https://yozm.day/?channel_id=$userId") link = Uri.parse("https://sodalive.net/?channel_id=$userId")
domainUriPrefix = "https://yozm.page.link" domainUriPrefix = "https://sodalive.page.link"
androidParameters { } androidParameters { }
iosParameters("kr.co.vividnext.yozm") { iosParameters("kr.co.vividnext.sodalive") {
appStoreId = "1630284226" appStoreId = "6461721697"
} }
}.addOnSuccessListener { }.addOnSuccessListener {
val uri = it.shortLink val uri = it.shortLink
if (uri != null) { if (uri != null) {
onSuccess("요즘라이브 ${creatorNickname}님의 채널입니다.\n$uri") onSuccess("소다라이브 ${creatorNickname}님의 채널입니다.\n$uri")
} }
}.addOnFailureListener { }.addOnFailureListener {
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.") _toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")

View File

@@ -4,5 +4,6 @@ import com.google.gson.annotations.SerializedName
data class PutModifyCheersRequest( data class PutModifyCheersRequest(
@SerializedName("cheersId") val cheersId: Long, @SerializedName("cheersId") val cheersId: Long,
@SerializedName("content") val content: String @SerializedName("content") var content: String? = null,
@SerializedName("isActive") var isActive: Boolean? = null,
) )

View File

@@ -17,8 +17,10 @@ import kr.co.vividnext.sodalive.extensions.dpToPx
class UserProfileCheersAdapter( class UserProfileCheersAdapter(
private val userId: Long, private val userId: Long,
private val enterReply: (Long, String) -> Unit, private val enterReply: (Long, String) -> Unit,
private val modifyReply: (Long, String) -> Unit, private val modifyReply: (Long, String?) -> Unit,
private val onClickReport: (Long) -> Unit private val modifyCheers: (Long, String) -> Unit,
private val onClickReport: (Long) -> Unit,
private val onClickDelete: (Long) -> Unit
) : RecyclerView.Adapter<UserProfileCheersAdapter.ViewHolder>() { ) : RecyclerView.Adapter<UserProfileCheersAdapter.ViewHolder>() {
val items = mutableListOf<GetCheersResponseItem>() val items = mutableListOf<GetCheersResponseItem>()
@@ -35,21 +37,42 @@ class UserProfileCheersAdapter(
binding.ivProfile.load(cheers.profileUrl) { binding.ivProfile.load(cheers.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(16.7f.dpToPx())) transformations(RoundedCornersTransformation(16.7f.dpToPx()))
} }
binding.tvContent.text = cheers.content binding.tvContent.text = cheers.content
binding.tvNickname.text = cheers.nickname binding.tvNickname.text = cheers.nickname
binding.tvDate.text = cheers.date binding.tvDate.text = cheers.date
if (
cheers.memberId == SharedPreferenceManager.userId ||
userId == SharedPreferenceManager.userId
) {
binding.etContentModify.setText(cheers.content)
binding.ivMenu.visibility = View.VISIBLE
binding.ivMenu.setOnClickListener { binding.ivMenu.setOnClickListener {
showOptionMenu( showOptionMenu(
context, context,
binding.ivMenu, binding.ivMenu,
cheersId = cheers.cheersId cheersId = cheers.cheersId,
memberId = cheers.memberId,
creatorId = userId,
onClickModify = {
binding.rlContentModify.visibility = View.VISIBLE
binding.tvContent.visibility = View.GONE
}
) )
} }
binding.tvModify.setOnClickListener {
binding.rlContentModify.visibility = View.GONE
binding.tvContent.visibility = View.VISIBLE
modifyCheers(cheers.cheersId, binding.etContentModify.text.toString())
}
} else {
binding.ivMenu.visibility = View.GONE
}
if (cheers.replyList.isNotEmpty()) { if (cheers.replyList.isNotEmpty()) {
binding.tvWriteReply.visibility = View.GONE binding.tvWriteReply.visibility = View.GONE
binding.llCheerReply.visibility = View.VISIBLE binding.llCheerReply.visibility = View.VISIBLE
@@ -106,16 +129,38 @@ class UserProfileCheersAdapter(
override fun getItemCount() = items.count() override fun getItemCount() = items.count()
private fun showOptionMenu(context: Context, v: View, cheersId: Long) { private fun showOptionMenu(
context: Context,
v: View,
cheersId: Long,
memberId: Long,
creatorId: Long,
onClickModify: () -> Unit
) {
val popup = PopupMenu(context, v) val popup = PopupMenu(context, v)
val inflater = popup.menuInflater val inflater = popup.menuInflater
if (memberId == SharedPreferenceManager.userId) {
inflater.inflate(R.menu.review_option_menu3, popup.menu)
} else if (creatorId == SharedPreferenceManager.userId) {
inflater.inflate(R.menu.review_option_menu2, popup.menu)
} else {
inflater.inflate(R.menu.review_option_menu, popup.menu) inflater.inflate(R.menu.review_option_menu, popup.menu)
}
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_review_report -> { R.id.menu_review_report -> {
onClickReport(cheersId) onClickReport(cheersId)
} }
R.id.menu_review_modify -> {
onClickModify()
}
R.id.menu_review_delete -> {
onClickDelete(cheersId)
}
} }
true true

View File

@@ -0,0 +1,17 @@
package kr.co.vividnext.sodalive.explorer.profile.donation
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.explorer.profile.UserDonationRankingResponse
data class GetDonationAllResponse(
@SerializedName("accumulatedCansToday")
val accumulatedCansToday: Int,
@SerializedName("accumulatedCansLastWeek")
val accumulatedCansLastWeek: Int,
@SerializedName("accumulatedCansThisMonth")
val accumulatedCansThisMonth: Int,
@SerializedName("totalCount")
val totalCount: Int,
@SerializedName("userDonationRanking")
val userDonationRanking: List<UserDonationRankingResponse>,
)

View File

@@ -22,7 +22,7 @@ class UserProfileDonationAdapter : RecyclerView.Adapter<UserProfileDonationAdapt
binding.ivProfile.load(item.profileImage) { binding.ivProfile.load(item.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }

View File

@@ -0,0 +1,154 @@
package kr.co.vividnext.sodalive.explorer.profile.donation
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
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.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ItemUserProfileDonationAllBinding
import kr.co.vividnext.sodalive.explorer.profile.UserDonationRankingResponse
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
class UserProfileDonationAllAdapter(private val userId: Long) :
RecyclerView.Adapter<UserProfileDonationAllAdapter.ViewHolder>() {
val items = mutableListOf<UserDonationRankingResponse>()
inner class ViewHolder(
private val context: Context,
private val binding: ItemUserProfileDonationAllBinding
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(item: UserDonationRankingResponse, position: Int) {
binding.tvRank.text = "${position + 1}"
binding.tvNickname.text = item.nickname
binding.ivProfile.load(item.profileImage) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(CircleCropTransformation())
}
if (item.donationCan > 0 && SharedPreferenceManager.userId == userId) {
binding.tvTotalDonationCan.visibility = View.VISIBLE
binding.tvTotalDonationCan.text = "${item.donationCan.moneyFormat()}"
}
val lp = binding.rlDonationRanking.layoutParams as RelativeLayout.LayoutParams
when (position) {
0 -> {
binding.ivBg.setImageResource(R.drawable.bg_circle_ffdc00_ffb600)
binding.ivBg.visibility = View.VISIBLE
binding.ivCrown.setImageResource(R.drawable.ic_crown_1)
binding.ivCrown.visibility = View.VISIBLE
binding.rlDonationRankingRoot.setBackgroundResource(
if (items.size == 1) {
R.drawable.bg_round_corner_4_7_2b2635
} else {
R.drawable.bg_top_round_corner_4_7_2b2635
}
)
lp.setMargins(
20.dpToPx().toInt(),
20.dpToPx().toInt(),
20.dpToPx().toInt(),
if (items.size == 1) {
20.dpToPx().toInt()
} else {
13.3f.dpToPx().toInt()
}
)
binding.rlDonationRanking.layoutParams = lp
}
1 -> {
binding.ivBg.setImageResource(R.drawable.bg_circle_ffffff_9f9f9f)
binding.ivBg.visibility = View.VISIBLE
binding.ivCrown.setImageResource(R.drawable.ic_crown_2)
binding.ivCrown.visibility = View.VISIBLE
if (items.size == 2) {
binding.rlDonationRankingRoot.setBackgroundResource(
R.drawable.bg_bottom_round_corner_4_7_2b2635
)
} else {
binding.rlDonationRankingRoot.setBackgroundColor(
ContextCompat.getColor(context, R.color.color_2b2635)
)
}
lp.setMargins(
20.dpToPx().toInt(),
0,
20.dpToPx().toInt(),
if (items.size == 2) {
20.dpToPx().toInt()
} else {
13.3f.dpToPx().toInt()
}
)
binding.rlDonationRanking.layoutParams = lp
}
2 -> {
binding.ivBg.setImageResource(R.drawable.bg_circle_e6a77a_c67e4a)
binding.ivBg.visibility = View.VISIBLE
binding.ivCrown.setImageResource(R.drawable.ic_crown_3)
binding.ivCrown.visibility = View.VISIBLE
binding.rlDonationRankingRoot.setBackgroundResource(
R.drawable.bg_bottom_round_corner_4_7_2b2635
)
lp.setMargins(
20.dpToPx().toInt(),
0,
20.dpToPx().toInt(),
20.dpToPx().toInt()
)
binding.rlDonationRanking.layoutParams = lp
}
else -> {
binding.ivBg.setImageResource(0)
binding.ivBg.visibility = View.GONE
binding.ivCrown.visibility = View.GONE
binding.rlDonationRanking.setBackgroundResource(0)
binding.rlDonationRanking.background = null
binding.rlDonationRankingRoot.setBackgroundResource(0)
binding.rlDonationRankingRoot.background = null
lp.setMargins(0, 0, 0, 0)
binding.rlDonationRanking.layoutParams = lp
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
parent.context,
ItemUserProfileDonationAllBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position], position)
}
override fun getItemCount() = items.count()
}

View File

@@ -1,10 +1,149 @@
package kr.co.vividnext.sodalive.explorer.profile.donation package kr.co.vividnext.sodalive.explorer.profile.donation
import android.annotation.SuppressLint
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.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.ActivityUserProfileLiveAllBinding import kr.co.vividnext.sodalive.databinding.ActivityUserProfileLiveAllBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
import org.koin.android.ext.android.inject
class UserProfileDonationAllViewActivity : BaseActivity<ActivityUserProfileLiveAllBinding>( class UserProfileDonationAllViewActivity : BaseActivity<ActivityUserProfileLiveAllBinding>(
ActivityUserProfileLiveAllBinding::inflate ActivityUserProfileLiveAllBinding::inflate
) { ) {
override fun setupView() {}
private val viewModel: UserProfileDonationAllViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var adapter: UserProfileDonationAllAdapter
private var userId: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
userId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
super.onCreate(savedInstanceState)
if (userId > 0) {
bindData()
viewModel.getCreatorProfileDonationRanking(userId)
} else {
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
finish()
}
}
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.text = "후원랭킹 전체보기"
binding.toolbar.tvBack.setOnClickListener { finish() }
setupDonationRankingView()
}
private fun setupDonationRankingView() {
val recyclerView = binding.rvLiveAll
adapter = UserProfileDonationAllAdapter(userId = userId)
recyclerView.layoutManager = LinearLayoutManager(
applicationContext,
LinearLayoutManager.VERTICAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.left = 20.dpToPx().toInt()
outRect.right = 20.dpToPx().toInt()
when (parent.getChildAdapterPosition(view)) {
0, 1, 2 -> {
outRect.top = 0
outRect.bottom = 0
outRect.left = 13.3f.dpToPx().toInt()
outRect.right = 13.3f.dpToPx().toInt()
}
3 -> {
outRect.top = 20.dpToPx().toInt()
outRect.bottom = 6.7f.dpToPx().toInt()
}
else -> {
outRect.top = 6.7f.dpToPx().toInt()
outRect.bottom = 6.7f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = adapter
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lastVisiblePosition = (recyclerView.layoutManager as LinearLayoutManager)
.findLastVisibleItemPosition()
val itemTotalCount = adapter.itemCount - 1
if (itemTotalCount > 0 && lastVisiblePosition == itemTotalCount) {
viewModel.getCreatorProfileDonationRanking(userId)
}
}
})
binding.swipeRefreshLayout.setOnRefreshListener {
adapter.items.clear()
viewModel.refresh(userId)
binding.swipeRefreshLayout.isRefreshing = false
}
if (SharedPreferenceManager.userId == userId) {
binding.llTotal.visibility = View.VISIBLE
} else {
binding.llTotal.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.donationLiveData.observe(this) {
binding.tvCanToday.text = it.accumulatedCansToday.moneyFormat()
binding.tvCanLastWeek.text = it.accumulatedCansLastWeek.moneyFormat()
binding.tvCanThisMonth.text = it.accumulatedCansThisMonth.moneyFormat()
binding.tvTotalCount.text = "${it.totalCount}"
adapter.items.addAll(it.userDonationRanking)
adapter.notifyDataSetChanged()
}
}
} }

View File

@@ -0,0 +1,81 @@
package kr.co.vividnext.sodalive.explorer.profile.donation
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
class UserProfileDonationAllViewModel(
private val repository: ExplorerRepository
) : 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 _donationLiveData = MutableLiveData<GetDonationAllResponse>()
val donationLiveData: LiveData<GetDonationAllResponse>
get() = _donationLiveData
private var isLast = false
private var page = 1
private val size = 10
fun getCreatorProfileDonationRanking(userId: Long) {
if (!_isLoading.value!! && !isLast) {
_isLoading.value = true
compositeDisposable.add(
repository.getCreatorProfileDonationRanking(
id = userId,
page = page,
size = size,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
if (it.data.userDonationRanking.isNotEmpty()) {
page += 1
_donationLiveData.postValue(it.data!!)
} else {
isLast = true
}
} 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 refresh(userId: Long) {
page = 1
isLast = false
getCreatorProfileDonationRanking(userId)
}
}

View File

@@ -1,10 +1,231 @@
package kr.co.vividnext.sodalive.explorer.profile.fantalk package kr.co.vividnext.sodalive.explorer.profile.fantalk
import android.annotation.SuppressLint
import android.app.Service
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 kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivityUserProfileFantalkAllBinding import kr.co.vividnext.sodalive.databinding.ActivityUserProfileFantalkAllBinding
import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.report.CheersReportDialog
import org.koin.android.ext.android.inject
class UserProfileFantalkAllViewActivity : BaseActivity<ActivityUserProfileFantalkAllBinding>( class UserProfileFantalkAllViewActivity : BaseActivity<ActivityUserProfileFantalkAllBinding>(
ActivityUserProfileFantalkAllBinding::inflate ActivityUserProfileFantalkAllBinding::inflate
) { ) {
override fun setupView() {} private val viewModel: UserProfileFantalkAllViewModel by inject()
private lateinit var imm: InputMethodManager
private lateinit var loadingDialog: LoadingDialog
private lateinit var cheersAdapter: UserProfileCheersAdapter
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
if (userId > 0) {
bindData()
viewModel.getCheersList(creatorId = userId)
} else {
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
finish()
}
}
@SuppressLint("SetTextI18n")
override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater)
binding.toolbar.tvBack.text = "팬 Talk 전체보기"
binding.toolbar.tvBack.setOnClickListener { finish() }
setupCheersView()
}
private fun setupCheersView() {
binding.ivSend.setOnClickListener {
hideKeyboard {
viewModel.writeCheers(
creatorId = userId,
cheersContent = binding.etCheer.text.toString()
)
}
}
val rvCheers = binding.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
)
}
},
modifyCheers = { cheersId, content ->
hideKeyboard {
viewModel.modifyCheers(
cheersId = cheersId,
creatorId = userId,
cheersContent = content,
)
}
},
onClickReport = { showCheersReportPopup(it) },
onClickDelete = {
SodaDialog(
activity = this@UserProfileFantalkAllViewActivity,
layoutInflater = layoutInflater,
title = "응원글 삭제",
desc = "삭제하시겠습니까?",
confirmButtonTitle = "삭제",
confirmButtonClick = {
viewModel.modifyCheers(
cheersId = it,
creatorId = userId,
isActive = false
)
},
cancelButtonTitle = "취소",
cancelButtonClick = {}
).show(screenWidth)
}
)
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
rvCheers.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val lastVisiblePosition = (recyclerView.layoutManager as LinearLayoutManager)
.findLastVisibleItemPosition()
val itemTotalCount = cheersAdapter.itemCount - 1
if (itemTotalCount > 0 && lastVisiblePosition == itemTotalCount) {
viewModel.getCheersList(userId)
}
}
})
}
private fun hideKeyboard(onAfterExecute: () -> Unit) {
handler.postDelayed({
imm.hideSoftInputFromWindow(
window.decorView.applicationWindowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
onAfterExecute()
}, 100)
}
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)
}
@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.cheersLiveData.observe(this) {
binding.etCheer.setText("")
binding.tvCheersCount.text = it.totalCount.toString()
binding.tvCheersCount.requestLayout()
cheersAdapter.items.addAll(it.cheers)
cheersAdapter.notifyDataSetChanged()
if (cheersAdapter.itemCount <= 0) {
binding.rvCheers.visibility = View.GONE
binding.tvNoCheers.visibility = View.VISIBLE
} else {
binding.rvCheers.visibility = View.VISIBLE
binding.tvNoCheers.visibility = View.GONE
}
binding.rvCheers.requestLayout()
}
}
} }

View File

@@ -0,0 +1,206 @@
package kr.co.vividnext.sodalive.explorer.profile.fantalk
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.explorer.profile.GetCheersResponse
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
import kr.co.vividnext.sodalive.report.ReportRepository
import kr.co.vividnext.sodalive.report.ReportRequest
import kr.co.vividnext.sodalive.report.ReportType
class UserProfileFantalkAllViewModel(
private val repository: ExplorerRepository,
private val reportRepository: ReportRepository
) : BaseViewModel() {
var cheersPage = 1
val pageSize = 10
private var isCheersLast = false
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _cheersLiveData = MutableLiveData<GetCheersResponse>()
val cheersLiveData: LiveData<GetCheersResponse>
get() = _cheersLiveData
fun getCheersList(creatorId: Long) {
if (!isCheersLast && !_isLoading.value!!) {
_isLoading.value = true
compositeDisposable.add(
repository.getCreatorProfileCheers(
creatorId = creatorId,
page = cheersPage,
size = pageSize,
token = "Bearer ${SharedPreferenceManager.token}"
).subscribe(
{
if (it.success && it.data != null) {
_cheersLiveData.postValue(it.data!!)
if (it.data.cheers.isNotEmpty()) {
cheersPage += 1
} else {
isCheersLast = true
}
} 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}
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 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) {
isCheersLast = false
cheersPage = 1
getCheersList(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? = null,
isActive: Boolean? = null
) {
if (cheersContent == null && isActive == null) {
_toastLiveData.postValue("변경사항이 없습니다.")
return
}
if (cheersContent != null && cheersContent.isBlank()) {
_toastLiveData.postValue("내용을 입력하세요")
return
}
_isLoading.value = true
val request = PutModifyCheersRequest(cheersId = cheersId)
if (cheersContent != null) {
request.content = cheersContent
}
if (isActive != null) {
request.isActive = isActive
}
compositeDisposable.add(
repository.modifyCheers(
request = request,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
isCheersLast = false
cheersPage = 1
getCheersList(creatorId)
} else {
val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
_toastLiveData.postValue(message)
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@@ -24,7 +24,7 @@ class UserFollowerListAdapter(
binding.tvNickname.text = item.nickname binding.tvNickname.text = item.nickname
binding.ivProfile.load(item.profileImage) { binding.ivProfile.load(item.profileImage) {
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
crossfade(true) crossfade(true)
} }

View File

@@ -56,9 +56,9 @@ class SodaFirebaseMessagingService : FirebaseMessagingService() {
intent.putExtra(Constants.EXTRA_ROOM_ID, roomId.toLong()) intent.putExtra(Constants.EXTRA_ROOM_ID, roomId.toLong())
} }
val socdocId = messageData["message_id"] val messageId = messageData["message_id"]
if (socdocId != null) { if (messageId != null) {
intent.putExtra(Constants.EXTRA_MESSAGE_ID, socdocId.toLong()) intent.putExtra(Constants.EXTRA_MESSAGE_ID, messageId.toLong())
} }
val audioContentId = messageData["content_id"] val audioContentId = messageData["content_id"]
@@ -66,6 +66,11 @@ class SodaFirebaseMessagingService : FirebaseMessagingService() {
intent.putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId.toLong()) intent.putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId.toLong())
} }
val channelId = messageData["channel_id"]
if (channelId != null) {
intent.putExtra(Constants.EXTRA_USER_ID, channelId.toLong())
}
val pendingIntent = val pendingIntent =
PendingIntent.getActivity( PendingIntent.getActivity(
this, this,
@@ -75,7 +80,7 @@ class SodaFirebaseMessagingService : FirebaseMessagingService() {
) )
val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId) val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.drawable.ic_notification)
.setContentTitle(messageData["title"]) .setContentTitle(messageData["title"])
.setContentText(messageData["message"]) .setContentText(messageData["message"])
.setSound(defaultSoundUri) .setSound(defaultSoundUri)

View File

@@ -14,8 +14,8 @@ data class GetRoomListResponse(
@SerializedName("price") val price: Int, @SerializedName("price") val price: Int,
@SerializedName("tags") val tags: List<String>, @SerializedName("tags") val tags: List<String>,
@SerializedName("channelName") val channelName: String?, @SerializedName("channelName") val channelName: String?,
@SerializedName("managerNickname") val managerNickname: String, @SerializedName("creatorNickname") val creatorNickname: String,
@SerializedName("managerId") val managerId: Long, @SerializedName("creatorId") val creatorId: Long,
@SerializedName("isReservation") val isReservation: Boolean, @SerializedName("isReservation") val isReservation: Boolean,
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean @SerializedName("isPrivateRoom") val isPrivateRoom: Boolean
) )

View File

@@ -9,7 +9,6 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import android.webkit.URLUtil
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
@@ -22,7 +21,6 @@ import com.zhpan.indicator.enums.IndicatorSlideMode
import com.zhpan.indicator.enums.IndicatorStyle import com.zhpan.indicator.enums.IndicatorStyle
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
@@ -48,6 +46,7 @@ import kr.co.vividnext.sodalive.live.room.dialog.LiveCancelDialog
import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog
import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog
import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditActivity import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditActivity
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.notification.MemberRole import kr.co.vividnext.sodalive.settings.notification.MemberRole
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -95,25 +94,6 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
message = "라이브를 불러오고 있습니다." message = "라이브를 불러오고 있습니다."
viewModel.getSummary() viewModel.getSummary()
try {
val roomId = requireArguments().getLong(Constants.EXTRA_ROOM_ID)
val channelId = requireArguments().getLong(Constants.EXTRA_USER_ID)
val audioContentId = requireArguments().getLong(Constants.EXTRA_AUDIO_CONTENT_ID)
if (roomId > 0) {
enterLiveRoom(roomId)
} else if (channelId > 0) {
val nextIntent = Intent(requireContext(), UserProfileActivity::class.java)
nextIntent.putExtra(Constants.EXTRA_USER_ID, channelId)
startActivity(nextIntent)
} else if (audioContentId > 0) {
val nextIntent = Intent(requireContext(), AudioContentDetailActivity::class.java)
nextIntent.putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
startActivity(nextIntent)
}
} catch (_: IllegalStateException) {
}
} }
private fun setupView() { private fun setupView() {
@@ -144,17 +124,6 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
} }
binding.swipeRefreshLayout.setOnRefreshListener { refreshSummary() } binding.swipeRefreshLayout.setOnRefreshListener { refreshSummary() }
val ivHowToUseLp = binding.ivHowToUse.layoutParams as LinearLayout.LayoutParams
ivHowToUseLp.width = screenWidth
ivHowToUseLp.height = (200 * screenWidth) / 1080
binding.ivHowToUse.layoutParams = ivHowToUseLp
binding.ivHowToUse.setOnClickListener {
val url = "https://blog.naver.com/yozmlive"
if (URLUtil.isValidUrl(url)) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}
} }
private fun refreshSummary() { private fun refreshSummary() {
@@ -186,7 +155,7 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
.layoutParams = layoutParams .layoutParams = layoutParams
binding.layoutRecommendLive.pager.apply { binding.layoutRecommendLive.pager.apply {
adapter = RecommendLiveAdapter(pagerWidth.roundToInt(), pagerHeight) { adapter = RecommendLiveAdapter(requireContext(), pagerWidth.roundToInt(), pagerHeight) {
startActivity( startActivity(
Intent(requireContext(), UserProfileActivity::class.java).apply { Intent(requireContext(), UserProfileActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, it) putExtra(Constants.EXTRA_USER_ID, it)
@@ -373,11 +342,6 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
recyclerView.visibility = View.GONE recyclerView.visibility = View.GONE
binding.layoutLiveNow.tvAllView.visibility = View.GONE binding.layoutLiveNow.tvAllView.visibility = View.GONE
binding.layoutLiveNow.llNoItems.visibility = View.VISIBLE binding.layoutLiveNow.llNoItems.visibility = View.VISIBLE
binding.layoutLiveNow.tvMakeRoom.setOnClickListener {
val intent = Intent(requireContext(), LiveRoomCreateActivity::class.java)
activityResultLauncher.launch(intent)
}
recyclerView.requestLayout() recyclerView.requestLayout()
binding.layoutLiveNow.llNoItems.requestLayout() binding.layoutLiveNow.llNoItems.requestLayout()
} else { } else {
@@ -460,12 +424,6 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
recyclerView.visibility = View.GONE recyclerView.visibility = View.GONE
binding.layoutLiveReservation.tvAllView.visibility = View.GONE binding.layoutLiveReservation.tvAllView.visibility = View.GONE
binding.layoutLiveReservation.llNoItems.visibility = View.VISIBLE binding.layoutLiveReservation.llNoItems.visibility = View.VISIBLE
binding.layoutLiveReservation.tvMakeRoom.setOnClickListener {
val intent = Intent(requireContext(), LiveRoomCreateActivity::class.java)
intent.putExtra(Constants.EXTRA_LIVE_TIME_NOW, false)
activityResultLauncher.launch(intent)
}
recyclerView.requestLayout() recyclerView.requestLayout()
binding.layoutLiveReservation.llNoItems.requestLayout() binding.layoutLiveReservation.llNoItems.requestLayout()
} else { } else {
@@ -496,7 +454,20 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
binding.eventBannerSlider.layoutParams = imageSliderLp binding.eventBannerSlider.layoutParams = imageSliderLp
binding.eventBannerSlider.apply { binding.eventBannerSlider.apply {
adapter = EventBannerAdapter(requireContext()) {} as BaseBannerAdapter<Any> adapter = EventBannerAdapter(requireContext()) {
if (it.detailImageUrl != null) {
val intent = Intent(requireActivity(), EventDetailActivity::class.java)
intent.putExtra(Constants.EXTRA_EVENT, it)
startActivity(intent)
} else if (!it.link.isNullOrBlank()) {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(it.link)
)
)
}
} as BaseBannerAdapter<Any>
setLifecycleRegistry(lifecycle) setLifecycleRegistry(lifecycle)
setScrollDuration(800) setScrollDuration(800)
}.create() }.create()

View File

@@ -25,10 +25,10 @@ class LiveNowAdapter(
fun bind(item: GetRoomListResponse) { fun bind(item: GetRoomListResponse) {
binding.ivCover.load(item.coverImageUrl) { binding.ivCover.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4.7f.dpToPx())) transformations(RoundedCornersTransformation(4.7f.dpToPx()))
} }
binding.tvManager.text = item.managerNickname binding.tvManager.text = item.creatorNickname
binding.tvNumberOfMembers.text = "${item.numberOfParticipate}" binding.tvNumberOfMembers.text = "${item.numberOfParticipate}"
binding.ivLock.visibility = if (item.isPrivateRoom) { binding.ivLock.visibility = if (item.isPrivateRoom) {
View.VISIBLE View.VISIBLE
@@ -44,12 +44,6 @@ class LiveNowAdapter(
binding.tvPrice.setBackgroundResource(R.drawable.bg_round_corner_10_643bc8) binding.tvPrice.setBackgroundResource(R.drawable.bg_round_corner_10_643bc8)
} }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
binding.root.setOnClickListener { onClick(item) } binding.root.setOnClickListener { onClick(item) }
} }
} }

View File

@@ -32,7 +32,7 @@ class LiveNowAllAdapter(
placeholder(R.drawable.bg_placeholder) placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(4.7f.dpToPx())) transformations(RoundedCornersTransformation(4.7f.dpToPx()))
} }
binding.tvNickname.text = item.managerNickname binding.tvNickname.text = item.creatorNickname
binding.tvTitle.text = item.title binding.tvTitle.text = item.title
binding.tvTotal.text = "/${item.numberOfPeople}" binding.tvTotal.text = "/${item.numberOfPeople}"
binding.tvNumberOfParticipants.text = item.numberOfParticipate.toString() binding.tvNumberOfParticipants.text = item.numberOfParticipate.toString()
@@ -68,17 +68,11 @@ class LiveNowAllAdapter(
binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds( binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds(
0, 0,
0, 0,
R.drawable.ic_coin_w, R.drawable.ic_can,
0 0
) )
} }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
binding.root.setOnClickListener { onClick(item) } binding.root.setOnClickListener { onClick(item) }
} }
} }

View File

@@ -1,15 +1,19 @@
package kr.co.vividnext.sodalive.live.recommend package kr.co.vividnext.sodalive.live.recommend
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import coil.load import com.bumptech.glide.Glide
import coil.transform.RoundedCornersTransformation import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.zhpan.bannerview.BaseBannerAdapter import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.bannerview.BaseViewHolder import com.zhpan.bannerview.BaseViewHolder
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.extensions.dpToPx
class RecommendLiveAdapter( class RecommendLiveAdapter(
private val context: Context,
private val itemWidth: Int, private val itemWidth: Int,
private val itemHeight: Int, private val itemHeight: Int,
private val onClick: (Long) -> Unit private val onClick: (Long) -> Unit
@@ -26,12 +30,20 @@ class RecommendLiveAdapter(
layoutParams.width = itemWidth layoutParams.width = itemWidth
layoutParams.height = itemHeight layoutParams.height = itemHeight
ivRecommendLive.load(data.imageUrl) { Glide
crossfade(true) .with(context)
placeholder(R.drawable.ic_logo) .asBitmap()
transformations(RoundedCornersTransformation(5.3f.dpToPx())) .load(data.imageUrl)
} .into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
ivRecommendLive.setImageBitmap(resource)
ivRecommendLive.layoutParams = layoutParams ivRecommendLive.layoutParams = layoutParams
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
ivRecommendLive.setOnClickListener { onClick(data.creatorId) } ivRecommendLive.setOnClickListener { onClick(data.creatorId) }
} }

View File

@@ -54,7 +54,7 @@ class LiveRecommendChannelAdapter(
fun bind(item: GetRecommendChannelResponse) { fun bind(item: GetRecommendChannelResponse) {
binding.ivRecommendChannel.load(item.profileImageUrl) { binding.ivRecommendChannel.load(item.profileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(30f.dpToPx())) transformations(RoundedCornersTransformation(30f.dpToPx()))
} }
binding.tvRecommendChannelNickname.text = item.nickname binding.tvRecommendChannelNickname.text = item.nickname

View File

@@ -61,7 +61,7 @@ class LiveReservationAdapter(
} }
private fun isMyLive(item: GetRoomListResponse) = private fun isMyLive(item: GetRoomListResponse) =
item.managerId == SharedPreferenceManager.userId && isMain item.creatorId == SharedPreferenceManager.userId && isMain
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun clear() { fun clear() {
@@ -76,11 +76,11 @@ class LiveReservationAdapter(
fun bind(item: GetRoomListResponse) { fun bind(item: GetRoomListResponse) {
binding.ivCover.load(item.coverImageUrl) { binding.ivCover.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4.7f.dpToPx())) transformations(RoundedCornersTransformation(4.7f.dpToPx()))
} }
binding.tvDate.text = item.beginDateTime binding.tvDate.text = item.beginDateTime
binding.tvNickname.text = item.managerNickname binding.tvNickname.text = item.creatorNickname
binding.tvTitle.text = item.title binding.tvTitle.text = item.title
binding.root.setOnClickListener { onClick(item) } binding.root.setOnClickListener { onClick(item) }
binding.ivLock.visibility = if (item.isPrivateRoom) { binding.ivLock.visibility = if (item.isPrivateRoom) {
@@ -102,12 +102,6 @@ class LiveReservationAdapter(
"${item.price.moneyFormat()}" "${item.price.moneyFormat()}"
} }
} }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
} }
} }
@@ -122,11 +116,11 @@ class LiveReservationAdapter(
} }
binding.ivCover.load(item.coverImageUrl) { binding.ivCover.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4f.dpToPx())) transformations(RoundedCornersTransformation(4f.dpToPx()))
} }
binding.tvDate.text = item.beginDateTime binding.tvDate.text = item.beginDateTime
binding.tvNickname.text = item.managerNickname binding.tvNickname.text = item.creatorNickname
binding.tvTitle.text = item.title binding.tvTitle.text = item.title
binding.root.setOnClickListener { onClick(item) } binding.root.setOnClickListener { onClick(item) }
@@ -135,12 +129,6 @@ class LiveReservationAdapter(
} else { } else {
View.GONE View.GONE
} }
binding.iv19.visibility = if (item.isAdult) {
View.VISIBLE
} else {
View.GONE
}
} }
} }
} }

View File

@@ -13,5 +13,5 @@ data class MakeLiveReservationResponse(
@SerializedName("price") val price: String, @SerializedName("price") val price: String,
@SerializedName("haveCan") val haveCan: Int, @SerializedName("haveCan") val haveCan: Int,
@SerializedName("useCan") val useCan: Int, @SerializedName("useCan") val useCan: Int,
@SerializedName("remainingCoin") val remainingCoin: Int @SerializedName("remainingCan") val remainingCan: Int
) : Parcelable ) : Parcelable

View File

@@ -34,9 +34,9 @@ class LiveReservationCompleteActivity : BaseActivity<ActivityLiveReservationComp
binding.tvDate.text = response.beginDateString binding.tvDate.text = response.beginDateString
binding.tvPrice.text = response.price binding.tvPrice.text = response.price
binding.tvHaveCoin.text = "${response.haveCan}" binding.tvHaveCan.text = "${response.haveCan}"
binding.tvUseCoin.text = "${response.useCan}" binding.tvUseCan.text = "${response.useCan}"
binding.tvRemainingCoin.text = "${response.remainingCoin}" binding.tvRemainingCan.text = "${response.remainingCan}"
binding.tvGoHome.setOnClickListener { binding.tvGoHome.setOnClickListener {
val intent = Intent(applicationContext, MainActivity::class.java) val intent = Intent(applicationContext, MainActivity::class.java)

View File

@@ -113,7 +113,7 @@ class LiveReservationCancelActivity : BaseActivity<ActivityLiveReservationCancel
startActivity(Intent(applicationContext, MainActivity::class.java)) startActivity(Intent(applicationContext, MainActivity::class.java))
} }
binding.tvCheckCoinStatus.setOnClickListener { binding.tvCheckCanStatus.setOnClickListener {
startActivity(Intent(applicationContext, CanStatusActivity::class.java)) startActivity(Intent(applicationContext, CanStatusActivity::class.java))
} }
@@ -147,15 +147,15 @@ class LiveReservationCancelActivity : BaseActivity<ActivityLiveReservationCancel
binding.tvTitle.text = response.title binding.tvTitle.text = response.title
binding.ivProfile.load(response.coverImageUrl) { binding.ivProfile.load(response.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4.7f.dpToPx())) transformations(RoundedCornersTransformation(4.7f.dpToPx()))
} }
if (response.price > 0) { if (response.price > 0) {
binding.tvCheckCoinStatus.visibility = View.VISIBLE binding.tvCheckCanStatus.visibility = View.VISIBLE
binding.tvPrice.text = "${response.price}코인" binding.tvPrice.text = "${response.price}"
} else { } else {
binding.tvCheckCoinStatus.visibility = View.GONE binding.tvCheckCanStatus.visibility = View.GONE
binding.tvPrice.text = "무료" binding.tvPrice.text = "무료"
} }
} }

View File

@@ -26,12 +26,12 @@ class LiveReservationStatusAdapter(
binding.tvTitle.text = item.title binding.tvTitle.text = item.title
binding.ivProfile.load(item.coverImageUrl) { binding.ivProfile.load(item.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(4.7f.dpToPx())) transformations(RoundedCornersTransformation(4.7f.dpToPx()))
} }
binding.tvPrice.text = if (item.price > 0) { binding.tvPrice.text = if (item.price > 0) {
"${item.price}코인" "${item.price}"
} else { } else {
"무료" "무료"
} }

View File

@@ -106,7 +106,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private var isMicrophoneMute = false private var isMicrophoneMute = false
private var isSpeaker = false private var isSpeaker = false
private var isSpeakerFold = false private var isSpeakerFold = false
private var isAvailableDonation = false
private val onBackPressedCallback = object : OnBackPressedCallback(true) { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
@@ -210,7 +209,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
}, },
onClickInviteSpeaker = { memberId -> onClickInviteSpeaker = { memberId ->
if (speakerListAdapter.itemCount <= 9) { if (speakerListAdapter.itemCount <= 4) {
inviteSpeaker(memberId) inviteSpeaker(memberId)
} else { } else {
showToast("스피커 정원이 초과했습니다.") showToast("스피커 정원이 초과했습니다.")
@@ -286,7 +285,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
}, },
onClickInviteSpeaker = { onClickInviteSpeaker = {
if (speakerListAdapter.itemCount <= 9) { if (speakerListAdapter.itemCount <= 4) {
inviteSpeaker(it) inviteSpeaker(it)
} else { } else {
showToast("스피커 정원이 초과했습니다.") showToast("스피커 정원이 초과했습니다.")
@@ -559,21 +558,27 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
viewModel.roomInfoLiveData.observe(this) { response -> viewModel.roomInfoLiveData.observe(this) { response ->
binding.tv19.visibility = if (response.isAdult) {
View.VISIBLE
} else {
View.GONE
}
binding.tvTitle.text = response.title binding.tvTitle.text = response.title
binding.ivCover.load(response.coverImageUrl) { binding.ivCover.load(response.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
} }
isAvailableDonation = response.isAvailableDonation binding.flDonation.visibility =
binding.flDonation.visibility = if (response.isAvailableDonation) { if (response.creatorId != SharedPreferenceManager.userId) {
View.VISIBLE View.VISIBLE
} else { } else {
View.GONE View.GONE
} }
if ( if (
response.managerId == SharedPreferenceManager.userId && response.creatorId == SharedPreferenceManager.userId &&
SharedPreferenceManager.role == MemberRole.CREATOR.name SharedPreferenceManager.role == MemberRole.CREATOR.name
) { ) {
binding.flDonationMessageList.visibility = View.VISIBLE binding.flDonationMessageList.visibility = View.VISIBLE
@@ -605,10 +610,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.flDonationMessageList.visibility = View.GONE binding.flDonationMessageList.visibility = View.GONE
} }
speakerListAdapter.managerId = response.managerId speakerListAdapter.managerId = response.creatorId
speakerListAdapter.updateList(response.speakerList) speakerListAdapter.updateList(response.speakerList)
if (response.managerId == SharedPreferenceManager.userId) { if (response.creatorId == SharedPreferenceManager.userId) {
binding.ivEdit.setOnClickListener { binding.ivEdit.setOnClickListener {
roomInfoEditDialog.setRoomInfo(response.title, response.notice) roomInfoEditDialog.setRoomInfo(response.title, response.notice)
roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl) roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl)
@@ -625,7 +630,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
if (newCoverImageUri != null) { if (newCoverImageUri != null) {
binding.ivCover.load(newCoverImageUri) { binding.ivCover.load(newCoverImageUri) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
} }
} }
@@ -676,27 +681,26 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.tvParticipate.text = "${response.participantsCount}" binding.tvParticipate.text = "${response.participantsCount}"
setNoticeAndClickableUrl(binding.tvNotice, response.notice) setNoticeAndClickableUrl(binding.tvNotice, response.notice)
binding.tvCreatorNickname.text = response.managerNickname binding.tvCreatorNickname.text = response.creatorNickname
binding.ivCreatorProfile.load(response.managerProfileUrl) { binding.ivCreatorProfile.load(response.creatorProfileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
binding.ivCreatorProfile.setOnClickListener { binding.ivCreatorProfile.setOnClickListener {
if (response.managerId != SharedPreferenceManager.userId) { if (response.creatorId != SharedPreferenceManager.userId) {
showLiveRoomUserProfileDialog(userId = response.managerId) showLiveRoomUserProfileDialog(userId = response.creatorId)
} }
} }
if (response.isAvailableDonation) { if (response.creatorId != SharedPreferenceManager.userId) {
binding.ivCreatorFollow.visibility = View.VISIBLE binding.ivCreatorFollow.visibility = View.VISIBLE
if (response.isFollowing) {
if (response.isFollowingManager) {
binding.ivCreatorFollow.setImageResource(R.drawable.btn_following) binding.ivCreatorFollow.setImageResource(R.drawable.btn_following)
binding.ivCreatorFollow.setOnClickListener { binding.ivCreatorFollow.setOnClickListener {
viewModel.creatorUnFollow( viewModel.creatorUnFollow(
creatorId = response.managerId, creatorId = response.creatorId,
roomId = roomId roomId = roomId
) )
} }
@@ -704,7 +708,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
binding.ivCreatorFollow.setImageResource(R.drawable.btn_follow) binding.ivCreatorFollow.setImageResource(R.drawable.btn_follow)
binding.ivCreatorFollow.setOnClickListener { binding.ivCreatorFollow.setOnClickListener {
viewModel.creatorFollow( viewModel.creatorFollow(
creatorId = response.managerId, creatorId = response.creatorId,
roomId = roomId roomId = roomId
) )
} }
@@ -1190,7 +1194,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
}, },
rtmChannelJoinSuccess = { rtmChannelJoinSuccess = {
if (userId == roomInfo.managerId) { if (userId == roomInfo.creatorId) {
setBroadcaster() setBroadcaster()
} else { } else {
setAudience() setAudience()

View File

@@ -118,7 +118,7 @@ class LiveRoomViewModel(
} }
fun getManagerNickname(): String { fun getManagerNickname(): String {
return roomInfoResponse.managerNickname return roomInfoResponse.creatorNickname
} }
fun setSpeaker(roomId: Long, userId: Long, onSuccess: () -> Unit) { fun setSpeaker(roomId: Long, userId: Long, onSuccess: () -> Unit) {
@@ -190,7 +190,7 @@ class LiveRoomViewModel(
getTotalDonationCan(roomId = roomId) getTotalDonationCan(roomId = roomId)
if (userId > 0) { if (userId > 0 && it.data.creatorId == SharedPreferenceManager.userId) {
val nickname = getUserNickname(userId) val nickname = getUserNickname(userId)
onSuccess(nickname) onSuccess(nickname)
} }
@@ -213,7 +213,7 @@ class LiveRoomViewModel(
} }
fun isEqualToHostId(memberId: Int): Boolean { fun isEqualToHostId(memberId: Int): Boolean {
return memberId == roomInfoResponse.managerId.toInt() return memberId == roomInfoResponse.creatorId.toInt()
} }
fun getMemberCan() { fun getMemberCan() {
@@ -241,11 +241,11 @@ class LiveRoomViewModel(
) { ) {
_isLoading.value = true _isLoading.value = true
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) { Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
link = Uri.parse("https://yozm.day/?room_id=$roomId") link = Uri.parse("https://sodalive.net/?room_id=$roomId")
domainUriPrefix = "https://yozm.page.link" domainUriPrefix = "https://sodalive.page.link"
androidParameters { } androidParameters { }
iosParameters("kr.co.vividnext.yozm") { iosParameters("kr.co.vividnext.sodalive") {
appStoreId = "1630284226" appStoreId = "6461721697"
} }
}.addOnSuccessListener { }.addOnSuccessListener {
val uri = it.shortLink val uri = it.shortLink

View File

@@ -160,7 +160,7 @@ data class LiveRoomNormalChat(
val itemBinding = binding as ItemLiveRoomChatBinding val itemBinding = binding as ItemLiveRoomChatBinding
itemBinding.ivProfile.load(profileUrl) { itemBinding.ivProfile.load(profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.3f.dpToPx())) transformations(RoundedCornersTransformation(23.3f.dpToPx()))
} }
@@ -233,7 +233,7 @@ data class LiveRoomNormalChat(
} else { } else {
itemBinding.llMessageBg.setBackgroundResource(R.drawable.bg_round_corner_3_3_99000000) itemBinding.llMessageBg.setBackgroundResource(R.drawable.bg_round_corner_3_3_99000000)
} }
itemBinding.ivCoin.visibility = View.GONE itemBinding.ivCan.visibility = View.GONE
itemBinding.tvDonationMessage.visibility = View.GONE itemBinding.tvDonationMessage.visibility = View.GONE
itemBinding.root.setBackgroundResource(0) itemBinding.root.setBackgroundResource(0)
itemBinding.root.setPadding(0) itemBinding.root.setPadding(0)
@@ -244,7 +244,7 @@ data class LiveRoomDonationChat(
@SerializedName("profileUrl") val profileUrl: String, @SerializedName("profileUrl") val profileUrl: String,
@SerializedName("nickname") val nickname: String, @SerializedName("nickname") val nickname: String,
@SerializedName("chat") val chat: String, @SerializedName("chat") val chat: String,
@SerializedName("coin") val coin: Int, @SerializedName("can") val can: Int,
@SerializedName("donationMessage") val donationMessage: String, @SerializedName("donationMessage") val donationMessage: String,
) : LiveRoomChat() { ) : LiveRoomChat() {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@@ -278,14 +278,14 @@ data class LiveRoomDonationChat(
itemBinding.ivProfile.load(profileUrl) { itemBinding.ivProfile.load(profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.3f.dpToPx())) transformations(RoundedCornersTransformation(23.3f.dpToPx()))
} }
itemBinding.tvChat.text = spChat itemBinding.tvChat.text = spChat
itemBinding.tvNickname.text = spNickname itemBinding.tvNickname.text = spNickname
itemBinding.ivProfile.setOnClickListener {} itemBinding.ivProfile.setOnClickListener {}
itemBinding.ivCoin.visibility = View.VISIBLE itemBinding.ivCan.visibility = View.VISIBLE
itemBinding.ivBg.visibility = View.GONE itemBinding.ivBg.visibility = View.GONE
itemBinding.ivCrown.visibility = View.GONE itemBinding.ivCrown.visibility = View.GONE
itemBinding.tvCreatorOrManager.visibility = View.GONE itemBinding.tvCreatorOrManager.visibility = View.GONE
@@ -302,27 +302,27 @@ data class LiveRoomDonationChat(
itemBinding.root.setBackgroundResource( itemBinding.root.setBackgroundResource(
when { when {
coin >= 100000 -> { can >= 100000 -> {
R.drawable.bg_round_corner_6_7_c25264 R.drawable.bg_round_corner_6_7_c25264
} }
coin >= 50000 -> { can >= 50000 -> {
R.drawable.bg_round_corner_6_7_e6d85e37 R.drawable.bg_round_corner_6_7_e6d85e37
} }
coin >= 10000 -> { can >= 10000 -> {
R.drawable.bg_round_corner_6_7_e6d38c38 R.drawable.bg_round_corner_6_7_e6d38c38
} }
coin >= 5000 -> { can >= 5000 -> {
R.drawable.bg_round_corner_6_7_e659548f R.drawable.bg_round_corner_6_7_e659548f
} }
coin >= 1000 -> { can >= 1000 -> {
R.drawable.bg_round_corner_6_7_e64d6aa4 R.drawable.bg_round_corner_6_7_e64d6aa4
} }
coin >= 500 -> { can >= 500 -> {
R.drawable.bg_round_corner_6_7_e62d7390 R.drawable.bg_round_corner_6_7_e62d7390
} }

View File

@@ -105,7 +105,7 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
binding.ivCover.background = null binding.ivCover.background = null
binding.ivCover.load(fileUri) { binding.ivCover.load(fileUri) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }
viewModel.coverImageUri = fileUri viewModel.coverImageUri = fileUri
@@ -282,7 +282,7 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
binding.ivCover.background = null binding.ivCover.background = null
binding.ivCover.load(it.coverImageUrl) { binding.ivCover.load(it.coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }
} }

View File

@@ -11,6 +11,7 @@ data class GetRoomDetailResponse(
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("notice") val notice: String, @SerializedName("notice") val notice: String,
@SerializedName("isPaid") val isPaid: Boolean, @SerializedName("isPaid") val isPaid: Boolean,
@SerializedName("isAdult") val isAdult: Boolean,
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean, @SerializedName("isPrivateRoom") val isPrivateRoom: Boolean,
@SerializedName("password") val password: Int?, @SerializedName("password") val password: Int?,
@SerializedName("tags") val tags: List<String>, @SerializedName("tags") val tags: List<String>,

View File

@@ -1,50 +0,0 @@
package kr.co.vividnext.sodalive.live.room.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.ItemLiveRoomDetailUserBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class LiveRoomDetailAdapter(
private val onClick: (GetRoomDetailUser) -> Unit
) : RecyclerView.Adapter<LiveRoomDetailAdapter.ViewHolder>() {
val items = mutableListOf<GetRoomDetailUser>()
inner class ViewHolder(
private val binding: ItemLiveRoomDetailUserBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetRoomDetailUser) {
binding.tvNickname.text = item.nickname
binding.ivProfile.load(item.profileImageUrl) {
crossfade(true)
placeholder(R.drawable.ic_logo)
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
}
binding.root.setOnClickListener { onClick(item) }
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return ViewHolder(
ItemLiveRoomDetailUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.count()
}

View File

@@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.live.room.detail
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -10,21 +9,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.URLUtil import android.webkit.URLUtil
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import coil.transform.RoundedCornersTransformation
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentLiveRoomDetailBinding import kr.co.vividnext.sodalive.databinding.FragmentLiveRoomDetailBinding
import kr.co.vividnext.sodalive.databinding.ItemLiveDetailUserSummaryBinding import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
class LiveRoomDetailFragment( class LiveRoomDetailFragment(
@@ -40,9 +35,6 @@ class LiveRoomDetailFragment(
private lateinit var binding: FragmentLiveRoomDetailBinding private lateinit var binding: FragmentLiveRoomDetailBinding
private var isAllProfileOpen = false
private lateinit var adapter: LiveRoomDetailAdapter
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var roomDetail: GetRoomDetailResponse private lateinit var roomDetail: GetRoomDetailResponse
@@ -65,58 +57,10 @@ class LiveRoomDetailFragment(
val behavior = BottomSheetBehavior.from<View>(bottomSheet!!) val behavior = BottomSheetBehavior.from<View>(bottomSheet!!)
behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED
setupAdapter()
bindData() bindData()
viewModel.getDetail(roomId) { dismiss() } viewModel.getDetail(roomId) { dismiss() }
binding.ivClose.setOnClickListener { dismiss() } binding.ivClose.setOnClickListener { dismiss() }
binding.tvOpenAllProfile.setOnClickListener {
isAllProfileOpen = !isAllProfileOpen
if (isAllProfileOpen) {
binding.llProfiles.visibility = View.GONE
binding.rvParticipate.visibility = View.VISIBLE
binding.tvParticipateExpression.visibility = View.VISIBLE
binding.tvOpenAllProfile.text = "닫기"
binding.tvOpenAllProfile.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_live_detail_top,
0,
0,
0
)
} else {
binding.llProfiles.visibility = View.VISIBLE
binding.rvParticipate.visibility = View.GONE
binding.tvParticipateExpression.visibility = View.GONE
binding.tvOpenAllProfile.text = "펼쳐보기"
binding.tvOpenAllProfile.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_live_detail_bottom,
0,
0,
0
)
}
}
}
private fun setupAdapter() {
val recyclerView = binding.rvParticipate
adapter = LiveRoomDetailAdapter {}
recyclerView.layoutManager = GridLayoutManager(requireContext(), 5)
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()
}
})
recyclerView.adapter = adapter
} }
private fun bindData() { private fun bindData() {
@@ -140,28 +84,26 @@ class LiveRoomDetailFragment(
@SuppressLint("SetTextI18n", "NotifyDataSetChanged") @SuppressLint("SetTextI18n", "NotifyDataSetChanged")
private fun setRoomDetail(response: GetRoomDetailResponse) { private fun setRoomDetail(response: GetRoomDetailResponse) {
binding.tvTitle.text = response.title binding.tv19.visibility = if (response.isAdult) {
binding.tvDate.text = response.beginDateTime
binding.tvParticipate.text = response.numberOfParticipants.toString()
binding.tvTotal.text = "/${response.numberOfParticipantsTotal}"
binding.tvOpenAllProfile.visibility = if (response.numberOfParticipants <= 0) {
View.GONE
} else {
View.VISIBLE View.VISIBLE
} else {
View.GONE
} }
binding.tvTitle.text = response.title
binding.tvDate.text = response.beginDateTime
if (response.price > 0) { if (response.price > 0) {
binding.tvCoin.text = response.price.toString() binding.tvCan.text = response.price.toString()
binding.tvCoin.setCompoundDrawablesWithIntrinsicBounds( binding.tvCan.setCompoundDrawablesWithIntrinsicBounds(
0, 0,
0, 0,
R.drawable.ic_can, R.drawable.ic_can,
0 0
) )
} else { } else {
binding.tvCoin.text = "무료" binding.tvCan.text = "무료"
binding.tvCoin.setCompoundDrawablesWithIntrinsicBounds( binding.tvCan.setCompoundDrawablesWithIntrinsicBounds(
0, 0,
0, 0,
0, 0,
@@ -170,13 +112,14 @@ class LiveRoomDetailFragment(
} }
setManagerProfile(manager = response.manager) setManagerProfile(manager = response.manager)
setParticipantUserSummary(response.participatingUsers)
binding.tvTags.text = response.tags.joinToString(" ") { "#$it" } binding.tvTags.text = response.tags.joinToString(" ") { "#$it" }
binding.tvContent.text = response.notice binding.tvContent.text = response.notice
binding.ivShare.setOnClickListener { shareRoom(response) }
binding.ivShare2.setOnClickListener { shareRoom(response) }
if (response.channelName.isNullOrBlank()) { if (response.channelName.isNullOrBlank()) {
binding.tvParticipateExpression.text = "예약자"
when { when {
response.manager.id == SharedPreferenceManager.userId -> { response.manager.id == SharedPreferenceManager.userId -> {
binding.llStartDelete.visibility = View.VISIBLE binding.llStartDelete.visibility = View.VISIBLE
@@ -216,7 +159,6 @@ class LiveRoomDetailFragment(
} }
} }
} else { } else {
binding.tvParticipateExpression.text = "참여자"
binding.tvReservationComplete.visibility = View.GONE binding.tvReservationComplete.visibility = View.GONE
binding.tvParticipateNow.visibility = View.VISIBLE binding.tvParticipateNow.visibility = View.VISIBLE
binding.tvReservation.visibility = View.GONE binding.tvReservation.visibility = View.GONE
@@ -226,9 +168,6 @@ class LiveRoomDetailFragment(
} }
binding.llStartDelete.visibility = View.GONE binding.llStartDelete.visibility = View.GONE
} }
adapter.items.addAll(response.participatingUsers)
adapter.notifyDataSetChanged()
} }
private fun setManagerProfile(manager: GetRoomDetailManager) { private fun setManagerProfile(manager: GetRoomDetailManager) {
@@ -236,7 +175,7 @@ class LiveRoomDetailFragment(
binding.tvManagerIntroduce.text = manager.introduce binding.tvManagerIntroduce.text = manager.introduce
binding.ivManagerProfile.load(manager.profileImageUrl) { binding.ivManagerProfile.load(manager.profileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
@@ -290,35 +229,28 @@ class LiveRoomDetailFragment(
if (manager.isCreator) { if (manager.isCreator) {
binding.tvManagerProfile.visibility = View.VISIBLE binding.tvManagerProfile.visibility = View.VISIBLE
binding.tvManagerProfile.setOnClickListener {} binding.tvManagerProfile.setOnClickListener {
val intent = Intent(requireActivity(), UserProfileActivity::class.java)
intent.putExtra(Constants.EXTRA_USER_ID, manager.id)
startActivity(intent)
}
} else { } else {
binding.tvManagerProfile.visibility = View.GONE binding.tvManagerProfile.visibility = View.GONE
} }
} }
private fun setParticipantUserSummary(participatingUsers: List<GetRoomDetailUser>) { private fun shareRoom(response: GetRoomDetailResponse) {
val userCount = if (participatingUsers.size > 10) { viewModel.shareRoomLink(
10 response.roomId,
} else { response.isPrivateRoom,
participatingUsers.size response.password
} ) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, it)
for (index in 0 until userCount) { val shareIntent = Intent.createChooser(intent, "라이브 공유")
val user = participatingUsers[index] startActivity(shareIntent)
val itemView = ItemLiveDetailUserSummaryBinding.inflate(layoutInflater)
itemView.ivProfile.load(user.profileImageUrl) {
crossfade(true)
placeholder(R.drawable.ic_logo)
transformations(RoundedCornersTransformation(16.7f.dpToPx()))
}
val lp = LinearLayout.LayoutParams(33.3f.dpToPx().toInt(), 33.3f.dpToPx().toInt())
if (index > 0) {
lp.setMargins(-16.7f.dpToPx().toInt(), 0, 0, 0)
}
itemView.root.layoutParams = lp
binding.llProfiles.addView(itemView.root)
} }
} }
} }

View File

@@ -63,4 +63,41 @@ class LiveRoomDetailViewModel(private val repository: LiveRepository) : BaseView
) )
) )
} }
fun shareRoomLink(
roomId: Long,
isPrivateRoom: Boolean,
password: Int?,
onSuccess: (String) -> Unit
) {
_isLoading.value = true
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
link = Uri.parse("https://sodalive.net/?room_id=$roomId")
domainUriPrefix = "https://sodalive.page.link"
androidParameters { }
iosParameters("kr.co.vividnext.sodalive") {
appStoreId = "6461721697"
}
}.addOnSuccessListener {
val uri = it.shortLink
if (uri != null) {
val message = if (isPrivateRoom) {
"${SharedPreferenceManager.nickname}님이 귀하를 " +
"소다라이브의 비공개라이브에 초대하였습니다.\n" +
"※ 라이브 참여: $uri\n" +
"(입장 비밀번호 : $password)"
} else {
"${SharedPreferenceManager.nickname}님이 귀하를 " +
"소다라이브의 공개라이브에 초대하였습니다.\n" +
"※ 라이브 참여: $uri"
}
onSuccess(message)
}
}.addOnFailureListener {
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
}.addOnCompleteListener {
_isLoading.value = false
}
}
} }

View File

@@ -36,11 +36,11 @@ class LiveRoomPasswordDialog(
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
if (can > 0) { if (can > 0) {
dialogView.tvCoin.visibility = View.VISIBLE dialogView.tvCan.visibility = View.VISIBLE
dialogView.tvCoin.text = can.moneyFormat() dialogView.tvCan.text = can.moneyFormat()
dialogView.tvConfirm.text = "으로 입장" dialogView.tvConfirm.text = "으로 입장"
} else { } else {
dialogView.tvCoin.visibility = View.GONE dialogView.tvCan.visibility = View.GONE
dialogView.tvConfirm.text = "입장하기" dialogView.tvConfirm.text = "입장하기"
} }

View File

@@ -5,11 +5,14 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
@@ -32,26 +35,36 @@ class LiveRoomDonationDialog(
bottomSheetDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) bottomSheetDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
bottomSheetDialog.setCancelable(false) bottomSheetDialog.setCancelable(false)
val bottomSheetInternal = bottomSheetDialog.findViewById<FrameLayout>(
com.google.android.material.R.id.design_bottom_sheet
)
if (bottomSheetInternal != null) {
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetInternal)
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheetBehavior.skipCollapsed = true
}
dialogView.tvCancel.setOnClickListener { bottomSheetDialog.dismiss() } dialogView.tvCancel.setOnClickListener { bottomSheetDialog.dismiss() }
dialogView.tvDonation.setOnClickListener { dialogView.tvDonation.setOnClickListener {
try { try {
val coin = dialogView.etDonationCoin.text.toString().toInt() val can = dialogView.etDonationCan.text.toString().toInt()
val message = dialogView.etDonationMessage.text.toString() val message = dialogView.etDonationMessage.text.toString()
if (coin > 0) { if (can > 0) {
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
onClickDonation(coin, message) onClickDonation(can, message)
} else { } else {
Toast.makeText( Toast.makeText(
activity, activity,
"1코인 이상 후원하실 수 있습니다.", "1 이상 후원하실 수 있습니다.",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Toast.makeText( Toast.makeText(
activity, activity,
"1코인 이상 후원하실 수 있습니다.", "1 이상 후원하실 수 있습니다.",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
@@ -70,24 +83,28 @@ class LiveRoomDonationDialog(
lp.height = WindowManager.LayoutParams.WRAP_CONTENT lp.height = WindowManager.LayoutParams.WRAP_CONTENT
bottomSheetDialog.window?.attributes = lp bottomSheetDialog.window?.attributes = lp
dialogView.scrollView.post {
dialogView.scrollView.fullScroll(View.FOCUS_DOWN)
}
} }
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun setupView() { private fun setupView() {
dialogView.tvCoin.text = SharedPreferenceManager.can.moneyFormat() dialogView.tvCan.text = SharedPreferenceManager.can.moneyFormat()
dialogView.tvPlus10.setOnClickListener { addCoin(10) } dialogView.tvPlus10.setOnClickListener { addCan(10) }
dialogView.tvPlus100.setOnClickListener { addCoin(100) } dialogView.tvPlus100.setOnClickListener { addCan(100) }
dialogView.tvPlus1000.setOnClickListener { addCoin(1000) } dialogView.tvPlus1000.setOnClickListener { addCan(1000) }
dialogView.tvPlus10000.setOnClickListener { addCoin(10000) } dialogView.tvPlus10000.setOnClickListener { addCan(10000) }
dialogView.ivProfile.load(SharedPreferenceManager.profileImage) { dialogView.ivProfile.load(SharedPreferenceManager.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
dialogView.tvCoin.setOnClickListener { dialogView.tvCan.setOnClickListener {
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
val intent = Intent(activity, CanChargeActivity::class.java) val intent = Intent(activity, CanChargeActivity::class.java)
@@ -97,12 +114,12 @@ class LiveRoomDonationDialog(
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun addCoin(coin: Int) { private fun addCan(can: Int) {
try { try {
val currentCoin = dialogView.etDonationCoin.text.toString().toInt() val currentCan = dialogView.etDonationCan.text.toString().toInt()
dialogView.etDonationCoin.setText((currentCoin + coin).toString()) dialogView.etDonationCan.setText((currentCan + can).toString())
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
dialogView.etDonationCoin.setText(coin.toString()) dialogView.etDonationCan.setText(can.toString())
} }
} }
} }

View File

@@ -21,7 +21,7 @@ class LiveRoomDonationMessageAdapter(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun bind(item: LiveRoomDonationMessage) { fun bind(item: LiveRoomDonationMessage) {
binding.tvNickname.text = "${item.nickname}님이" binding.tvNickname.text = "${item.nickname}님이"
binding.tvCoinMessage.text = item.canMessage binding.tvCanMessage.text = item.canMessage
binding.tvDonationMessage.text = "\"${item.donationMessage}\"" binding.tvDonationMessage.text = "\"${item.donationMessage}\""
binding.ivDelete.setOnClickListener { onClickDeleteMessage(item.uuid) } binding.ivDelete.setOnClickListener { onClickDeleteMessage(item.uuid) }

View File

@@ -45,7 +45,7 @@ class LiveRoomDonationRankingAdapter :
binding.ivProfile.load(item.profileImage) { binding.ivProfile.load(item.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }

View File

@@ -35,7 +35,7 @@ class LiveRoomDonationRankingDialog(
adapter.items.clear() adapter.items.clear()
adapter.items.addAll(it.donationList) adapter.items.addAll(it.donationList)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
dialogView.tvTotalCoin.text = it.totalCan.moneyFormat() dialogView.tvTotalCan.text = it.totalCan.moneyFormat()
dialogView.tvTotalCount.text = it.totalCount.moneyFormat() dialogView.tvTotalCount.text = it.totalCount.moneyFormat()
} }
} }

View File

@@ -10,17 +10,17 @@ data class GetRoomInfoResponse(
@SerializedName("channelName") val channelName: String, @SerializedName("channelName") val channelName: String,
@SerializedName("rtcToken") val rtcToken: String, @SerializedName("rtcToken") val rtcToken: String,
@SerializedName("rtmToken") val rtmToken: String, @SerializedName("rtmToken") val rtmToken: String,
@SerializedName("managerId") val managerId: Long, @SerializedName("creatorId") val creatorId: Long,
@SerializedName("managerNickname") val managerNickname: String, @SerializedName("creatorNickname") val creatorNickname: String,
@SerializedName("managerProfileUrl") val managerProfileUrl: String, @SerializedName("creatorProfileUrl") val creatorProfileUrl: String,
@SerializedName("isFollowingManager") val isFollowingManager: Boolean, @SerializedName("isFollowing") val isFollowing: Boolean,
@SerializedName("isAdult") val isAdult: Boolean,
@SerializedName("participantsCount") val participantsCount: Int, @SerializedName("participantsCount") val participantsCount: Int,
@SerializedName("totalAvailableParticipantsCount") val totalAvailableParticipantsCount: Int, @SerializedName("totalAvailableParticipantsCount") val totalAvailableParticipantsCount: Int,
@SerializedName("speakerList") val speakerList: List<LiveRoomMember>, @SerializedName("speakerList") val speakerList: List<LiveRoomMember>,
@SerializedName("listenerList") val listenerList: List<LiveRoomMember>, @SerializedName("listenerList") val listenerList: List<LiveRoomMember>,
@SerializedName("managerList") val managerList: List<LiveRoomMember>, @SerializedName("managerList") val managerList: List<LiveRoomMember>,
@SerializedName("donationRankingTop3UserIds") val donationRankingTop3UserIds: List<Long>, @SerializedName("donationRankingTop3UserIds") val donationRankingTop3UserIds: List<Long>,
@SerializedName("isAvailableDonation") val isAvailableDonation: Boolean = false,
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean, @SerializedName("isPrivateRoom") val isPrivateRoom: Boolean,
@SerializedName("password") val password: String? = null @SerializedName("password") val password: String? = null
) )

View File

@@ -52,7 +52,7 @@ class LiveRoomProfileDialog(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun bindData() { private fun bindData() {
roomInfoLiveData.observe(activity) { roomInfoLiveData.observe(activity) {
adapter.managerId = it.managerId adapter.managerId = it.creatorId
adapter.totalUserCount = it.totalAvailableParticipantsCount adapter.totalUserCount = it.totalAvailableParticipantsCount
dialogView.tvParticipate.text = "${it.participantsCount}" dialogView.tvParticipate.text = "${it.participantsCount}"
dialogView.tvTotalPeoples.text = "/${it.totalAvailableParticipantsCount}" dialogView.tvTotalPeoples.text = "/${it.totalAvailableParticipantsCount}"

View File

@@ -57,8 +57,8 @@ data class LiveRoomProfileItemSpeakerTitle(
val itemBinding = binding as ItemLiveRoomProfileHeaderBinding val itemBinding = binding as ItemLiveRoomProfileHeaderBinding
itemBinding.tvTitle.text = title itemBinding.tvTitle.text = title
itemBinding.tvSpeakerCount.text = "$speakerCount" itemBinding.tvSpeakerCount.text = "$speakerCount"
itemBinding.tvSpeakerTotalCount.text = if (totalUserCount > 9) { itemBinding.tvSpeakerTotalCount.text = if (totalUserCount > 4) {
"/9" "/4"
} else { } else {
"/${totalUserCount - 1}" "/${totalUserCount - 1}"
} }
@@ -118,7 +118,7 @@ data class LiveRoomProfileItemMaster(
itemBinding.tvNickname.text = nickname itemBinding.tvNickname.text = nickname
itemBinding.ivProfile.load(profileUrl) { itemBinding.ivProfile.load(profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
if (id != SharedPreferenceManager.userId) { if (id != SharedPreferenceManager.userId) {
@@ -137,7 +137,7 @@ data class LiveRoomProfileItemManager(
itemBinding.tvNickname.text = nickname itemBinding.tvNickname.text = nickname
itemBinding.ivProfile.load(profileUrl) { itemBinding.ivProfile.load(profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
if (id != SharedPreferenceManager.userId) { if (id != SharedPreferenceManager.userId) {
@@ -159,7 +159,7 @@ data class LiveRoomProfileItemUser(
itemBinding.tvNickname.text = nickname itemBinding.tvNickname.text = nickname
itemBinding.ivProfile.load(profileUrl) { itemBinding.ivProfile.load(profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }

View File

@@ -19,7 +19,7 @@ class LiveRoomProfileListAdapter : RecyclerView.Adapter<LiveRoomProfileListAdapt
fun bind(item: LiveRoomMember) { fun bind(item: LiveRoomMember) {
binding.ivProfile.load(item.profileImage) { binding.ivProfile.load(item.profileImage) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.3f.dpToPx())) transformations(RoundedCornersTransformation(23.3f.dpToPx()))
if (activeSpeakers.contains(item.id.toInt())) { if (activeSpeakers.contains(item.id.toInt())) {

View File

@@ -78,7 +78,7 @@ class LiveRoomUserProfileDialog(
userProfileLiveData.observe(activity) { userProfile -> userProfileLiveData.observe(activity) { userProfile ->
dialogView.ivProfile.load(userProfile.profileUrl) { dialogView.ivProfile.load(userProfile.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(8.dpToPx())) transformations(RoundedCornersTransformation(8.dpToPx()))
} }

View File

@@ -36,7 +36,7 @@ class LiveTagAdapter(
binding.ivTag.load(item.image) { binding.ivTag.load(item.image) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(30f.dpToPx())) transformations(RoundedCornersTransformation(30f.dpToPx()))
} }
binding.tvTag.text = item.tag binding.tvTag.text = item.tag

View File

@@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName
data class EditLiveRoomInfoRequest( data class EditLiveRoomInfoRequest(
@SerializedName("title") val title: String?, @SerializedName("title") val title: String?,
@SerializedName("content") val notice: String?, @SerializedName("notice") val notice: String?,
@SerializedName("numberOfPeople") val numberOfPeople: Int?, @SerializedName("numberOfPeople") val numberOfPeople: Int?,
@SerializedName("beginDateTimeString") val beginDateTimeString: String?, @SerializedName("beginDateTimeString") val beginDateTimeString: String?,
@SerializedName("timezone") val timezone: String? @SerializedName("timezone") val timezone: String?

View File

@@ -49,7 +49,7 @@ class LiveRoomInfoEditDialog(
this.coverImageUri = coverImageUri this.coverImageUri = coverImageUri
dialogView.ivCover.load(coverImageUri) { dialogView.ivCover.load(coverImageUri) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }
} }
@@ -58,7 +58,7 @@ class LiveRoomInfoEditDialog(
this.coverImageUrl = coverImageUrl this.coverImageUrl = coverImageUrl
dialogView.ivCover.load(coverImageUrl) { dialogView.ivCover.load(coverImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }
} }

View File

@@ -52,40 +52,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
executeDeeplink()
val bundle = intent.getBundleExtra(Constants.EXTRA_DATA)
if (bundle != null) {
try {
val roomId = bundle.getLong(Constants.EXTRA_ROOM_ID)
val channelId = bundle.getLong(Constants.EXTRA_USER_ID)
val audioContentId = bundle.getLong(Constants.EXTRA_AUDIO_CONTENT_ID)
val isLiveReservation = bundle.getBoolean(Constants.EXTRA_LIVE_RESERVATION_RESPONSE)
if (roomId > 0) {
if (isLiveReservation) {
liveFragment.reservationRoom(roomId)
} else {
handler.postDelayed({
liveFragment.enterLiveRoom(roomId)
}, 500)
}
} else if (channelId > 0) {
val nextIntent = Intent(applicationContext, UserProfileActivity::class.java)
nextIntent.putExtra(Constants.EXTRA_USER_ID, channelId)
startActivity(nextIntent)
} else if (audioContentId > 0) {
val nextIntent = Intent(
applicationContext,
AudioContentDetailActivity::class.java
)
nextIntent.putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
startActivity(nextIntent)
}
} catch (_: IllegalStateException) {
}
}
checkReceivedMessage(intent)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -95,7 +62,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
getMemberInfo() getMemberInfo()
getEventPopup() getEventPopup()
checkReceivedMessage(intent)
handler.postDelayed({ executeDeeplink() }, 500)
} }
override fun onResume() { override fun onResume() {
@@ -115,25 +83,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
unregisterReceiver(audioContentReceiver) unregisterReceiver(audioContentReceiver)
} }
private fun checkReceivedMessage(intent: Intent) {
handler.postDelayed({
val messageId =
intent.getBundleExtra(Constants.EXTRA_DATA)?.getLong(Constants.EXTRA_MESSAGE_ID)
if (messageId != null && messageId > 0) {
changeFragment(MainViewModel.CurrentTab.MESSAGE)
setTabSelected(binding.tabSuda, isSelected = false)
setTabSelected(binding.tabExplorer, isSelected = false)
setTabSelected(binding.tabMessage, isSelected = true)
setTabSelected(binding.tabMy, isSelected = false)
}
}, 500)
}
override fun setupView() { override fun setupView() {
loadingDialog = LoadingDialog(this, layoutInflater) loadingDialog = LoadingDialog(this, layoutInflater)
liveFragment = LiveFragment().apply { liveFragment = LiveFragment()
arguments = intent.getBundleExtra(Constants.EXTRA_DATA)
}
notificationSettingsDialog = NotificationSettingsDialog( notificationSettingsDialog = NotificationSettingsDialog(
this, this,
@@ -149,6 +101,57 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
setupBottomTabLayout() setupBottomTabLayout()
} }
private fun executeDeeplink() {
val bundle = intent.getBundleExtra(Constants.EXTRA_DATA)
if (bundle != null) {
try {
val roomId = bundle.getLong(Constants.EXTRA_ROOM_ID)
val channelId = bundle.getLong(Constants.EXTRA_USER_ID)
val messageId = bundle.getLong(Constants.EXTRA_MESSAGE_ID)
val contentId = bundle.getLong(Constants.EXTRA_AUDIO_CONTENT_ID)
val isLiveReservation = bundle.getBoolean(Constants.EXTRA_LIVE_RESERVATION_RESPONSE)
if (roomId > 0) {
changeFragment(MainViewModel.CurrentTab.LIVE)
setTabSelected(binding.tabLive, isSelected = true)
setTabSelected(binding.tabExplorer, isSelected = false)
setTabSelected(binding.tabMessage, isSelected = false)
setTabSelected(binding.tabMy, isSelected = false)
setTabSelected(binding.tabContent, isSelected = false)
handler.postDelayed({
if (isLiveReservation) {
liveFragment.reservationRoom(roomId)
} else {
liveFragment.enterLiveRoom(roomId)
}
}, 500)
} else if (channelId > 0) {
val nextIntent = Intent(applicationContext, UserProfileActivity::class.java)
nextIntent.putExtra(Constants.EXTRA_USER_ID, channelId)
startActivity(nextIntent)
} else if (contentId > 0) {
val nextIntent = Intent(
applicationContext,
AudioContentDetailActivity::class.java
)
nextIntent.putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
startActivity(nextIntent)
} else if (messageId > 0) {
handler.postDelayed({
changeFragment(MainViewModel.CurrentTab.MESSAGE)
setTabSelected(binding.tabLive, isSelected = false)
setTabSelected(binding.tabExplorer, isSelected = false)
setTabSelected(binding.tabMessage, isSelected = true)
setTabSelected(binding.tabMy, isSelected = false)
setTabSelected(binding.tabContent, isSelected = false)
}, 500)
}
} catch (_: IllegalStateException) {
}
}
}
private fun setupBottomTabLayout() { private fun setupBottomTabLayout() {
setupTab( setupTab(
binding = binding.tabContent, binding = binding.tabContent,
@@ -162,7 +165,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
) )
setupTab( setupTab(
binding = binding.tabSuda, binding = binding.tabLive,
title = "라이브", title = "라이브",
imageSrc = R.drawable.ic_tabbar_live, imageSrc = R.drawable.ic_tabbar_live,
colorStateList = ContextCompat.getColorStateList( colorStateList = ContextCompat.getColorStateList(
@@ -207,7 +210,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
viewModel.currentTab.observe(this) { viewModel.currentTab.observe(this) {
setTabSelected(binding.tabContent, isSelected = false) setTabSelected(binding.tabContent, isSelected = false)
setTabSelected(binding.tabSuda, isSelected = false) setTabSelected(binding.tabLive, isSelected = false)
setTabSelected(binding.tabExplorer, isSelected = false) setTabSelected(binding.tabExplorer, isSelected = false)
setTabSelected(binding.tabMessage, isSelected = false) setTabSelected(binding.tabMessage, isSelected = false)
setTabSelected(binding.tabMy, isSelected = false) setTabSelected(binding.tabMy, isSelected = false)
@@ -219,7 +222,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
} }
MainViewModel.CurrentTab.LIVE -> { MainViewModel.CurrentTab.LIVE -> {
setTabSelected(binding.tabSuda, isSelected = true) setTabSelected(binding.tabLive, isSelected = true)
} }
MainViewModel.CurrentTab.EXPLORER -> { MainViewModel.CurrentTab.EXPLORER -> {

View File

@@ -39,7 +39,7 @@ class MainViewModel(
MY MY
} }
private val _currentTab = MutableLiveData(CurrentTab.LIVE) private val _currentTab = MutableLiveData(CurrentTab.CONTENT)
val currentTab: LiveData<CurrentTab> val currentTab: LiveData<CurrentTab>
get() = _currentTab get() = _currentTab

View File

@@ -20,7 +20,7 @@ class SelectMessageRecipientAdapter(
fun bind(item: GetRoomDetailUser) { fun bind(item: GetRoomDetailUser) {
binding.ivProfile.load(item.profileImageUrl) { binding.ivProfile.load(item.profileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.4f.dpToPx())) transformations(RoundedCornersTransformation(23.4f.dpToPx()))
} }

View File

@@ -23,7 +23,7 @@ class TextMessageAdapter(
if (SharedPreferenceManager.nickname == item.recipientNickname) { if (SharedPreferenceManager.nickname == item.recipientNickname) {
binding.ivProfile.load(item.senderProfileImageUrl) { binding.ivProfile.load(item.senderProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.4f.dpToPx())) transformations(RoundedCornersTransformation(23.4f.dpToPx()))
} }
@@ -31,7 +31,7 @@ class TextMessageAdapter(
} else { } else {
binding.ivProfile.load(item.recipientProfileImageUrl) { binding.ivProfile.load(item.recipientProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.4f.dpToPx())) transformations(RoundedCornersTransformation(23.4f.dpToPx()))
} }

View File

@@ -114,7 +114,7 @@ class TextMessageDetailActivity : BaseActivity<ActivityTextMessageDetailBinding>
if (SharedPreferenceManager.nickname == messageItem?.recipientNickname) { if (SharedPreferenceManager.nickname == messageItem?.recipientNickname) {
binding.ivProfile.load(messageItem?.senderProfileImageUrl) { binding.ivProfile.load(messageItem?.senderProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }
@@ -122,7 +122,7 @@ class TextMessageDetailActivity : BaseActivity<ActivityTextMessageDetailBinding>
} else { } else {
binding.ivProfile.load(messageItem?.recipientProfileImageUrl) { binding.ivProfile.load(messageItem?.recipientProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(13.3f.dpToPx())) transformations(RoundedCornersTransformation(13.3f.dpToPx()))
} }

View File

@@ -57,7 +57,7 @@ class VoiceMessageAdapter(
if (SharedPreferenceManager.nickname == item.recipientNickname) { if (SharedPreferenceManager.nickname == item.recipientNickname) {
binding.ivProfile.load(item.senderProfileImageUrl) { binding.ivProfile.load(item.senderProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.4f.dpToPx())) transformations(RoundedCornersTransformation(23.4f.dpToPx()))
} }
@@ -65,7 +65,7 @@ class VoiceMessageAdapter(
} else { } else {
binding.ivProfile.load(item.recipientProfileImageUrl) { binding.ivProfile.load(item.recipientProfileImageUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.4f.dpToPx())) transformations(RoundedCornersTransformation(23.4f.dpToPx()))
} }

View File

@@ -103,7 +103,7 @@ class VoiceMessageWriteFragment(
private fun setReceiver(userId: Long, nickname: String, profileUrl: String) { private fun setReceiver(userId: Long, nickname: String, profileUrl: String) {
binding.ivProfile.load(profileUrl) { binding.ivProfile.load(profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(23.4f.dpToPx())) transformations(RoundedCornersTransformation(23.4f.dpToPx()))
} }
binding.tvNickname.text = nickname binding.tvNickname.text = nickname
@@ -180,7 +180,7 @@ class VoiceMessageWriteFragment(
} }
fileNameMedia = fileNameMedia =
"${requireActivity().filesDir.path}/socdoc_${System.currentTimeMillis()}.mp3" "${requireActivity().filesDir.path}/voice_message_${System.currentTimeMillis()}.mp3"
val fileMedia = File(fileNameMedia) val fileMedia = File(fileNameMedia)
if (!fileMedia.exists()) { if (!fileMedia.exists()) {

View File

@@ -2,28 +2,37 @@ package kr.co.vividnext.sodalive.mypage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.webkit.URLUtil import android.webkit.URLUtil
import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import com.google.gson.Gson import com.google.gson.Gson
import kr.co.vividnext.sodalive.R 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.order.AudioContentOrderListAdapter
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentMyBinding import kr.co.vividnext.sodalive.databinding.FragmentMyBinding
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.live.reservation_status.LiveReservationStatusActivity
import kr.co.vividnext.sodalive.mypage.auth.Auth import kr.co.vividnext.sodalive.mypage.auth.Auth
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusActivity import kr.co.vividnext.sodalive.mypage.can.status.CanStatusActivity
import kr.co.vividnext.sodalive.live.reservation_status.LiveReservationStatusActivity import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateActivity
import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterActivity import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterActivity
import kr.co.vividnext.sodalive.settings.SettingsActivity import kr.co.vividnext.sodalive.settings.SettingsActivity
import kr.co.vividnext.sodalive.settings.notification.MemberRole import kr.co.vividnext.sodalive.settings.notification.MemberRole
@@ -34,6 +43,7 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
private val viewModel: MyPageViewModel by inject() private val viewModel: MyPageViewModel by inject()
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private lateinit var orderListAdapter: AudioContentOrderListAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -49,6 +59,42 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
} }
private fun setupView() { private fun setupView() {
orderListAdapter = AudioContentOrderListAdapter {
startActivity(
Intent(requireContext(), AudioContentDetailActivity::class.java)
.apply { putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) }
)
}
binding.rvOrderList.layoutManager = LinearLayoutManager(
requireContext(),
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)
when (parent.getChildAdapterPosition(view)) {
orderListAdapter.itemCount - 1 -> {
outRect.bottom = 0
}
else -> {
outRect.bottom = 13.3f.dpToPx().toInt()
}
}
}
})
binding.rvOrderList.adapter = orderListAdapter
binding.ivSettings.setOnClickListener { binding.ivSettings.setOnClickListener {
startActivity( startActivity(
Intent( Intent(
@@ -58,7 +104,14 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
) )
} }
binding.ivEdit.setOnClickListener {} binding.ivEdit.setOnClickListener {
startActivity(
Intent(
requireActivity(),
ProfileUpdateActivity::class.java
)
)
}
binding.llTotalCan.setOnClickListener { binding.llTotalCan.setOnClickListener {
startActivity( startActivity(
@@ -124,9 +177,20 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
} else { } else {
binding.tvMyChannel.visibility = View.GONE binding.tvMyChannel.visibility = View.GONE
} }
val ivHowToUseLp = binding.ivHowToUse.layoutParams as LinearLayout.LayoutParams
ivHowToUseLp.width = screenWidth
ivHowToUseLp.height = (200 * screenWidth) / 1080
binding.ivHowToUse.layoutParams = ivHowToUseLp
binding.ivHowToUse.setOnClickListener {
val url = "https://blog.naver.com/sodalive_official"
if (URLUtil.isValidUrl(url)) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n", "NotifyDataSetChanged")
private fun bindData() { private fun bindData() {
viewModel.toastLiveData.observe(viewLifecycleOwner) { viewModel.toastLiveData.observe(viewLifecycleOwner) {
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() } it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
@@ -149,7 +213,7 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
binding.ivProfile.load(it.profileUrl) { binding.ivProfile.load(it.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_logo) placeholder(R.drawable.ic_place_holder)
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
binding.tvNickname.text = it.nickname binding.tvNickname.text = it.nickname
@@ -191,7 +255,24 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
} }
binding.tvTotalCan.text = (it.chargeCan + it.rewardCan).moneyFormat() binding.tvTotalCan.text = (it.chargeCan + it.rewardCan).moneyFormat()
binding.tvReservationSuda.text = "${it.liveReservationCount}" binding.tvReservationLive.text = "${it.liveReservationCount}"
if (it.orderList.totalCount > 0) {
binding.llOrderList.visibility = View.VISIBLE
orderListAdapter.items.addAll(it.orderList.items)
orderListAdapter.notifyDataSetChanged()
binding.tvAllOrderList.setOnClickListener {
startActivity(
Intent(
requireContext(),
AudioContentOrderListActivity::class.java
)
)
}
} else {
binding.llOrderList.visibility = View.GONE
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.mypage package kr.co.vividnext.sodalive.mypage
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListResponse
data class MyPageResponse( data class MyPageResponse(
@SerializedName("nickname") val nickname: String, @SerializedName("nickname") val nickname: String,
@@ -14,4 +15,5 @@ data class MyPageResponse(
@SerializedName("liveReservationCount") val liveReservationCount: Int, @SerializedName("liveReservationCount") val liveReservationCount: Int,
@SerializedName("likeCount") val likeCount: Int, @SerializedName("likeCount") val likeCount: Int,
@SerializedName("isAuth") val isAuth: Boolean, @SerializedName("isAuth") val isAuth: Boolean,
@SerializedName("orderList") val orderList: GetAudioContentOrderListResponse
) )

View File

@@ -207,7 +207,7 @@ class CanPaymentActivity : BaseActivity<ActivityCanPaymentBinding>(
viewModel.verify( viewModel.verify(
request, request,
onSuccess = { onSuccess = {
Toast.makeText(applicationContext, "코인이 충전되었습니다", Toast.LENGTH_LONG).show() Toast.makeText(applicationContext, "이 충전되었습니다", Toast.LENGTH_LONG).show()
if (prevLiveRoom) { if (prevLiveRoom) {
setResult(RESULT_OK) setResult(RESULT_OK)
} else { } else {

View File

@@ -0,0 +1,20 @@
package kr.co.vividnext.sodalive.mypage.profile
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.user.Gender
data class ProfileResponse(
@SerializedName("userId") val userId: Long,
@SerializedName("email") val email: String,
@SerializedName("nickname") val nickname: String,
@SerializedName("gender") val gender: Gender,
@SerializedName("profileUrl") val profileUrl: String,
@SerializedName("chargeCan") val chargeCan: Int,
@SerializedName("rewardCan") val rewardCan: Int,
@SerializedName("youtubeUrl") val youtubeUrl: String?,
@SerializedName("instagramUrl") val instagramUrl: String?,
@SerializedName("blogUrl") val blogUrl: String?,
@SerializedName("websiteUrl") val websiteUrl: String?,
@SerializedName("introduce") val introduce: String,
@SerializedName("tags") val tags: List<String>
)

View File

@@ -0,0 +1,277 @@
package kr.co.vividnext.sodalive.mypage.profile
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import coil.load
import coil.transform.CircleCropTransformation
import com.github.dhaval2404.imagepicker.ImagePicker
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.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityProfileUpdateBinding
import kr.co.vividnext.sodalive.databinding.ItemLiveTagSelectedBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.mypage.profile.nickname.NicknameUpdateActivity
import kr.co.vividnext.sodalive.mypage.profile.password.ModifyPasswordActivity
import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagFragment
import kr.co.vividnext.sodalive.user.Gender
import org.koin.android.ext.android.inject
import java.util.concurrent.TimeUnit
class ProfileUpdateActivity : BaseActivity<ActivityProfileUpdateBinding>(
ActivityProfileUpdateBinding::inflate
) {
private val viewModel: ProfileUpdateViewModel by inject()
private val imageResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val resultCode = result.resultCode
val data = result.data
if (resultCode == RESULT_OK) {
// Image Uri will not be null for RESULT_OK
val fileUri = data?.data!!
binding.ivProfile.background = null
viewModel.updateProfileImage(fileUri) {
binding.ivProfile.load(it) {
crossfade(true)
transformations(CircleCropTransformation())
}
}
} else if (resultCode == ImagePicker.RESULT_ERROR) {
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
}
}
private val tagFragment: MemberTagFragment by lazy {
MemberTagFragment(viewModel.tags) { tag, isChecked ->
when {
isChecked -> {
viewModel.addTag(tag)
return@MemberTagFragment true
}
!isChecked -> {
viewModel.removeTag(tag)
return@MemberTagFragment true
}
else -> {
return@MemberTagFragment false
}
}
}
}
private lateinit var loadingDialog: LoadingDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getRealPathFromURI = {
RealPathUtil.getRealPath(applicationContext, it)
}
bindData()
}
override fun onStart() {
super.onStart()
viewModel.getUserInfo()
}
private fun bindData() {
compositeDisposable.add(
binding.etBlog.textChanges().skip(1)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe {
viewModel.blogUrl = it.toString()
}
)
compositeDisposable.add(
binding.etWebsite.textChanges().skip(1)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe {
viewModel.websiteUrl = it.toString()
}
)
compositeDisposable.add(
binding.etYoutube.textChanges().skip(1)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe {
viewModel.youtubeUrl = it.toString()
}
)
compositeDisposable.add(
binding.etInstagram.textChanges().skip(1)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe {
viewModel.instagramUrl = it.toString()
}
)
viewModel.genderLiveData.observe(this) {
binding.tvMale.isSelected = false
binding.tvFemale.isSelected = false
binding.tvNone.isSelected = false
when (it) {
Gender.MALE -> binding.tvMale.isSelected = true
Gender.FEMALE -> binding.tvFemale.isSelected = true
Gender.NONE -> binding.tvNone.isSelected = true
else -> {
}
}
}
viewModel.userInfoLiveData.observe(this) {
binding.ivProfile.load(it.profileUrl) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(CircleCropTransformation())
}
binding.tvEmail.text = it.email
binding.tvNickname.text = it.nickname
it.youtubeUrl?.let { url -> binding.etYoutube.setText(url) }
it.instagramUrl?.let { url -> binding.etInstagram.setText(url) }
it.websiteUrl?.let { url -> binding.etWebsite.setText(url) }
it.blogUrl?.let { url -> binding.etBlog.setText(url) }
binding.etIntroduce.setText(it.introduce)
SharedPreferenceManager.nickname = it.nickname
}
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.selectedTagLiveData.observe(this) {
binding.llSelectTags.removeAllViews()
binding.llSelectTags.visibility = if (it.isNotEmpty()) {
View.VISIBLE
} else {
View.GONE
}
for (index in it.indices) {
val tag = it[index]
val itemView = ItemLiveTagSelectedBinding.inflate(layoutInflater)
itemView.tvTag.text = tag
itemView.ivRemove.setOnClickListener {
viewModel.removeTag(tag)
}
binding.llSelectTags.addView(itemView.root)
if (index > 0) {
val layoutParams = itemView.root.layoutParams as LinearLayout.LayoutParams
layoutParams.marginStart = 10.dpToPx().toInt()
itemView.root.layoutParams = layoutParams
}
}
}
}
override fun setupView() {
binding.toolbar.tvBack.text = "프로필 수정"
binding.toolbar.tvBack.setOnClickListener { finish() }
loadingDialog = LoadingDialog(this, layoutInflater)
compositeDisposable.add(
binding.etIntroduce.textChanges()
.debounce(500, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.subscribe {
viewModel.introduce = it.toString()
}
)
binding.tvMale.setOnClickListener {
viewModel.changeGender(Gender.MALE)
}
binding.tvFemale.setOnClickListener {
viewModel.changeGender(Gender.FEMALE)
}
binding.tvNone.setOnClickListener {
viewModel.changeGender(Gender.NONE)
}
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.tvSelectTag.setOnClickListener {
if (tagFragment.isAdded) return@setOnClickListener
tagFragment.show(supportFragmentManager, tagFragment.tag)
}
binding.tvModifyPassword.setOnClickListener {
startActivity(
Intent(
applicationContext,
ModifyPasswordActivity::class.java
)
)
}
binding.tvSave.setOnClickListener {
viewModel.updateProfile {
finish()
}
}
binding.tvChangeNickname.setOnClickListener {
startActivity(
Intent(
applicationContext,
NicknameUpdateActivity::class.java
)
)
}
}
}

View File

@@ -0,0 +1,20 @@
package kr.co.vividnext.sodalive.mypage.profile
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.user.Gender
data class ProfileUpdateRequest(
@SerializedName("email") val email: String,
@SerializedName("password") val password: String? = null,
@SerializedName("modifyPassword") val modifyPassword: String? = null,
@SerializedName("nickname") val nickname: String? = null,
@SerializedName("gender") val gender: Gender? = null,
@SerializedName("insertTags") val insertTags: List<String>? = null,
@SerializedName("removeTags") val removeTags: List<String>? = null,
@SerializedName("introduce") val introduce: String? = null,
@SerializedName("youtubeUrl") val youtubeUrl: String? = null,
@SerializedName("instagramUrl") val instagramUrl: String? = null,
@SerializedName("websiteUrl") val websiteUrl: String? = null,
@SerializedName("blogUrl") val blogUrl: String? = null,
@SerializedName("container") val container: String = "aos"
)

Some files were not shown because too many files have changed in this diff Show More