diff --git a/android/festago/data/build.gradle.kts b/android/festago/data/build.gradle.kts index 03e2336ec..912a80fbb 100644 --- a/android/festago/data/build.gradle.kts +++ b/android/festago/data/build.gradle.kts @@ -78,6 +78,13 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + + // room + val room_version = "2.6.1" + implementation("androidx.room:room-runtime:$room_version") + annotationProcessor("androidx.room:room-compiler:$room_version") + kapt("androidx.room:room-compiler:$room_version") + implementation("androidx.room:room-ktx:$room_version") } fun getSecretKey(propertyKey: String): String { diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dao/RecentSearchQueryDao.kt b/android/festago/data/src/main/java/com/festago/festago/data/dao/RecentSearchQueryDao.kt new file mode 100644 index 000000000..611889f8c --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/dao/RecentSearchQueryDao.kt @@ -0,0 +1,22 @@ +package com.festago.festago.data.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Query +import androidx.room.Upsert +import com.festago.festago.data.model.RecentSearchQueryEntity + +@Dao +interface RecentSearchQueryDao { + @Query(value = "SELECT * FROM recentSearchQueries ORDER BY created_at DESC LIMIT :limit") + suspend fun getRecentSearchQueryEntities(limit: Int): List + + @Upsert + suspend fun insertOrReplaceRecentSearchQuery(recentSearchQuery: RecentSearchQueryEntity) + + @Delete + suspend fun deleteRecentSearchQuery(recentSearchQuery: RecentSearchQueryEntity) + + @Query(value = "DELETE FROM recentSearchQueries") + suspend fun clearRecentSearchQueries() +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/database/FestagoDatabase.kt b/android/festago/data/src/main/java/com/festago/festago/data/database/FestagoDatabase.kt new file mode 100644 index 000000000..6db6158bf --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/database/FestagoDatabase.kt @@ -0,0 +1,11 @@ +package com.festago.festago.data.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.festago.festago.data.dao.RecentSearchQueryDao +import com.festago.festago.data.model.RecentSearchQueryEntity + +@Database(entities = [RecentSearchQueryEntity::class], version = 1) +abstract class FestagoDatabase : RoomDatabase() { + abstract fun recentSearchQueryDao(): RecentSearchQueryDao +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DaosModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DaosModule.kt new file mode 100644 index 000000000..6ad5ca163 --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DaosModule.kt @@ -0,0 +1,17 @@ +package com.festago.festago.data.di.singletonscope + +import com.festago.festago.data.dao.RecentSearchQueryDao +import com.festago.festago.data.database.FestagoDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object DaosModule { + + @Provides + fun providesRecentSearchQueryDao(database: FestagoDatabase): RecentSearchQueryDao = + database.recentSearchQueryDao() +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DatabaseModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DatabaseModule.kt new file mode 100644 index 000000000..2c4e9c218 --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DatabaseModule.kt @@ -0,0 +1,25 @@ +package com.festago.festago.data.di.singletonscope + +import android.content.Context +import androidx.room.Room +import com.festago.festago.data.database.FestagoDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + @Provides + @Singleton + fun providesFestagoDatabase( + @ApplicationContext context: Context, + ): FestagoDatabase = Room.databaseBuilder( + context, + FestagoDatabase::class.java, + "festago-database", + ).build() +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt index 2ca7f616d..556bcf3da 100644 --- a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt +++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt @@ -1,11 +1,15 @@ package com.festago.festago.data.di.singletonscope +import com.festago.festago.data.repository.DefaultRecentSearchRepository import com.festago.festago.data.repository.FakeArtistRepository import com.festago.festago.data.repository.FakeFestivalRepository import com.festago.festago.data.repository.FakeSchoolRepository +import com.festago.festago.data.repository.FakeSearchRepository import com.festago.festago.domain.repository.ArtistRepository import com.festago.festago.domain.repository.FestivalRepository +import com.festago.festago.domain.repository.RecentSearchRepository import com.festago.festago.domain.repository.SchoolRepository +import com.festago.festago.domain.repository.SearchRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -27,4 +31,12 @@ interface RepositoryModule { @Binds @Singleton fun bindsSchoolRepository(schoolRepository: FakeSchoolRepository): SchoolRepository + + @Binds + @Singleton + fun bindsRecentSearchRepository(recentSearchRepository: DefaultRecentSearchRepository): RecentSearchRepository + + @Binds + @Singleton + fun bindsSearchRepository(searchRepository: FakeSearchRepository): SearchRepository } diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistSearchResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistSearchResponse.kt new file mode 100644 index 000000000..098e28470 --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistSearchResponse.kt @@ -0,0 +1,21 @@ +package com.festago.festago.data.dto.artist + +import com.festago.festago.domain.model.search.ArtistSearch +import kotlinx.serialization.Serializable + +@Serializable +data class ArtistSearchResponse( + val id: Long, + val name: String, + val profileImageUrl: String, + val todayStage: Int, + val upcomingStage: Int, +) { + fun toDomain() = ArtistSearch( + id = id, + name = name, + profileImageUrl = profileImageUrl, + todayStage = todayStage, + upcomingStage = upcomingStage, + ) +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolSearchResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolSearchResponse.kt new file mode 100644 index 000000000..a8ab759a7 --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolSearchResponse.kt @@ -0,0 +1,20 @@ +package com.festago.festago.data.dto.school + +import com.festago.festago.domain.model.search.SchoolSearch +import kotlinx.serialization.Serializable +import java.time.LocalDate + +@Serializable +data class SchoolSearchResponse( + val id: Long, + val name: String, + val logoUrl: String, + val upcomingFestivalStartDate: String?, +) { + fun toDomain() = SchoolSearch( + id = id, + name = name, + logoUrl = logoUrl, + upcomingFestivalStartDate = upcomingFestivalStartDate?.let { LocalDate.parse(it) }, + ) +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/model/RecentSearchQueryEntity.kt b/android/festago/data/src/main/java/com/festago/festago/data/model/RecentSearchQueryEntity.kt new file mode 100644 index 000000000..2076f4bbe --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/model/RecentSearchQueryEntity.kt @@ -0,0 +1,18 @@ +package com.festago.festago.data.model + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.festago.festago.domain.model.recentsearch.RecentSearchQuery + +@Entity( + tableName = "recentSearchQueries", +) +data class RecentSearchQueryEntity( + @PrimaryKey + val query: String, + @ColumnInfo(name = "created_at") + val createdAt: Long, +) { + fun toDomain() = RecentSearchQuery(query = query, queriedDate = createdAt) +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultRecentSearchRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultRecentSearchRepository.kt new file mode 100644 index 000000000..f6506e01c --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultRecentSearchRepository.kt @@ -0,0 +1,39 @@ +package com.festago.festago.data.repository + +import com.festago.festago.data.dao.RecentSearchQueryDao +import com.festago.festago.data.model.RecentSearchQueryEntity +import com.festago.festago.domain.model.recentsearch.RecentSearchQuery +import com.festago.festago.domain.repository.RecentSearchRepository +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class DefaultRecentSearchRepository @Inject constructor( + private val recentSearchQueryDao: RecentSearchQueryDao, +) : RecentSearchRepository { + + override suspend fun insertOrReplaceRecentSearch(searchQuery: String) { + recentSearchQueryDao.insertOrReplaceRecentSearchQuery( + RecentSearchQueryEntity( + query = searchQuery, + createdAt = System.currentTimeMillis(), + ), + ) + } + + override suspend fun deleteRecentSearch(searchQuery: String) { + recentSearchQueryDao.deleteRecentSearchQuery( + RecentSearchQueryEntity( + query = searchQuery, + createdAt = System.currentTimeMillis(), + ), + ) + } + + override suspend fun getRecentSearchQueries(limit: Int): List { + return recentSearchQueryDao.getRecentSearchQueryEntities(limit).map { recentSearchQueries -> + recentSearchQueries.toDomain() + } + } + + override suspend fun clearRecentSearches() = recentSearchQueryDao.clearRecentSearchQueries() +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSearchRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSearchRepository.kt new file mode 100644 index 000000000..322fbcb23 --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSearchRepository.kt @@ -0,0 +1,33 @@ +package com.festago.festago.data.repository + +import com.festago.festago.data.service.SearchRetrofitService +import com.festago.festago.data.util.onSuccessOrCatch +import com.festago.festago.data.util.runCatchingResponse +import com.festago.festago.domain.model.festival.Festival +import com.festago.festago.domain.model.search.ArtistSearch +import com.festago.festago.domain.model.search.SchoolSearch +import com.festago.festago.domain.repository.SearchRepository +import javax.inject.Inject + +class DefaultSearchRepository @Inject constructor( + private val searchRetrofitService: SearchRetrofitService, +) : SearchRepository { + + override suspend fun searchFestivals(searchQuery: String): Result> { + return runCatchingResponse { searchRetrofitService.searchFestivals(searchQuery) }.onSuccessOrCatch { festivalResponses -> + festivalResponses.map { it.toDomain() } + } + } + + override suspend fun searchArtists(searchQuery: String): Result> { + return runCatchingResponse { + searchRetrofitService.searchArtists(searchQuery) + }.onSuccessOrCatch { artistSearchResponses -> artistSearchResponses.map { it.toDomain() } } + } + + override suspend fun searchSchools(searchQuery: String): Result> { + return runCatchingResponse { + searchRetrofitService.searchSchools(searchQuery) + }.onSuccessOrCatch { schoolSearchResponses -> schoolSearchResponses.map { it.toDomain() } } + } +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt index 382516c38..fe695f2ee 100644 --- a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt +++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt @@ -391,8 +391,8 @@ object FakeFestivals { Festival( id = 1, name = "뉴진스 콘서트", - startDate = LocalDate.MIN, - endDate = LocalDate.MAX, + startDate = LocalDate.now().minusDays(5L), + endDate = LocalDate.now().minusDays(3L), imageUrl = "data:image/jpeg;base64,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", school = School(id = 1L, name = "고려대", imageUrl = ""), artists = listOf( @@ -406,8 +406,8 @@ object FakeFestivals { Festival( id = 2, name = "아이브 콘서트", - startDate = LocalDate.MIN, - endDate = LocalDate.MAX, + startDate = LocalDate.now().minusDays(2L), + endDate = LocalDate.now().plusDays(1L), imageUrl = "data:image/jpeg;base64,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", school = School(id = 1L, name = "연세대", imageUrl = ""), artists = listOf( @@ -441,8 +441,8 @@ object FakeFestivals { Festival( id = 3, name = "아이들 콘서트", - startDate = LocalDate.MIN, - endDate = LocalDate.MAX, + startDate = LocalDate.now().plusDays(5L), + endDate = LocalDate.now().plusDays(6L), imageUrl = "data:image/jpeg;base64,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", school = School(id = 1L, name = "연세대", imageUrl = ""), artists = listOf( @@ -486,8 +486,8 @@ object FakeFestivals { Festival( id = 5, name = "아이브 콘서트", - startDate = LocalDate.MIN, - endDate = LocalDate.MAX, + startDate = LocalDate.now().plusDays(10L), + endDate = LocalDate.now().plusDays(11L), imageUrl = "data:image/jpeg;base64,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", school = School(id = 1L, name = "연세대", imageUrl = ""), artists = listOf( diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSearchRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSearchRepository.kt new file mode 100644 index 000000000..bb269124b --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSearchRepository.kt @@ -0,0 +1,94 @@ +package com.festago.festago.data.repository + +import com.festago.festago.domain.model.festival.Festival +import com.festago.festago.domain.model.search.ArtistSearch +import com.festago.festago.domain.model.search.SchoolSearch +import com.festago.festago.domain.repository.SearchRepository +import kotlinx.coroutines.delay +import java.time.LocalDate +import javax.inject.Inject + +class FakeSearchRepository @Inject constructor() : SearchRepository { + private var times = 0 + override suspend fun searchFestivals(searchQuery: String): Result> { + delay(1000) + times++ + if (times % 2 == 0) { + return Result.success(FakeFestivals.popularFestivals) + } + return Result.success(listOf()) + } + + override suspend fun searchArtists(searchQuery: String): Result> { + delay(1000) + if (times % 5 == 0) { + return Result.failure(Exception()) + } + return Result.success( + listOf( + ArtistSearch( + id = 6L, + name = "뉴진스", + profileImageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg", + todayStage = 2, + upcomingStage = 1, + ), + ArtistSearch( + id = 1L, + name = "BTS", + profileImageUrl = "https://i.namu.wiki/i/gpgJvt_C2vKJS4VA4K_Vm57Y5WoS83ofshxhJlQaT4P9Tu0N96vZ2OcdeAN7ZtRAM26UyyQs3sualkKk6i_SrRMvwVKrU015XJqzJ7wKRbOub_oUAxPSFre_8D5De3oy-fCxL0uZ-HGvsWxIX57yrw.webp", + todayStage = 2, + upcomingStage = 2, + ), + ArtistSearch( + id = 2L, + name = "싸이", + profileImageUrl = "https://i.namu.wiki/i/VH58lI8f-y8QSoxFH9IAjjCobySN0lflZ4rMy6Un7qawUwAyi9UfeseZWCzxH-lQeZk7q_eUyTHGlZBAPqSLWliIKWYDLaAgomVtOyAQg60aCpF3oNTBOgUe_hig3rbHW-YAgoj95Fww3MCToyM6MA.webp", + todayStage = 2, + upcomingStage = 3, + ), + ArtistSearch( + id = 10L, + name = "마마무", + profileImageUrl = "https://i.namu.wiki/i/Mre8tXnE40mB9_UwXIwASMEAUSVhHvyjJxXq-lQo40C3bLWYfxXBeai8t6TugyomPjFgxL3VfDA2zn65HlzqPXgTKlvdRl1gJ6PGZLxYYk8Uhk8L6va7zm_etSK5UzVLE56fUATqUCq-6tRQXigmYQ.webp", + todayStage = 2, + upcomingStage = 4, + ), + ArtistSearch( + id = 11L, + name = "블랙핑크", + profileImageUrl = "https://i.namu.wiki/i/VZxRYO8_CXa2QbOSZgttDq5ue5QEu_Fbk1Lwo3qpasLAfS802YExcnmVmDhCq3ONF0ExzhACz_YkZbxOGmIfjuPDZnFo7i0pWaT05NluHRHGfp9NqsAT6WBNb0k5KecOyDvakXk0VH2fUo4ojSwC6g.webp", + todayStage = 1, + upcomingStage = 5, + ), + ), + ) + } + + override suspend fun searchSchools(searchQuery: String): Result> { + delay(1000) + return Result.success( + listOf( + SchoolSearch( + id = 1L, + name = "부경대학교", + logoUrl = "htts://www.pknu.ac.kr/images/front/sub/univ_logo00.png", + upcomingFestivalStartDate = LocalDate.now().plusDays(10L), + ), + SchoolSearch( + id = 2L, + name = "서울대학교", + logoUrl = "https://blog.kakaocdn.net/dn/CYoCP/btrSeivmaxD/e7JaOZVPI3Je55nAJaHDMK/img.png", + upcomingFestivalStartDate = LocalDate.now().plusDays(3L), + ), + SchoolSearch( + id = 3L, + name = "서울과학기술대학교", + logoUrl = "https://www.seoultech.ac.kr/site/www/images/intro/img_ui01_01.gif", + upcomingFestivalStartDate = null, + ), + + ), + ) + } +} diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/SearchRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/SearchRetrofitService.kt new file mode 100644 index 000000000..794b43f9d --- /dev/null +++ b/android/festago/data/src/main/java/com/festago/festago/data/service/SearchRetrofitService.kt @@ -0,0 +1,25 @@ +package com.festago.festago.data.service + +import com.festago.festago.data.dto.artist.ArtistSearchResponse +import com.festago.festago.data.dto.festival.FestivalResponse +import com.festago.festago.data.dto.school.SchoolSearchResponse +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface SearchRetrofitService { + @GET("api/v1/search/festivals") + suspend fun searchFestivals( + @Query("keyword") keyword: String, + ): Response> + + @GET("api/v1/search/artists") + suspend fun searchArtists( + @Query("keyword") keyword: String, + ): Response> + + @GET("api/v1/search/schools") + suspend fun searchSchools( + @Query("keyword") keyword: String, + ): Response> +} diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/recentsearch/RecentSearchQuery.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/recentsearch/RecentSearchQuery.kt new file mode 100644 index 000000000..197b84bf3 --- /dev/null +++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/recentsearch/RecentSearchQuery.kt @@ -0,0 +1,6 @@ +package com.festago.festago.domain.model.recentsearch + +data class RecentSearchQuery( + val query: String, + val queriedDate: Long, +) diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/ArtistSearch.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/ArtistSearch.kt new file mode 100644 index 000000000..fb472d087 --- /dev/null +++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/ArtistSearch.kt @@ -0,0 +1,9 @@ +package com.festago.festago.domain.model.search + +data class ArtistSearch( + val id: Long, + val name: String, + val profileImageUrl: String, + val todayStage: Int, + val upcomingStage: Int, +) diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/SchoolSearch.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/SchoolSearch.kt new file mode 100644 index 000000000..dde84b81e --- /dev/null +++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/SchoolSearch.kt @@ -0,0 +1,10 @@ +package com.festago.festago.domain.model.search + +import java.time.LocalDate + +data class SchoolSearch( + val id: Long, + val name: String, + val logoUrl: String, + val upcomingFestivalStartDate: LocalDate?, +) diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/RecentSearchRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/RecentSearchRepository.kt new file mode 100644 index 000000000..b24c8c6b8 --- /dev/null +++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/RecentSearchRepository.kt @@ -0,0 +1,10 @@ +package com.festago.festago.domain.repository + +import com.festago.festago.domain.model.recentsearch.RecentSearchQuery + +interface RecentSearchRepository { + suspend fun insertOrReplaceRecentSearch(searchQuery: String) + suspend fun deleteRecentSearch(searchQuery: String) + suspend fun getRecentSearchQueries(limit: Int): List + suspend fun clearRecentSearches() +} diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SearchRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SearchRepository.kt new file mode 100644 index 000000000..b2129f848 --- /dev/null +++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SearchRepository.kt @@ -0,0 +1,11 @@ +package com.festago.festago.domain.repository + +import com.festago.festago.domain.model.festival.Festival +import com.festago.festago.domain.model.search.ArtistSearch +import com.festago.festago.domain.model.search.SchoolSearch + +interface SearchRepository { + suspend fun searchFestivals(searchQuery: String): Result> + suspend fun searchArtists(searchQuery: String): Result> + suspend fun searchSchools(searchQuery: String): Result> +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/customview/ClearEditText.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/customview/ClearEditText.kt new file mode 100644 index 000000000..2df1be761 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/customview/ClearEditText.kt @@ -0,0 +1,92 @@ +package com.festago.festago.presentation.ui.customview + +import android.content.Context +import android.graphics.drawable.Drawable +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.view.View.OnFocusChangeListener +import android.view.View.OnTouchListener +import android.view.inputmethod.InputMethodManager +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +import com.festago.festago.presentation.R + +class ClearEditText(context: Context, attrs: AttributeSet) : + AppCompatEditText(context, attrs), + TextWatcher, + OnTouchListener, + OnFocusChangeListener { + + private val clearDrawable: Drawable by lazy { + val drawable = ContextCompat.getDrawable(context, R.drawable.ic_circle_close)!! + DrawableCompat.wrap(drawable) + } + private var onFocusChangeListener: OnFocusChangeListener? = null + private var onTouchListener: OnTouchListener? = null + + init { + clearDrawable.setBounds( + 0, + 0, + clearDrawable.intrinsicWidth, + clearDrawable.intrinsicHeight, + ) + setClearIconVisible(false) + super.setOnTouchListener(this) + super.setOnFocusChangeListener(this) + addTextChangedListener(this) + } + + override fun setOnFocusChangeListener(onFocusChangeListener: OnFocusChangeListener) { + this.onFocusChangeListener = onFocusChangeListener + } + + override fun setOnTouchListener(onTouchListener: OnTouchListener) { + this.onTouchListener = onTouchListener + } + + override fun onFocusChange(view: View, hasFocus: Boolean) { + setClearIconVisible(text!!.isNotEmpty()) + if (onFocusChangeListener != null) { + onFocusChangeListener!!.onFocusChange(view, hasFocus) + } + } + + override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { + val x = motionEvent.x.toInt() + if (clearDrawable.isVisible && x > width - paddingRight - clearDrawable.intrinsicWidth) { + if (motionEvent.action == MotionEvent.ACTION_UP) { + error = null + text = null + isFocusableInTouchMode = true + val inputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + requestFocus() + inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) + } + return true + } + return if (onTouchListener != null) { + onTouchListener!!.onTouch(view, motionEvent) + } else { + false + } + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (!isFocused) return + setClearIconVisible(s.isNotEmpty()) + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + + override fun afterTextChanged(s: Editable) = Unit + + private fun setClearIconVisible(visible: Boolean) { + clearDrawable.setVisible(visible, false) + setCompoundDrawables(null, null, if (visible) clearDrawable else null, null) + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/customview/FestagoButton.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/customview/FestagoButton.kt new file mode 100644 index 000000000..af1318081 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/customview/FestagoButton.kt @@ -0,0 +1,27 @@ +package com.festago.festago.presentation.ui.customview + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import com.festago.festago.presentation.R +import com.festago.festago.presentation.databinding.BtnFestagoBinding + +class FestagoButton(context: Context, attrs: AttributeSet) : + ConstraintLayout(context, attrs) { + + private val binding by lazy { + BtnFestagoBinding.inflate(LayoutInflater.from(context), this, true) + } + + init { + initAttrs(attrs) + } + + private fun initAttrs(attrs: AttributeSet) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FestagoButton) + val title = typedArray.getString(R.styleable.FestagoButton_title) + binding.tvFestagoBtn.text = title + typedArray.recycle() + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt index bdc2b89d2..06763528a 100644 --- a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt @@ -1,5 +1,8 @@ package com.festago.festago.presentation.ui.home import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject -class HomeViewModel : ViewModel() +@HiltViewModel +class HomeViewModel @Inject constructor() : ViewModel() diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt index 1f69952b8..edb26a588 100644 --- a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt @@ -14,7 +14,7 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.festago.festago.presentation.databinding.FragmentFestivalListBinding -import com.festago.festago.presentation.ui.home.festivallist.FestivalListFragmentDirections.actionFestivalListFragmentToSchoolDetailFragment +import com.festago.festago.presentation.ui.home.festivallist.FestivalListFragmentDirections.actionFestivalListFragmentToSearchFragment import com.festago.festago.presentation.ui.home.festivallist.festival.FestivalListAdapter import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalListUiState import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalMoreItemUiState @@ -188,7 +188,7 @@ class FestivalListFragment : Fragment() { } private fun showSchoolDetail() { - findNavController().navigate(actionFestivalListFragmentToSchoolDetailFragment(0)) + findNavController().navigate(actionFestivalListFragmentToSearchFragment()) } private fun showNotificationList() { diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchEvent.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchEvent.kt new file mode 100644 index 000000000..535934c0f --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchEvent.kt @@ -0,0 +1,9 @@ +package com.festago.festago.presentation.ui.search + +sealed interface SearchEvent { + class ShowFestivalDetail(val festivalId: Long) : SearchEvent + class ShowArtistDetail(val artistId: Long) : SearchEvent + class ShowSchoolDetail(val schoolId: Long) : SearchEvent + class UpdateSearchQuery(val searchQuery: String) : SearchEvent + object SearchBlank : SearchEvent +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchFragment.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchFragment.kt new file mode 100644 index 000000000..069511de6 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchFragment.kt @@ -0,0 +1,225 @@ +package com.festago.festago.presentation.ui.search + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnNextLayout +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.festago.festago.presentation.R +import com.festago.festago.presentation.databinding.FragmentSearchBinding +import com.festago.festago.presentation.ui.search.recentsearch.RecentSearchAdapter +import com.festago.festago.presentation.ui.search.screen.ItemSearchScreenUiState +import com.festago.festago.presentation.ui.search.screen.ItemSearchScreenUiState.ArtistSearchScreen +import com.festago.festago.presentation.ui.search.screen.ItemSearchScreenUiState.FestivalSearchScreen +import com.festago.festago.presentation.ui.search.screen.ItemSearchScreenUiState.SchoolSearchScreen +import com.festago.festago.presentation.ui.search.screen.SearchScreenAdapter +import com.festago.festago.presentation.ui.search.uistate.SearchUiState +import com.festago.festago.presentation.util.repeatOnStarted +import com.festago.festago.presentation.util.setOnApplyWindowInsetsCompatListener +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class SearchFragment : Fragment() { + + private var _binding: FragmentSearchBinding? = null + private val binding get() = _binding!! + + private val vm: SearchViewModel by viewModels() + + private lateinit var recentSearchAdapter: RecentSearchAdapter + private lateinit var searchScreenAdapter: SearchScreenAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentSearchBinding.inflate(inflater) + binding.root.setOnApplyWindowInsetsCompatListener { view, windowInsets -> + val statusBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()) + view.setPadding(0, statusBarInsets.top, 0, 0) + windowInsets + } + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initObserve() + initView() + } + + private fun initObserve() { + repeatOnStarted(viewLifecycleOwner) { + vm.uiState.collect { + binding.uiState = it + updateUi(it) + } + } + repeatOnStarted(viewLifecycleOwner) { + vm.event.collect { + handleEvent(it) + } + } + } + + private fun updateUi(uiState: SearchUiState) { + when (uiState) { + is SearchUiState.Loading, + is SearchUiState.Error, + -> Unit + + is SearchUiState.RecentSearchSuccess -> handleRecentSearchSuccess(uiState) + is SearchUiState.SearchSuccess -> handleSuccessSearch(uiState) + } + } + + private fun initView() { + initRecyclerView() + initBack() + initSearch() + initDeleteAll() + initViewPager() + } + + private fun initViewPager() { + searchScreenAdapter = SearchScreenAdapter() + binding.vpSearch.adapter = searchScreenAdapter + TabLayoutMediator(binding.tlSearch, binding.vpSearch) { tab, position -> + tab.text = when (position) { + ItemSearchScreenUiState.FESTIVAL_POSITION -> getString(R.string.search_tl_tab_festival) + ItemSearchScreenUiState.ARTIST_POSITION -> getString(R.string.search_tl_tab_Artist) + ItemSearchScreenUiState.SCHOOL_POSITION -> getString(R.string.search_tl_tab_school) + else -> "" + } + tab.view.setOnClickListener { + val currentScreen = when (tab.position) { + ItemSearchScreenUiState.FESTIVAL_POSITION -> FestivalSearchScreen(listOf()) + ItemSearchScreenUiState.ARTIST_POSITION -> ArtistSearchScreen(listOf()) + ItemSearchScreenUiState.SCHOOL_POSITION -> SchoolSearchScreen(listOf()) + else -> FestivalSearchScreen(listOf()) + } + vm.currentScreen = currentScreen + } + }.attach() + } + + private fun initRecyclerView() { + recentSearchAdapter = RecentSearchAdapter() + binding.rvRecentSearch.adapter = recentSearchAdapter + } + + private fun initBack() { + binding.ivBack.setOnClickListener { + requireActivity().onBackPressedDispatcher.onBackPressed() + } + } + + private fun initDeleteAll() { + binding.tvDeleteAll.setOnClickListener { + vm.deleteAllRecentSearch() + } + } + + private fun initSearch() { + binding.etSearch.setOnKeyListener { editText, keyCode, event -> + if ((event.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + vm.search(binding.etSearch.text.toString()) + val inputMethodManager = + context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(editText.windowToken, 0) + true + } else { + false + } + } + vm.loadRecentSearch() + } + + private fun handleRecentSearchSuccess(uiState: SearchUiState.RecentSearchSuccess) { + recentSearchAdapter.submitList(uiState.recentSearchQueries) + } + + private fun handleSuccessSearch(uiState: SearchUiState.SearchSuccess) { + searchScreenAdapter.submitList( + listOf( + FestivalSearchScreen(uiState.searchedFestivals, ::requestAddSearchQuery), + ArtistSearchScreen(uiState.searchedArtists, ::requestAddSearchQuery), + SchoolSearchScreen(uiState.searchedSchools, ::requestAddSearchQuery), + ), + ) + initSearchTab() + } + + private fun initSearchTab() { + binding.vpSearch.doOnNextLayout { + binding.vpSearch.setCurrentItem(vm.currentScreen.screenPosition, false) + } + } + + private fun handleEvent(event: SearchEvent) { + when (event) { + is SearchEvent.ShowFestivalDetail -> { + findNavController().navigate( + SearchFragmentDirections.actionSearchFragmentToFestivalDetailFragment( + event.festivalId, + ), + ) + } + + is SearchEvent.ShowArtistDetail -> { + findNavController().navigate( + SearchFragmentDirections.actionSearchFragmentToArtistDetailFragment( + event.artistId, + ), + ) + } + + is SearchEvent.ShowSchoolDetail -> { + findNavController().navigate( + SearchFragmentDirections.actionSearchFragmentToSchoolDetailFragment( + event.schoolId, + ), + ) + } + + is SearchEvent.SearchBlank -> { + Toast.makeText( + requireContext(), + getString(R.string.search_cant_search_blank), + Toast.LENGTH_SHORT, + ).show() + } + + is SearchEvent.UpdateSearchQuery -> { + binding.etSearch.setText(event.searchQuery) + } + } + } + + private fun requestAddSearchQuery() { + startBrowser("https://forms.gle/y17dmCFw1jAYLR9H7") + } + + private fun startBrowser(url: String) { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(url) + startActivity(intent) + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchViewModel.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchViewModel.kt new file mode 100644 index 000000000..58e02f39d --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/SearchViewModel.kt @@ -0,0 +1,170 @@ +package com.festago.festago.presentation.ui.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.festago.festago.common.analytics.AnalyticsHelper +import com.festago.festago.common.analytics.logNetworkFailure +import com.festago.festago.domain.model.festival.Festival +import com.festago.festago.domain.model.recentsearch.RecentSearchQuery +import com.festago.festago.domain.model.search.ArtistSearch +import com.festago.festago.domain.model.search.SchoolSearch +import com.festago.festago.domain.repository.RecentSearchRepository +import com.festago.festago.domain.repository.SearchRepository +import com.festago.festago.presentation.ui.search.screen.ItemSearchScreenUiState +import com.festago.festago.presentation.ui.search.screen.ItemSearchScreenUiState.FestivalSearchScreen +import com.festago.festago.presentation.ui.search.uistate.ArtistSearchItemUiState +import com.festago.festago.presentation.ui.search.uistate.ArtistUiState +import com.festago.festago.presentation.ui.search.uistate.FestivalSearchItemUiState +import com.festago.festago.presentation.ui.search.uistate.RecentSearchItemUiState +import com.festago.festago.presentation.ui.search.uistate.SchoolSearchItemUiState +import com.festago.festago.presentation.ui.search.uistate.SchoolUiState +import com.festago.festago.presentation.ui.search.uistate.SearchUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val recentSearchRepository: RecentSearchRepository, + private val searchRepository: SearchRepository, + private val analyticsHelper: AnalyticsHelper, +) : ViewModel() { + + private val _uiState: MutableStateFlow = + MutableStateFlow(SearchUiState.RecentSearchSuccess(listOf())) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _event = MutableSharedFlow() + val event: SharedFlow = _event.asSharedFlow() + + var currentScreen: ItemSearchScreenUiState = FestivalSearchScreen(listOf()) + + fun loadRecentSearch() { + if (uiState.value is SearchUiState.SearchSuccess) return + viewModelScope.launch { + _uiState.value = SearchUiState.RecentSearchSuccess( + recentSearchRepository.getRecentSearchQueries(10).map { it.toUiState() }, + ) + } + } + + fun search(searchQuery: String) { + viewModelScope.launch { + if (searchQuery == "") { + _event.emit(SearchEvent.SearchBlank) + return@launch + } + _uiState.value = SearchUiState.Loading + recentSearchRepository.insertOrReplaceRecentSearch(searchQuery) + val deferredFestivals = async { searchRepository.searchFestivals(searchQuery) } + val deferredArtist = async { searchRepository.searchArtists(searchQuery) } + val deferredSchools = async { searchRepository.searchSchools(searchQuery) } + runCatching { + _uiState.value = SearchUiState.SearchSuccess( + deferredFestivals.await().getOrThrow().map { it.toUiState() }, + deferredArtist.await().getOrThrow().map { it.toUiState() }, + deferredSchools.await().getOrThrow().map { it.toUiState() }, + ) + }.onFailure { + _uiState.value = SearchUiState.Error + analyticsHelper.logNetworkFailure( + key = KEY_SEARCH, + value = it.message.toString(), + ) + } + } + } + + private fun deleteRecentSearch(searchQuery: String) { + viewModelScope.launch { + recentSearchRepository.deleteRecentSearch(searchQuery) + loadRecentSearch() + } + } + + fun deleteAllRecentSearch() { + viewModelScope.launch { + recentSearchRepository.clearRecentSearches() + loadRecentSearch() + } + } + + private fun RecentSearchQuery.toUiState() = RecentSearchItemUiState( + recentQuery = query, + onQuerySearched = ::searchRecentQuery, + onRecentSearchDeleted = ::deleteRecentSearch, + ) + + private fun Festival.toUiState() = FestivalSearchItemUiState( + id = id, + name = name, + startDate = startDate, + endDate = endDate, + imageUrl = imageUrl, + schoolUiState = SchoolUiState( + id = school.id, + name = school.name, + ), + artists = artists.map { artist -> + ArtistUiState(artist.id, artist.name, artist.imageUrl, ::showArtistDetail) + }, + ::showFestivalDetail, + ) + + private fun ArtistSearch.toUiState() = ArtistSearchItemUiState( + id = id, + name = name, + profileImageUrl = profileImageUrl, + todayStage = todayStage, + upcomingStage = upcomingStage, + onArtistDetailClick = ::showArtistDetail, + ) + + private fun SchoolSearch.toUiState() = SchoolSearchItemUiState( + id = id, + name = name, + logoUrl = logoUrl, + upcomingFestivalStartDate = upcomingFestivalStartDate, + onSchoolSearchClick = ::showSchoolDetail, + ) + + private fun showFestivalDetail(festivalId: Long) { + viewModelScope.launch { + _event.emit(SearchEvent.ShowFestivalDetail(festivalId)) + } + } + + private fun showArtistDetail(artistId: Long) { + viewModelScope.launch { + _event.emit(SearchEvent.ShowArtistDetail(artistId)) + } + } + + private fun showSchoolDetail(schoolId: Long) { + viewModelScope.launch { + _event.emit(SearchEvent.ShowSchoolDetail(schoolId)) + } + } + + private fun searchRecentQuery(searchQuery: String) { + search(searchQuery) + updateSearchQuery(searchQuery) + } + + private fun updateSearchQuery(searchQuery: String) { + viewModelScope.launch { + _event.emit(SearchEvent.UpdateSearchQuery(searchQuery)) + } + } + + companion object { + private const val KEY_SEARCH = "KEY_SEARCH" + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/artistsearch/ArtistSearchAdapter.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/artistsearch/ArtistSearchAdapter.kt new file mode 100644 index 000000000..49e677853 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/artistsearch/ArtistSearchAdapter.kt @@ -0,0 +1,32 @@ +package com.festago.festago.presentation.ui.search.artistsearch + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import com.festago.festago.presentation.ui.search.uistate.ArtistSearchItemUiState + +class ArtistSearchAdapter : ListAdapter(diffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtistSearchViewHolder { + return ArtistSearchViewHolder.of(parent) + } + + override fun onBindViewHolder(holder: ArtistSearchViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + companion object { + + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ArtistSearchItemUiState, + newItem: ArtistSearchItemUiState, + ): Boolean = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: ArtistSearchItemUiState, + newItem: ArtistSearchItemUiState, + ): Boolean = oldItem == newItem + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/artistsearch/ArtistSearchViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/artistsearch/ArtistSearchViewHolder.kt new file mode 100644 index 000000000..5f74472e8 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/artistsearch/ArtistSearchViewHolder.kt @@ -0,0 +1,63 @@ +package com.festago.festago.presentation.ui.search.artistsearch + +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.festago.festago.presentation.R +import com.festago.festago.presentation.databinding.ItemSearchArtistBinding +import com.festago.festago.presentation.ui.search.uistate.ArtistSearchItemUiState + +class ArtistSearchViewHolder( + private val binding: ItemSearchArtistBinding, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: ArtistSearchItemUiState) { + binding.item = item + binding.tvTodayStageCount.setStageCount( + count = item.todayStage, + stringRes = R.string.search_artist_tv_today_stage_count, + ) + binding.tvUpcomingStageCount.setStageCount( + item.upcomingStage, + stringRes = R.string.search_artist_tv_upcoming_stage_count, + ) + } + + private fun TextView.setStageCount(count: Int, @StringRes stringRes: Int) { + val stageCountText = context.getString(stringRes, count) + text = SpannableString(stageCountText).apply { + getPartialColorText( + start = COLOR_INDEX, + end = COLOR_INDEX + 1, + color = context.getColor(R.color.secondary_pink_01), + ) + } + } + + private fun SpannableString.getPartialColorText( + start: Int, + end: Int, + @ColorInt color: Int, + ) { + setSpan(ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + companion object { + private const val COLOR_INDEX = 6 + + fun of(parent: ViewGroup): ArtistSearchViewHolder { + val binding = ItemSearchArtistBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return ArtistSearchViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/FestivalSearchAdapter.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/FestivalSearchAdapter.kt new file mode 100644 index 000000000..bcc748f03 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/FestivalSearchAdapter.kt @@ -0,0 +1,33 @@ +package com.festago.festago.presentation.ui.search.festivalsearch + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import com.festago.festago.presentation.ui.search.uistate.FestivalSearchItemUiState + +class FestivalSearchAdapter : + ListAdapter(diffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FestivalSearchViewHolder { + return FestivalSearchViewHolder.of(parent) + } + + override fun onBindViewHolder(holder: FestivalSearchViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + companion object { + + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: FestivalSearchItemUiState, + newItem: FestivalSearchItemUiState, + ): Boolean = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: FestivalSearchItemUiState, + newItem: FestivalSearchItemUiState, + ): Boolean = oldItem == newItem + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/FestivalSearchViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/FestivalSearchViewHolder.kt new file mode 100644 index 000000000..bf60e0712 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/FestivalSearchViewHolder.kt @@ -0,0 +1,91 @@ +package com.festago.festago.presentation.ui.search.festivalsearch + +import android.content.res.Resources +import android.graphics.Rect +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources +import androidx.recyclerview.widget.RecyclerView +import com.festago.festago.presentation.R +import com.festago.festago.presentation.databinding.ItemSearchFestivalBinding +import com.festago.festago.presentation.ui.search.festivalsearch.artist.ArtistAdapter +import com.festago.festago.presentation.ui.search.uistate.FestivalSearchItemUiState +import java.time.LocalDate + +class FestivalSearchViewHolder( + private val binding: ItemSearchFestivalBinding, +) : RecyclerView.ViewHolder(binding.root) { + + private val artistAdapter = ArtistAdapter() + + init { + binding.rvFestivalArtists.adapter = artistAdapter + binding.rvFestivalArtists.addItemDecoration(ArtistItemDecoration()) + } + + fun bind(item: FestivalSearchItemUiState) { + binding.item = item + artistAdapter.submitList(item.artists) + binding.tvFestivalDDay.bindFestivalDday(item) + } + + private fun TextView.bindFestivalDday(item: FestivalSearchItemUiState) { + when { + LocalDate.now() > item.endDate -> Unit + + LocalDate.now() >= item.startDate -> { + text = context.getString(R.string.festival_list_tv_dday_in_progress) + setTextColor(context.getColor(R.color.secondary_pink_01)) + background = AppCompatResources.getDrawable( + context, + R.drawable.bg_festival_list_dday_in_progress, + ) + } + + else -> { + val dDay = LocalDate.now().toEpochDay() - item.startDate.toEpochDay() + val backgroundColor = if (dDay >= -7L) { + context.getColor(R.color.secondary_pink_01) + } else { + context.getColor(R.color.contents_gray_07) + } + setBackgroundColor(backgroundColor) + setTextColor(context.getColor(R.color.background_gray_01)) + text = context.getString(R.string.festival_detail_tv_dday_format, dDay.toString()) + } + } + } + + private class ArtistItemDecoration : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State, + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.right = 8.dpToPx + } + + private val Int.dpToPx: Int + get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this.toFloat(), + Resources.getSystem().displayMetrics, + ).toInt() + } + + companion object { + fun of(parent: ViewGroup): FestivalSearchViewHolder { + val binding = ItemSearchFestivalBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return FestivalSearchViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/artist/ArtistAdapter.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/artist/ArtistAdapter.kt new file mode 100644 index 000000000..103758f3f --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/artist/ArtistAdapter.kt @@ -0,0 +1,35 @@ +package com.festago.festago.presentation.ui.search.festivalsearch.artist + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import com.festago.festago.presentation.ui.search.uistate.ArtistUiState + +class ArtistAdapter : ListAdapter(diffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtistViewHolder { + return ArtistViewHolder.of(parent) + } + + override fun onBindViewHolder(holder: ArtistViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + companion object { + private val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ArtistUiState, + newItem: ArtistUiState, + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: ArtistUiState, + newItem: ArtistUiState, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/artist/ArtistViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/artist/ArtistViewHolder.kt new file mode 100644 index 000000000..cb3f878a4 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/festivalsearch/artist/ArtistViewHolder.kt @@ -0,0 +1,27 @@ +package com.festago.festago.presentation.ui.search.festivalsearch.artist + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.festago.festago.presentation.databinding.ItemSearchFestivalArtistBinding +import com.festago.festago.presentation.ui.search.uistate.ArtistUiState + +class ArtistViewHolder( + private val binding: ItemSearchFestivalArtistBinding, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: ArtistUiState) { + binding.item = item + } + + companion object { + fun of(parent: ViewGroup): ArtistViewHolder { + val binding = ItemSearchFestivalArtistBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return ArtistViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/recentsearch/RecentSearchAdapter.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/recentsearch/RecentSearchAdapter.kt new file mode 100644 index 000000000..03721096e --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/recentsearch/RecentSearchAdapter.kt @@ -0,0 +1,36 @@ +package com.festago.festago.presentation.ui.search.recentsearch + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import com.festago.festago.presentation.ui.search.uistate.RecentSearchItemUiState + +class RecentSearchAdapter : ListAdapter(diffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentSearchViewHolder { + return RecentSearchViewHolder.of(parent) + } + + override fun onBindViewHolder(holder: RecentSearchViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + companion object { + + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: RecentSearchItemUiState, + newItem: RecentSearchItemUiState, + ): Boolean { + return oldItem.recentQuery == newItem.recentQuery + } + + override fun areContentsTheSame( + oldItem: RecentSearchItemUiState, + newItem: RecentSearchItemUiState, + ): Boolean { + return oldItem == newItem + } + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/recentsearch/RecentSearchViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/recentsearch/RecentSearchViewHolder.kt new file mode 100644 index 000000000..1f76330ca --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/recentsearch/RecentSearchViewHolder.kt @@ -0,0 +1,27 @@ +package com.festago.festago.presentation.ui.search.recentsearch + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.festago.festago.presentation.databinding.ItemRecentSearchBinding +import com.festago.festago.presentation.ui.search.uistate.RecentSearchItemUiState + +class RecentSearchViewHolder( + val binding: ItemRecentSearchBinding, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: RecentSearchItemUiState) { + binding.item = item + } + + companion object { + fun of(parent: ViewGroup): RecentSearchViewHolder { + val binding = ItemRecentSearchBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return RecentSearchViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/schoolSearchAdatper/SchoolSearchAdapter.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/schoolSearchAdatper/SchoolSearchAdapter.kt new file mode 100644 index 000000000..2735e3d71 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/schoolSearchAdatper/SchoolSearchAdapter.kt @@ -0,0 +1,32 @@ +package com.festago.festago.presentation.ui.search.schoolSearchAdatper + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import com.festago.festago.presentation.ui.search.uistate.SchoolSearchItemUiState + +class SchoolSearchAdapter : ListAdapter(diffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SchoolSearchViewHolder { + return SchoolSearchViewHolder.of(parent) + } + + override fun onBindViewHolder(holder: SchoolSearchViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + companion object { + + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SchoolSearchItemUiState, + newItem: SchoolSearchItemUiState, + ): Boolean = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: SchoolSearchItemUiState, + newItem: SchoolSearchItemUiState, + ): Boolean = oldItem == newItem + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/schoolSearchAdatper/SchoolSearchViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/schoolSearchAdatper/SchoolSearchViewHolder.kt new file mode 100644 index 000000000..26d3b37e4 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/schoolSearchAdatper/SchoolSearchViewHolder.kt @@ -0,0 +1,56 @@ +package com.festago.festago.presentation.ui.search.schoolSearchAdatper + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.festago.festago.presentation.R +import com.festago.festago.presentation.databinding.ItemSearchSchoolBinding +import com.festago.festago.presentation.ui.search.uistate.SchoolSearchItemUiState +import java.time.LocalDate + +class SchoolSearchViewHolder( + private val binding: ItemSearchSchoolBinding, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: SchoolSearchItemUiState) { + binding.item = item + binding.tvSchoolFestivalDday.setSchoolFestivalDday(item.upcomingFestivalStartDate) + } + + private fun TextView.setSchoolFestivalDday( + upcomingFestivalStartDate: LocalDate?, + ) { + when { + upcomingFestivalStartDate == null -> { + text = context.getString(R.string.search_school_tv_no_plan) + setTextColor(context.getColor(R.color.contents_gray_05)) + } + + LocalDate.now() >= upcomingFestivalStartDate -> { + text = context.getString(R.string.search_school_tv_dday_in_progress) + setTextColor(context.getColor(R.color.secondary_pink_01)) + } + + LocalDate.now() < upcomingFestivalStartDate -> { + val dDay = + LocalDate.now().toEpochDay() - upcomingFestivalStartDate.toEpochDay() + text = context.getString(R.string.search_school_tv_dday_format, dDay.toString()) + val colorId = + if (dDay >= -7L) R.color.secondary_pink_01 else R.color.contents_gray_07 + setTextColor(context.getColor(colorId)) + } + } + } + + companion object { + fun of(parent: ViewGroup): SchoolSearchViewHolder { + val binding = ItemSearchSchoolBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return SchoolSearchViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/ArtistSearchScreenViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/ArtistSearchScreenViewHolder.kt new file mode 100644 index 000000000..fe9233a74 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/ArtistSearchScreenViewHolder.kt @@ -0,0 +1,44 @@ +package com.festago.festago.presentation.ui.search.screen + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.festago.festago.presentation.databinding.ItemArtistSearchScreenBinding +import com.festago.festago.presentation.ui.search.artistsearch.ArtistSearchAdapter + +class ArtistSearchScreenViewHolder( + private val binding: ItemArtistSearchScreenBinding, +) : SearchScreenViewHolder(binding) { + + private val artistSearchAdapter: ArtistSearchAdapter = ArtistSearchAdapter() + + init { + binding.rvArtists.adapter = artistSearchAdapter + } + + fun bind(item: ItemSearchScreenUiState.ArtistSearchScreen) { + artistSearchAdapter.submitList(item.artistSearches) + setNoResultVisibility(item) + binding.btnFestago.setOnClickListener { + item.onAddSearchQueryClick() + } + } + + private fun setNoResultVisibility(item: ItemSearchScreenUiState.ArtistSearchScreen) { + val visibility = if (item.artistSearches.isEmpty()) View.VISIBLE else View.GONE + binding.tvNoResult.visibility = visibility + binding.tvNoResultGuide.visibility = visibility + binding.btnFestago.visibility = visibility + } + + companion object { + fun of(parent: ViewGroup): ArtistSearchScreenViewHolder { + val binding = ItemArtistSearchScreenBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return ArtistSearchScreenViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/FestivalSearchScreenViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/FestivalSearchScreenViewHolder.kt new file mode 100644 index 000000000..eb33d7c81 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/FestivalSearchScreenViewHolder.kt @@ -0,0 +1,44 @@ +package com.festago.festago.presentation.ui.search.screen + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.festago.festago.presentation.databinding.ItemFestivalSearchScreenBinding +import com.festago.festago.presentation.ui.search.festivalsearch.FestivalSearchAdapter + +class FestivalSearchScreenViewHolder( + private val binding: ItemFestivalSearchScreenBinding, +) : SearchScreenViewHolder(binding) { + + private val festivalSearchAdapter: FestivalSearchAdapter = FestivalSearchAdapter() + + init { + binding.rvFestivals.adapter = festivalSearchAdapter + } + + fun bind(item: ItemSearchScreenUiState.FestivalSearchScreen) { + festivalSearchAdapter.submitList(item.festivalSearches) + setNoResultVisibility(item) + binding.btnFestago.setOnClickListener { + item.onAddSearchQueryClick() + } + } + + private fun setNoResultVisibility(item: ItemSearchScreenUiState.FestivalSearchScreen) { + val visibility = if (item.festivalSearches.isEmpty()) View.VISIBLE else View.GONE + binding.tvNoResult.visibility = visibility + binding.tvNoResultGuide.visibility = visibility + binding.btnFestago.visibility = visibility + } + + companion object { + fun of(parent: ViewGroup): FestivalSearchScreenViewHolder { + val binding = ItemFestivalSearchScreenBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return FestivalSearchScreenViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/ItemSearchScreenUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/ItemSearchScreenUiState.kt new file mode 100644 index 000000000..78a886270 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/ItemSearchScreenUiState.kt @@ -0,0 +1,30 @@ +package com.festago.festago.presentation.ui.search.screen + +import com.festago.festago.presentation.ui.search.uistate.ArtistSearchItemUiState +import com.festago.festago.presentation.ui.search.uistate.FestivalSearchItemUiState +import com.festago.festago.presentation.ui.search.uistate.SchoolSearchItemUiState + +sealed class ItemSearchScreenUiState( + val screenPosition: Int, +) { + data class FestivalSearchScreen( + val festivalSearches: List, + val onAddSearchQueryClick: () -> Unit = {}, + ) : ItemSearchScreenUiState(FESTIVAL_POSITION) + + data class ArtistSearchScreen( + val artistSearches: List, + val onAddSearchQueryClick: () -> Unit = {}, + ) : ItemSearchScreenUiState(ARTIST_POSITION) + + data class SchoolSearchScreen( + val schoolSearches: List, + val onAddSearchQueryClick: () -> Unit = {}, + ) : ItemSearchScreenUiState(SCHOOL_POSITION) + + companion object { + const val FESTIVAL_POSITION = 0 + const val ARTIST_POSITION = 1 + const val SCHOOL_POSITION = 2 + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SchoolSearchScreenViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SchoolSearchScreenViewHolder.kt new file mode 100644 index 000000000..8702fa879 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SchoolSearchScreenViewHolder.kt @@ -0,0 +1,42 @@ +package com.festago.festago.presentation.ui.search.screen + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.festago.festago.presentation.databinding.ItemSchoolSearchScreenBinding +import com.festago.festago.presentation.ui.search.schoolSearchAdatper.SchoolSearchAdapter + +class SchoolSearchScreenViewHolder(private val binding: ItemSchoolSearchScreenBinding) : + SearchScreenViewHolder(binding) { + private val schoolSearchAdapter: SchoolSearchAdapter = SchoolSearchAdapter() + + init { + binding.rvSchools.adapter = schoolSearchAdapter + } + + fun bind(item: ItemSearchScreenUiState.SchoolSearchScreen) { + schoolSearchAdapter.submitList(item.schoolSearches) + setNoResultVisibility(item) + binding.btnFestago.setOnClickListener { + item.onAddSearchQueryClick() + } + } + + private fun setNoResultVisibility(item: ItemSearchScreenUiState.SchoolSearchScreen) { + val visibility = if (item.schoolSearches.isEmpty()) View.VISIBLE else View.GONE + binding.tvNoResult.visibility = visibility + binding.tvNoResultGuide.visibility = visibility + binding.btnFestago.visibility = visibility + } + + companion object { + fun of(parent: ViewGroup): SchoolSearchScreenViewHolder { + val binding = ItemSchoolSearchScreenBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return SchoolSearchScreenViewHolder(binding) + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SearchScreenAdapter.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SearchScreenAdapter.kt new file mode 100644 index 000000000..23c7781aa --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SearchScreenAdapter.kt @@ -0,0 +1,47 @@ +package com.festago.festago.presentation.ui.search.screen + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter + +class SearchScreenAdapter : ListAdapter(diffUtil) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchScreenViewHolder { + return when (viewType) { + 1 -> FestivalSearchScreenViewHolder.of(parent) + 2 -> ArtistSearchScreenViewHolder.of(parent) + 3 -> SchoolSearchScreenViewHolder.of(parent) + else -> throw IllegalArgumentException("Invalid viewType") + } + } + + override fun onBindViewHolder(holder: SearchScreenViewHolder, position: Int) { + val item = getItem(position) + return when (holder) { + is FestivalSearchScreenViewHolder -> holder.bind(item as ItemSearchScreenUiState.FestivalSearchScreen) + is ArtistSearchScreenViewHolder -> holder.bind(item as ItemSearchScreenUiState.ArtistSearchScreen) + is SchoolSearchScreenViewHolder -> holder.bind(item as ItemSearchScreenUiState.SchoolSearchScreen) + } + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is ItemSearchScreenUiState.FestivalSearchScreen -> 1 + is ItemSearchScreenUiState.ArtistSearchScreen -> 2 + is ItemSearchScreenUiState.SchoolSearchScreen -> 3 + } + } + + companion object { + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ItemSearchScreenUiState, + newItem: ItemSearchScreenUiState, + ): Boolean = oldItem === newItem + + override fun areContentsTheSame( + oldItem: ItemSearchScreenUiState, + newItem: ItemSearchScreenUiState, + ): Boolean = oldItem == newItem + } + } +} diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SearchScreenViewHolder.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SearchScreenViewHolder.kt new file mode 100644 index 000000000..40df9c049 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/screen/SearchScreenViewHolder.kt @@ -0,0 +1,7 @@ +package com.festago.festago.presentation.ui.search.screen + +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView + +sealed class SearchScreenViewHolder(binding: ViewDataBinding) : + RecyclerView.ViewHolder(binding.root) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/ArtistSearchItemUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/ArtistSearchItemUiState.kt new file mode 100644 index 000000000..99eaf90e9 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/ArtistSearchItemUiState.kt @@ -0,0 +1,10 @@ +package com.festago.festago.presentation.ui.search.uistate + +data class ArtistSearchItemUiState( + val id: Long, + val name: String, + val profileImageUrl: String, + val todayStage: Int, + val upcomingStage: Int, + val onArtistDetailClick: (Long) -> Unit, +) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/ArtistUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/ArtistUiState.kt new file mode 100644 index 000000000..9d559f34b --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/ArtistUiState.kt @@ -0,0 +1,8 @@ +package com.festago.festago.presentation.ui.search.uistate + +data class ArtistUiState( + val id: Long, + val name: String, + val imageUrl: String, + val onArtistDetailClick: (Long) -> Unit, +) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/FestivalSearchItemUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/FestivalSearchItemUiState.kt new file mode 100644 index 000000000..218141b52 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/FestivalSearchItemUiState.kt @@ -0,0 +1,14 @@ +package com.festago.festago.presentation.ui.search.uistate + +import java.time.LocalDate + +data class FestivalSearchItemUiState( + val id: Long, + val name: String, + val startDate: LocalDate, + val endDate: LocalDate, + val imageUrl: String, + val schoolUiState: SchoolUiState, + val artists: List, + val onFestivalSearchClick: (festivalId: Long) -> Unit, +) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/RecentSearchItemUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/RecentSearchItemUiState.kt new file mode 100644 index 000000000..72ad05d82 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/RecentSearchItemUiState.kt @@ -0,0 +1,7 @@ +package com.festago.festago.presentation.ui.search.uistate + +data class RecentSearchItemUiState( + val recentQuery: String, + val onQuerySearched: (recentQuery: String) -> Unit, + val onRecentSearchDeleted: (recentQuery: String) -> Unit, +) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SchoolSearchItemUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SchoolSearchItemUiState.kt new file mode 100644 index 000000000..8b4a459fc --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SchoolSearchItemUiState.kt @@ -0,0 +1,11 @@ +package com.festago.festago.presentation.ui.search.uistate + +import java.time.LocalDate + +data class SchoolSearchItemUiState( + val id: Long, + val name: String, + val logoUrl: String, + val upcomingFestivalStartDate: LocalDate?, + val onSchoolSearchClick: (schoolId: Long) -> Unit, +) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SchoolUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SchoolUiState.kt new file mode 100644 index 000000000..446acb2ba --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SchoolUiState.kt @@ -0,0 +1,6 @@ +package com.festago.festago.presentation.ui.search.uistate + +data class SchoolUiState( + val id: Long, + val name: String, +) diff --git a/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SearchUiState.kt b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SearchUiState.kt new file mode 100644 index 000000000..d80d392f2 --- /dev/null +++ b/android/festago/presentation/src/main/java/com/festago/festago/presentation/ui/search/uistate/SearchUiState.kt @@ -0,0 +1,24 @@ +package com.festago.festago.presentation.ui.search.uistate + +sealed interface SearchUiState { + object Loading : SearchUiState + + data class RecentSearchSuccess( + val recentSearchQueries: List, + ) : SearchUiState + + data class SearchSuccess( + val searchedFestivals: List, + val searchedArtists: List, + val searchedSchools: List, + ) : SearchUiState + + object Error : SearchUiState + + val shouldShowNotEmptyRecentSearchSuccess get() = this is RecentSearchSuccess && recentSearchQueries.isNotEmpty() + val shouldShowEmptyRecentSearchSuccess get() = this is RecentSearchSuccess && recentSearchQueries.isEmpty() + val shouldShowRecentSearchSuccess get() = this is RecentSearchSuccess + val shouldShowSearchSuccess get() = this is SearchSuccess + val shouldShowLoading get() = this is Loading + val shouldShowError get() = this is Error +} diff --git a/android/festago/presentation/src/main/res/drawable/ic_arrow_back_ios_new_24.xml b/android/festago/presentation/src/main/res/drawable/ic_arrow_back_ios_new_24.xml deleted file mode 100644 index 6bd5650e1..000000000 --- a/android/festago/presentation/src/main/res/drawable/ic_arrow_back_ios_new_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/android/festago/presentation/src/main/res/drawable/ic_back_gray_07.xml b/android/festago/presentation/src/main/res/drawable/ic_back_gray_07.xml new file mode 100644 index 000000000..58dfdf648 --- /dev/null +++ b/android/festago/presentation/src/main/res/drawable/ic_back_gray_07.xml @@ -0,0 +1,13 @@ + + + diff --git a/android/festago/presentation/src/main/res/drawable/ic_circle_close.xml b/android/festago/presentation/src/main/res/drawable/ic_circle_close.xml new file mode 100644 index 000000000..b12812144 --- /dev/null +++ b/android/festago/presentation/src/main/res/drawable/ic_circle_close.xml @@ -0,0 +1,13 @@ + + + diff --git a/android/festago/presentation/src/main/res/drawable/ic_close.xml b/android/festago/presentation/src/main/res/drawable/ic_close.xml new file mode 100644 index 000000000..03c1b023f --- /dev/null +++ b/android/festago/presentation/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/festago/presentation/src/main/res/drawable/ic_next.xml b/android/festago/presentation/src/main/res/drawable/ic_next.xml new file mode 100644 index 000000000..d9d292377 --- /dev/null +++ b/android/festago/presentation/src/main/res/drawable/ic_next.xml @@ -0,0 +1,13 @@ + + + diff --git a/android/festago/presentation/src/main/res/drawable/ic_x_close.xml b/android/festago/presentation/src/main/res/drawable/ic_x_close.xml new file mode 100644 index 000000000..0d1129d89 --- /dev/null +++ b/android/festago/presentation/src/main/res/drawable/ic_x_close.xml @@ -0,0 +1,13 @@ + + + diff --git a/android/festago/presentation/src/main/res/layout/activity_notification_list.xml b/android/festago/presentation/src/main/res/layout/activity_notification_list.xml index c637ebb1a..d132f9d45 100644 --- a/android/festago/presentation/src/main/res/layout/activity_notification_list.xml +++ b/android/festago/presentation/src/main/res/layout/activity_notification_list.xml @@ -21,10 +21,10 @@ + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/fragment_artist_detail.xml b/android/festago/presentation/src/main/res/layout/fragment_artist_detail.xml index d2fe488db..9c9daa8b9 100644 --- a/android/festago/presentation/src/main/res/layout/fragment_artist_detail.xml +++ b/android/festago/presentation/src/main/res/layout/fragment_artist_detail.xml @@ -47,10 +47,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_artist_search_screen.xml b/android/festago/presentation/src/main/res/layout/item_artist_search_screen.xml new file mode 100644 index 000000000..36e3037be --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_artist_search_screen.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_festival_search_screen.xml b/android/festago/presentation/src/main/res/layout/item_festival_search_screen.xml new file mode 100644 index 000000000..30893979e --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_festival_search_screen.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_recent_search.xml b/android/festago/presentation/src/main/res/layout/item_recent_search.xml new file mode 100644 index 000000000..dff693669 --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_recent_search.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_school_search_screen.xml b/android/festago/presentation/src/main/res/layout/item_school_search_screen.xml new file mode 100644 index 000000000..bf5c0dc52 --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_school_search_screen.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_search_artist.xml b/android/festago/presentation/src/main/res/layout/item_search_artist.xml new file mode 100644 index 000000000..30d024ad8 --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_search_artist.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_search_festival.xml b/android/festago/presentation/src/main/res/layout/item_search_festival.xml new file mode 100644 index 000000000..80f71338b --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_search_festival.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_search_festival_artist.xml b/android/festago/presentation/src/main/res/layout/item_search_festival_artist.xml new file mode 100644 index 000000000..c1c402ca9 --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_search_festival_artist.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/layout/item_search_school.xml b/android/festago/presentation/src/main/res/layout/item_search_school.xml new file mode 100644 index 000000000..7652d366b --- /dev/null +++ b/android/festago/presentation/src/main/res/layout/item_search_school.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/festago/presentation/src/main/res/navigation/festival_list.xml b/android/festago/presentation/src/main/res/navigation/festival_list.xml index 9df3fa8be..554a8d764 100644 --- a/android/festago/presentation/src/main/res/navigation/festival_list.xml +++ b/android/festago/presentation/src/main/res/navigation/festival_list.xml @@ -25,6 +25,11 @@ app:destination="@id/festivalDetailFragment" app:enterAnim="@android:anim/slide_in_left" app:popExitAnim="@android:anim/slide_out_right" /> + + + + + + + diff --git a/android/festago/presentation/src/main/res/values/attrs.xml b/android/festago/presentation/src/main/res/values/attrs.xml new file mode 100644 index 000000000..23b2df7d8 --- /dev/null +++ b/android/festago/presentation/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/festago/presentation/src/main/res/values/strings.xml b/android/festago/presentation/src/main/res/values/strings.xml index 544729f9a..9d27be328 100644 --- a/android/festago/presentation/src/main/res/values/strings.xml +++ b/android/festago/presentation/src/main/res/values/strings.xml @@ -33,4 +33,21 @@ D%1$s 종료 + + 학교명, 아티스트명, 축제명으로 입력하세요. + 최근 검색어 + 전체 삭제 + 최근 검색 내역이 없어요 + 축제 + 아티스트 + 학교 + 검색어를 입력해주세요 + 축제 중 + D%1$s + 예정 없음 + 오늘 공연 %d개 + 공연 예정 %d개 + 앗! 검색 결과가 없네요 + 찾으시는 정보가 없다면 저희에게 알려주세요 + 추가 요청하러 가기