From f15ece1b213d0f45626393ce74a58f8d5f70fa63 Mon Sep 17 00:00:00 2001 From: yuri-0415 Date: Sun, 22 Oct 2023 20:11:32 +0900 Subject: [PATCH] update home ui and logic --- .idea/.gitignore | 3 + .idea/gradle.xml | 13 + .idea/misc.xml | 4 + .idea/vcs.xml | 6 + frontend/.idea/deploymentTargetDropDown.xml | 4 +- frontend/app/build.gradle.kts | 10 +- .../java/com/example/haengsha/MainActivity.kt | 3 + .../haengsha/model/dataSource/AppContainer.kt | 16 +- .../model/dataSource/EventDataRepository.kt | 17 + .../network/apiService/EventApiService.kt | 76 ++++ .../model/viewModel/event/EventViewModel.kt | 75 ++++ .../com/example/haengsha/ui/HaengshaApp.kt | 4 +- .../haengsha/ui/screens/home/EventCard.kt | 115 +++++ .../example/haengsha/ui/screens/home/Home.kt | 213 ++++++++- .../haengsha/ui/screens/home/TabView.kt | 423 ++++++++++++++++++ frontend/build.gradle.kts | 2 +- 16 files changed, 968 insertions(+), 16 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 frontend/app/src/main/java/com/example/haengsha/model/dataSource/EventDataRepository.kt create mode 100644 frontend/app/src/main/java/com/example/haengsha/model/network/apiService/EventApiService.kt create mode 100644 frontend/app/src/main/java/com/example/haengsha/model/viewModel/event/EventViewModel.kt create mode 100644 frontend/app/src/main/java/com/example/haengsha/ui/screens/home/EventCard.kt create mode 100644 frontend/app/src/main/java/com/example/haengsha/ui/screens/home/TabView.kt diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..de8896e --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6ed36dd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/.idea/deploymentTargetDropDown.xml b/frontend/.idea/deploymentTargetDropDown.xml index 5d88396..495f769 100644 --- a/frontend/.idea/deploymentTargetDropDown.xml +++ b/frontend/.idea/deploymentTargetDropDown.xml @@ -7,11 +7,11 @@ - + - + \ No newline at end of file diff --git a/frontend/app/build.gradle.kts b/frontend/app/build.gradle.kts index 0c676a4..f129cb1 100644 --- a/frontend/app/build.gradle.kts +++ b/frontend/app/build.gradle.kts @@ -58,10 +58,10 @@ dependencies { implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3") implementation("com.google.android.material:material:1.11.0-beta01") implementation("androidx.navigation:navigation-compose:2.7.4") + implementation("androidx.compose.material3:material3-android:1.2.0-alpha09") implementation("androidx.navigation:navigation-runtime-ktx:2.7.4") implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") implementation("androidx.navigation:navigation-ui-ktx:2.7.4") @@ -79,6 +79,14 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") implementation("com.github.GrenderG:Toasty:1.5.2") + // The view calendar library + implementation("com.kizitonwose.calendar:view:2.4.0") + // The compose calendar library + implementation("com.kizitonwose.calendar:compose:2.4.0") + implementation("com.google.accompanist:accompanist-pager:0.12.0") + implementation("androidx.tv:tv-material:1.0.0-alpha10") + implementation("androidx.compose.runtime:runtime-livedata:1.5.4") + testImplementation("junit:junit:4.13.2") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1") diff --git a/frontend/app/src/main/java/com/example/haengsha/MainActivity.kt b/frontend/app/src/main/java/com/example/haengsha/MainActivity.kt index c2e2f71..c945665 100644 --- a/frontend/app/src/main/java/com/example/haengsha/MainActivity.kt +++ b/frontend/app/src/main/java/com/example/haengsha/MainActivity.kt @@ -1,8 +1,10 @@ package com.example.haengsha +import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -11,6 +13,7 @@ import com.example.haengsha.ui.HaengshaApp import com.example.haengsha.ui.theme.HaengshaTheme class MainActivity : ComponentActivity() { + @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { diff --git a/frontend/app/src/main/java/com/example/haengsha/model/dataSource/AppContainer.kt b/frontend/app/src/main/java/com/example/haengsha/model/dataSource/AppContainer.kt index 8360750..aeb2c44 100644 --- a/frontend/app/src/main/java/com/example/haengsha/model/dataSource/AppContainer.kt +++ b/frontend/app/src/main/java/com/example/haengsha/model/dataSource/AppContainer.kt @@ -1,22 +1,36 @@ package com.example.haengsha.model.dataSource +import com.example.haengsha.model.network.apiService.EventApiService import com.example.haengsha.model.network.apiService.LoginApiService import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient import retrofit2.Retrofit +import java.util.concurrent.TimeUnit interface AppContainer { val loginDataRepository: LoginDataRepository + val eventDataRepository: EventDataRepository } class HaengshaAppContainer : AppContainer { - private val baseUrl = "http://ec2-43-201-28-141.ap-northeast-2.compute.amazonaws.com:8080/" + private val baseUrl = "http://ec2-52-79-228-36.ap-northeast-2.compute.amazonaws.com:8080/" + // Create an OkHttpClient with a default timeout + private val okHttpClient = OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) // Set the read timeout to 30 seconds + .connectTimeout(30, TimeUnit.SECONDS) // Set the connect timeout to 30 seconds + .build() + private val retrofit = Retrofit.Builder() .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) .baseUrl(baseUrl) + .client(okHttpClient) .build() + private val retrofitEventService: EventApiService by lazy { retrofit.create(EventApiService::class.java)} + override val eventDataRepository: EventDataRepository by lazy{ NetworkEventDataRepository(retrofitEventService)} + private val retrofitLoginService: LoginApiService by lazy { retrofit.create(LoginApiService::class.java) } diff --git a/frontend/app/src/main/java/com/example/haengsha/model/dataSource/EventDataRepository.kt b/frontend/app/src/main/java/com/example/haengsha/model/dataSource/EventDataRepository.kt new file mode 100644 index 0000000..7d08c41 --- /dev/null +++ b/frontend/app/src/main/java/com/example/haengsha/model/dataSource/EventDataRepository.kt @@ -0,0 +1,17 @@ +package com.example.haengsha.model.dataSource + +import com.example.haengsha.model.network.apiService.EventApiService +import com.example.haengsha.model.network.apiService.EventResponse + +interface EventDataRepository { + suspend fun getEventByDate(eventType: Int, date: String): List +} + + +class NetworkEventDataRepository( + private val eventApiService: EventApiService +) : EventDataRepository { + override suspend fun getEventByDate(eventType: Int, date: String): List { + return eventApiService.getEventByDate(eventType, date) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/haengsha/model/network/apiService/EventApiService.kt b/frontend/app/src/main/java/com/example/haengsha/model/network/apiService/EventApiService.kt new file mode 100644 index 0000000..717aee5 --- /dev/null +++ b/frontend/app/src/main/java/com/example/haengsha/model/network/apiService/EventApiService.kt @@ -0,0 +1,76 @@ +package com.example.haengsha.model.network.apiService + +import android.os.Build +import androidx.annotation.RequiresApi +import com.example.haengsha.ui.screens.home.EventCardData +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import retrofit2.http.GET +import retrofit2.http.Path +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Serializable +data class EventResponse( + @SerialName("nickname") val organizer: String, + @SerialName("title") val title: String, + @SerialName("content") val content: String, + @SerialName("place") val place: String, + @SerialName("image") val image: String?, + @SerialName("is_festival") val isFestival: Boolean, + @SerialName("like_count") val likeCount: Int, + @SerialName("favorite_count") val favoriteCount: Int, + @SerialName("time") val time: String, + @SerialName("event_durations") val eventDurations: List +) + +@Serializable +data class EventDurationResponse( + @SerialName("event_day") val eventDay: String +) + +@RequiresApi(Build.VERSION_CODES.O) +fun EventResponse.toEventCardData(): EventCardData { + var startDate = stringToDate(eventDurations[0].eventDay) + var endDate = startDate + if (eventDurations.size>1) { + endDate = stringToDate(eventDurations[eventDurations.size-1].eventDay) + } + + var eventType = "Festival" + if (!isFestival){ + eventType = "Academic" + } + return EventCardData( + organizer = organizer, + eventTitle = title, + startDate = startDate, + endDate = endDate, + likes = likeCount, + favorites = favoriteCount, + eventType = eventType, + place = place, + time = time + ) +} + +interface EventApiService { + @GET("/api/post/festival/{eventType}/date/{date}/{date}") + suspend fun getEventByDate( + @Path("eventType") eventType: Int, // Replace with appropriate type + @Path("date") date: String + ): List // Change the return type to a list of EventResponse +} + +@RequiresApi(Build.VERSION_CODES.O) +fun stringToDate(dateString: String): LocalDate { + var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + try { + return LocalDate.parse(dateString, formatter) + //val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + //date = format.parse(dateString)!! + } catch (e: Exception) { + e.printStackTrace() + } + return LocalDate.now() +} diff --git a/frontend/app/src/main/java/com/example/haengsha/model/viewModel/event/EventViewModel.kt b/frontend/app/src/main/java/com/example/haengsha/model/viewModel/event/EventViewModel.kt new file mode 100644 index 0000000..61b05a6 --- /dev/null +++ b/frontend/app/src/main/java/com/example/haengsha/model/viewModel/event/EventViewModel.kt @@ -0,0 +1,75 @@ +package com.example.haengsha.model.viewModel.event + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.example.haengsha.HaengshaApplication +import com.example.haengsha.model.dataSource.EventDataRepository +import com.example.haengsha.model.network.apiService.EventResponse +import com.example.haengsha.model.network.apiService.toEventCardData +import com.example.haengsha.ui.screens.home.EventCardData +import com.example.haengsha.ui.screens.home.SharedViewModel +import kotlinx.coroutines.launch +import retrofit2.HttpException +import java.io.Closeable +import java.io.IOException +import java.time.LocalDate + +@RequiresApi(Build.VERSION_CODES.O) +open class EventViewModel( + private val eventDataRepository: EventDataRepository, + private val sharedViewModel: SharedViewModel +) : ViewModel() { + + companion object { + private lateinit var sharedViewModelInstance: SharedViewModel + + fun Factory(sharedViewModel: SharedViewModel): ViewModelProvider.Factory { + sharedViewModelInstance = sharedViewModel + return viewModelFactory { + initializer { + val application = + this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as HaengshaApplication + val eventDataRepository = application.container.eventDataRepository + EventViewModel(eventDataRepository, sharedViewModel) + } + } + } + } + + + @RequiresApi(Build.VERSION_CODES.O) + fun getEventByDate(eventType: String, date: LocalDate) { + viewModelScope.launch { + try { + var eventTypeConverted = if (eventType == "Festival") 1 else 0 + + val eventGetResponse: List = + eventDataRepository.getEventByDate(eventTypeConverted, date.toString()) + + val eventCardDataList: List = + eventGetResponse.map { it.toEventCardData() } + + if (eventType == "Festival") { + sharedViewModel.updateFestivalItems(eventCardDataList) + } else { + sharedViewModel.updateAcademicItems(eventCardDataList) + } + + sharedViewModel.updateSelectedDate(date) + + } catch (e: HttpException) { + val errorMessage = e.response()?.errorBody()?.string() ?: "이벤트를 불러오지 못했습니다." + } catch (e: IOException) { + val errorMessage = "이벤트를 불러오지 못했습니다." + } catch (e: Exception) { + val errorMessage = "이벤트를 불러오지 못했습니다." + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/haengsha/ui/HaengshaApp.kt b/frontend/app/src/main/java/com/example/haengsha/ui/HaengshaApp.kt index 0feaf66..f987bd8 100644 --- a/frontend/app/src/main/java/com/example/haengsha/ui/HaengshaApp.kt +++ b/frontend/app/src/main/java/com/example/haengsha/ui/HaengshaApp.kt @@ -1,5 +1,7 @@ package com.example.haengsha.ui +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -17,6 +19,7 @@ import com.example.haengsha.ui.screens.home.Home import com.example.haengsha.ui.screens.login.Login import com.example.haengsha.ui.screens.setting.Setting +@RequiresApi(Build.VERSION_CODES.O) @Composable fun HaengshaApp() { val userViewModel: UserViewModel = viewModel() @@ -36,7 +39,6 @@ fun HaengshaApp() { } composable(MainRoute.Home.route) { Home( - userUiState = userUiState, mainNavController = mainNavController ) } diff --git a/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/EventCard.kt b/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/EventCard.kt new file mode 100644 index 0000000..5e9b7f8 --- /dev/null +++ b/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/EventCard.kt @@ -0,0 +1,115 @@ +package com.example.haengsha.ui.screens.home + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.haengsha.ui.theme.LikePink +import com.example.haengsha.ui.theme.md_theme_light_onSurfaceVariant +import com.example.haengsha.ui.theme.poppins +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@RequiresApi(Build.VERSION_CODES.O) +fun formatDateToMMDD(date: LocalDate): String { + val formatter = DateTimeFormatter.ofPattern("MM-dd") + return date.format(formatter) +} + + +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun EventCard( + organizer: String, + eventTitle: String, + startDate: LocalDate, + endDate: LocalDate, + likes: Int, +) { + val formattedStartDate = formatDateToMMDD(startDate) + val formattedEndDate = formatDateToMMDD(endDate) + + OutlinedCard( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + border = BorderStroke(1.dp, md_theme_light_onSurfaceVariant), + modifier = Modifier + .fillMaxWidth() + .height(150.dp) + .padding(all = 16.dp), + + ) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(2f) + ) { + Text( + text = organizer + "\n" + eventTitle, // 기관 명 + 행사 명 -> Text로 변경 + modifier = Modifier + .padding(16.dp) + .align(Alignment.CenterStart), + textAlign = TextAlign.Left, + fontSize = 16.sp, + fontFamily = poppins + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.End + ) { + Text( + text = "$formattedStartDate - $formattedEndDate", + modifier = Modifier + .padding(16.dp) + .align(Alignment.End), + textAlign = TextAlign.Right, + fontFamily = poppins, + ) + Text( + text = "♥ $likes", // 좋아요 수 -> Text로 변경 + modifier = Modifier + .padding(16.dp) + .align(Alignment.End), + textAlign = TextAlign.Right, + color = LikePink, + fontFamily = poppins + ) + } + } + } + } +} + + diff --git a/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/Home.kt b/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/Home.kt index b68752d..0ca4b12 100644 --- a/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/Home.kt +++ b/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/Home.kt @@ -1,30 +1,223 @@ package com.example.haengsha.ui.screens.home +import android.annotation.SuppressLint +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer 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.wrapContentHeight +import androidx.compose.material3.Button +import androidx.compose.material3.DatePicker +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import com.example.haengsha.model.uiState.UserUiState +import androidx.navigation.compose.rememberNavController +import com.example.haengsha.model.viewModel.event.EventViewModel +import com.example.haengsha.ui.theme.HaengshaBlue +import com.kizitonwose.calendar.compose.WeekCalendar +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.atStartOfMonth +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import java.time.format.TextStyle +import java.util.Locale + +class SharedViewModel() : ViewModel() { + @RequiresApi(Build.VERSION_CODES.O) + private val _selectedDate = MutableLiveData(LocalDate.now()) + + @RequiresApi(Build.VERSION_CODES.O) + val selectedDate: LiveData = _selectedDate + + // Initialize _festivalItems and _academicItems with initial data + @RequiresApi(Build.VERSION_CODES.O) + private val _festivalItems = MutableLiveData>() + @RequiresApi(Build.VERSION_CODES.O) + private val _academicItems = MutableLiveData>() + + + @RequiresApi(Build.VERSION_CODES.O) + val festivalItems: LiveData> = _festivalItems + + + @RequiresApi(Build.VERSION_CODES.O) + val academicItems: LiveData> = _academicItems + + + // Update functions to set LiveData properties + @RequiresApi(Build.VERSION_CODES.O) + fun updateSelectedDate(newDate: LocalDate) { + _selectedDate.value = newDate + } + + @RequiresApi(Build.VERSION_CODES.O) + fun updateFestivalItems(newItems: List) { + _festivalItems.value = newItems + } + + @RequiresApi(Build.VERSION_CODES.O) + fun updateAcademicItems(newItems: List) { + _academicItems.value = newItems + } +} + +@RequiresApi(Build.VERSION_CODES.O) +fun formatDateToYYYYMMDD(date: MutableLiveData): String { + val localDate = date.value ?: LocalDate.now() + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + return localDate.format(formatter) +} + + +@OptIn(ExperimentalMaterial3Api::class) +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@RequiresApi(Build.VERSION_CODES.O) @Composable -fun Home(userUiState: UserUiState, mainNavController: NavController) { - HomeScreen(userUiState = userUiState) +fun HomeScreen(sharedViewModel: SharedViewModel) { + val eventViewModel: EventViewModel = + viewModel(factory = EventViewModel.Factory(sharedViewModel)) + val currentDate = remember { LocalDate.now() } + val currentMonth = remember { YearMonth.now() } + val startDate = remember { currentMonth.minusMonths(100).atStartOfMonth() } // Adjust as needed + val endDate = remember { currentMonth.plusMonths(100).atEndOfMonth() } // Adjust as needed + val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library + var selection by remember { mutableStateOf(currentDate) } + val state = rememberWeekCalendarState( + startDate = startDate, + endDate = endDate, + firstVisibleWeekDate = currentDate, + firstDayOfWeek = firstDayOfWeek + ) + + var isDatePickerDialogVisible by remember { mutableStateOf(false )} + + Scaffold(topBar = { + TopAppBar( + title = { Text(text = "Home") }, + ) + }) { padding -> + Column ( + modifier = Modifier.fillMaxSize().padding(padding) + ) { + WeekCalendar( + state = state, + dayContent = { day -> + Day(day.date, isSelected = selection == day.date) { clicked -> + if (selection != clicked) { + selection = clicked + //pickDate = clicked + eventViewModel.getEventByDate(eventType = "Academic", clicked) + eventViewModel.getEventByDate(eventType = "Festival", clicked) + } + } + }, + ) + TabView(sharedViewModel, selection, 0) + Button( + onClick = { + isDatePickerDialogVisible = true + } + ) { + Text("Pick Date") + } + } + + + } +} + +@RequiresApi(Build.VERSION_CODES.O) +private val dateFormatter = DateTimeFormatter.ofPattern("dd") + +@RequiresApi(Build.VERSION_CODES.O) +fun DayOfWeek.displayText(uppercase: Boolean = false): String { + return getDisplayName(TextStyle.SHORT, Locale.ENGLISH).let { value -> + if (uppercase) value.uppercase(Locale.ENGLISH) else value + } } +@RequiresApi(Build.VERSION_CODES.O) @Composable -fun HomeScreen(userUiState: UserUiState) { - /* TODO 여기에 홈 UI 넣기 */ +private fun Day(date: LocalDate, isSelected: Boolean, onClick: (LocalDate) -> Unit) { Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .clickable { onClick(date) }, + contentAlignment = Alignment.Center, ) { - Column { - Text(text = "This is HomeScreen") - Text(text = "User token: ${userUiState.token}") + Column( + modifier = Modifier.padding(vertical = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = date.dayOfWeek.displayText(), + fontSize = 12.sp, + fontWeight = FontWeight.Light, + ) + Text( + text = dateFormatter.format(date), + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + ) + } + if (isSelected) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(5.dp) + .background(HaengshaBlue) + .align(Alignment.BottomCenter), + ) } } +} + +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun Home(mainNavController: NavController) { + val sharedViewModel = viewModel() + Column { + HomeScreen(sharedViewModel) + + } +} + +@RequiresApi(Build.VERSION_CODES.O) +@Preview(showBackground = true) +@Composable +fun HomeScreenPreview() { + Home(rememberNavController()) } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/TabView.kt b/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/TabView.kt new file mode 100644 index 0000000..ae8e6c8 --- /dev/null +++ b/frontend/app/src/main/java/com/example/haengsha/ui/screens/home/TabView.kt @@ -0,0 +1,423 @@ +package com.example.haengsha.ui.screens.home + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.PrimaryTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.tv.material3.ExperimentalTvMaterial3Api +import com.example.haengsha.R +import com.example.haengsha.model.viewModel.event.EventViewModel +import com.example.haengsha.ui.theme.HaengshaBlue +import com.example.haengsha.ui.theme.LikePink +import com.example.haengsha.ui.theme.md_theme_light_onSurfaceVariant +import com.example.haengsha.ui.theme.poppins +import kotlinx.coroutines.launch +import java.time.LocalDate + +data class TabItem( + val title: String, val eventCards: List +) + + +data class EventCardData( + val organizer: String, + val eventTitle: String, + val startDate: LocalDate, + val endDate: LocalDate, + val likes: Int, + val favorites: Int, + val eventType: String, + val place: String = "", + val time: String = "" + // val Image: // Image URL 변경 필요 (임시로 nudge_image 사용함) +) + + +@RequiresApi(Build.VERSION_CODES.O) +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun TabView(sharedViewModel: SharedViewModel, selectedDate: LocalDate, selectedTabIndex: Int) { + + val academicItems by sharedViewModel.academicItems.observeAsState() + val festivalItems by sharedViewModel.festivalItems.observeAsState() + var showDialog by remember { mutableStateOf(false) } + var selectedEvent: EventCardData? by remember { mutableStateOf(null) } + var showEventCardPopup by remember { mutableStateOf(false) } + val tabItems = listOf(academicItems?.let { + TabItem( + title = "Academic", eventCards = it + ) + }, festivalItems?.let { + TabItem( + title = "Festival", eventCards = it + ) + }) + + // Remember the selected tab + var selectedTabIndex by remember { mutableIntStateOf(0) } + + // Pager state + var pagerState = rememberPagerState { + tabItems.size + } + + // Coroutine scope + val coroutineScope = rememberCoroutineScope() + + Column(modifier = Modifier.fillMaxSize()) { + // Tab row + PrimaryTabRow(selectedTabIndex = selectedTabIndex, + contentColor = md_theme_light_onSurfaceVariant, + indicator = { tabPositions -> + TabRowDefaults.PrimaryIndicator( + modifier = Modifier.tabIndicatorOffset(tabPositions[0]), color = HaengshaBlue + ) + }) { + // Tab items + tabItems.forEachIndexed { index, item -> + Tab( + selected = (index == selectedTabIndex), + onClick = { + selectedTabIndex = index + // Change the page when the tab is changed + coroutineScope.launch { + pagerState.animateScrollToPage(selectedTabIndex) + } + }, + text = { + if (index == 0) Text(text = "Academic") + else Text(text = "Festival") + }, + ) + } + } + + // Pager + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxWidth(), + + ) { index -> + + // App content + LazyColumn( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + // Button at the top of the HorizontalPager + Button( + onClick = { + showDialog = true + }, + colors = androidx.compose.material3.ButtonDefaults.buttonColors(HaengshaBlue), + modifier = Modifier + .padding(16.dp) + .shadow( + elevation = 10.dp, + spotColor = Color(0x1A18274B), + ambientColor = Color(0x1A18274B) + ) + .shadow( + elevation = 10.dp, + spotColor = Color(0x26000000), + ambientColor = Color(0x26000000) + ) + .width(200.dp) + .height(50.dp) + + ) { + Text(text = "맞춤 추천 받기") + } + } + val itemsToDisplay = if (index == 1) festivalItems else academicItems + //val itemsToDisplay = if (index == 1) festivalItems else academicItems + items(itemsToDisplay.orEmpty()) { eventCardData -> + + Box(modifier = Modifier.clickable { + showEventCardPopup = true + selectedEvent = eventCardData + }) { + EventCard( + organizer = eventCardData.organizer, + eventTitle = eventCardData.eventTitle, + startDate = eventCardData.startDate, + endDate = eventCardData.endDate, + likes = eventCardData.likes + ) + } + } + } + } + + if (showDialog) { + // Display the AlertDialog with "Here is popup" + AlertDialog(onDismissRequest = { + // Close the dialog when clicked outside + showDialog = false + }, title = { + Text(text = "Popup Title") + }, text = { + Text(text = "Here is popup") + }, confirmButton = { + Button(onClick = { + // Handle confirm button click + showDialog = false + }) { + Text("OK") + } + }, dismissButton = { + Button(onClick = { + // Handle dismiss button click + showDialog = false + }) { + Text("Cancel") + } + }) + } + + var buttonWidth by remember { mutableStateOf(0.dp) } + var buttonHeight by remember { mutableStateOf(0.dp) } + + if (showEventCardPopup) { + AlertDialog( + onDismissRequest = { + // Close the popup when clicked outside + showEventCardPopup = false + }, + title = { + Column( + verticalArrangement = Arrangement.spacedBy( + 8.dp, Alignment.CenterVertically + ), + horizontalAlignment = Alignment.Start, + ) { + + Text( + text = selectedEvent?.eventTitle ?: "N/A", style = TextStyle( + fontSize = 18.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.poppins_bold)), + fontWeight = FontWeight(400), + color = Color(0xFF343A40), + ) + ) + + Row { + + Text( + text = selectedEvent?.eventType ?: "N/A", style = TextStyle( + fontSize = 11.sp, + lineHeight = 17.sp, + fontFamily = poppins, + fontWeight = FontWeight(400), + color = Color(0xFF868E96), + ) + ) + Text( + text = " | ", style = TextStyle( + fontSize = 11.sp, + lineHeight = 17.sp, + fontFamily = poppins, + fontWeight = FontWeight(400), + color = Color(0xFF868E96), + ) + ) + + Text( + text = selectedEvent?.organizer ?: "N/A", style = TextStyle( + fontSize = 11.sp, + lineHeight = 17.sp, + fontFamily = poppins, + fontWeight = FontWeight(400), + color = Color(0xFF868E96), + ) + ) + } + + } + }, + text = { + // Use a Column to ensure proper spacing of text + Column { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), // Adjust the padding as needed + contentAlignment = Alignment.Center + + ) { + Image( + painter = painterResource(id = R.drawable.nudge_image), + contentDescription = "image description", + contentScale = ContentScale.Crop, // Maintain aspect ratio + modifier = Modifier.fillMaxWidth() + ) + } + + Column { + + val startDateText = + selectedEvent?.startDate?.let { formatDateToMMDD(it) } + val endDateText = selectedEvent?.endDate?.let { formatDateToMMDD(it) } + + + Text( + text = "주최 | " + selectedEvent?.organizer, style = TextStyle( + fontSize = 12.sp, + lineHeight = 19.56.sp, + fontFamily = poppins, + fontWeight = FontWeight(500), + color = Color(0xFF000000), + textAlign = TextAlign.Center, + ) + ) + + Text( + text = "일자 | $startDateText - $endDateText", style = TextStyle( + fontSize = 12.sp, + lineHeight = 19.56.sp, + fontFamily = poppins, + fontWeight = FontWeight(500), + color = Color(0xFF000000), + textAlign = TextAlign.Center, + ) + ) + + Text( + text = "장소 | " + selectedEvent?.place, style = TextStyle( + fontSize = 12.sp, + lineHeight = 19.56.sp, + fontFamily = poppins, + fontWeight = FontWeight(500), + color = Color(0xFF000000), + textAlign = TextAlign.Center, + ) + ) + + Text( + text = "시간 | " + selectedEvent?.time, style = TextStyle( + fontSize = 12.sp, + lineHeight = 19.56.sp, + fontFamily = poppins, + fontWeight = FontWeight(500), + color = Color(0xFF000000), + textAlign = TextAlign.Center, + ) + ) + + Row(modifier = Modifier.padding(top = 10.dp)) { + Image( + painter = painterResource(id = R.drawable.like_fill_icon), + contentDescription = "image description", + ) + + Text( + text = selectedEvent?.likes.toString(), style = TextStyle( + fontSize = 10.sp, + fontFamily = poppins, + fontWeight = FontWeight(500), + color = LikePink, + textAlign = TextAlign.Center, + ) + ) + } + } + } + }, + confirmButton = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .padding(0.dp) + ) { + Button( + onClick = { + showEventCardPopup = false + }, + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(0.dp), // 패딩 제거 + + colors = androidx.compose.material3.ButtonDefaults.buttonColors(Color.White), + + ) { + Text( + text = "닫기", + style = TextStyle( + fontSize = 13.sp, + fontFamily = poppins, + fontWeight = FontWeight(500), + color = Color(0xFF000000), + textAlign = TextAlign.Center, + textDecoration = TextDecoration.Underline, + ), + modifier = Modifier.padding(0.dp) // 텍스트 주위의 패딩 제거 + ) + } + } + }, + modifier = Modifier + .shadow( + elevation = 10.dp, + spotColor = Color(0x40000000), + ambientColor = Color(0x40000000) + ) + .width(300.dp) + .height(550.dp) + .background(color = Color(0xFFFFFFFF)), + containerColor = Color(0xFFFFFFFF) + ) + } + } +} diff --git a/frontend/build.gradle.kts b/frontend/build.gradle.kts index b4cf450..0403714 100644 --- a/frontend/build.gradle.kts +++ b/frontend/build.gradle.kts @@ -2,4 +2,4 @@ plugins { id("com.android.application") version "8.1.2" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false -} \ No newline at end of file +}