Compare commits
6 Commits
25d549b06f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a6e3f71ca5 | |||
| c78849e994 | |||
| 5609560b77 | |||
| 4bb91c605a | |||
| 9dfad913bc | |||
| 4815cac49b |
@@ -63,8 +63,8 @@ android {
|
|||||||
applicationId "kr.co.vividnext.sodalive"
|
applicationId "kr.co.vividnext.sodalive"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode 231
|
versionCode 234
|
||||||
versionName "1.53.0"
|
versionName "1.54.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import kr.co.vividnext.sodalive.audio_content.upload.theme.GetAudioContentThemeR
|
|||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListResponse
|
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListResponse
|
||||||
import kr.co.vividnext.sodalive.home.AudioContentMainItem
|
import kr.co.vividnext.sodalive.home.AudioContentMainItem
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
@@ -39,8 +38,6 @@ import retrofit2.http.Query
|
|||||||
interface AudioContentApi {
|
interface AudioContentApi {
|
||||||
@GET("/audio-content/all")
|
@GET("/audio-content/all")
|
||||||
fun getAllAudioContents(
|
fun getAllAudioContents(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("isFree") isFree: Boolean?,
|
@Query("isFree") isFree: Boolean?,
|
||||||
@@ -54,7 +51,6 @@ interface AudioContentApi {
|
|||||||
fun getAudioContentList(
|
fun getAudioContentList(
|
||||||
@Query("creator-id") id: Long,
|
@Query("creator-id") id: Long,
|
||||||
@Query("category-id") categoryId: Long,
|
@Query("category-id") categoryId: Long,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("sort-type") sort: AudioContentViewModel.Sort,
|
@Query("sort-type") sort: AudioContentViewModel.Sort,
|
||||||
@@ -63,8 +59,6 @@ interface AudioContentApi {
|
|||||||
|
|
||||||
@GET("/audio-content/replay-live")
|
@GET("/audio-content/replay-live")
|
||||||
fun getAudioContentReplayLiveList(
|
fun getAudioContentReplayLiveList(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Flowable<ApiResponse<List<GetAudioContentMainItem>>>
|
): Flowable<ApiResponse<List<GetAudioContentMainItem>>>
|
||||||
|
|
||||||
@@ -75,8 +69,6 @@ interface AudioContentApi {
|
|||||||
|
|
||||||
@GET("/audio-content/theme/active")
|
@GET("/audio-content/theme/active")
|
||||||
fun getAudioContentActiveThemeList(
|
fun getAudioContentActiveThemeList(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("isFree") isFree: Boolean?,
|
@Query("isFree") isFree: Boolean?,
|
||||||
@Query("isPointAvailableOnly") isPointAvailableOnly: Boolean?,
|
@Query("isPointAvailableOnly") isPointAvailableOnly: Boolean?,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
@@ -85,8 +77,6 @@ interface AudioContentApi {
|
|||||||
@GET("/audio-content/theme/{id}/content")
|
@GET("/audio-content/theme/{id}/content")
|
||||||
fun getAudioContentByTheme(
|
fun getAudioContentByTheme(
|
||||||
@Path("id") id: Long,
|
@Path("id") id: Long,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("sort-type") sort: AudioContentViewModel.Sort,
|
@Query("sort-type") sort: AudioContentViewModel.Sort,
|
||||||
@@ -175,8 +165,6 @@ interface AudioContentApi {
|
|||||||
@GET("/audio-content/main/new")
|
@GET("/audio-content/main/new")
|
||||||
fun getNewContentOfTheme(
|
fun getNewContentOfTheme(
|
||||||
@Query("theme") theme: String,
|
@Query("theme") theme: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetAudioContentMainItem>>>
|
): Single<ApiResponse<List<GetAudioContentMainItem>>>
|
||||||
|
|
||||||
@@ -184,8 +172,6 @@ interface AudioContentApi {
|
|||||||
fun getNewContentAllOfTheme(
|
fun getNewContentAllOfTheme(
|
||||||
@Query("isFree") isFree: Boolean,
|
@Query("isFree") isFree: Boolean,
|
||||||
@Query("theme") theme: String,
|
@Query("theme") theme: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
@@ -205,8 +191,6 @@ interface AudioContentApi {
|
|||||||
|
|
||||||
@GET("/audio-content/main/theme")
|
@GET("/audio-content/main/theme")
|
||||||
fun getNewContentThemeList(
|
fun getNewContentThemeList(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<String>>>
|
): Single<ApiResponse<List<String>>>
|
||||||
|
|
||||||
@@ -225,8 +209,6 @@ interface AudioContentApi {
|
|||||||
|
|
||||||
@GET("/audio-content/main/curation-list")
|
@GET("/audio-content/main/curation-list")
|
||||||
fun getCurationList(
|
fun getCurationList(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest
|
|||||||
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
|
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
|
||||||
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
|
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
|
||||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
|
import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
@@ -27,7 +25,6 @@ class AudioContentRepository(
|
|||||||
) = api.getAudioContentList(
|
) = api.getAudioContentList(
|
||||||
id = id,
|
id = id,
|
||||||
categoryId = categoryId,
|
categoryId = categoryId,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
sort = sort,
|
sort = sort,
|
||||||
@@ -35,8 +32,6 @@ class AudioContentRepository(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun getAudioContentReplayLiveList(token: String) = api.getAudioContentReplayLiveList(
|
fun getAudioContentReplayLiveList(token: String) = api.getAudioContentReplayLiveList(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,16 +118,12 @@ class AudioContentRepository(
|
|||||||
) = api.getNewContentAllOfTheme(
|
) = api.getNewContentAllOfTheme(
|
||||||
isFree = isFree,
|
isFree = isFree,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getNewContentThemeList(token: String) = api.getNewContentThemeList(
|
fun getNewContentThemeList(token: String) = api.getNewContentThemeList(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -188,8 +179,6 @@ class AudioContentRepository(
|
|||||||
token: String
|
token: String
|
||||||
) = api.getAudioContentByTheme(
|
) = api.getAudioContentByTheme(
|
||||||
id = themeId,
|
id = themeId,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
sort = sort,
|
sort = sort,
|
||||||
@@ -205,8 +194,6 @@ class AudioContentRepository(
|
|||||||
theme: String? = null,
|
theme: String? = null,
|
||||||
token: String
|
token: String
|
||||||
) = api.getAllAudioContents(
|
) = api.getAllAudioContents(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
isFree = isFree,
|
isFree = isFree,
|
||||||
@@ -221,8 +208,6 @@ class AudioContentRepository(
|
|||||||
isPointAvailableOnly: Boolean? = null,
|
isPointAvailableOnly: Boolean? = null,
|
||||||
token: String
|
token: String
|
||||||
) = api.getAudioContentActiveThemeList(
|
) = api.getAudioContentActiveThemeList(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
isFree = isFree,
|
isFree = isFree,
|
||||||
isPointAvailableOnly = isPointAvailableOnly,
|
isPointAvailableOnly = isPointAvailableOnly,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import io.reactivex.rxjava3.core.Single
|
|||||||
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListResponse
|
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesContentListResponse
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesDetailResponse
|
import kr.co.vividnext.sodalive.audio_content.series.detail.GetSeriesDetailResponse
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
@@ -15,8 +14,6 @@ interface SeriesApi {
|
|||||||
fun getSeriesList(
|
fun getSeriesList(
|
||||||
@Query("creatorId") creatorId: Long?,
|
@Query("creatorId") creatorId: Long?,
|
||||||
@Query("sortType") sortType: SeriesListAllViewModel.SeriesSortType,
|
@Query("sortType") sortType: SeriesListAllViewModel.SeriesSortType,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("isOriginal") isOriginal: Boolean?,
|
@Query("isOriginal") isOriginal: Boolean?,
|
||||||
@Query("isCompleted") isCompleted: Boolean?,
|
@Query("isCompleted") isCompleted: Boolean?,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@@ -27,14 +24,12 @@ interface SeriesApi {
|
|||||||
@GET("/audio-content/series/{id}")
|
@GET("/audio-content/series/{id}")
|
||||||
fun getSeriesDetail(
|
fun getSeriesDetail(
|
||||||
@Path("id") seriesId: Long,
|
@Path("id") seriesId: Long,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<GetSeriesDetailResponse>>
|
): Single<ApiResponse<GetSeriesDetailResponse>>
|
||||||
|
|
||||||
@GET("/audio-content/series/{id}/content")
|
@GET("/audio-content/series/{id}/content")
|
||||||
fun getSeriesContentList(
|
fun getSeriesContentList(
|
||||||
@Path("id") seriesId: Long,
|
@Path("id") seriesId: Long,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("sortType") sortType: SeriesListAllViewModel.SeriesSortType,
|
@Query("sortType") sortType: SeriesListAllViewModel.SeriesSortType,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package kr.co.vividnext.sodalive.audio_content.series
|
package kr.co.vividnext.sodalive.audio_content.series
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
|
|
||||||
class SeriesRepository(private val api: SeriesApi) {
|
class SeriesRepository(private val api: SeriesApi) {
|
||||||
fun getSeriesList(
|
fun getSeriesList(
|
||||||
creatorId: Long?,
|
creatorId: Long?,
|
||||||
@@ -15,8 +12,6 @@ class SeriesRepository(private val api: SeriesApi) {
|
|||||||
) = api.getSeriesList(
|
) = api.getSeriesList(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
sortType = sortType,
|
sortType = sortType,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
isOriginal = isOriginal,
|
isOriginal = isOriginal,
|
||||||
isCompleted = isCompleted,
|
isCompleted = isCompleted,
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
@@ -26,7 +21,6 @@ class SeriesRepository(private val api: SeriesApi) {
|
|||||||
|
|
||||||
fun getSeriesDetail(seriesId: Long, token: String) = api.getSeriesDetail(
|
fun getSeriesDetail(seriesId: Long, token: String) = api.getSeriesDetail(
|
||||||
seriesId = seriesId,
|
seriesId = seriesId,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,7 +32,6 @@ class SeriesRepository(private val api: SeriesApi) {
|
|||||||
token: String
|
token: String
|
||||||
) = api.getSeriesContentList(
|
) = api.getSeriesContentList(
|
||||||
seriesId = seriesId,
|
seriesId = seriesId,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
sortType = sortType,
|
sortType = sortType,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.GetSeriesGenr
|
|||||||
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse
|
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
@@ -14,23 +13,17 @@ import retrofit2.http.Query
|
|||||||
interface SeriesMainApi {
|
interface SeriesMainApi {
|
||||||
@GET("/audio-content/series/main")
|
@GET("/audio-content/series/main")
|
||||||
fun fetchHome(
|
fun fetchHome(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<SeriesHomeResponse>>
|
): Single<ApiResponse<SeriesHomeResponse>>
|
||||||
|
|
||||||
@GET("/audio-content/series/main/recommend")
|
@GET("/audio-content/series/main/recommend")
|
||||||
fun getRecommendSeriesList(
|
fun getRecommendSeriesList(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
||||||
|
|
||||||
@GET("/audio-content/series/main/day-of-week")
|
@GET("/audio-content/series/main/day-of-week")
|
||||||
fun getDayOfWeekSeriesList(
|
fun getDayOfWeekSeriesList(
|
||||||
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
@@ -38,16 +31,12 @@ interface SeriesMainApi {
|
|||||||
|
|
||||||
@GET("/audio-content/series/main/genre-list")
|
@GET("/audio-content/series/main/genre-list")
|
||||||
fun getGenreList(
|
fun getGenreList(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetSeriesGenreListResponse>>>
|
): Single<ApiResponse<List<GetSeriesGenreListResponse>>>
|
||||||
|
|
||||||
@GET("/audio-content/series/main/list-by-genre")
|
@GET("/audio-content/series/main/list-by-genre")
|
||||||
fun getSeriesListByGenre(
|
fun getSeriesListByGenre(
|
||||||
@Query("genreId") genreId: Long,
|
@Query("genreId") genreId: Long,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
package kr.co.vividnext.sodalive.audio_content.series.main
|
package kr.co.vividnext.sodalive.audio_content.series.main
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
|
|
||||||
class SeriesMainRepository(
|
class SeriesMainRepository(
|
||||||
private val api: SeriesMainApi
|
private val api: SeriesMainApi
|
||||||
) {
|
) {
|
||||||
fun fetchData(token: String) = api.fetchHome(
|
fun fetchData(token: String) = api.fetchHome(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getRecommendSeriesList(token: String) = api.getRecommendSeriesList(
|
fun getRecommendSeriesList(token: String) = api.getRecommendSeriesList(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,16 +20,12 @@ class SeriesMainRepository(
|
|||||||
token: String
|
token: String
|
||||||
) = api.getDayOfWeekSeriesList(
|
) = api.getDayOfWeekSeriesList(
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGenreList(token: String) = api.getGenreList(
|
fun getGenreList(token: String) = api.getGenreList(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,8 +36,6 @@ class SeriesMainRepository(
|
|||||||
token: String
|
token: String
|
||||||
) = api.getSeriesListByGenre(
|
) = api.getSeriesListByGenre(
|
||||||
genreId = genreId,
|
genreId = genreId,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ interface ExplorerApi {
|
|||||||
fun getCreatorProfile(
|
fun getCreatorProfile(
|
||||||
@Path("id") id: Long,
|
@Path("id") id: Long,
|
||||||
@Query("timezone") timezone: String,
|
@Query("timezone") timezone: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<GetCreatorProfileResponse>>
|
): Single<ApiResponse<GetCreatorProfileResponse>>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.explorer
|
|||||||
import io.reactivex.rxjava3.core.Flowable
|
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.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse
|
import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse
|
||||||
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
|
||||||
@@ -25,7 +24,6 @@ class ExplorerRepository(
|
|||||||
fun getCreatorProfile(id: Long, token: String) = api.getCreatorProfile(
|
fun getCreatorProfile(id: Long, token: String) = api.getCreatorProfile(
|
||||||
id = id,
|
id = id,
|
||||||
timezone = TimeZone.getDefault().id,
|
timezone = TimeZone.getDefault().id,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import io.reactivex.rxjava3.core.Single
|
|||||||
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentRankingItem
|
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentRankingItem
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
|
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
@@ -13,39 +12,29 @@ interface HomeApi {
|
|||||||
@GET("/api/home")
|
@GET("/api/home")
|
||||||
fun getHomeData(
|
fun getHomeData(
|
||||||
@Query("timezone") timezone: String,
|
@Query("timezone") timezone: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<GetHomeResponse>>
|
): Single<ApiResponse<GetHomeResponse>>
|
||||||
|
|
||||||
@GET("/api/home/latest-content")
|
@GET("/api/home/latest-content")
|
||||||
fun getLatestContentByTheme(
|
fun getLatestContentByTheme(
|
||||||
@Query("theme") theme: String,
|
@Query("theme") theme: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<AudioContentMainItem>>>
|
): Single<ApiResponse<List<AudioContentMainItem>>>
|
||||||
|
|
||||||
@GET("/api/home/day-of-week-series")
|
@GET("/api/home/day-of-week-series")
|
||||||
fun getDayOfWeekSeriesList(
|
fun getDayOfWeekSeriesList(
|
||||||
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
||||||
|
|
||||||
@GET("/api/home/recommend-contents")
|
@GET("/api/home/recommend-contents")
|
||||||
fun getRecommendContents(
|
fun getRecommendContents(
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<AudioContentMainItem>>>
|
): Single<ApiResponse<List<AudioContentMainItem>>>
|
||||||
|
|
||||||
@GET("/api/home/content-ranking")
|
@GET("/api/home/content-ranking")
|
||||||
fun getContentRankingBySort(
|
fun getContentRankingBySort(
|
||||||
@Query("sort") sort: ContentRankingSortType,
|
@Query("sort") sort: ContentRankingSortType,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetAudioContentRankingItem>>>
|
): Single<ApiResponse<List<GetAudioContentRankingItem>>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
package kr.co.vividnext.sodalive.home
|
package kr.co.vividnext.sodalive.home
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
class HomeRepository(private val api: HomeApi) {
|
class HomeRepository(private val api: HomeApi) {
|
||||||
fun fetchData(token: String) = api.getHomeData(
|
fun fetchData(token: String) = api.getHomeData(
|
||||||
timezone = TimeZone.getDefault().id,
|
timezone = TimeZone.getDefault().id,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getLatestContentByTheme(theme: String, token: String) = api.getLatestContentByTheme(
|
fun getLatestContentByTheme(theme: String, token: String) = api.getLatestContentByTheme(
|
||||||
theme = theme,
|
theme = theme,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,14 +17,10 @@ class HomeRepository(private val api: HomeApi) {
|
|||||||
dayOfWeek: SeriesPublishedDaysOfWeek, token: String
|
dayOfWeek: SeriesPublishedDaysOfWeek, token: String
|
||||||
) = api.getDayOfWeekSeriesList(
|
) = api.getDayOfWeekSeriesList(
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getRecommendContents(token: String) = api.getRecommendContents(
|
fun getRecommendContents(token: String) = api.getRecommendContents(
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,8 +29,6 @@ class HomeRepository(private val api: HomeApi) {
|
|||||||
token: String
|
token: String
|
||||||
) = api.getContentRankingBySort(
|
) = api.getContentRankingBySort(
|
||||||
sort = sortType,
|
sort = sortType,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartTotalResponse
|
|||||||
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
|
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse
|
import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse
|
||||||
import kr.co.vividnext.sodalive.live.room.tag.GetLiveTagResponse
|
import kr.co.vividnext.sodalive.live.room.tag.GetLiveTagResponse
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
@@ -55,7 +54,6 @@ interface LiveApi {
|
|||||||
@Query("timezone") timezone: String,
|
@Query("timezone") timezone: String,
|
||||||
@Query("dateString") dateString: String?,
|
@Query("dateString") dateString: String?,
|
||||||
@Query("status") status: LiveRoomStatus,
|
@Query("status") status: LiveRoomStatus,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
@@ -250,8 +248,6 @@ interface LiveApi {
|
|||||||
@GET("/api/live")
|
@GET("/api/live")
|
||||||
fun getLiveMain(
|
fun getLiveMain(
|
||||||
@Query("timezone") timezone: String,
|
@Query("timezone") timezone: String,
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<LiveMainResponse>>
|
): Single<ApiResponse<LiveMainResponse>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.live
|
|||||||
import io.reactivex.rxjava3.core.Flowable
|
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.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.live.reservation.MakeLiveReservationRequest
|
import kr.co.vividnext.sodalive.live.reservation.MakeLiveReservationRequest
|
||||||
import kr.co.vividnext.sodalive.live.reservation_status.CancelLiveReservationRequest
|
import kr.co.vividnext.sodalive.live.reservation_status.CancelLiveReservationRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.CancelLiveRequest
|
import kr.co.vividnext.sodalive.live.room.CancelLiveRequest
|
||||||
@@ -20,7 +19,6 @@ import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationResponse
|
|||||||
import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest
|
import kr.co.vividnext.sodalive.live.room.kick_out.LiveRoomKickOutRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
|
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.menu.MenuApi
|
import kr.co.vividnext.sodalive.live.room.menu.MenuApi
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
|
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
|
||||||
import kr.co.vividnext.sodalive.user.UserApi
|
import kr.co.vividnext.sodalive.user.UserApi
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
@@ -43,7 +41,6 @@ class LiveRepository(
|
|||||||
timezone = TimeZone.getDefault().id,
|
timezone = TimeZone.getDefault().id,
|
||||||
dateString = dateString,
|
dateString = dateString,
|
||||||
status = status,
|
status = status,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
@@ -287,8 +284,6 @@ class LiveRepository(
|
|||||||
|
|
||||||
fun getLiveMain(token: String) = api.getLiveMain(
|
fun getLiveMain(token: String) = api.getLiveMain(
|
||||||
timezone = TimeZone.getDefault().id,
|
timezone = TimeZone.getDefault().id,
|
||||||
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
// joinChannel 중복 호출 방지 플래그
|
// joinChannel 중복 호출 방지 플래그
|
||||||
private var hasInvokedJoinChannel = false
|
private var hasInvokedJoinChannel = false
|
||||||
|
|
||||||
|
// RTM/RTC 연결 완료 추적 플래그 (둘 다 연결되면 레이아웃 강제 갱신)
|
||||||
|
private var isRtcJoined = false
|
||||||
|
private var isRtmJoined = false
|
||||||
|
|
||||||
private var v2vSourceLanguage: String? = null
|
private var v2vSourceLanguage: String? = null
|
||||||
private var v2vTargetLanguage: String? = null
|
private var v2vTargetLanguage: String? = null
|
||||||
private var isV2vAvailable = false
|
private var isV2vAvailable = false
|
||||||
@@ -415,7 +419,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 포그라운드 진입 시 API 레벨별 캡처/녹화 감지를 시작한다.
|
// 포그라운드 진입 시 API 레벨별 캡처/녹화 감지를 시작한다.
|
||||||
registerCaptureSecurityCallbacks()
|
syncCaptureSecurityPolicyByRole()
|
||||||
|
|
||||||
if (this::layoutManager.isInitialized) {
|
if (this::layoutManager.isInitialized) {
|
||||||
layoutManager.scrollToPosition(chatAdapter.itemCount - 1)
|
layoutManager.scrollToPosition(chatAdapter.itemCount - 1)
|
||||||
@@ -731,6 +735,41 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// RTM과 RTC가 모두 연결되면 키보드를 잠깐 올렸다 내려 레이아웃을 강제 갱신한다.
|
||||||
|
// 로딩 다이얼로그가 화면을 덮고 있는 동안 수행하여 사용자에게 변화가 보이지 않도록 한다.
|
||||||
|
private fun tryForceLayoutRefresh(): Boolean {
|
||||||
|
if (!isRtcJoined || !isRtmJoined) return false
|
||||||
|
|
||||||
|
handler.post {
|
||||||
|
// 키보드가 화면을 밀어올리지 않도록 임시로 adjustNothing 전환
|
||||||
|
window.setSoftInputMode(
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
|
||||||
|
or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.etChat.requestFocus()
|
||||||
|
imm.showSoftInput(binding.etChat, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
|
||||||
|
handler.postDelayed({
|
||||||
|
imm.hideSoftInputFromWindow(
|
||||||
|
binding.etChat.windowToken,
|
||||||
|
InputMethodManager.HIDE_NOT_ALWAYS
|
||||||
|
)
|
||||||
|
binding.etChat.clearFocus()
|
||||||
|
|
||||||
|
// 원래 softInputMode 복원
|
||||||
|
window.setSoftInputMode(
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
|
||||||
|
or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
|
||||||
|
)
|
||||||
|
|
||||||
|
// 키보드 트릭 완료 후 로딩 다이얼로그 dismiss
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun applyKeyboardPanInsets() {
|
private fun applyKeyboardPanInsets() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
return
|
return
|
||||||
@@ -1256,6 +1295,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
}
|
}
|
||||||
|
|
||||||
isHost = response.creatorId == SharedPreferenceManager.userId
|
isHost = response.creatorId == SharedPreferenceManager.userId
|
||||||
|
syncCaptureSecurityPolicyByRole()
|
||||||
binding.tvChatFreezeSwitch.visibility = if (isHost) {
|
binding.tvChatFreezeSwitch.visibility = if (isHost) {
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
@@ -1605,8 +1645,18 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerCaptureSecurityCallbacks() {
|
private fun syncCaptureSecurityPolicyByRole() {
|
||||||
registerScreenRecordingCallback()
|
if (isHost) {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
unregisterScreenRecordingCallback()
|
||||||
|
clearCapturePrivacyMuteState()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
if (isForeground) {
|
||||||
|
registerScreenRecordingCallback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -1669,7 +1719,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun syncCapturePrivacyMuteState() {
|
private fun syncCapturePrivacyMuteState() {
|
||||||
val shouldMute = isScreenRecordingActive
|
val shouldMute = !isHost && isScreenRecordingActive
|
||||||
if (isCapturePrivacyMuted == shouldMute) {
|
if (isCapturePrivacyMuted == shouldMute) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2177,6 +2227,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
override fun onJoinChannelSuccess(channel: String, uid: Int, elapsed: Int) {
|
override fun onJoinChannelSuccess(channel: String, uid: Int, elapsed: Int) {
|
||||||
super.onJoinChannelSuccess(channel, uid, elapsed)
|
super.onJoinChannelSuccess(channel, uid, elapsed)
|
||||||
Logger.e("onJoinChannelSuccess - uid: $uid, channel: $channel")
|
Logger.e("onJoinChannelSuccess - uid: $uid, channel: $channel")
|
||||||
|
isRtcJoined = true
|
||||||
|
tryForceLayoutRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStreamMessage(uid: Int, streamId: Int, data: ByteArray?) {
|
override fun onStreamMessage(uid: Int, streamId: Int, data: ByteArray?) {
|
||||||
@@ -2823,8 +2875,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
rtmToken = roomInfo.rtmToken,
|
rtmToken = roomInfo.rtmToken,
|
||||||
channelName = roomInfo.channelName,
|
channelName = roomInfo.channelName,
|
||||||
rtmChannelJoinSuccess = {
|
rtmChannelJoinSuccess = {
|
||||||
handler.post {
|
isRtmJoined = true
|
||||||
loadingDialog.dismiss()
|
// 두 채널 모두 연결 시 키보드 트릭 후 dismiss, 아니면 즉시 dismiss
|
||||||
|
if (!tryForceLayoutRefresh()) {
|
||||||
|
handler.post { loadingDialog.dismiss() }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId == roomInfo.creatorId) {
|
if (userId == roomInfo.creatorId) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingData
|
|||||||
import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository
|
import kr.co.vividnext.sodalive.audio_content.PlaybackTrackingRepository
|
||||||
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.settings.ContentType
|
||||||
import kr.co.vividnext.sodalive.settings.event.EventItem
|
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||||
import kr.co.vividnext.sodalive.settings.event.EventRepository
|
import kr.co.vividnext.sodalive.settings.event.EventRepository
|
||||||
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
|
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
|
||||||
@@ -103,9 +104,19 @@ class MainViewModel(
|
|||||||
SharedPreferenceManager.point = data.point
|
SharedPreferenceManager.point = data.point
|
||||||
SharedPreferenceManager.role = data.role.name
|
SharedPreferenceManager.role = data.role.name
|
||||||
SharedPreferenceManager.isAuth = data.isAuth
|
SharedPreferenceManager.isAuth = data.isAuth
|
||||||
SharedPreferenceManager.countryCode = data.countryCode.ifBlank { "KR" }
|
|
||||||
SharedPreferenceManager.isAdultContentVisible = data.isAdultContentVisible
|
val localCountryCode = SharedPreferenceManager.countryCode.ifBlank { "KR" }
|
||||||
SharedPreferenceManager.contentPreference = data.contentType.ordinal
|
val resolvedCountryCode = data.countryCode?.ifBlank { "KR" } ?: localCountryCode
|
||||||
|
val resolvedIsAdultContentVisible =
|
||||||
|
data.isAdultContentVisible ?: SharedPreferenceManager.isAdultContentVisible
|
||||||
|
val resolvedContentType =
|
||||||
|
data.contentType
|
||||||
|
?: ContentType.entries.getOrNull(SharedPreferenceManager.contentPreference)
|
||||||
|
?: ContentType.ALL
|
||||||
|
|
||||||
|
SharedPreferenceManager.countryCode = resolvedCountryCode
|
||||||
|
SharedPreferenceManager.isAdultContentVisible = resolvedIsAdultContentVisible
|
||||||
|
SharedPreferenceManager.contentPreference = resolvedContentType.ordinal
|
||||||
SharedPreferenceManager.isAuditionNotification =
|
SharedPreferenceManager.isAuditionNotification =
|
||||||
data.auditionNotice ?: false
|
data.auditionNotice ?: false
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.btnIdentityVerification.root.visibility = View.GONE
|
binding.btnIdentityVerification.root.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.isAuth) {
|
if (it.isAuth) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.search
|
|||||||
|
|
||||||
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.settings.ContentType
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
@@ -11,16 +10,12 @@ interface SearchApi {
|
|||||||
@GET("/search")
|
@GET("/search")
|
||||||
fun searchUnified(
|
fun searchUnified(
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<SearchUnifiedResponse>>
|
): Single<ApiResponse<SearchUnifiedResponse>>
|
||||||
|
|
||||||
@GET("/search/creators")
|
@GET("/search/creators")
|
||||||
fun searchCreatorList(
|
fun searchCreatorList(
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
@@ -29,8 +24,6 @@ interface SearchApi {
|
|||||||
@GET("/search/contents")
|
@GET("/search/contents")
|
||||||
fun searchContentList(
|
fun searchContentList(
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
@@ -39,8 +32,6 @@ interface SearchApi {
|
|||||||
@GET("/search/series")
|
@GET("/search/series")
|
||||||
fun searchSeriesList(
|
fun searchSeriesList(
|
||||||
@Query("keyword") keyword: String,
|
@Query("keyword") keyword: String,
|
||||||
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
|
||||||
@Query("contentType") contentType: ContentType,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.search
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
|
||||||
import kr.co.vividnext.sodalive.settings.ContentType
|
|
||||||
|
|
||||||
class SearchRepository(private val api: SearchApi) {
|
class SearchRepository(private val api: SearchApi) {
|
||||||
fun searchUnified(
|
fun searchUnified(
|
||||||
keyword: String,
|
keyword: String,
|
||||||
token: String
|
token: String
|
||||||
) = api.searchUnified(
|
) = api.searchUnified(
|
||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,8 +16,6 @@ class SearchRepository(private val api: SearchApi) {
|
|||||||
token: String
|
token: String
|
||||||
) = api.searchCreatorList(
|
) = api.searchCreatorList(
|
||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
@@ -35,8 +28,6 @@ class SearchRepository(private val api: SearchApi) {
|
|||||||
token: String
|
token: String
|
||||||
) = api.searchContentList(
|
) = api.searchContentList(
|
||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
@@ -49,8 +40,6 @@ class SearchRepository(private val api: SearchApi) {
|
|||||||
token: String
|
token: String
|
||||||
) = api.searchSeriesList(
|
) = api.searchSeriesList(
|
||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
|
||||||
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ data class GetMemberInfoResponse(
|
|||||||
@SerializedName("auditionNotice")
|
@SerializedName("auditionNotice")
|
||||||
val auditionNotice: Boolean?,
|
val auditionNotice: Boolean?,
|
||||||
@SerializedName("countryCode")
|
@SerializedName("countryCode")
|
||||||
val countryCode: String,
|
val countryCode: String? = null,
|
||||||
@SerializedName("isAdultContentVisible")
|
@SerializedName("isAdultContentVisible")
|
||||||
val isAdultContentVisible: Boolean,
|
val isAdultContentVisible: Boolean? = null,
|
||||||
@SerializedName("contentType")
|
@SerializedName("contentType")
|
||||||
val contentType: ContentType
|
val contentType: ContentType? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class MemberRole {
|
enum class MemberRole {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package kr.co.vividnext.sodalive.settings.notification
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class GetMemberInfoResponseCompatibilityTest {
|
||||||
|
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `구서버 응답에서 신규 필드가 없어도 역직렬화된다`() {
|
||||||
|
val json = """
|
||||||
|
{
|
||||||
|
"can": 10,
|
||||||
|
"point": 120,
|
||||||
|
"isAuth": true,
|
||||||
|
"gender": "F",
|
||||||
|
"signupDate": "2024-01-01, 00:00:00",
|
||||||
|
"chargeCount": 3,
|
||||||
|
"role": "USER",
|
||||||
|
"messageNotice": true,
|
||||||
|
"followingChannelLiveNotice": false,
|
||||||
|
"followingChannelUploadContentNotice": true,
|
||||||
|
"auditionNotice": false
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val response = gson.fromJson(json, GetMemberInfoResponse::class.java)
|
||||||
|
|
||||||
|
assertEquals(10, response.can)
|
||||||
|
assertEquals(120, response.point)
|
||||||
|
assertTrue(response.isAuth)
|
||||||
|
assertEquals(MemberRole.USER, response.role)
|
||||||
|
assertNull(response.countryCode)
|
||||||
|
assertNull(response.isAdultContentVisible)
|
||||||
|
assertNull(response.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `신규 필드가 있으면 정상 매핑된다`() {
|
||||||
|
val json = """
|
||||||
|
{
|
||||||
|
"can": 10,
|
||||||
|
"point": 120,
|
||||||
|
"isAuth": true,
|
||||||
|
"gender": "F",
|
||||||
|
"signupDate": "2024-01-01, 00:00:00",
|
||||||
|
"chargeCount": 3,
|
||||||
|
"role": "CREATOR",
|
||||||
|
"messageNotice": true,
|
||||||
|
"followingChannelLiveNotice": false,
|
||||||
|
"followingChannelUploadContentNotice": true,
|
||||||
|
"auditionNotice": false,
|
||||||
|
"countryCode": "US",
|
||||||
|
"isAdultContentVisible": true,
|
||||||
|
"contentType": "FEMALE"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val response = gson.fromJson(json, GetMemberInfoResponse::class.java)
|
||||||
|
|
||||||
|
assertEquals("US", response.countryCode)
|
||||||
|
assertEquals(true, response.isAdultContentVisible)
|
||||||
|
assertEquals(ContentType.FEMALE, response.contentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
docs/20260327_콘텐츠보기설정파라미터전송정리.md
Normal file
36
docs/20260327_콘텐츠보기설정파라미터전송정리.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 20260327_콘텐츠보기설정파라미터전송정리
|
||||||
|
|
||||||
|
## 작업 목적
|
||||||
|
- `PATCH /member/content-preference`를 제외한 모든 API 요청에서 `isAdultContentVisible`, `contentType` 전송을 제거한다.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [x] 코드베이스에서 `isAdultContentVisible`, `contentType` 전송 위치를 전수 확인한다.
|
||||||
|
- [x] 예외 API(`PATCH /member/content-preference`) 선언 및 호출 경로를 확인한다.
|
||||||
|
- [x] 예외 API 외 DTO/호출부에서 두 파라미터를 제거한다.
|
||||||
|
- [x] 수정 파일 진단, 테스트, 빌드 검증을 수행한다.
|
||||||
|
- [x] 검증 기록을 문서 하단에 누적한다.
|
||||||
|
|
||||||
|
## 수용 기준 (Pass/Fail)
|
||||||
|
- [x] PASS: `PATCH /member/content-preference`에서는 `isAdultContentVisible`, `contentType`가 유지된다.
|
||||||
|
- [x] PASS: 그 외 API 요청에서는 `isAdultContentVisible`, `contentType`가 전송되지 않는다.
|
||||||
|
- [x] PASS: `./gradlew :app:testDebugUnitTest`가 성공한다.
|
||||||
|
- [x] PASS: `./gradlew :app:assembleDebug`가 성공한다.
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-03-27
|
||||||
|
- 무엇/왜/어떻게: `/member/content-preference` PATCH를 제외한 API 요청에서 `isAdultContentVisible`, `contentType` 전송을 제거했다. Retrofit API 시그니처와 각 Repository 호출 인자를 함께 정리해 누락 없이 제거했다.
|
||||||
|
- 실행 명령/도구:
|
||||||
|
- `task(explore: 전송 경로 탐색 2건)`
|
||||||
|
- `grep("@Query(\"isAdultContentVisible\")|@Query(\"contentType\")", include="*Api.kt")`
|
||||||
|
- `apply_patch(Live/Home/Search/Explorer/AudioContent/Series API·Repository 수정)`
|
||||||
|
- `lsp_diagnostics(수정된 .kt 파일 14개)`
|
||||||
|
- `grep("@PATCH(\"/member/content-preference\")", include="UserApi.kt")`
|
||||||
|
- `grep("@Query(\"isAdultContentVisible\")|@Query(\"contentType\")", include="*Api.kt")`
|
||||||
|
- `grep("isAdultContentVisible|contentType", include="*Request*.kt")`
|
||||||
|
- `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과:
|
||||||
|
- `UserApi.kt`의 `@PATCH("/member/content-preference")`는 유지됨을 확인했다.
|
||||||
|
- 전체 `*Api.kt`에서 `@Query("isAdultContentVisible")`, `@Query("contentType")`가 0건임을 확인했다.
|
||||||
|
- `*Request*.kt`에서 두 필드는 `UpdateContentPreferenceRequest.kt` 1건만 남아 예외 API 요청으로 제한됨을 확인했다.
|
||||||
|
- `lsp_diagnostics`는 Kotlin LSP 미구성으로 진단 불가 메시지를 확인했다.
|
||||||
|
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 성공(`BUILD SUCCESSFUL`).
|
||||||
43
docs/20260328_라이브룸방장캡쳐녹화허용.md
Normal file
43
docs/20260328_라이브룸방장캡쳐녹화허용.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# 20260328 라이브룸 방장 캡쳐/녹화 허용
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [x] 방장 판별 시점과 캡처 보안 적용 지점을 확인한다. (QA: `isHost` 갱신 지점과 `FLAG_SECURE` 적용 지점 라인 확인)
|
||||||
|
- [x] 방장일 때만 `FLAG_SECURE`를 해제하고, 청취자는 기존 차단 상태를 유지한다. (QA: 방장/비방장 분기에서 `addFlags`/`clearFlags` 동작 확인)
|
||||||
|
- [x] 방장일 때 녹화 감지 기반 강제 mute가 적용되지 않도록 정합을 맞춘다. (QA: `syncCapturePrivacyMuteState` 분기 및 콜백 등록/해제 흐름 확인)
|
||||||
|
- [x] 진단/빌드/테스트/수동 QA를 수행하고 결과를 기록한다. (QA: 실행 명령과 결과 로그 확인)
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇: 방장 예외 적용을 위한 코드베이스/외부 레퍼런스 병렬 탐색을 수행했다.
|
||||||
|
- 왜: `FLAG_SECURE`를 역할 기반으로 런타임 토글할 때 라이프사이클/콜백 경합 없이 최소 변경으로 구현하기 위해서다.
|
||||||
|
- 어떻게:
|
||||||
|
- 내부 탐색(`explore`):
|
||||||
|
- `bg_016c0dfd` (host 보안 플로우 맵)
|
||||||
|
- `bg_ba4aa673` (host 판별 지연 시 race 위험 분석)
|
||||||
|
- `bg_3132d80b` (저장소 내 역할 기반 secure 패턴 탐색)
|
||||||
|
- 외부 탐색(`librarian`):
|
||||||
|
- `bg_1875bb8f` (Android 공식 문서/AOSP의 addFlags/clearFlags 근거)
|
||||||
|
- `bg_d010820d` (OSS 동적 토글 사례: Fenix/Signal/Bitwarden)
|
||||||
|
- 직접 검색:
|
||||||
|
- `grep`/`ast_grep_search`/`sg run`으로 `FLAG_SECURE`, `isHost`, 콜백 등록/해제, mute 계산식을 교차 확인
|
||||||
|
- `rg`는 로컬 환경에 설치되어 있지 않아(`command -v rg` 결과 없음) `grep`/`sg`로 대체 검증
|
||||||
|
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇: `LiveRoomActivity`에 방장 전용 캡처/녹화 허용 정책을 구현했다.
|
||||||
|
- 왜: 사용자 요청대로 방장(host)은 캡처/화면녹화를 허용하고, 청취자는 기존 차단 정책을 유지해야 하기 때문이다.
|
||||||
|
- 어떻게:
|
||||||
|
- `isHost` 판별 직후 정책 동기화를 위해 `syncCaptureSecurityPolicyByRole()`를 추가했다.
|
||||||
|
- 방장(`isHost=true`): `window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)` + 녹화 콜백 해제 + 강제 mute 상태 정리
|
||||||
|
- 청취자(`isHost=false`): `window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)` + 포그라운드에서 녹화 콜백 등록 유지
|
||||||
|
- `viewModel.roomInfoLiveData.observe`에서 `isHost` 갱신 직후 `syncCaptureSecurityPolicyByRole()`를 호출해 비동기 roomInfo 도착 시점에도 즉시 반영되도록 했다.
|
||||||
|
- `syncCapturePrivacyMuteState()`를 `val shouldMute = !isHost && isScreenRecordingActive`로 변경해 방장은 녹화 중에도 강제 mute 대상에서 제외했다.
|
||||||
|
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇: 진단/빌드/테스트/수동 QA를 완료했다.
|
||||||
|
- 왜: 컴파일 안정성과 요청 동작(방장 허용, 청취자 유지)을 실제 증거로 확인하기 위해서다.
|
||||||
|
- 어떻게:
|
||||||
|
- LSP 진단: `.kt` 서버 미구성으로 `lsp_diagnostics` 불가(환경 제약 확인), `.md` 파일 diagnostics는 없음.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과: `BUILD SUCCESSFUL`
|
||||||
|
- 수동 QA 명령: `python3` 스크립트로 정책 함수/분기/호출 순서/mute 계산식을 점검
|
||||||
|
- 수동 QA 결과: `MANUAL_QA_PASS: host can bypass capture security while listeners remain protected in source flow`
|
||||||
42
docs/20260328_라이브룸캡쳐녹화차단점검.md
Normal file
42
docs/20260328_라이브룸캡쳐녹화차단점검.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 20260328 라이브룸 캡쳐/화면녹화 차단 점검
|
||||||
|
|
||||||
|
## 구현/점검 체크리스트
|
||||||
|
- [x] `LiveRoomActivity` 내 캡쳐/화면녹화 차단 적용 지점 확인
|
||||||
|
- [x] 전체 코드베이스에서 차단 해제/우회 가능 경로(`clearFlags`, `FLAG_SECURE` 재설정 등) 탐색
|
||||||
|
- [x] 화면녹화 감지 및 후속 처리(음소거/콜백 등록 해제) 로직 검증
|
||||||
|
- [x] 외부 레퍼런스(Android 공식 동작)와 현재 구현 정합성 검증
|
||||||
|
- [x] 점검 결과 및 근거(명령/파일/라인) 기록
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇: 라이브룸 캡처/화면녹화 차단 적용 여부를 코드베이스 전역과 외부 레퍼런스로 교차 점검했다.
|
||||||
|
- 왜: 사용자 질문("현재 모든 사람이 캡쳐와 화면녹화가 불가능한지")에 대해 단일 파일 확인이 아닌 우회 경로/플랫폼 제약까지 포함한 근거를 확보해야 했기 때문이다.
|
||||||
|
- 어떻게:
|
||||||
|
- 병렬 탐색(내부): `explore` 에이전트 3건
|
||||||
|
- `bg_baa23d06` (LiveRoomActivity 보안 플로우 추적)
|
||||||
|
- `bg_c991f78f` (우회/해제 경로 탐색)
|
||||||
|
- `bg_a5c8b08e` (대체 라이브 진입 화면 탐색)
|
||||||
|
- 병렬 탐색(외부): `librarian` 에이전트 2건
|
||||||
|
- `bg_b2336b84` (Android 공식 동작/제약)
|
||||||
|
- `bg_320d7f9b` (OSS 구현 패턴 비교)
|
||||||
|
- 직접 검색/정적 검증 명령:
|
||||||
|
- `grep "FLAG_SECURE|registerScreenRecordingCallback|addScreenRecordingCallback|removeScreenRecordingCallback"` (저장소 전역)
|
||||||
|
- `ast_grep_search`/`sg run`으로 `window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)` 및 `window.clearFlags(...)` 존재 여부 확인
|
||||||
|
- `grep "clearFlags\(|setFlags\("` (app/src/main/java 전역)
|
||||||
|
- `read`로 `LiveRoomActivity.kt` 라이프사이클/콜백/mute 처리 라인 직접 확인
|
||||||
|
- SDK 레퍼런스 확인: `/Users/klaus/Library/Android/sdk/platforms/android-35/data/api-versions.xml`
|
||||||
|
- 결과(핵심 근거):
|
||||||
|
- `LiveRoomActivity.kt:390`에서 `window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)`가 무조건 적용된다.
|
||||||
|
- 앱 코드에서 `window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)`는 발견되지 않았다(`sg run`/`ast_grep_search`/`grep` 교차 검증).
|
||||||
|
- 녹화 감지/후처리 로직:
|
||||||
|
- 등록: `LiveRoomActivity.kt:1627` (`windowManager.addScreenRecordingCallback`)
|
||||||
|
- 해제: `LiveRoomActivity.kt:1654` (`windowManager.removeScreenRecordingCallback`)
|
||||||
|
- 상태 반영: `LiveRoomActivity.kt:1659-1693` (`isScreenRecordingActive` -> `isCapturePrivacyMuted` -> `applyEffectiveAudioMuteState`)
|
||||||
|
- API 레벨 근거:
|
||||||
|
- `addScreenRecordingCallback`/`removeScreenRecordingCallback`/`SCREEN_RECORDING_STATE_VISIBLE`는 API 35부터(`api-versions.xml:70441,70451,70475`).
|
||||||
|
- `FLAG_SECURE`는 Android 플랫폼 상수로 존재(`api-versions.xml:70558`).
|
||||||
|
- 권한 근거:
|
||||||
|
- `AndroidManifest.xml:19`에 `<uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />` 선언이 존재한다.
|
||||||
|
- 결론:
|
||||||
|
- **LiveRoomActivity 화면에 입장한 사용자 기준으로는 캡처/녹화 노출이 차단되도록 구현되어 있다(FLAG_SECURE 적용 + 해제 경로 부재).**
|
||||||
|
- 다만 Android 공식 문서 범위상 `FLAG_SECURE`는 기본적으로 스크린샷/비보안 디스플레이 노출 차단을 보장하며, 모든 녹화 시나리오 100% 차단을 플랫폼이 절대 보장한다고 단정할 수는 없다.
|
||||||
29
docs/20260328_마이페이지본인인증버튼숨김정렬유지.md
Normal file
29
docs/20260328_마이페이지본인인증버튼숨김정렬유지.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 마이페이지 본인인증 버튼 숨김 시 정렬 유지 수정
|
||||||
|
|
||||||
|
## 작업 목표
|
||||||
|
- 국가가 한국이 아닌 경우 `btn_identity_verification`을 숨기더라도 Function Buttons Grid의 다른 아이콘 위치가 기존과 동일하게 유지되도록 수정한다.
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
- [x] AC1: `countryCode != "KR"`인 경우 `btn_identity_verification`이 화면에 보이지 않는다.
|
||||||
|
- QA: `btnIdentityVerification.root.visibility`가 `View.INVISIBLE`로 설정되어 슬롯 공간이 유지되는지 코드 확인
|
||||||
|
- [x] AC2: `countryCode != "KR"`인 경우에도 같은 행의 다른 버튼(`btn_notice`, `btn_event`, `btn_customer_service`) 위치가 기존과 동일하게 유지된다.
|
||||||
|
- QA: `View.GONE` 대신 `View.INVISIBLE` 사용 여부 확인
|
||||||
|
- [x] AC3: `countryCode == "KR"`인 경우 기존 본인인증 버튼 노출/동작 로직이 유지된다.
|
||||||
|
- QA: KR 분기에서 기존 `View.VISIBLE` + 인증 상태별 버튼 설정 코드 보존 확인
|
||||||
|
- [x] AC4: 변경 파일 진단/테스트/빌드 검증을 통과한다.
|
||||||
|
- QA: `lsp_diagnostics`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:assembleDebug`
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇/왜/어떻게: Function Buttons Grid 두 번째 행이 `LinearLayout`의 `layout_weight` 기반이어서 `btn_identity_verification`을 `GONE` 처리하면 남은 버튼이 재배치된다. 슬롯은 유지하고 아이콘만 숨기기 위해 non-KR 분기에서 `View.GONE`을 `View.INVISIBLE`로 변경했다.
|
||||||
|
- 실행 명령/도구:
|
||||||
|
- `apply_patch(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)`
|
||||||
|
- `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)`
|
||||||
|
- `lsp_diagnostics(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)`
|
||||||
|
- `lsp_diagnostics(docs/20260328_마이페이지본인인증버튼숨김정렬유지.md)`
|
||||||
|
- `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||||
|
- 결과:
|
||||||
|
- non-KR 분기에서 `btnIdentityVerification.root.visibility = View.INVISIBLE`로 반영되어 버튼 슬롯 유지 조건을 충족했다.
|
||||||
|
- KR 분기의 `View.VISIBLE` 및 인증 상태별 버튼 구성 로직은 변경 없이 유지됐다.
|
||||||
|
- `.kt` 파일 대상 `lsp_diagnostics`는 현재 환경에 Kotlin LSP가 없어 실행 불가(`No LSP server configured for extension: .kt`)였고, 문서 파일 진단은 이슈 없음.
|
||||||
|
- `:app:testDebugUnitTest`, `:app:assembleDebug`를 포함한 Gradle 실행이 `BUILD SUCCESSFUL`로 완료됐다.
|
||||||
49
docs/20260328_멤버정보응답하위호환수정.md
Normal file
49
docs/20260328_멤버정보응답하위호환수정.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# 20260328_멤버정보응답하위호환수정.md
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
- 이전 서버의 `/member/info` 응답에 `countryCode`, `isAdultContentVisible`, `contentType`가 없어도 신규 앱이 동일하게 동작하도록 하위 호환을 보장한다.
|
||||||
|
|
||||||
|
## 요구사항 해석(확정)
|
||||||
|
- `GetMemberInfoResponse`의 신규 필드 3개는 구서버 응답에서 누락될 수 있으므로 nullable로 처리한다.
|
||||||
|
- `MainViewModel.getMemberInfo()` 동기화 시 누락된 값은 로컬 저장값(없으면 안전 기본값)으로 대체한다.
|
||||||
|
|
||||||
|
## 완료 기준 (Acceptance Criteria)
|
||||||
|
- [x] AC1: 구서버 응답(JSON에 신규 3개 필드 누락) 역직렬화가 실패하지 않는다.
|
||||||
|
- [x] AC2: 구서버 응답 수신 시 `SharedPreferenceManager.countryCode/isAdultContentVisible/contentPreference`가 null로 오염되지 않고 기존 동작을 유지한다.
|
||||||
|
- [x] AC3: 관련 단위 테스트와 디버그 빌드가 성공한다.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [x] `app/src/main/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponse.kt`
|
||||||
|
- 신규 필드(`countryCode`, `isAdultContentVisible`, `contentType`)를 nullable + default null로 변경
|
||||||
|
- [x] `app/src/main/java/kr/co/vividnext/sodalive/main/MainViewModel.kt`
|
||||||
|
- 멤버 정보 동기화 시 신규 필드 null-safe fallback 적용
|
||||||
|
- [x] `app/src/test/java/kr/co/vividnext/sodalive/settings/notification/GetMemberInfoResponseCompatibilityTest.kt`
|
||||||
|
- 구서버 응답 누락 필드 역직렬화 및 fallback 동작 검증 테스트 추가
|
||||||
|
- [x] 검증 실행
|
||||||
|
- `lsp_diagnostics`(수정 파일)
|
||||||
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest"`
|
||||||
|
- `./gradlew :app:testDebugUnitTest`
|
||||||
|
- `./gradlew :app:assembleDebug`
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 기록 템플릿(후속 누적):
|
||||||
|
- YYYY-MM-DD
|
||||||
|
- 무엇/왜/어떻게:
|
||||||
|
- 실행 명령/도구:
|
||||||
|
- `명령 또는 사용 도구`
|
||||||
|
- 결과:
|
||||||
|
|
||||||
|
- 2026-03-28
|
||||||
|
- 무엇/왜/어떻게: 구서버(`/member/info`)에서 신규 필드 3종이 누락돼도 신규 앱이 동일 동작하도록 응답 모델 nullable 처리 + 멤버 정보 동기화 fallback 로직을 적용했고, 역직렬화 호환 테스트를 추가했다.
|
||||||
|
- 실행 명령/도구:
|
||||||
|
- `apply_patch(GetMemberInfoResponse.kt, MainViewModel.kt, GetMemberInfoResponseCompatibilityTest.kt, docs/20260328_멤버정보응답하위호환수정.md)`
|
||||||
|
- `lsp_diagnostics(GetMemberInfoResponse.kt, MainViewModel.kt, GetMemberInfoResponseCompatibilityTest.kt)`
|
||||||
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest"`
|
||||||
|
- `./gradlew :app:testDebugUnitTest`
|
||||||
|
- `./gradlew :app:assembleDebug`
|
||||||
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest" :app:assembleDebug`
|
||||||
|
- `read(app/build/test-results/testDebugUnitTest/TEST-kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponseCompatibilityTest.xml)`
|
||||||
|
- 결과:
|
||||||
|
- `lsp_diagnostics`는 `.kt` LSP 서버 미구성으로 실행 불가를 확인했다.
|
||||||
|
- 호환성 테스트 2건(구서버 누락 필드 역직렬화/신규 필드 정상 매핑)이 모두 통과했다.
|
||||||
|
- 전체 단위 테스트와 디버그 빌드가 모두 성공했고, 마지막 재검증 명령에서도 성공을 재확인했다.
|
||||||
57
docs/20260329_라이브룸_UI미갱신_버그수정.md
Normal file
57
docs/20260329_라이브룸_UI미갱신_버그수정.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 라이브룸 UI 미갱신 버그 수정
|
||||||
|
|
||||||
|
## 현상
|
||||||
|
- 라이브 입장 후 공지, 메뉴판 터치 시 UI가 보이지 않음
|
||||||
|
- 상대방 채팅이 화면에 갱신되지 않음
|
||||||
|
- 방장이 아닌 유저에게서 두드러지게 나타남
|
||||||
|
- 키보드가 올라오거나 화면에 변화가 생기면 모든 것이 해결됨
|
||||||
|
|
||||||
|
## 원인 분석
|
||||||
|
- `BaseActivity`에서 `WindowCompat.setDecorFitsSystemWindows(window, false)` (edge-to-edge) 적용
|
||||||
|
- `LiveRoomActivity`의 manifest에 `adjustPan` 설정과 edge-to-edge가 충돌
|
||||||
|
- `adjustPan`은 시스템이 창을 pan 하려 하지만, edge-to-edge 모드에서는 앱이 insets을 직접 처리
|
||||||
|
- 이 충돌로 DecorView 내부 스크롤 트래킹 상태가 불일치하여 `invalidate()` 더티 영역 계산 오류 발생
|
||||||
|
- 키보드가 올라가면 시스템이 WindowInsets를 재분배하고, `OnApplyWindowInsetsListener`에서 `setPadding()` 호출 → `requestLayout()` → 전체 레이아웃 패스가 강제 수행되어 해결됨
|
||||||
|
|
||||||
|
## 수정 방법
|
||||||
|
- RTM과 RTC가 모두 연결 완료된 시점에 키보드를 프로그래밍적으로 올렸다 내려 레이아웃을 강제 갱신
|
||||||
|
- `isRtcJoined`, `isRtmJoined` 플래그로 두 연결 상태를 추적
|
||||||
|
- 두 플래그가 모두 true가 되면 `tryForceLayoutRefresh()`를 호출
|
||||||
|
|
||||||
|
### 눈속임 처리 (사용자에게 변화가 보이지 않도록)
|
||||||
|
1. 로딩 다이얼로그가 화면을 덮고 있는 동안 키보드 트릭을 수행
|
||||||
|
2. `adjustNothing`으로 임시 전환하여 키보드가 화면을 밀어올리지 않도록 방지
|
||||||
|
3. 키보드 show → 200ms 후 hide → `adjustPan` 복원 → 로딩 다이얼로그 dismiss
|
||||||
|
4. RTM 콜백의 `loadingDialog.dismiss()`를 `tryForceLayoutRefresh()` 내부로 이동
|
||||||
|
|
||||||
|
## 수정 계획
|
||||||
|
|
||||||
|
- [x] `isRtcJoined`, `isRtmJoined` 플래그 추가
|
||||||
|
- [x] `onJoinChannelSuccess`에서 `isRtcJoined = true` 설정 및 `tryForceLayoutRefresh()` 호출
|
||||||
|
- [x] RTM 성공 콜백에서 `isRtmJoined = true` 설정 및 `tryForceLayoutRefresh()` 호출
|
||||||
|
- [x] `tryForceLayoutRefresh()` 메서드 구현
|
||||||
|
- [x] adjustNothing 임시 전환으로 화면 이동 방지
|
||||||
|
- [x] 로딩 다이얼로그 뒤에서 키보드 트릭 수행
|
||||||
|
- [x] 완료 후 adjustPan 복원 및 로딩 다이얼로그 dismiss
|
||||||
|
- [x] Boolean 반환으로 RTM 콜백에서 fallback dismiss 처리
|
||||||
|
- [x] 빌드 검증
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
|
||||||
|
### 2026-03-29 빌드 검증 (1차 - adjustNothing 방식)
|
||||||
|
- 명령: `./gradlew :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL (16s, 46 tasks)
|
||||||
|
- 변경 파일: `AndroidManifest.xml` (adjustPan→adjustNothing), `LiveRoomActivity.kt` (API S→R)
|
||||||
|
- 비고: 실기기에서 효과 없음 → 되돌림
|
||||||
|
|
||||||
|
### 2026-03-29 빌드 검증 (2차 - 키보드 강제 갱신 방식)
|
||||||
|
- 명령: `./gradlew :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL (21s, 46 tasks)
|
||||||
|
- 변경 파일: `LiveRoomActivity.kt` (RTM/RTC 연결 완료 후 키보드 올렸다 내리기)
|
||||||
|
- 비고: 키보드가 화면을 위로 밀어올리는 것이 사용자에게 보임 → 눈속임 개선 필요
|
||||||
|
|
||||||
|
### 2026-03-29 빌드 검증 (3차 - 눈속임 개선)
|
||||||
|
- 명령: `./gradlew :app:assembleDebug`
|
||||||
|
- 결과: BUILD SUCCESSFUL (18s, 46 tasks)
|
||||||
|
- 변경 파일: `LiveRoomActivity.kt`
|
||||||
|
- 방식: adjustNothing 임시 전환 + 로딩 다이얼로그 뒤에서 키보드 트릭 수행
|
||||||
Reference in New Issue
Block a user