diff --git a/app/build.gradle b/app/build.gradle index c66fa01d4..361c2453d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,7 +88,6 @@ dependencies { implementation projects.feature.home implementation projects.feature.detail implementation projects.feature.search - implementation projects.feature.theater implementation projects.feature.settings implementation projects.feature.theme.api runtimeOnly projects.feature.theme.impl diff --git a/core/designsystem/src/main/java/soup/movie/core/designsystem/icon/MovieIcons.kt b/core/designsystem/src/main/java/soup/movie/core/designsystem/icon/MovieIcons.kt index 94a8ebbea..4b6388a61 100644 --- a/core/designsystem/src/main/java/soup/movie/core/designsystem/icon/MovieIcons.kt +++ b/core/designsystem/src/main/java/soup/movie/core/designsystem/icon/MovieIcons.kt @@ -19,11 +19,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.Subject import androidx.compose.material.icons.outlined.PrivacyTip -import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.Close -import androidx.compose.material.icons.rounded.DragHandle -import androidx.compose.material.icons.rounded.Edit import androidx.compose.material.icons.rounded.FilterList import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.Palette @@ -34,12 +31,9 @@ import androidx.compose.material.icons.rounded.ViewModule import soup.movie.core.designsystem.R object MovieIcons { - val Add = Icons.Rounded.Add val ArrowBack = Icons.AutoMirrored.Rounded.ArrowBack val Check = Icons.Rounded.Check val Close = Icons.Rounded.Close - val DragHandle = Icons.Rounded.DragHandle - val Edit = Icons.Rounded.Edit val FilterList = Icons.Rounded.FilterList val Info = Icons.Rounded.Info val Palette = Icons.Rounded.Palette @@ -58,7 +52,6 @@ object MovieIcons { val LoadingLogo = R.drawable.ic_loading_logo val YouTube = R.drawable.ic_logo_youtube val Metacritic = R.drawable.ic_metacritic - val NoTheaters = R.drawable.ic_round_no_theaters val RottenTomatoes = R.drawable.ic_rt val RottenTomatoesFresh = R.drawable.ic_rt_fresh val RottenTomatoesRotten = R.drawable.ic_rt_rotten diff --git a/core/designsystem/src/main/res/drawable/ic_round_no_theaters.xml b/core/designsystem/src/main/res/drawable/ic_round_no_theaters.xml deleted file mode 100644 index 1048b5421..000000000 --- a/core/designsystem/src/main/res/drawable/ic_round_no_theaters.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/core/resources/src/main/res/values-ko/strings.xml b/core/resources/src/main/res/values-ko/strings.xml index 75c6db290..ae44beb23 100644 --- a/core/resources/src/main/res/values-ko/strings.xml +++ b/core/resources/src/main/res/values-ko/strings.xml @@ -36,31 +36,14 @@ 설정 - 자주가는 극장 - 선택된 극장이 없습니다 테마 - 극장 순서 설정 - - 선호하는 극장 편집 - - %d개까지 선택할 수 있습니다.\n더 추가하려면 선택된 항목을 지워주세요. - - 극장 선택 - 선호하는 극장을 선택해주세요 - 완료 - 취소 - - 선택된 극장이 없습니다 - 테마 선택 어둡게 밝게 기본 (시스템 설정) 기본 (절전모드 시 어둡게) - 지도 - 청소년관람불가 15세관람가 12세관람가 diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml index b4c960eb3..bff0e296f 100644 --- a/core/resources/src/main/res/values/strings.xml +++ b/core/resources/src/main/res/values/strings.xml @@ -36,31 +36,14 @@ Settings - Favorite Theaters - There are no favorite theaters Theme - Theater Order Setting - - Edit Favorite Theaters - - The favorite theaters\'s count is limited to %d.\nIf wants to add more, remove seleted items. - - Select theater - Please select the favorite theaters - Confirm - Cancel - - There are no favorite theaters - Select theme Dark Light Default (Set system setting) Default (Set by battery saver) - Map - PG 18 PG 15 PG 12 diff --git a/data/database/api/src/main/java/soup/movie/data/database/LocalDataSource.kt b/data/database/api/src/main/java/soup/movie/data/database/LocalDataSource.kt index a57f694f6..299fa237b 100644 --- a/data/database/api/src/main/java/soup/movie/data/database/LocalDataSource.kt +++ b/data/database/api/src/main/java/soup/movie/data/database/LocalDataSource.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.Flow import soup.movie.model.MovieListModel import soup.movie.model.MovieModel import soup.movie.model.OpenDateAlarmModel -import soup.movie.model.TheaterAreaGroupModel interface LocalDataSource { @@ -32,9 +31,6 @@ interface LocalDataSource { suspend fun getAllMovieList(): List suspend fun getNowMovieList(): List - fun saveCodeList(response: TheaterAreaGroupModel) - fun getCodeList(): TheaterAreaGroupModel? - suspend fun addFavoriteMovie(movie: MovieModel) suspend fun removeFavoriteMovie(movieId: String) fun getFavoriteMovieList(): Flow> diff --git a/data/database/impl/src/main/java/soup/movie/data/database/impl/LocalDataSourceImpl.kt b/data/database/impl/src/main/java/soup/movie/data/database/impl/LocalDataSourceImpl.kt index 1bbd5e594..17f71b109 100644 --- a/data/database/impl/src/main/java/soup/movie/data/database/impl/LocalDataSourceImpl.kt +++ b/data/database/impl/src/main/java/soup/movie/data/database/impl/LocalDataSourceImpl.kt @@ -35,7 +35,6 @@ import soup.movie.log.Logger import soup.movie.model.MovieListModel import soup.movie.model.MovieModel import soup.movie.model.OpenDateAlarmModel -import soup.movie.model.TheaterAreaGroupModel import javax.inject.Inject class LocalDataSourceImpl @Inject constructor( @@ -44,8 +43,6 @@ class LocalDataSourceImpl @Inject constructor( private val cacheDao: MovieCacheDao, ) : LocalDataSource { - private var codeResponse: TheaterAreaGroupModel? = null - override suspend fun saveNowMovieList(movieList: MovieListModel) { saveMovieListAs(TYPE_NOW, movieList) favoriteMovieDao.updateAll(movieList.list.map { it.toFavoriteMovieEntity() }) @@ -111,14 +108,6 @@ class LocalDataSourceImpl @Inject constructor( } } - override fun saveCodeList(response: TheaterAreaGroupModel) { - codeResponse = response - } - - override fun getCodeList(): TheaterAreaGroupModel? { - return codeResponse - } - override suspend fun addFavoriteMovie(movie: MovieModel) { favoriteMovieDao.insertFavoriteMovie(movie.toFavoriteMovieEntity()) } diff --git a/data/model/src/main/java/soup/movie/model/AreaModel.kt b/data/model/src/main/java/soup/movie/model/AreaModel.kt deleted file mode 100644 index 7c2c464d1..000000000 --- a/data/model/src/main/java/soup/movie/model/AreaModel.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.model - -data class AreaModel( - val code: String, - val name: String, -) diff --git a/data/model/src/main/java/soup/movie/model/TheaterAreaGroupModel.kt b/data/model/src/main/java/soup/movie/model/TheaterAreaGroupModel.kt deleted file mode 100644 index 705bce527..000000000 --- a/data/model/src/main/java/soup/movie/model/TheaterAreaGroupModel.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.model - -data class TheaterAreaGroupModel( - val cgv: List, - val lotte: List, - val megabox: List, -) diff --git a/data/model/src/main/java/soup/movie/model/TheaterAreaModel.kt b/data/model/src/main/java/soup/movie/model/TheaterAreaModel.kt deleted file mode 100644 index 61f74c4d6..000000000 --- a/data/model/src/main/java/soup/movie/model/TheaterAreaModel.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.model - -data class TheaterAreaModel( - val area: AreaModel, - val theaterList: List, -) diff --git a/data/model/src/main/java/soup/movie/model/TheaterModel.kt b/data/model/src/main/java/soup/movie/model/TheaterModel.kt deleted file mode 100644 index 9a11c4550..000000000 --- a/data/model/src/main/java/soup/movie/model/TheaterModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.model - -data class TheaterModel( - val id: String, - val type: TheaterTypeModel, - val code: String, - val name: String, - val lng: Double, - val lat: Double, -) diff --git a/data/model/src/main/java/soup/movie/model/TheaterTypeModel.kt b/data/model/src/main/java/soup/movie/model/TheaterTypeModel.kt deleted file mode 100644 index 3ce26188f..000000000 --- a/data/model/src/main/java/soup/movie/model/TheaterTypeModel.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2022 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.model - -enum class TheaterTypeModel { - CGV, - LOTTE, - MEGABOX, -} diff --git a/data/network/api/src/main/java/soup/movie/data/network/RemoteDataSource.kt b/data/network/api/src/main/java/soup/movie/data/network/RemoteDataSource.kt index b6199541e..f25d7a74a 100644 --- a/data/network/api/src/main/java/soup/movie/data/network/RemoteDataSource.kt +++ b/data/network/api/src/main/java/soup/movie/data/network/RemoteDataSource.kt @@ -17,7 +17,6 @@ package soup.movie.data.network import soup.movie.data.network.response.MovieDetailResponse import soup.movie.data.network.response.MovieListResponse -import soup.movie.data.network.response.TheaterAreaGroupResponse interface RemoteDataSource { @@ -31,7 +30,4 @@ interface RemoteDataSource { // 영화 상세정보 suspend fun getMovieDetail(movieId: String): MovieDetailResponse - - // 공통코드 - suspend fun getCodeList(): TheaterAreaGroupResponse } diff --git a/data/network/api/src/main/java/soup/movie/data/network/response/AreaResponse.kt b/data/network/api/src/main/java/soup/movie/data/network/response/AreaResponse.kt deleted file mode 100644 index eacc72eba..000000000 --- a/data/network/api/src/main/java/soup/movie/data/network/response/AreaResponse.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.data.network.response - -import kotlinx.serialization.Serializable -import soup.movie.model.AreaModel - -@Serializable -class AreaResponse( - val code: String, - val name: String, -) - -fun AreaResponse.asModel(): AreaModel { - return AreaModel( - code = code, - name = name, - ) -} diff --git a/data/network/api/src/main/java/soup/movie/data/network/response/TheaterAreaGroupResponse.kt b/data/network/api/src/main/java/soup/movie/data/network/response/TheaterAreaGroupResponse.kt deleted file mode 100644 index c10eaad54..000000000 --- a/data/network/api/src/main/java/soup/movie/data/network/response/TheaterAreaGroupResponse.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.data.network.response - -import kotlinx.serialization.Serializable -import soup.movie.model.TheaterAreaGroupModel - -@Serializable -class TheaterAreaGroupResponse( - val lastUpdateTime: Long, - val cgv: List = emptyList(), - val lotte: List = emptyList(), - val megabox: List = emptyList(), -) - -fun TheaterAreaGroupResponse.asModel(): TheaterAreaGroupModel { - return TheaterAreaGroupModel( - cgv = cgv.map { it.asModel() }, - lotte = lotte.map { it.asModel() }, - megabox = megabox.map { it.asModel() }, - ) -} diff --git a/data/network/api/src/main/java/soup/movie/data/network/response/TheaterAreaResponse.kt b/data/network/api/src/main/java/soup/movie/data/network/response/TheaterAreaResponse.kt deleted file mode 100644 index 75c9e5d9c..000000000 --- a/data/network/api/src/main/java/soup/movie/data/network/response/TheaterAreaResponse.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.data.network.response - -import kotlinx.serialization.Serializable -import soup.movie.model.TheaterAreaModel - -@Serializable -class TheaterAreaResponse( - val area: AreaResponse, - val theaterList: List = emptyList(), -) - -fun TheaterAreaResponse.asModel(): TheaterAreaModel { - return TheaterAreaModel( - area = area.asModel(), - theaterList = theaterList.map { it.asModel() }, - ) -} diff --git a/data/network/api/src/main/java/soup/movie/data/network/response/TheaterResponse.kt b/data/network/api/src/main/java/soup/movie/data/network/response/TheaterResponse.kt deleted file mode 100644 index b0d0274d5..000000000 --- a/data/network/api/src/main/java/soup/movie/data/network/response/TheaterResponse.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.data.network.response - -import kotlinx.serialization.Serializable -import soup.movie.model.TheaterModel -import soup.movie.model.TheaterTypeModel - -@Serializable -class TheaterResponse( - val type: String, - val code: String, - val name: String, - val lng: Double, - val lat: Double, -) - -fun TheaterResponse.asModel(): TheaterModel { - return TheaterModel( - id = "$type:$code", - type = TheaterTypeParser.parse(type), - code = code, - name = name, - lng = lng, - lat = lat, - ) -} - -private object TheaterTypeParser { - - fun parse(type: String): TheaterTypeModel { - return when (type) { - "C" -> TheaterTypeModel.CGV - "L" -> TheaterTypeModel.LOTTE - "M" -> TheaterTypeModel.MEGABOX - else -> throw IllegalArgumentException("$type is not valid type.") - } - } -} diff --git a/data/network/impl/src/main/java/soup/movie/data/network/impl/MovieApiService.kt b/data/network/impl/src/main/java/soup/movie/data/network/impl/MovieApiService.kt index 0cc15e7b1..dc0bed9d0 100644 --- a/data/network/impl/src/main/java/soup/movie/data/network/impl/MovieApiService.kt +++ b/data/network/impl/src/main/java/soup/movie/data/network/impl/MovieApiService.kt @@ -21,7 +21,6 @@ import retrofit2.http.Path import soup.movie.data.network.impl.OkHttpInterceptors.HEADER_USE_CACHE import soup.movie.data.network.response.MovieDetailResponse import soup.movie.data.network.response.MovieListResponse -import soup.movie.data.network.response.TheaterAreaGroupResponse interface MovieApiService { @@ -47,8 +46,4 @@ interface MovieApiService { @Headers(HEADER_USE_CACHE) @GET("detail/{movieId}.json") suspend fun getMovieDetail(@Path("movieId") movieId: String): MovieDetailResponse - - // 공통코드 - @GET("code.json") - suspend fun getCodeList(): TheaterAreaGroupResponse } diff --git a/data/network/impl/src/main/java/soup/movie/data/network/impl/RemoteDataSourceImpl.kt b/data/network/impl/src/main/java/soup/movie/data/network/impl/RemoteDataSourceImpl.kt index bbbbc5185..b3319a4c8 100644 --- a/data/network/impl/src/main/java/soup/movie/data/network/impl/RemoteDataSourceImpl.kt +++ b/data/network/impl/src/main/java/soup/movie/data/network/impl/RemoteDataSourceImpl.kt @@ -18,7 +18,6 @@ package soup.movie.data.network.impl import soup.movie.data.network.RemoteDataSource import soup.movie.data.network.response.MovieDetailResponse import soup.movie.data.network.response.MovieListResponse -import soup.movie.data.network.response.TheaterAreaGroupResponse import javax.inject.Inject class RemoteDataSourceImpl @Inject constructor( @@ -44,8 +43,4 @@ class RemoteDataSourceImpl @Inject constructor( override suspend fun getMovieDetail(movieId: String): MovieDetailResponse { return apiService.getMovieDetail(movieId) } - - override suspend fun getCodeList(): TheaterAreaGroupResponse { - return apiService.getCodeList() - } } diff --git a/data/repository/api/src/main/java/soup/movie/data/repository/TheaterRepository.kt b/data/repository/api/src/main/java/soup/movie/data/repository/TheaterRepository.kt deleted file mode 100644 index c0c2c2e18..000000000 --- a/data/repository/api/src/main/java/soup/movie/data/repository/TheaterRepository.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2022 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.data.repository - -import soup.movie.model.TheaterAreaGroupModel - -interface TheaterRepository { - suspend fun getCodeList(): TheaterAreaGroupModel -} diff --git a/data/repository/impl/src/main/java/soup/movie/data/repository/impl/TheaterRepositoryImpl.kt b/data/repository/impl/src/main/java/soup/movie/data/repository/impl/TheaterRepositoryImpl.kt deleted file mode 100644 index 0490b0b14..000000000 --- a/data/repository/impl/src/main/java/soup/movie/data/repository/impl/TheaterRepositoryImpl.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.data.repository.impl - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext -import soup.movie.common.IoDispatcher -import soup.movie.data.database.LocalDataSource -import soup.movie.data.network.RemoteDataSource -import soup.movie.data.network.response.asModel -import soup.movie.data.repository.TheaterRepository -import soup.movie.model.TheaterAreaGroupModel -import javax.inject.Inject - -class TheaterRepositoryImpl @Inject constructor( - private val local: LocalDataSource, - private val remote: RemoteDataSource, - @IoDispatcher private val ioDispatcher: CoroutineDispatcher, -) : TheaterRepository { - - override suspend fun getCodeList(): TheaterAreaGroupModel { - return withContext(ioDispatcher) { - local.getCodeList() ?: remote.getCodeList().asModel().also { - local.saveCodeList(it) - } - } - } -} diff --git a/data/repository/impl/src/main/java/soup/movie/data/repository/impl/di/RepositoryModule.kt b/data/repository/impl/src/main/java/soup/movie/data/repository/impl/di/RepositoryModule.kt index 6f13d4336..ee6741874 100644 --- a/data/repository/impl/src/main/java/soup/movie/data/repository/impl/di/RepositoryModule.kt +++ b/data/repository/impl/src/main/java/soup/movie/data/repository/impl/di/RepositoryModule.kt @@ -20,9 +20,7 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import soup.movie.data.repository.MovieRepository -import soup.movie.data.repository.TheaterRepository import soup.movie.data.repository.impl.MovieRepositoryImpl -import soup.movie.data.repository.impl.TheaterRepositoryImpl import javax.inject.Singleton @Module @@ -34,10 +32,4 @@ interface RepositoryModule { fun provideMovieRepository( movieRepositoryImpl: MovieRepositoryImpl, ): MovieRepository - - @Binds - @Singleton - fun provideTheaterRepository( - theaterRepositoryImpl: TheaterRepositoryImpl, - ): TheaterRepository } diff --git a/data/settings/api/src/main/java/soup/movie/data/settings/AppSettings.kt b/data/settings/api/src/main/java/soup/movie/data/settings/AppSettings.kt index 301b54b0a..93f3c702a 100644 --- a/data/settings/api/src/main/java/soup/movie/data/settings/AppSettings.kt +++ b/data/settings/api/src/main/java/soup/movie/data/settings/AppSettings.kt @@ -16,7 +16,6 @@ package soup.movie.data.settings import kotlinx.coroutines.flow.Flow -import soup.movie.model.TheaterModel import soup.movie.model.settings.AgeFilter import soup.movie.model.settings.GenreFilter import soup.movie.model.settings.TheaterFilter @@ -35,8 +34,4 @@ interface AppSettings { suspend fun setThemeOption(themeOption: String) suspend fun getThemeOption(): String fun getThemeOptionFlow(): Flow - - suspend fun setFavoriteTheaterList(list: List) - suspend fun getFavoriteTheaterList(): List - fun getFavoriteTheaterListFlow(): Flow> } diff --git a/data/settings/impl/src/main/java/soup/movie/data/settings/impl/AppSettingsImpl.kt b/data/settings/impl/src/main/java/soup/movie/data/settings/impl/AppSettingsImpl.kt index 1e73f42ad..0a05eebb4 100644 --- a/data/settings/impl/src/main/java/soup/movie/data/settings/impl/AppSettingsImpl.kt +++ b/data/settings/impl/src/main/java/soup/movie/data/settings/impl/AppSettingsImpl.kt @@ -23,25 +23,28 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json +import soup.movie.common.ApplicationScope +import soup.movie.common.IoDispatcher import soup.movie.data.settings.AppSettings -import soup.movie.model.TheaterModel -import soup.movie.model.TheaterTypeModel import soup.movie.model.settings.AgeFilter import soup.movie.model.settings.GenreFilter import soup.movie.model.settings.TheaterFilter - -class AppSettingsImpl( - private val context: Context, - private val ioDispatcher: CoroutineDispatcher, +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppSettingsImpl @Inject constructor( + @ApplicationContext private val context: Context, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher, + @ApplicationScope private val coroutineScope: CoroutineScope, ) : AppSettings { private val Context.preferencesName: String @@ -56,6 +59,12 @@ class AppSettingsImpl( private val theaterFilterKey = intPreferencesKey("theater_filter") + init { + coroutineScope.launch { + clearFavoriteTheaterList() + } + } + override suspend fun setTheaterFilter(theaterFilter: TheaterFilter) { context.dataStore.edit { settings -> settings[theaterFilterKey] = theaterFilter.toFlags() @@ -125,27 +134,10 @@ class AppSettingsImpl( private val favoriteTheaterListKey = stringPreferencesKey("favorite_theaters") - override suspend fun setFavoriteTheaterList(list: List) { + private suspend fun clearFavoriteTheaterList() { withContext(ioDispatcher) { context.dataStore.edit { settings -> - val rawList = list.map { it.toRaw() } - settings[favoriteTheaterListKey] = Json.encodeToString(rawList) - } - } - } - - override suspend fun getFavoriteTheaterList(): List { - return getFavoriteTheaterListFlow().first() - } - - override fun getFavoriteTheaterListFlow(): Flow> { - return context.dataStore.data.map { preferences -> - val string = preferences[favoriteTheaterListKey] - if (string != null) { - val rawList: List = Json.decodeFromString(string) - rawList.map { it.toModel() } - } else { - emptyList() + settings.remove(favoriteTheaterListKey) } } } @@ -155,31 +147,3 @@ class AppSettingsImpl( private const val SEPARATOR = "|" } } - -@Serializable -private data class RawTheater( - val id: String, - val type: String, - val code: String, - val name: String, - val lng: Double, - val lat: Double, -) { - fun toModel() = TheaterModel( - id = id, - type = TheaterTypeModel.valueOf(type), - code = code, - name = name, - lng = lng, - lat = lat, - ) -} - -private fun TheaterModel.toRaw() = RawTheater( - id = id, - type = type.name, - code = code, - name = name, - lng = lng, - lat = lat, -) diff --git a/data/settings/impl/src/main/java/soup/movie/data/settings/impl/di/DataSettingsModule.kt b/data/settings/impl/src/main/java/soup/movie/data/settings/impl/di/DataSettingsModule.kt index a3e3a41e0..a64030685 100644 --- a/data/settings/impl/src/main/java/soup/movie/data/settings/impl/di/DataSettingsModule.kt +++ b/data/settings/impl/src/main/java/soup/movie/data/settings/impl/di/DataSettingsModule.kt @@ -15,28 +15,19 @@ */ package soup.movie.data.settings.impl.di -import android.content.Context +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.CoroutineDispatcher -import soup.movie.common.IoDispatcher import soup.movie.data.settings.AppSettings import soup.movie.data.settings.impl.AppSettingsImpl -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -class DataSettingsModule { +interface DataSettingsModule { - @Singleton - @Provides - fun provideAppSettings( - @ApplicationContext context: Context, - @IoDispatcher ioDispatcher: CoroutineDispatcher, - ): AppSettings { - return AppSettingsImpl(context, ioDispatcher) - } + @Binds + fun bindAppSettings( + impl: AppSettingsImpl, + ): AppSettings } diff --git a/feature/settings/build.gradle b/feature/settings/build.gradle index f6f818612..31b5b59fe 100644 --- a/feature/settings/build.gradle +++ b/feature/settings/build.gradle @@ -15,7 +15,6 @@ dependencies { implementation projects.core.resources implementation projects.data.settings.api implementation projects.data.model - implementation projects.feature.theater implementation projects.feature.theme.api implementation libs.kotlin.stdlib diff --git a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsNavGraph.kt b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsNavGraph.kt index 085aa41f2..a4a68c5a4 100644 --- a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsNavGraph.kt +++ b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsNavGraph.kt @@ -22,18 +22,12 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import soup.compose.material.motion.animation.materialSharedAxisZIn import soup.compose.material.motion.animation.materialSharedAxisZOut -import soup.movie.feature.theater.edit.TheaterEditScreen -import soup.movie.feature.theater.edit.TheaterEditViewModel -import soup.movie.feature.theater.sort.TheaterSortScreen -import soup.movie.feature.theater.sort.TheaterSortViewModel import soup.movie.feature.theme.ThemeEntry import soup.movie.feature.theme.rememberThemeEntry private enum class Screen(val route: String) { Settings("SettingsScreen"), ThemeOption("ThemeEditScreen"), - TheaterSort("TheaterSortScreen"), - TheaterEdit("TheaterEditScreen"), } @Composable @@ -54,33 +48,11 @@ fun SettingsNavGraph() { onThemeEditClick = { navController.navigate(Screen.ThemeOption.route) }, - onTheaterEditClick = { - navController.navigate(Screen.TheaterSort.route) - }, ) } composable(Screen.ThemeOption.route) { val entry: ThemeEntry = rememberThemeEntry() entry.ThemeOptionScreen() } - composable(Screen.TheaterSort.route) { - val viewModel = hiltViewModel() - TheaterSortScreen( - viewModel = viewModel, - upPress = { - navController.navigateUp() - }, - onAddItemClick = { - navController.navigate(Screen.TheaterEdit.route) - }, - ) - } - composable(Screen.TheaterEdit.route) { - val viewModel = hiltViewModel() - TheaterEditScreen( - viewModel = viewModel, - upPress = { navController.navigateUp() }, - ) - } } } diff --git a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsScreen.kt index 9149a8186..a56ef403d 100644 --- a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsScreen.kt @@ -15,12 +15,8 @@ */ package soup.movie.feature.settings -import android.content.Context -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -43,30 +39,21 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import soup.movie.core.designsystem.UnelevatedButton import soup.movie.core.designsystem.icon.MovieIcons import soup.movie.core.designsystem.theme.MovieTheme import soup.movie.core.designsystem.util.debounce -import soup.movie.core.external.Cgv -import soup.movie.core.external.LotteCinema -import soup.movie.core.external.Megabox -import soup.movie.feature.theater.TheaterChip import soup.movie.feature.theme.stringResIdOf -import soup.movie.model.TheaterModel -import soup.movie.model.TheaterTypeModel import soup.movie.resources.R @Composable internal fun SettingsScreen( viewModel: SettingsViewModel, onThemeEditClick: () -> Unit, - onTheaterEditClick: () -> Unit, ) { Scaffold( modifier = Modifier, @@ -76,9 +63,7 @@ internal fun SettingsScreen( ) }, ) { paddingValues -> - val context = LocalContext.current val theme by viewModel.themeUiModel.collectAsState() - val theater by viewModel.theaterUiModel.collectAsState() Column( modifier = Modifier .padding(paddingValues) @@ -87,12 +72,6 @@ internal fun SettingsScreen( ) { SettingsThemeItem(theme, onClick = onThemeEditClick) SettingsDivider() - SettingsTheaterItem( - theater?.theaterList.orEmpty(), - onItemClick = { theater -> context.executeWeb(theater) }, - onEditClick = onTheaterEditClick, - ) - SettingsDivider() } } } @@ -149,56 +128,6 @@ private fun SettingsThemeItem( } } -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun SettingsTheaterItem( - theaterList: List, - onItemClick: (TheaterModel) -> Unit, - onEditClick: () -> Unit, -) { - Column( - modifier = Modifier.padding(top = 12.dp, bottom = 24.dp), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - SettingsCategory( - text = stringResource(R.string.settings_category_theater), - modifier = Modifier.weight(1f), - ) - IconButton( - onClick = { debounce(onEditClick) }, - modifier = Modifier.size(48.dp), - ) { - Icon( - MovieIcons.Edit, - contentDescription = null, - tint = MovieTheme.colors.onBackground, - ) - } - } - Spacer(modifier = Modifier.height(12.dp)) - Box { - if (theaterList.isEmpty()) { - Text( - text = stringResource(R.string.settings_theater_empty_description), - textAlign = TextAlign.Center, - style = MovieTheme.typography.body2, - ) - } else { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), - ) { - theaterList.forEach { theater -> - TheaterChip(theater, onItemClick) - } - } - } - } - } -} - @Composable private fun SettingsCategory( text: String, @@ -217,11 +146,3 @@ private fun SettingsCategory( private fun SettingsDivider() { Divider(color = MovieTheme.colors.divider) } - -private fun Context.executeWeb(theater: TheaterModel) { - return when (theater.type) { - TheaterTypeModel.CGV -> Cgv.executeWeb(this, theater.code) - TheaterTypeModel.LOTTE -> LotteCinema.executeWeb(this, theater.code) - TheaterTypeModel.MEGABOX -> Megabox.executeWeb(this, theater.code) - } -} diff --git a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsUiModel.kt b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsUiModel.kt index 8f2f801c6..c7c6d6878 100644 --- a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsUiModel.kt +++ b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsUiModel.kt @@ -17,16 +17,10 @@ package soup.movie.feature.settings import androidx.annotation.Keep import soup.movie.feature.theme.ThemeOption -import soup.movie.model.TheaterModel -sealed class SettingsUiModel - -@Keep -data class TheaterSettingUiModel( - val theaterList: List, -) : SettingsUiModel() +sealed interface SettingsUiModel @Keep data class ThemeSettingUiModel( val themeOption: ThemeOption, -) +) : SettingsUiModel diff --git a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsViewModel.kt index 8de7602d4..ba8d8cef1 100644 --- a/feature/settings/src/main/java/soup/movie/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/java/soup/movie/feature/settings/SettingsViewModel.kt @@ -43,14 +43,4 @@ class SettingsViewModel @Inject constructor( initialValue = null, started = SharingStarted.WhileSubscribed(5_000), ) - - val theaterUiModel: StateFlow = - appSettings.getFavoriteTheaterListFlow() - .map { TheaterSettingUiModel(it) } - .distinctUntilChanged() - .stateIn( - scope = viewModelScope, - initialValue = null, - started = SharingStarted.WhileSubscribed(5_000), - ) } diff --git a/feature/theater/.gitignore b/feature/theater/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/feature/theater/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/feature/theater/build.gradle b/feature/theater/build.gradle deleted file mode 100644 index 0d085ac20..000000000 --- a/feature/theater/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - id "moop.android.library" - id "moop.android.compose" - id "moop.android.hilt" -} - -android { - namespace "soup.movie.feature.theater" -} - -dependencies { - implementation projects.core.kotlin - implementation projects.core.designsystem - implementation projects.core.logger - implementation projects.core.resources - implementation projects.data.settings.api - implementation projects.data.repository.api - implementation projects.data.model - - implementation libs.kotlin.stdlib - - implementation libs.androidx.activity.compose - implementation libs.compose.foundation - implementation libs.compose.material - implementation libs.compose.ui - - testImplementation projects.testing - androidTestImplementation projects.testing -} diff --git a/feature/theater/src/main/AndroidManifest.xml b/feature/theater/src/main/AndroidManifest.xml deleted file mode 100644 index cc947c567..000000000 --- a/feature/theater/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/DraggableList.kt b/feature/theater/src/main/java/soup/movie/feature/theater/DraggableList.kt deleted file mode 100644 index 8d99c675f..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/DraggableList.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater - -import androidx.compose.foundation.gestures.detectDragGestures -import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress -import androidx.compose.foundation.lazy.LazyListItemInfo -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.pointer.pointerInput - -interface DraggableListState { - fun onDragStart(offset: Offset) - fun onDragStart(index: Int) - fun onDrag(offset: Offset) - fun onDragEnd() - fun onDragCancel() - fun getItemState(index: Int): DraggableItemState -} - -@Stable -data class DraggableItemState( - val dragging: Boolean, - val offset: Offset, - val zIndex: Float, -) - -@Composable -fun rememberDraggableListState( - lazyListState: LazyListState, - onMove: (fromIndex: Int, toIndex: Int) -> Unit, -): DraggableListState { - val onMoveState = rememberUpdatedState(onMove) - return remember(lazyListState) { - DefaultDraggableListState(lazyListState = lazyListState, onMove = onMoveState.value) - } -} - -fun Modifier.draggableList( - listState: DraggableListState, -): Modifier = pointerInput(Unit) { - detectDragGesturesAfterLongPress( - onDragStart = { offset -> - listState.onDragStart(offset) - }, - onDrag = { change, offset -> - change.consume() - listState.onDrag(offset) - }, - onDragEnd = { - listState.onDragEnd() - }, - onDragCancel = { - listState.onDragCancel() - }, - ) -} - -fun Modifier.draggableItem( - index: Int, - listState: DraggableListState, -): Modifier = pointerInput(Unit) { - detectDragGestures( - onDragStart = { - listState.onDragStart(index) - }, - onDrag = { change, offset -> - change.consume() - listState.onDrag(offset) - }, - onDragEnd = { - listState.onDragEnd() - }, - onDragCancel = { - listState.onDragCancel() - }, - ) -} - -class DefaultDraggableListState( - private val lazyListState: LazyListState, - private val onMove: (fromIndex: Int, toIndex: Int) -> Unit, -) : DraggableListState { - - private var draggingDistance by mutableStateOf(0f) - private var draggingItem by mutableStateOf(null) - private var currentIndexOfDraggingItem by mutableStateOf(null) - - private inline val LazyListItemInfo.offsetEnd: Int - get() = offset + size - - private val visibleItemsInfo: List - get() = lazyListState.layoutInfo.visibleItemsInfo - - override fun onDragStart(offset: Offset) { - visibleItemsInfo - .firstOrNull { offset.y.toInt() in it.offset..it.offsetEnd } - ?.also { - draggingItem = it - currentIndexOfDraggingItem = it.index - } - } - - override fun onDragStart(index: Int) { - visibleItemsInfo - .find(absoluteIndex = index) - ?.also { - draggingItem = it - currentIndexOfDraggingItem = it.index - } - } - - override fun onDrag(offset: Offset) { - draggingDistance += offset.y - - val draggingItem = draggingItem ?: return - val currentIndex = currentIndexOfDraggingItem ?: return - val hovered = visibleItemsInfo.find(absoluteIndex = currentIndex) ?: return - val startOffset = draggingItem.offset + draggingDistance - val endOffset = draggingItem.offsetEnd + draggingDistance - visibleItemsInfo - .filterNot { item -> startOffset > item.offsetEnd || item.offset > endOffset || hovered.index == item.index } - .firstOrNull { item -> - if (startOffset <= hovered.offset) { - startOffset < item.offset - } else { - endOffset > item.offsetEnd - } - }?.also { item -> - onMove(currentIndex, item.index) - currentIndexOfDraggingItem = item.index - } - } - - override fun onDragEnd() { - resetDraggingState() - } - - override fun onDragCancel() { - resetDraggingState() - } - - override fun getItemState(index: Int): DraggableItemState { - val isDraggingItem = index == currentIndexOfDraggingItem - val offset = if (isDraggingItem) { - val hovered = visibleItemsInfo.find(absoluteIndex = index) - if (hovered != null) { - val offsetY = (draggingItem?.offset ?: 0) + draggingDistance - hovered.offset - Offset(x = 0f, y = offsetY) - } else { - Offset.Zero - } - } else { - Offset.Zero - } - return DraggableItemState( - dragging = isDraggingItem, - offset = offset, - zIndex = if (isDraggingItem) 1f else 0f, - ) - } - - private fun resetDraggingState() { - draggingDistance = 0f - draggingItem = null - currentIndexOfDraggingItem = null - } - - private fun List.find(absoluteIndex: Int): LazyListItemInfo? { - return find { it.index == absoluteIndex } - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/TheaterChip.kt b/feature/theater/src/main/java/soup/movie/feature/theater/TheaterChip.kt deleted file mode 100644 index 5262c3998..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/TheaterChip.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater - -import androidx.compose.foundation.BorderStroke -import androidx.compose.material.Chip -import androidx.compose.material.ChipDefaults -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import soup.movie.core.designsystem.theme.MovieTheme -import soup.movie.core.designsystem.util.debounce -import soup.movie.model.TheaterModel -import soup.movie.model.TheaterTypeModel - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun TheaterChip( - theater: TheaterModel, - onTheaterClick: (TheaterModel) -> Unit = {}, -) { - when (theater.type) { - TheaterTypeModel.CGV -> CgvChip( - text = theater.name, - onClick = { debounce { onTheaterClick(theater) } }, - ) - TheaterTypeModel.LOTTE -> LotteChip( - text = theater.name, - onClick = { debounce { onTheaterClick(theater) } }, - ) - TheaterTypeModel.MEGABOX -> MegaboxChip( - text = theater.name, - onClick = { debounce { onTheaterClick(theater) } }, - ) - } -} - -@ExperimentalMaterialApi -@Composable -private fun CgvChip( - text: String, - onClick: () -> Unit = {}, - enabled: Boolean = true, -) { - Chip( - onClick = onClick, - enabled = enabled, - border = BorderStroke(width = 1.dp, color = Color(0x229E9E9E)), - colors = ChipDefaults.chipColors( - backgroundColor = Color.White, - contentColor = MovieTheme.colors.onCgv, - ), - ) { - Text( - text = text, - fontWeight = FontWeight.Bold, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun LotteChip( - text: String, - onClick: () -> Unit = {}, - enabled: Boolean = true, -) { - Chip( - onClick = onClick, - enabled = enabled, - colors = ChipDefaults.chipColors( - backgroundColor = Color(0xFFED1D24), - contentColor = Color.White, - ), - ) { - Text( - text = text, - fontWeight = FontWeight.Bold, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun MegaboxChip( - text: String, - onClick: () -> Unit = {}, - enabled: Boolean = true, -) { - Chip( - onClick = onClick, - enabled = enabled, - colors = ChipDefaults.chipColors( - backgroundColor = Color(0xFF352263), - contentColor = Color.White, - ), - ) { - Text( - text = text, - fontWeight = FontWeight.Bold, - ) - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/TheaterFilterChip.kt b/feature/theater/src/main/java/soup/movie/feature/theater/TheaterFilterChip.kt deleted file mode 100644 index e294966d4..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/TheaterFilterChip.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater - -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Image -import androidx.compose.material.ChipDefaults -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FilterChip -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import soup.movie.core.designsystem.icon.MovieIcons -import soup.movie.core.designsystem.theme.MovieTheme -import soup.movie.model.TheaterModel -import soup.movie.model.TheaterTypeModel - -@OptIn(ExperimentalMaterialApi::class) -@Composable -internal fun TheaterFilterChip( - theater: TheaterModel, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, -) { - when (theater.type) { - TheaterTypeModel.CGV -> CgvFilterChip( - text = theater.name, - checked = checked, - onCheckedChange = onCheckedChange, - ) - TheaterTypeModel.LOTTE -> LotteFilterChip( - text = theater.name, - checked = checked, - onCheckedChange = onCheckedChange, - ) - TheaterTypeModel.MEGABOX -> MegaboxFilterChip( - text = theater.name, - checked = checked, - onCheckedChange = onCheckedChange, - ) - } -} - -@ExperimentalMaterialApi -@Composable -private fun CgvFilterChip( - text: String, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - enabled: Boolean = true, -) { - FilterChip( - selected = checked, - onClick = { onCheckedChange(!checked) }, - selectedIcon = { - Image( - MovieIcons.Check, - contentDescription = null, - colorFilter = ColorFilter.tint(MovieTheme.colors.onCgv), - ) - }, - enabled = enabled, - border = BorderStroke(width = 1.dp, color = Color(0x229E9E9E)), - colors = ChipDefaults.filterChipColors( - selectedBackgroundColor = MovieTheme.colors.cgv, - selectedContentColor = MovieTheme.colors.onCgv, - backgroundColor = Color(0x55FFFFFF), - contentColor = Color(0x66000000), - ), - ) { - Text( - text = text, - fontWeight = FontWeight.Bold, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun LotteFilterChip( - text: String, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - enabled: Boolean = true, -) { - FilterChip( - selected = checked, - onClick = { onCheckedChange(!checked) }, - selectedIcon = { - Image( - MovieIcons.Check, - contentDescription = null, - colorFilter = ColorFilter.tint(MovieTheme.colors.onLotte), - ) - }, - enabled = enabled, - colors = ChipDefaults.filterChipColors( - selectedBackgroundColor = Color(0xFFED1D24), - selectedContentColor = Color.White, - backgroundColor = Color(0x66ED1D24), - contentColor = Color(0x77FFFFFF), - ), - ) { - Text( - text = text, - fontWeight = FontWeight.Bold, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun MegaboxFilterChip( - text: String, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - enabled: Boolean = true, -) { - FilterChip( - selected = checked, - onClick = { onCheckedChange(!checked) }, - selectedIcon = { - Image( - MovieIcons.Check, - contentDescription = null, - colorFilter = ColorFilter.tint(MovieTheme.colors.onMegabox), - ) - }, - enabled = enabled, - colors = ChipDefaults.filterChipColors( - selectedBackgroundColor = Color(0xFF352263), - selectedContentColor = Color.White, - backgroundColor = Color(0x77352263), - contentColor = Color(0x77FFFFFF), - ), - ) { - Text( - text = text, - fontWeight = FontWeight.Bold, - ) - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/di/TheaterEditDomainModule.kt b/feature/theater/src/main/java/soup/movie/feature/theater/di/TheaterEditDomainModule.kt deleted file mode 100644 index 9eaf168ef..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/di/TheaterEditDomainModule.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.scopes.ViewModelScoped -import soup.movie.data.repository.TheaterRepository -import soup.movie.data.settings.AppSettings -import soup.movie.feature.theater.edit.TheaterEditManager - -@Module -@InstallIn(ViewModelComponent::class) -class TheaterEditDomainModule { - - @Provides - @ViewModelScoped - fun provideTheaterEditManager( - repository: TheaterRepository, - appSettings: AppSettings, - ): TheaterEditManager { - return TheaterEditManager( - repository, - appSettings, - ) - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/edit/PagerTab.kt b/feature/theater/src/main/java/soup/movie/feature/theater/edit/PagerTab.kt deleted file mode 100644 index e362af6d2..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/edit/PagerTab.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2024 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.edit - -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.PagerState -import androidx.compose.foundation.pager.VerticalPager -import androidx.compose.material.ScrollableTabRow -import androidx.compose.material.TabPosition -import androidx.compose.material.TabRow -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.layout -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.lerp - -/** - * From https://github.com/google/accompanist/tree/main/pager-indicators - * - * This indicator syncs up a [TabRow] or [ScrollableTabRow] tab indicator with a - * [HorizontalPager] or [VerticalPager]. - */ -fun Modifier.pagerTabIndicatorOffset( - pagerState: PagerState, - tabPositions: List, - pageIndexMapping: (Int) -> Int = { it }, -): Modifier { - val stateBridge = object : PagerStateBridge { - override val currentPage: Int - get() = pagerState.currentPage - override val currentPageOffset: Float - get() = pagerState.currentPageOffsetFraction - } - - return pagerTabIndicatorOffset(stateBridge, tabPositions, pageIndexMapping) -} - -private fun Modifier.pagerTabIndicatorOffset( - pagerState: PagerStateBridge, - tabPositions: List, - pageIndexMapping: (Int) -> Int = { it }, -): Modifier = layout { measurable, constraints -> - if (tabPositions.isEmpty()) { - // If there are no pages, nothing to show - layout(constraints.maxWidth, 0) {} - } else { - val currentPage = minOf(tabPositions.lastIndex, pageIndexMapping(pagerState.currentPage)) - val currentTab = tabPositions[currentPage] - val previousTab = tabPositions.getOrNull(currentPage - 1) - val nextTab = tabPositions.getOrNull(currentPage + 1) - val fraction = pagerState.currentPageOffset - val indicatorWidth = if (fraction > 0 && nextTab != null) { - lerp(currentTab.width, nextTab.width, fraction).roundToPx() - } else if (fraction < 0 && previousTab != null) { - lerp(currentTab.width, previousTab.width, -fraction).roundToPx() - } else { - currentTab.width.roundToPx() - } - val indicatorOffset = if (fraction > 0 && nextTab != null) { - lerp(currentTab.left, nextTab.left, fraction).roundToPx() - } else if (fraction < 0 && previousTab != null) { - lerp(currentTab.left, previousTab.left, -fraction).roundToPx() - } else { - currentTab.left.roundToPx() - } - val placeable = measurable.measure( - Constraints( - minWidth = indicatorWidth, - maxWidth = indicatorWidth, - minHeight = 0, - maxHeight = constraints.maxHeight, - ), - ) - layout(constraints.maxWidth, maxOf(placeable.height, constraints.minHeight)) { - placeable.placeRelative( - indicatorOffset, - maxOf(constraints.minHeight - placeable.height, 0), - ) - } - } -} - -internal interface PagerStateBridge { - val currentPage: Int - val currentPageOffset: Float -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditChildScreen.kt b/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditChildScreen.kt deleted file mode 100644 index 2b0e7784b..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditChildScreen.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.edit - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.launch -import soup.movie.core.designsystem.showToast -import soup.movie.core.designsystem.theme.MovieTheme -import soup.movie.feature.theater.TheaterFilterChip -import soup.movie.model.TheaterModel -import soup.movie.resources.R - -@Composable -internal fun CgvScreen(viewModel: TheaterEditViewModel) { - val uiModel by viewModel.cgvUiModel.collectAsState(TheaterEditChildUiModel(emptyList())) - TheaterEditChildScreen( - uiModel = uiModel, - onAddTheater = { viewModel.add(it) }, - onRemoveTheater = { viewModel.remove(it) }, - ) -} - -@Composable -internal fun LotteScreen(viewModel: TheaterEditViewModel) { - val uiModel by viewModel.lotteUiModel.collectAsState(TheaterEditChildUiModel(emptyList())) - TheaterEditChildScreen( - uiModel = uiModel, - onAddTheater = { viewModel.add(it) }, - onRemoveTheater = { viewModel.remove(it) }, - ) -} - -@Composable -internal fun MegaboxScreen(viewModel: TheaterEditViewModel) { - val uiModel by viewModel.megaboxUiModel.collectAsState(TheaterEditChildUiModel(emptyList())) - TheaterEditChildScreen( - uiModel = uiModel, - onAddTheater = { viewModel.add(it) }, - onRemoveTheater = { viewModel.remove(it) }, - ) -} - -@Composable -private fun TheaterEditChildScreen( - uiModel: TheaterEditChildUiModel, - onAddTheater: (TheaterModel) -> Boolean, - onRemoveTheater: (TheaterModel) -> Unit, -) { - val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(all = 8.dp), - ) { - items(uiModel.areaGroupList) { item -> - TheaterAreaItem( - item.title, - item.theaterList, - onCheckedChange = { theater, checked -> - coroutineScope.launch { - if (checked) { - if (onAddTheater(theater).not()) { - context.showToast( - context.getString( - R.string.theater_select_limit_description, - TheaterEditManager.MAX_ITEMS, - ), - ) - } - } else { - onRemoveTheater(theater) - } - } - }, - ) - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun TheaterAreaItem( - title: String, - theaterList: List, - onCheckedChange: (TheaterModel, Boolean) -> Unit, -) { - Column(modifier = Modifier.padding(vertical = 8.dp)) { - Text( - text = title, - modifier = Modifier.padding(all = 8.dp), - fontWeight = FontWeight.Bold, - color = MovieTheme.colors.onBackground, - style = MovieTheme.typography.subtitle1, - ) - FlowRow( - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), - modifier = Modifier.padding(all = 4.dp), - ) { - theaterList.forEach { item -> - TheaterFilterChip( - item.theater, - checked = item.checked, - onCheckedChange = { checked -> onCheckedChange(item.theater, checked) }, - ) - } - } - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditManager.kt b/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditManager.kt deleted file mode 100644 index ed002c499..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditManager.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.edit - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.runBlocking -import soup.movie.data.repository.TheaterRepository -import soup.movie.data.settings.AppSettings -import soup.movie.model.TheaterAreaGroupModel -import soup.movie.model.TheaterAreaModel -import soup.movie.model.TheaterModel - -class TheaterEditManager( - private val repository: TheaterRepository, - private val appSettings: AppSettings, -) { - - private val cgvSubject = MutableStateFlow>(emptyList()) - private val lotteSubject = MutableStateFlow>(emptyList()) - private val megaboxSubject = MutableStateFlow>(emptyList()) - private val selectedTheatersChannel = MutableStateFlow>(emptyList()) - - private var theaterList: List = emptyList() - private var selectedItemSet: MutableSet = mutableSetOf() - - fun asCgvFlow(): Flow> = cgvSubject - - fun asLotteFlow(): Flow> = lotteSubject - - fun asMegaboxFlow(): Flow> = megaboxSubject - - fun asSelectedTheaterListFlow(): Flow> = selectedTheatersChannel - - suspend fun loadAsync(): TheaterAreaGroupModel { - setupSelectedList() - return repository.getCodeList().also { - setupTotalList(it) - cgvSubject.value = it.cgv - lotteSubject.value = it.lotte - megaboxSubject.value = it.megabox - } - } - - private fun setupTotalList(group: TheaterAreaGroupModel) { - theaterList = group.run { - (cgv + lotte + megabox).flatMap(TheaterAreaModel::theaterList) - } - } - - private fun setupSelectedList() { - // TODO: Avoid blocking threads on DataStore - selectedItemSet = runBlocking { appSettings.getFavoriteTheaterList() }.toMutableSet() - selectedTheatersChannel.value = selectedItemSet.asSequence() - .sortedBy { it.type } - .toList() - } - - private fun updateSelectedItemCount() { - selectedTheatersChannel.value = theaterList.asSequence() - .filter { - selectedItemSet.any { selectedItem -> - selectedItem == it - } - } - .sortedBy { it.type } - .toList() - } - - fun add(theater: TheaterModel): Boolean { - val isUnderLimit = selectedItemSet.size < MAX_ITEMS - if (isUnderLimit) { - selectedItemSet.add(theater) - updateSelectedItemCount() - } - return isUnderLimit - } - - fun remove(theater: TheaterModel) { - selectedItemSet.remove(theater) - updateSelectedItemCount() - } - - fun save() { - // TODO: Avoid blocking threads on DataStore - runBlocking { - appSettings.setFavoriteTheaterList( - theaterList.asSequence() - .filter { - selectedItemSet.any { selectedItem -> - selectedItem.id == it.id - } - } - .sortedBy { it.type } - .toList(), - ) - } - } - - companion object { - - const val MAX_ITEMS = 10 - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditScreen.kt b/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditScreen.kt deleted file mode 100644 index e8e15a969..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditScreen.kt +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.edit - -import android.view.MotionEvent -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.requiredSize -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.BottomSheetScaffold -import androidx.compose.material.BottomSheetState -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Divider -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Tab -import androidx.compose.material.TabRow -import androidx.compose.material.TabRowDefaults -import androidx.compose.material.Text -import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.input.pointer.pointerInteropFilter -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import soup.movie.core.designsystem.icon.MovieIcons -import soup.movie.core.designsystem.theme.MovieTheme -import soup.movie.core.designsystem.util.debounce -import soup.movie.feature.theater.TheaterChip -import soup.movie.model.TheaterModel -import soup.movie.resources.R - -private enum class Page(val title: String) { - CGV("CGV"), - Lotte("롯데시네마"), - Megabox("메가박스"), - ; - - companion object { - fun of(page: Int): Page { - return when (page) { - 0 -> CGV - 1 -> Lotte - else -> Megabox - } - } - } -} - -@OptIn( - ExperimentalComposeUiApi::class, - ExperimentalMaterialApi::class, - ExperimentalFoundationApi::class, -) -@Composable -fun TheaterEditScreen( - viewModel: TheaterEditViewModel, - upPress: () -> Unit, -) { - val pages = Page.values() - val pagerState = rememberPagerState(pageCount = { pages.size }) - val coroutineScope = rememberCoroutineScope() - val bottomSheetScaffoldState = rememberBottomSheetScaffoldState() - FirstLaunchedEffect { - delay(500) - bottomSheetScaffoldState.bottomSheetState.expand() - delay(500) - bottomSheetScaffoldState.bottomSheetState.collapse() - } - BottomSheetScaffold( - scaffoldState = bottomSheetScaffoldState, - topBar = { - TabRow( - selectedTabIndex = pagerState.currentPage, - indicator = { tabPositions -> - TabRowDefaults.Indicator( - modifier = Modifier.pagerTabIndicatorOffset(pagerState, tabPositions), - color = MovieTheme.colors.secondary, - ) - }, - modifier = Modifier.shadow(elevation = 4.dp), - ) { - pages.forEachIndexed { index, page -> - Tab( - text = { Text(page.title) }, - selected = pagerState.currentPage == index, - onClick = { - coroutineScope.launch { - pagerState.animateScrollToPage(index) - } - }, - selectedContentColor = MovieTheme.colors.secondary, - unselectedContentColor = MovieTheme.colors.onBackground, - ) - } - } - }, - sheetPeekHeight = 60.dp, - sheetElevation = if (MovieTheme.colors.isLight) 16.dp else 0.dp, - sheetContent = { - val uiModel by viewModel.footerUiModel.collectAsState( - TheaterEditFooterUiModel(emptyList()), - ) - TheaterEditFooter( - uiModel = uiModel, - onClick = { - coroutineScope.launch { - bottomSheetScaffoldState.bottomSheetState.toggle() - } - }, - onTheaterClick = { - viewModel.remove(it) - }, - onConfirmClick = { - viewModel.onConfirmClick() - upPress() - }, - ) - }, - ) { paddingValues -> - Box( - modifier = Modifier - .padding(paddingValues) - .pointerInteropFilter { - if (it.action == MotionEvent.ACTION_DOWN) { - if (bottomSheetScaffoldState.bottomSheetState.isExpanded) { - coroutineScope.launch { - bottomSheetScaffoldState.bottomSheetState.collapse() - } - return@pointerInteropFilter true - } - } - false - }, - ) { - HorizontalPager(state = pagerState) { page -> - when (Page.of(page)) { - Page.CGV -> CgvScreen(viewModel) - Page.Lotte -> LotteScreen(viewModel) - Page.Megabox -> MegaboxScreen(viewModel) - } - } - val viewState by viewModel.contentUiModel.collectAsState( - TheaterEditContentUiModel.LoadingState, - ) - if (viewState is TheaterEditContentUiModel.LoadingState) { - CircularProgressIndicator( - color = MovieTheme.colors.secondary, - modifier = Modifier.align(Alignment.Center), - ) - } - } - } -} - -@Composable -private fun TheaterEditFooter( - uiModel: TheaterEditFooterUiModel, - onClick: () -> Unit, - onTheaterClick: (TheaterModel) -> Unit, - onConfirmClick: () -> Unit, -) { - Column( - modifier = Modifier.clickable( - onClick = { debounce(onClick) }, - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ), - ) { - TheaterEditFooterPeek( - currentCount = uiModel.theaterList.size, - isFull = uiModel.isFull(), - onConfirmClick = onConfirmClick, - ) - TheaterEditFooterContents( - theaterList = uiModel.theaterList, - onTheaterClick = onTheaterClick, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun TheaterEditFooterPeek( - currentCount: Int, - isFull: Boolean, - onConfirmClick: () -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .requiredHeight(60.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "자주가는 극장은 최대 10개까지\n선택할 수 있습니다.", - modifier = Modifier - .padding(horizontal = 16.dp) - .weight(1f), - fontSize = 14.sp, - color = MovieTheme.colors.onSurface, - ) - Surface( - onClick = { debounce(onConfirmClick) }, - modifier = Modifier - .padding(end = 8.dp) - .requiredSize(100.dp, 36.dp), - shape = RoundedCornerShape(percent = 50), - color = if (isFull) MovieTheme.colors.error else MovieTheme.colors.secondary, - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - MovieIcons.Check, - contentDescription = null, - modifier = Modifier.padding(start = 8.dp), - ) - // TODO: Ticker Animation - Text( - text = currentCount.toString(), - modifier = Modifier - .padding(end = 6.dp) - .weight(1f), - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.End, - ) - Text( - text = "/ 10", - modifier = Modifier.padding(end = 16.dp), - fontSize = 14.sp, - ) - } - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun TheaterEditFooterContents( - theaterList: List, - onTheaterClick: (TheaterModel) -> Unit, -) { - Box( - modifier = Modifier - .fillMaxWidth() - .requiredHeight(180.dp), - ) { - Divider(color = MovieTheme.colors.divider) - if (theaterList.isEmpty()) { - Text( - text = stringResource(R.string.theater_empty_description), - modifier = Modifier.align(Alignment.Center), - textAlign = TextAlign.Center, - style = MovieTheme.typography.body2, - ) - } else { - FlowRow( - modifier = Modifier.padding(all = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Top), - ) { - theaterList.forEach { theater -> - TheaterChip(theater, onTheaterClick) - } - } - } - } -} - -@Composable -private fun FirstLaunchedEffect(block: suspend CoroutineScope.() -> Unit) { - var first by rememberSaveable { mutableStateOf(true) } - LaunchedEffect(Unit) { - if (first) { - first = false - block() - } - } -} - -@ExperimentalMaterialApi -private suspend fun BottomSheetState.toggle() { - if (isCollapsed) { - expand() - } else { - collapse() - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditUiModel.kt b/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditUiModel.kt deleted file mode 100644 index d8bb6ae8a..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditUiModel.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.edit - -import androidx.annotation.Keep -import soup.movie.model.TheaterModel - -sealed class TheaterEditContentUiModel { - - @Keep - data object LoadingState : TheaterEditContentUiModel() - - @Keep - data object ErrorState : TheaterEditContentUiModel() - - @Keep - data object DoneState : TheaterEditContentUiModel() -} - -@Keep -data class TheaterEditChildUiModel( - val areaGroupList: List, -) - -@Keep -data class TheaterEditAreaGroupUiModel( - val title: String, - val theaterList: List, -) - -@Keep -data class TheaterEditTheaterUiModel( - val theater: TheaterModel, - val checked: Boolean, -) - -@Keep -data class TheaterEditFooterUiModel( - val theaterList: List, -) { - - fun isFull(): Boolean = theaterList.size >= TheaterEditManager.MAX_ITEMS -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditViewModel.kt b/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditViewModel.kt deleted file mode 100644 index f49ab7cec..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/edit/TheaterEditViewModel.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.edit - -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext -import soup.movie.common.DefaultDispatcher -import soup.movie.log.Logger -import soup.movie.model.TheaterAreaModel -import soup.movie.model.TheaterModel -import javax.inject.Inject - -@HiltViewModel -class TheaterEditViewModel @Inject constructor( - private val manager: TheaterEditManager, - @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher, -) : ViewModel() { - - val contentUiModel = flow { - emit(TheaterEditContentUiModel.LoadingState) - try { - manager.loadAsync() - emit(TheaterEditContentUiModel.DoneState) - } catch (t: Throwable) { - Logger.w(t) - emit(TheaterEditContentUiModel.ErrorState) - } - } - - val cgvUiModel = combine( - manager.asCgvFlow(), - manager.asSelectedTheaterListFlow(), - ) { cgv, selectedList -> - cgv.toUiModel(selectedList) - } - - val lotteUiModel = combine( - manager.asLotteFlow(), - manager.asSelectedTheaterListFlow(), - ) { lotte, selectedList -> - lotte.toUiModel(selectedList) - } - - val megaboxUiModel = combine( - manager.asMegaboxFlow(), - manager.asSelectedTheaterListFlow(), - ) { megabox, selectedList -> - megabox.toUiModel(selectedList) - } - - val footerUiModel = manager.asSelectedTheaterListFlow() - .map { TheaterEditFooterUiModel(it) } - - fun add(theater: TheaterModel): Boolean { - return manager.add(theater) - } - - fun remove(theater: TheaterModel) { - manager.remove(theater) - } - - fun onConfirmClick() { - manager.save() - } - - private suspend fun List.toUiModel(selectedList: List) = - withContext(defaultDispatcher) { - TheaterEditChildUiModel( - map { theaterArea -> - TheaterEditAreaGroupUiModel( - title = theaterArea.area.name, - theaterList = theaterArea.theaterList.map { theater -> - TheaterEditTheaterUiModel( - theater = theater, - checked = selectedList.any { it.id == theater.id }, - ) - }, - ) - }, - ) - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/sort/MutableList.kt b/feature/theater/src/main/java/soup/movie/feature/theater/sort/MutableList.kt deleted file mode 100644 index 0cd59d9b7..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/sort/MutableList.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.sort - -fun MutableList.swap(i: Int, j: Int) { - // instead of using a raw type here, it's possible to capture - // the wildcard but it will require a call to a supplementary - // private method - this[i] = set(j, this[i]) -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/sort/TheaterSortScreen.kt b/feature/theater/src/main/java/soup/movie/feature/theater/sort/TheaterSortScreen.kt deleted file mode 100644 index 829ff000e..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/sort/TheaterSortScreen.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.sort - -import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.FloatingActionButton -import androidx.compose.material.Icon -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex -import kotlinx.coroutines.launch -import soup.movie.core.designsystem.icon.MovieIcons -import soup.movie.core.designsystem.theme.MovieTheme -import soup.movie.core.designsystem.util.debounce -import soup.movie.feature.theater.TheaterChip -import soup.movie.feature.theater.draggableItem -import soup.movie.feature.theater.draggableList -import soup.movie.feature.theater.rememberDraggableListState -import soup.movie.model.TheaterModel -import soup.movie.resources.R - -@Composable -fun TheaterSortScreen( - viewModel: TheaterSortViewModel, - upPress: () -> Unit, - onAddItemClick: () -> Unit, -) { - val coroutineScope = rememberCoroutineScope() - BackHandler { - coroutineScope.launch { - viewModel.saveSnapshot() - upPress() - } - } - Scaffold( - topBar = { - TopAppBar( - title = { Text(text = stringResource(R.string.theater_sort_title)) }, - ) - }, - floatingActionButton = { - FloatingActionButton(onClick = { debounce(onAddItemClick) }) { - Icon( - MovieIcons.Add, - contentDescription = stringResource(R.string.theater_select_action_confirm), - ) - } - }, - ) { paddingValues -> - val selectedTheaters = viewModel.selectedTheaters - val modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - - if (selectedTheaters.isEmpty()) { - TheaterSortNoItem(modifier) - } else { - TheaterSortReorderList( - selectedTheaters, - modifier = modifier, - onMove = { fromPosition, toPosition -> - viewModel.onItemMove(fromPosition, toPosition) - }, - ) - } - } -} - -@Composable -private fun TheaterSortNoItem(modifier: Modifier = Modifier) { - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.fillMaxSize(), - ) { - Image( - painterResource(MovieIcons.NoTheaters), - contentDescription = null, - modifier = Modifier - .width(48.dp) - .height(68.dp), - colorFilter = ColorFilter.tint(color = MovieTheme.colors.onBackground), - ) - Text( - stringResource(R.string.theater_empty_description), - color = MovieTheme.colors.onBackground, - modifier = Modifier.padding(8.dp), - ) - } -} - -@Composable -private fun TheaterSortReorderList( - selectedTheaters: List, - onMove: (fromIndex: Int, toIndex: Int) -> Unit, - modifier: Modifier = Modifier, -) { - val lazyListState = rememberLazyListState() - val draggableListState = rememberDraggableListState(lazyListState, onMove = onMove) - LazyColumn( - modifier = modifier.draggableList(draggableListState), - state = lazyListState, - ) { - itemsIndexed(selectedTheaters) { index, theater -> - val itemState = draggableListState.getItemState(index) - val transY by animateFloatAsState(targetValue = itemState.offset.y) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .padding(horizontal = 12.dp) - .graphicsLayer { translationY = transY } - .zIndex(itemState.zIndex), - ) { - TheaterChip(theater) - Spacer(modifier = Modifier.weight(1f)) - Image( - MovieIcons.DragHandle, - contentDescription = null, - contentScale = ContentScale.Inside, - modifier = Modifier - .width(48.dp) - .fillMaxHeight() - .draggableItem(index, draggableListState), - colorFilter = ColorFilter.tint(color = MovieTheme.colors.onBackground), - ) - } - } - } -} diff --git a/feature/theater/src/main/java/soup/movie/feature/theater/sort/TheaterSortViewModel.kt b/feature/theater/src/main/java/soup/movie/feature/theater/sort/TheaterSortViewModel.kt deleted file mode 100644 index 49fdb1331..000000000 --- a/feature/theater/src/main/java/soup/movie/feature/theater/sort/TheaterSortViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2021 SOUP - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package soup.movie.feature.theater.sort - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import soup.movie.data.settings.AppSettings -import soup.movie.model.TheaterModel -import javax.inject.Inject - -@HiltViewModel -class TheaterSortViewModel @Inject constructor( - private val appSettings: AppSettings, -) : ViewModel() { - - private var listSnapshot = mutableListOf() - - var selectedTheaters by mutableStateOf>(emptyList()) - private set - - init { - appSettings.getFavoriteTheaterListFlow() - .distinctUntilChanged() - .onEach { updateTheaters(it) } - .launchIn(viewModelScope) - } - - private fun updateTheaters(it: List) { - listSnapshot = it.toMutableList() - selectedTheaters = it - } - - fun onItemMove(fromPosition: Int, toPosition: Int) { - listSnapshot.swap(fromPosition, toPosition) - updateTheaters(listSnapshot) - } - - suspend fun saveSnapshot() { - appSettings.setFavoriteTheaterList(listSnapshot.toList()) - } -}