From b26fe4725c1025f6f66355d083fed69de762ac11 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 14 Jun 2024 15:30:40 +0900 Subject: [PATCH 01/28] [ADD] init timetable compose setting --- gradle/libs.versions.toml | 1 + koin/build.gradle | 8 +-- koin/src/main/AndroidManifest.xml | 2 + .../in/koreatech/koin/compose/ui/Color.kt | 6 +++ .../in/koreatech/koin/compose/ui/Theme.kt | 53 +++++++++++++++++++ .../KoinNavigationDrawerActivity.kt | 15 +++++- .../koin/ui/timetablev2/TimetableActivity.kt | 44 +++++++++++++++ .../viewmodel/TimetableViewModel.kt | 5 ++ .../main/res/layout/activity_timetable.xml | 48 +++++++++++++++++ 9 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt create mode 100644 koin/src/main/java/in/koreatech/koin/compose/ui/Theme.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt create mode 100644 koin/src/main/res/layout/activity_timetable.xml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 387923eb6..c30b620eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,6 +61,7 @@ androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayo androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtxVersion" } androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtxVersion" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtxVersion" } +androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelKtxVersion" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerviewVersion" } androidx-runner = { module = "androidx.test:runner", version.ref = "runnerVersion" } androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCryptoVersion" } diff --git a/koin/build.gradle b/koin/build.gradle index bd5f7cee0..cf78c3fc1 100644 --- a/koin/build.gradle +++ b/koin/build.gradle @@ -1,12 +1,11 @@ plugins { - id 'com.android.application' + alias(libs.plugins.koin.compose) + alias(libs.plugins.koin.orbit) id 'kotlin-android' - id 'kotlin-kapt' id 'com.google.gms.google-services' id 'com.google.firebase.crashlytics' id 'com.google.firebase.appdistribution' id 'com.google.dagger.hilt.android' - id 'org.jetbrains.kotlin.android' } def applicationName = 'koin' @@ -131,4 +130,7 @@ dependencies { implementation(libs.napier) implementation(libs.powerSpinner) + + implementation(libs.coil.compose) + implementation(libs.androidx.lifecycle.compose) } diff --git a/koin/src/main/AndroidManifest.xml b/koin/src/main/AndroidManifest.xml index 2abd51936..d1a3a6243 100644 --- a/koin/src/main/AndroidManifest.xml +++ b/koin/src/main/AndroidManifest.xml @@ -46,6 +46,8 @@ android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/black" /> + Unit, +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (darkTheme) DarkColorScheme else LightColorScheme + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colors = colorScheme, + content = content + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index b0e8502e1..ec19c7219 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -6,7 +6,6 @@ import android.content.pm.PackageManager import android.graphics.Typeface import android.os.Build import android.os.Bundle -import android.util.Log import android.view.MenuItem import android.view.View import android.widget.Button @@ -289,7 +288,8 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), MenuState.Store -> goToStoreActivity() MenuState.Timetable -> { if (userState.value == null || userState.value?.isAnonymous == true) { - goToAnonymousTimeTableActivity() + goToTimetableActivityV2() +// goToAnonymousTimeTableActivity() } else { goToTimetableActivty() } @@ -421,6 +421,17 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), } } + /** + * @TEST + */ + private fun goToTimetableActivityV2() { + if (menuState != MenuState.Main) { + goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) + } else { + startActivity(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) + } + } + private fun goToTimetableActivty() { if (menuState != MenuState.Main) { goToActivityFinish(Intent(this, TimetableActivity::class.java)) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt new file mode 100644 index 000000000..46ae88006 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -0,0 +1,44 @@ +package `in`.koreatech.koin.ui.timetablev2 + +import android.os.Bundle +import `in`.koreatech.koin.R +import `in`.koreatech.koin.compose.ui.TimetableTheme +import `in`.koreatech.koin.core.appbar.AppBarBase +import `in`.koreatech.koin.databinding.ActivityTimetableBinding +import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity +import `in`.koreatech.koin.ui.navigation.state.MenuState + +class TimetableActivity : KoinNavigationDrawerActivity() { + private lateinit var binding: ActivityTimetableBinding + + override val screenTitle: String + get() = getString(R.string.navigation_item_timetable) + override val menuState: MenuState + get() = MenuState.Timetable + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityTimetableBinding.inflate(layoutInflater) + setContentView(binding.root) + initEvent() + + + binding.composeView.setContent { + TimetableTheme { + } + } + } + + private fun initEvent() { + handleAppBarEvent() + } + + private fun handleAppBarEvent() { + binding.koinBaseAppbar.setOnClickListener { + when (it.id) { + AppBarBase.getLeftButtonId() -> onBackPressed() + AppBarBase.getRightButtonId() -> toggleNavigationDrawer() + } + } + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt new file mode 100644 index 000000000..a64956b8c --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -0,0 +1,5 @@ +package `in`.koreatech.koin.ui.timetablev2.viewmodel + +import androidx.lifecycle.ViewModel + +class TimetableViewModel : ViewModel() diff --git a/koin/src/main/res/layout/activity_timetable.xml b/koin/src/main/res/layout/activity_timetable.xml new file mode 100644 index 000000000..3d9689de1 --- /dev/null +++ b/koin/src/main/res/layout/activity_timetable.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + From 8aca5ac323fae27ccab6f92e9fa8fd5e4a2029f9 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 14 Jun 2024 17:58:19 +0900 Subject: [PATCH 02/28] [ADD] timetable content header & get semester api --- .../koreatech/koin/data/api/TimetableApi.kt | 10 ++ .../data/di/network/NoAuthNetworkModule.kt | 8 ++ .../data/di/repository/RepositoryModule.kt | 63 +++++++++- .../data/di/source/RemoteDataSourceModule.kt | 8 ++ .../koin/data/mapper/TimetableMapper.kt | 9 ++ .../repository/TimetableRepositoryImpl.kt | 14 +++ .../response/timetable/SemestersResponse.kt | 10 ++ .../remote/TimetableRemoteDataSource.kt | 12 ++ .../koin/domain/model/timetable/Semester.kt | 12 ++ .../domain/repository/TimetableRepository.kt | 7 ++ .../usecase/timetable/GetSemesterUseCase.kt | 16 +++ .../in/koreatech/koin/compose/ui/Theme.kt | 6 +- .../koin/ui/timetablev2/TimetableActivity.kt | 9 ++ .../ui/timetablev2/TimetableSideEffect.kt | 5 + .../koin/ui/timetablev2/TimetableState.kt | 7 ++ .../ui/timetablev2/view/SemesterDropdown.kt | 118 ++++++++++++++++++ .../view/TimetableContentHeader.kt | 65 ++++++++++ .../timetablev2/view/TimetableSaveButton.kt | 56 +++++++++ .../ui/timetablev2/view/TimetableScreen.kt | 71 +++++++++++ .../viewmodel/TimetableViewModel.kt | 28 ++++- koin/src/main/res/values/strings.xml | 1 + 21 files changed, 528 insertions(+), 7 deletions(-) create mode 100644 data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/response/timetable/SemestersResponse.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/model/timetable/Semester.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSideEffect.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSaveButton.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt diff --git a/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt b/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt new file mode 100644 index 000000000..ff7578af8 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt @@ -0,0 +1,10 @@ +package `in`.koreatech.koin.data.api + +import `in`.koreatech.koin.data.constant.URLConstant.SEMESTERS +import `in`.koreatech.koin.data.response.timetable.SemesterResponse +import retrofit2.http.GET + +interface TimetableApi { + @GET(SEMESTERS) + suspend fun getSemesters(): List +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt b/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt index 5458516ac..cca5576c8 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt @@ -111,4 +111,12 @@ object NoAuthNetworkModule { ): LandApi { return retrofit.create(LandApi::class.java) } + + @Provides + @Singleton + fun providesTimetableApi( + @NoAuth retrofit: Retrofit + ): TimetableApi { + return retrofit.create(TimetableApi::class.java) + } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt index 9ea5f1982..16517ba3a 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt @@ -6,10 +6,57 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import `in`.koreatech.koin.data.repository.* -import `in`.koreatech.koin.data.source.local.* -import `in`.koreatech.koin.data.source.remote.* -import `in`.koreatech.koin.domain.repository.* +import `in`.koreatech.koin.data.repository.BusRepositoryImpl +import `in`.koreatech.koin.data.repository.DeptRepositoryImpl +import `in`.koreatech.koin.data.repository.DiningRepositoryImpl +import `in`.koreatech.koin.data.repository.LandRepositoryImpl +import `in`.koreatech.koin.data.repository.NotificationRepositoryImpl +import `in`.koreatech.koin.data.repository.OwnerChangePasswordRepositoryImpl +import `in`.koreatech.koin.data.repository.OwnerRegisterRepositoryImpl +import `in`.koreatech.koin.data.repository.OwnerSignupRepositoryImpl +import `in`.koreatech.koin.data.repository.OwnerVerificationCodeRepositoryImpl +import `in`.koreatech.koin.data.repository.PreSignedUrlRepositoryImpl +import `in`.koreatech.koin.data.repository.SignupRepositoryImpl +import `in`.koreatech.koin.data.repository.StoreRepositoryImpl +import `in`.koreatech.koin.data.repository.TimetableRepositoryImpl +import `in`.koreatech.koin.data.repository.TokenRepositoryImpl +import `in`.koreatech.koin.data.repository.UploadUrlRepositoryImpl +import `in`.koreatech.koin.data.repository.UserRepositoryImpl +import `in`.koreatech.koin.data.repository.VersionRepositoryImpl +import `in`.koreatech.koin.data.source.local.BusLocalDataSource +import `in`.koreatech.koin.data.source.local.DeptLocalDataSource +import `in`.koreatech.koin.data.source.local.SignupTermsLocalDataSource +import `in`.koreatech.koin.data.source.local.TokenLocalDataSource +import `in`.koreatech.koin.data.source.local.VersionLocalDataSource +import `in`.koreatech.koin.data.source.remote.BusRemoteDataSource +import `in`.koreatech.koin.data.source.remote.DeptRemoteDataSource +import `in`.koreatech.koin.data.source.remote.DiningRemoteDataSource +import `in`.koreatech.koin.data.source.remote.LandRemoteDataSource +import `in`.koreatech.koin.data.source.remote.NotificationRemoteDataSource +import `in`.koreatech.koin.data.source.remote.OwnerRemoteDataSource +import `in`.koreatech.koin.data.source.remote.PreSignedUrlRemoteDataSource +import `in`.koreatech.koin.data.source.remote.StoreRemoteDataSource +import `in`.koreatech.koin.data.source.remote.TimetableRemoteDataSource +import `in`.koreatech.koin.data.source.remote.UploadUrlRemoteDataSource +import `in`.koreatech.koin.data.source.remote.UserRemoteDataSource +import `in`.koreatech.koin.data.source.remote.VersionRemoteDataSource +import `in`.koreatech.koin.domain.repository.BusRepository +import `in`.koreatech.koin.domain.repository.DeptRepository +import `in`.koreatech.koin.domain.repository.DiningRepository +import `in`.koreatech.koin.domain.repository.LandRepository +import `in`.koreatech.koin.domain.repository.NotificationRepository +import `in`.koreatech.koin.domain.repository.OwnerChangePasswordRepository +import `in`.koreatech.koin.domain.repository.OwnerRegisterRepository +import `in`.koreatech.koin.domain.repository.OwnerSignupRepository +import `in`.koreatech.koin.domain.repository.OwnerVerificationCodeRepository +import `in`.koreatech.koin.domain.repository.PreSignedUrlRepository +import `in`.koreatech.koin.domain.repository.SignupRepository +import `in`.koreatech.koin.domain.repository.StoreRepository +import `in`.koreatech.koin.domain.repository.TimetableRepository +import `in`.koreatech.koin.domain.repository.TokenRepository +import `in`.koreatech.koin.domain.repository.UploadUrlRepository +import `in`.koreatech.koin.domain.repository.UserRepository +import `in`.koreatech.koin.domain.repository.VersionRepository import javax.inject.Singleton @Module @@ -154,4 +201,12 @@ object RepositoryModule { ): OwnerChangePasswordRepository { return OwnerChangePasswordRepositoryImpl(ownerRemoteDataSource) } + + @Provides + @Singleton + fun providesTimetableRepository( + timetableRemoteDataSource: TimetableRemoteDataSource + ): TimetableRepository { + return TimetableRepositoryImpl(timetableRemoteDataSource) + } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt index 5090bd351..b592a304f 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt @@ -100,4 +100,12 @@ object RemoteDataSourceModule { ): PreSignedUrlRemoteDataSource { return PreSignedUrlRemoteDataSource(preSignedUrlApi) } + + @Provides + @Singleton + fun providesTimetableRemoteDataSource( + timetableApi: TimetableApi, + ): TimetableRemoteDataSource { + return TimetableRemoteDataSource((timetableApi)) + } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt new file mode 100644 index 000000000..c3a824e81 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt @@ -0,0 +1,9 @@ +package `in`.koreatech.koin.data.mapper + +import `in`.koreatech.koin.data.response.timetable.SemesterResponse +import `in`.koreatech.koin.domain.model.timetable.Semester + +fun SemesterResponse.toSemester() = Semester( + id = this.id, + semester = this.semester +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt new file mode 100644 index 000000000..cdb557c2f --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -0,0 +1,14 @@ +package `in`.koreatech.koin.data.repository + +import `in`.koreatech.koin.data.mapper.toSemester +import `in`.koreatech.koin.data.source.remote.TimetableRemoteDataSource +import `in`.koreatech.koin.domain.model.timetable.Semester +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class TimetableRepositoryImpl @Inject constructor( + private val timetableRemoteDataSource: TimetableRemoteDataSource, +) : TimetableRepository { + override suspend fun loadSemesters(): List = + timetableRemoteDataSource.loadSemesters().map { it.toSemester() } +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/timetable/SemestersResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/timetable/SemestersResponse.kt new file mode 100644 index 000000000..bcebd5e99 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/timetable/SemestersResponse.kt @@ -0,0 +1,10 @@ +package `in`.koreatech.koin.data.response.timetable + +import com.google.gson.annotations.SerializedName + +data class SemesterResponse( + @SerializedName("id") + val id: Int, + @SerializedName("semester") + val semester: String, +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt new file mode 100644 index 000000000..3ce6a5fa9 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt @@ -0,0 +1,12 @@ +package `in`.koreatech.koin.data.source.remote + +import `in`.koreatech.koin.data.api.TimetableApi +import `in`.koreatech.koin.data.response.timetable.SemesterResponse +import javax.inject.Inject + +class TimetableRemoteDataSource @Inject constructor( + private val timetableApi: TimetableApi, +) { + suspend fun loadSemesters(): List = + timetableApi.getSemesters() +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Semester.kt b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Semester.kt new file mode 100644 index 000000000..586679f11 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Semester.kt @@ -0,0 +1,12 @@ +package `in`.koreatech.koin.domain.model.timetable + +data class Semester( + val id: Int = 0, + val semester: String = "", +) { + /** + * @sample + * 20242 : 2024년 2학기 + */ + fun format() = "${semester.take(4)}년 ${semester.drop(4)}학기" +} diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt new file mode 100644 index 000000000..187591a00 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -0,0 +1,7 @@ +package `in`.koreatech.koin.domain.repository + +import `in`.koreatech.koin.domain.model.timetable.Semester + +interface TimetableRepository { + suspend fun loadSemesters(): List +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt new file mode 100644 index 000000000..ae96e155d --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt @@ -0,0 +1,16 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.Semester +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class GetSemesterUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, +) { + suspend operator fun invoke(): List = + try { + timetableRepository.loadSemesters() + } catch (e: Exception) { + emptyList() + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/compose/ui/Theme.kt b/koin/src/main/java/in/koreatech/koin/compose/ui/Theme.kt index a1c6b30e2..e545e2739 100644 --- a/koin/src/main/java/in/koreatech/koin/compose/ui/Theme.kt +++ b/koin/src/main/java/in/koreatech/koin/compose/ui/Theme.kt @@ -8,6 +8,7 @@ import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat @@ -15,11 +16,12 @@ import androidx.core.view.WindowCompat private val LightColorScheme = lightColors( primary = ColorPrimary, secondary = ColorSecondary, + ) private val DarkColorScheme = darkColors( -// primary = Purple40, -// secondary = PurpleGrey40, + primary = ColorPrimary, + secondary = ColorSecondary, ) @Composable diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index 46ae88006..8bf6ad2c6 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -7,6 +7,7 @@ import `in`.koreatech.koin.core.appbar.AppBarBase import `in`.koreatech.koin.databinding.ActivityTimetableBinding import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity import `in`.koreatech.koin.ui.navigation.state.MenuState +import `in`.koreatech.koin.ui.timetablev2.view.TimetableScreen class TimetableActivity : KoinNavigationDrawerActivity() { private lateinit var binding: ActivityTimetableBinding @@ -25,6 +26,14 @@ class TimetableActivity : KoinNavigationDrawerActivity() { binding.composeView.setContent { TimetableTheme { + TimetableScreen( + content = { + + }, + sheetContent = { + + } + ) } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSideEffect.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSideEffect.kt new file mode 100644 index 000000000..beaccde09 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSideEffect.kt @@ -0,0 +1,5 @@ +package `in`.koreatech.koin.ui.timetablev2 + +sealed class TimetableSideEffect { + data class Toast(val message: String): TimetableSideEffect() +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt new file mode 100644 index 000000000..87b16dfdc --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt @@ -0,0 +1,7 @@ +package `in`.koreatech.koin.ui.timetablev2 + +import `in`.koreatech.koin.domain.model.timetable.Semester + +data class TimetableState( + val semesters: List = emptyList() +) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt new file mode 100644 index 000000000..825ba8e33 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt @@ -0,0 +1,118 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ExposedDropdownMenuBox +import androidx.compose.material.ExposedDropdownMenuDefaults +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.domain.model.timetable.Semester + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun SemesterDropdown( + semesters: List, + modifier: Modifier = Modifier, + onSemesterTextChanged: (semester: String) -> Unit, +) { + var expanded by rememberSaveable { + mutableStateOf(false) + } + var selectedText by rememberSaveable { + mutableStateOf("") + } + + selectedText.ifBlank { + if (semesters.isNotEmpty()) semesters[0].format() + else "" + }.let { + selectedText = it + } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + modifier = modifier + ) { + TextField( + readOnly = true, + value = selectedText, + onValueChange = { }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon( + expanded = expanded + ) + }, + colors = ExposedDropdownMenuDefaults.textFieldColors( + unfocusedIndicatorColor = Color.Black, + backgroundColor = Color.White, + focusedIndicatorColor = Color.Transparent, + focusedTrailingIconColor = Color.Black, + trailingIconColor = Color.Black, + textColor = Color.Black + ), + shape = RoundedCornerShape(4.dp), + modifier = Modifier + .wrapContentSize() + .border(width = 1.dp, Color.Black, shape = RoundedCornerShape(4.dp)), + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { + expanded = false + } + ) { + semesters.forEach { semester -> + DropdownMenuItem( + onClick = { + onSemesterTextChanged(semester.semester) + selectedText = semester.format() + expanded = false + } + ) { + Text( + text = semester.format(), + fontSize = 14.sp, + color = Color.Black + ) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun SemesterDropdownPreview() { + val semesters = listOf( + Semester(1, "20241"), + Semester(2, "20242"), + ) + Box(modifier = Modifier.fillMaxSize()) { + SemesterDropdown( + modifier = Modifier + .fillMaxWidth(0.5f) + .padding(4.dp), + semesters = semesters, + onSemesterTextChanged = {} + ) + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt new file mode 100644 index 000000000..8e35eb55e --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt @@ -0,0 +1,65 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.compose.ui.ColorPrimary +import `in`.koreatech.koin.domain.model.timetable.Semester + +@Composable +fun TimetableContentHeader( + semesters: List, + modifier: Modifier = Modifier, + onSavedImage: () -> Unit, + onSemesterTextChanged: (semester: String) -> Unit, +) { + Row( + modifier = modifier + .height(IntrinsicSize.Max), + verticalAlignment = Alignment.CenterVertically + ) { + SemesterDropdown( + semesters = semesters, + modifier = Modifier + .weight(1f) + .padding(4.dp), + onSemesterTextChanged = onSemesterTextChanged + ) + TimetableSaveButton( + modifier = Modifier + .padding(4.dp) + .weight(1f, fill = false) + .fillMaxHeight() + .background(color = ColorPrimary, shape = RoundedCornerShape(4.dp)) + .padding(8.dp), + onClick = onSavedImage + ) + } +} + +@Preview(showBackground = true) +@Composable +fun TimetableContentHeaderPreview() { + val semesters = listOf( + Semester(1, "20241"), + Semester(2, "20242"), + ) + Box(modifier = Modifier.fillMaxSize()) { + TimetableContentHeader( + semesters = semesters, + onSavedImage = {}, + onSemesterTextChanged = {} + ) + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSaveButton.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSaveButton.kt new file mode 100644 index 000000000..93c125008 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSaveButton.kt @@ -0,0 +1,56 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.R +import `in`.koreatech.koin.compose.ui.ColorPrimary + +@Composable +fun TimetableSaveButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Row( + modifier = modifier.clickable { onClick() }, + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(id = R.drawable.ic_save_image), + contentDescription = null + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = stringResource(id = R.string.timetable_save_image), + color = Color.White, + fontSize = 12.sp + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun TimetableSaveButtonPreview() { + Box(modifier = Modifier.fillMaxSize()) { + TimetableSaveButton( + modifier = Modifier + .background(ColorPrimary), + onClick = {} + ) + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt new file mode 100644 index 000000000..4fce2edc6 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -0,0 +1,71 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import android.content.Context +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.BottomSheetScaffold +import androidx.compose.material.BottomSheetValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.rememberBottomSheetScaffoldState +import androidx.compose.material.rememberBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect +import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun TimetableScreen( + modifier: Modifier = Modifier, + context: Context = LocalContext.current, + timetableViewModel: TimetableViewModel = viewModel(), + content: @Composable ColumnScope.() -> Unit, + sheetContent: @Composable ColumnScope.() -> Unit, +) { + timetableViewModel.loadSemesters() + + val state by timetableViewModel.collectAsState() + + timetableViewModel.collectSideEffect { + when (it) { + is TimetableSideEffect.Toast -> Unit + } + } + + val sheetState = rememberBottomSheetState( + initialValue = BottomSheetValue.Collapsed + ) + val scaffoldState = rememberBottomSheetScaffoldState( + bottomSheetState = sheetState + ) + + BottomSheetScaffold( + modifier = modifier, + scaffoldState = scaffoldState, + sheetContent = sheetContent, + sheetBackgroundColor = Color.White, + sheetPeekHeight = 0.dp, + ) { + Column( + modifier = Modifier.fillMaxHeight() + ) { + content() + TimetableContentHeader( + semesters = state.semesters, + modifier = Modifier.fillMaxWidth(), + onSavedImage = {}, + onSemesterTextChanged = {} + ) + } + } +} + diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index a64956b8c..c36830485 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -1,5 +1,31 @@ package `in`.koreatech.koin.ui.timetablev2.viewmodel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase +import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect +import `in`.koreatech.koin.ui.timetablev2.TimetableState +import kotlinx.coroutines.launch +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject -class TimetableViewModel : ViewModel() +@HiltViewModel +class TimetableViewModel @Inject constructor( + private val getSemesterUseCase: GetSemesterUseCase, +) : ContainerHost, ViewModel() { + override val container: Container = + container(TimetableState()) + + fun loadSemesters() = intent { + viewModelScope.launch { + getSemesterUseCase.invoke().let { + reduce { state.copy(semesters = it) } + } + } + } +} diff --git a/koin/src/main/res/values/strings.xml b/koin/src/main/res/values/strings.xml index 46b40d3ed..f75385d5b 100644 --- a/koin/src/main/res/values/strings.xml +++ b/koin/src/main/res/values/strings.xml @@ -148,6 +148,7 @@ 학교 메일로 비밀번호 초기화를 완료해 주세요. 이동하실래요? + 이미지 저장하기 저장되었습니다. 저장에 실패했습니다. 해당 수업을 삭제하시겠습니까? From 1d6e3674fbda33a3615cd5863cca8899fd4b44ba Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 14 Jun 2024 19:36:48 +0900 Subject: [PATCH 03/28] [ADD] timetable UI & departments and lectures api --- data/src/main/assets/department.json | 52 ++++++ .../koreatech/koin/data/api/TimetableApi.kt | 8 + .../data/di/repository/RepositoryModule.kt | 6 +- .../data/di/source/LocalDataSourceModule.kt | 9 + .../koin/data/mapper/TimetableMapper.kt | 25 +++ .../repository/TimetableRepositoryImpl.kt | 12 ++ .../response/timetable/DepartmentResponse.kt | 15 ++ .../response/timetable/LectureResponse.kt | 31 ++++ ...mestersResponse.kt => SemesterResponse.kt} | 0 .../source/local/TimetableLocalDataSource.kt | 15 ++ .../remote/TimetableRemoteDataSource.kt | 4 + .../in/koreatech/koin/data/util/AssetUtils.kt | 23 +++ .../koin/domain/model/timetable/Department.kt | 6 + .../koin/domain/model/timetable/Lecture.kt | 86 +++++++++ .../domain/repository/TimetableRepository.kt | 4 + .../timetable/GetDepartmentsUseCase.kt | 16 ++ .../usecase/timetable/GetLecturesUseCase.kt | 16 ++ .../koin/domain/util/ext/StringExtensions.kt | 15 ++ .../koin/model/timetable/TimeBlock.kt | 14 ++ .../koin/model/timetable/TimetableEvent.kt | 39 ++++ .../model/timetable/TimetableEventType.kt | 5 + .../koin/ui/timetablev2/TimetableActivity.kt | 60 ++++++- .../koin/ui/timetablev2/TimetableState.kt | 6 +- .../koin/ui/timetablev2/TimetableView.kt | 47 +++++ .../ui/timetablev2/component/DayHeader.kt | 30 ++++ .../ui/timetablev2/component/SidebarLabel.kt | 36 ++++ .../ui/timetablev2/view/SemesterDropdown.kt | 5 +- .../koin/ui/timetablev2/view/Timetable.kt | 96 ++++++++++ .../view/TimetableBottomSheetContent.kt | 45 +++++ .../ui/timetablev2/view/TimetableContent.kt | 169 ++++++++++++++++++ .../ui/timetablev2/view/TimetableEventTime.kt | 109 +++++++++++ .../ui/timetablev2/view/TimetableHeader.kt | 41 +++++ .../ui/timetablev2/view/TimetableScreen.kt | 22 ++- .../ui/timetablev2/view/TimetableSidebar.kt | 55 ++++++ .../viewmodel/TimetableViewModel.kt | 21 +++ .../in/koreatech/koin/util/BitmapUtils.kt | 101 +++++++++++ .../koin/util/ext/TimetableExtensions.kt | 33 ++++ .../koreatech/koin/util/ext/TypeExtensions.kt | 8 + 38 files changed, 1272 insertions(+), 13 deletions(-) create mode 100644 data/src/main/assets/department.json create mode 100644 data/src/main/java/in/koreatech/koin/data/response/timetable/DepartmentResponse.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/response/timetable/LectureResponse.kt rename data/src/main/java/in/koreatech/koin/data/response/timetable/{SemestersResponse.kt => SemesterResponse.kt} (100%) create mode 100644 data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/util/AssetUtils.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/model/timetable/Department.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetDepartmentsUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetLecturesUseCase.kt create mode 100644 koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt create mode 100644 koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt create mode 100644 koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEventType.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DayHeader.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSidebar.kt create mode 100644 koin/src/main/java/in/koreatech/koin/util/BitmapUtils.kt create mode 100644 koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt create mode 100644 koin/src/main/java/in/koreatech/koin/util/ext/TypeExtensions.kt diff --git a/data/src/main/assets/department.json b/data/src/main/assets/department.json new file mode 100644 index 000000000..2ee4621fd --- /dev/null +++ b/data/src/main/assets/department.json @@ -0,0 +1,52 @@ +{ + "departments": [ + { + "id": 1, + "name": "컴퓨터공학부" + }, + { + "id": 2, + "name": "기계공학부" + }, + { + "id": 3, + "name": "전기전자통신공학부" + }, + { + "id": 4, + "name": "에너지신소재화학공학부" + }, + { + "id": 5, + "name": "산업경영학부" + }, + { + "id": 6, + "name": "메카트로닉스공학부" + }, + { + "id": 7, + "name": "디자인건축공학부" + }, + { + "id": 8, + "name": "고용서비스정책학과" + }, + { + "id": 9, + "name": "안전공학과" + }, + { + "id": 10, + "name": "교양학부" + }, + { + "id": 11, + "name": "HRD학과" + }, + { + "id": 12, + "name": "융합학과" + } + ] +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt b/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt index ff7578af8..d2b202f10 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/TimetableApi.kt @@ -1,10 +1,18 @@ package `in`.koreatech.koin.data.api +import `in`.koreatech.koin.data.constant.URLConstant.LECTURE import `in`.koreatech.koin.data.constant.URLConstant.SEMESTERS +import `in`.koreatech.koin.data.response.timetable.LectureResponse import `in`.koreatech.koin.data.response.timetable.SemesterResponse import retrofit2.http.GET +import retrofit2.http.Query interface TimetableApi { @GET(SEMESTERS) suspend fun getSemesters(): List + + @GET(LECTURE) + suspend fun getLectures( + @Query("semester_date") semester: String + ): List } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt index 16517ba3a..c49db953c 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt @@ -26,6 +26,7 @@ import `in`.koreatech.koin.data.repository.VersionRepositoryImpl import `in`.koreatech.koin.data.source.local.BusLocalDataSource import `in`.koreatech.koin.data.source.local.DeptLocalDataSource import `in`.koreatech.koin.data.source.local.SignupTermsLocalDataSource +import `in`.koreatech.koin.data.source.local.TimetableLocalDataSource import `in`.koreatech.koin.data.source.local.TokenLocalDataSource import `in`.koreatech.koin.data.source.local.VersionLocalDataSource import `in`.koreatech.koin.data.source.remote.BusRemoteDataSource @@ -205,8 +206,9 @@ object RepositoryModule { @Provides @Singleton fun providesTimetableRepository( - timetableRemoteDataSource: TimetableRemoteDataSource + timetableRemoteDataSource: TimetableRemoteDataSource, + timetableLocalDataSource: TimetableLocalDataSource ): TimetableRepository { - return TimetableRepositoryImpl(timetableRemoteDataSource) + return TimetableRepositoryImpl(timetableRemoteDataSource, timetableLocalDataSource) } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt index a99b061dc..cd4fa58b7 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt @@ -10,6 +10,7 @@ import `in`.koreatech.koin.core.qualifier.IoDispatcher import `in`.koreatech.koin.data.source.local.BusLocalDataSource import `in`.koreatech.koin.data.source.local.DeptLocalDataSource import `in`.koreatech.koin.data.source.local.SignupTermsLocalDataSource +import `in`.koreatech.koin.data.source.local.TimetableLocalDataSource import `in`.koreatech.koin.data.source.local.TokenLocalDataSource import `in`.koreatech.koin.data.source.local.VersionLocalDataSource import kotlinx.coroutines.CoroutineDispatcher @@ -58,4 +59,12 @@ object LocalDataSourceModule { ) : DeptLocalDataSource { return DeptLocalDataSource(applicationContext) } + + @Provides + @Singleton + fun providesTimetableLocalDataSource( + @ApplicationContext applicationContext: Context + ): TimetableLocalDataSource { + return TimetableLocalDataSource(applicationContext) + } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt index c3a824e81..a1b0fdaff 100644 --- a/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt +++ b/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt @@ -1,9 +1,34 @@ package `in`.koreatech.koin.data.mapper +import `in`.koreatech.koin.data.response.timetable.DepartmentResponse +import `in`.koreatech.koin.data.response.timetable.LectureResponse import `in`.koreatech.koin.data.response.timetable.SemesterResponse +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester fun SemesterResponse.toSemester() = Semester( id = this.id, semester = this.semester +) + +fun LectureResponse.toLecture() = Lecture( + id = this.id ?: 0, + code = this.code ?: "", + name = this.name ?: "", + grades = this.grades ?: "", + lectureClass = this.lectureClass ?: "", + regularNumber = this.regularNumber ?: "", + department = this.department ?: "", + target = this.target ?: "", + professor = this.professor ?: "", + isEnglish = this.isEnglish ?: "", + designScore = this.designScore ?: "", + isElearning = this.isElearning ?: "", + classTime = this.classTime, +) + +fun DepartmentResponse.toDepartment() = Department( + id = this.id, + name = this.name ) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index cdb557c2f..8984927b8 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -1,14 +1,26 @@ package `in`.koreatech.koin.data.repository +import `in`.koreatech.koin.data.mapper.toDepartment +import `in`.koreatech.koin.data.mapper.toLecture import `in`.koreatech.koin.data.mapper.toSemester +import `in`.koreatech.koin.data.source.local.TimetableLocalDataSource import `in`.koreatech.koin.data.source.remote.TimetableRemoteDataSource +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.domain.repository.TimetableRepository import javax.inject.Inject class TimetableRepositoryImpl @Inject constructor( private val timetableRemoteDataSource: TimetableRemoteDataSource, + private val timetableLocalDataSource: TimetableLocalDataSource, ) : TimetableRepository { override suspend fun loadSemesters(): List = timetableRemoteDataSource.loadSemesters().map { it.toSemester() } + + override suspend fun loadLectures(semester: String): List = + timetableRemoteDataSource.loadLectures(semester).map { it.toLecture() } + + override suspend fun loadDepartments(): List = + timetableLocalDataSource.loadDepartments()?.map { it.toDepartment() }.orEmpty() } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/timetable/DepartmentResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/timetable/DepartmentResponse.kt new file mode 100644 index 000000000..bb0587dd3 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/timetable/DepartmentResponse.kt @@ -0,0 +1,15 @@ +package `in`.koreatech.koin.data.response.timetable + +import com.google.gson.annotations.SerializedName + +data class DepartmentsResponse( + @SerializedName("departments") + val departments: List, +) + +data class DepartmentResponse( + @SerializedName("id") + val id: Int, + @SerializedName("name") + val name: String, +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/timetable/LectureResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/timetable/LectureResponse.kt new file mode 100644 index 000000000..4ffbca52f --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/timetable/LectureResponse.kt @@ -0,0 +1,31 @@ +package `in`.koreatech.koin.data.response.timetable + +import com.google.gson.annotations.SerializedName + +data class LectureResponse( + var id: Int? = 0, + @SerializedName("code") // 과목코드 : BSM314 + val code: String? = "", + @SerializedName("name") // 강의명 : 물리적 사고 + val name: String? = "", + @SerializedName("grades") // 학년 : 3 + val grades: String? = "", + @SerializedName("lecture_class") // 분반 : 01 + val lectureClass: String? = "", + @SerializedName("regular_number") // 수강인원 : 0~40 + val regularNumber: String? = "", + @SerializedName("department") // 학부 : 교양학부 + val department: String? = "", + @SerializedName("target") // 대상 : 기공1 + val target: String? = "", + @SerializedName("professor") // 교수 : 이미리 + val professor: String? = "", + @SerializedName("is_english") // 영어수업인지 : N/Y + val isEnglish: String? = "", + @SerializedName("design_score") // 설계학점 : 0 + val designScore: String? = "", + @SerializedName("is_elearning") // 이러닝인지 : N/Y + val isElearning: String? = "", + @SerializedName("class_time") // 강의시간 : 0~429 + val classTime: List = emptyList(), +) diff --git a/data/src/main/java/in/koreatech/koin/data/response/timetable/SemestersResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/timetable/SemesterResponse.kt similarity index 100% rename from data/src/main/java/in/koreatech/koin/data/response/timetable/SemestersResponse.kt rename to data/src/main/java/in/koreatech/koin/data/response/timetable/SemesterResponse.kt diff --git a/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt new file mode 100644 index 000000000..6985791c8 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt @@ -0,0 +1,15 @@ +package `in`.koreatech.koin.data.source.local + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import `in`.koreatech.koin.data.response.timetable.DepartmentResponse +import `in`.koreatech.koin.data.response.timetable.DepartmentsResponse +import `in`.koreatech.koin.data.util.readData +import javax.inject.Inject + +class TimetableLocalDataSource @Inject constructor( + @ApplicationContext private val context: Context, +) { + fun loadDepartments(): List? = + context.readData("department.json")?.departments +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt index 3ce6a5fa9..125d5d49b 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.data.source.remote import `in`.koreatech.koin.data.api.TimetableApi +import `in`.koreatech.koin.data.response.timetable.LectureResponse import `in`.koreatech.koin.data.response.timetable.SemesterResponse import javax.inject.Inject @@ -9,4 +10,7 @@ class TimetableRemoteDataSource @Inject constructor( ) { suspend fun loadSemesters(): List = timetableApi.getSemesters() + + suspend fun loadLectures(semester: String): List = + timetableApi.getLectures(semester) } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/util/AssetUtils.kt b/data/src/main/java/in/koreatech/koin/data/util/AssetUtils.kt new file mode 100644 index 000000000..9315d60d9 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/util/AssetUtils.kt @@ -0,0 +1,23 @@ +package `in`.koreatech.koin.data.util + +import android.content.Context +import com.google.gson.Gson +import java.io.IOException + +inline fun Context.readData(assetName: String): T? { + return try { + val inputStream = this.resources.assets.open(assetName) + val buffer = ByteArray(inputStream.available()) + inputStream.read(buffer) + inputStream.close() + + val gson = Gson() + gson.fromJson(String(buffer), T::class.java) + } catch (e: IOException) { + e.message + null + } catch (e: Exception) { + e.message + null + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Department.kt b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Department.kt new file mode 100644 index 000000000..eb7ff1836 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Department.kt @@ -0,0 +1,6 @@ +package `in`.koreatech.koin.domain.model.timetable + +data class Department( + val id: Int = 0, + val name: String = "" +) \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt new file mode 100644 index 000000000..3904e92f6 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt @@ -0,0 +1,86 @@ +package `in`.koreatech.koin.domain.model.timetable + +import `in`.koreatech.koin.domain.util.ext.toDepartmentString +import java.time.DayOfWeek +import java.time.LocalTime + +data class Lecture( + var id: Int = 0, + val code: String = "", + val name: String = "", + val grades: String = "", + val lectureClass: String = "", + val regularNumber: String = "", + val department: String = "", + val target: String = "", + val professor: String = "", + val isEnglish: String = "", + val designScore: String = "", + val isElearning: String = "", + val classTime: List = emptyList(), +) { + fun findDayOfWeekAndTime(): Map> { + return classTime.groupBy { it / 100 } + .mapValues { entry -> + /** + * @input : [0,1,100,101] + */ + entry.value.sorted().map { value -> + val timeIndex = if (entry.key == 0) value else value % (entry.key * 100) + LocalTime.of(9 + timeIndex / 2, (timeIndex % 2) * 30) + } + /** + * @output : [09:00, 09:30], [09:00, 09:30] + */ + } + .mapKeys { + /** + * @input : {0=[09:00, 09:30], 1=[09:00, 09:30]} + */ + when (it.key) { + 0 -> DayOfWeek.MONDAY + 1 -> DayOfWeek.TUESDAY + 2 -> DayOfWeek.WEDNESDAY + 3 -> DayOfWeek.THURSDAY + 4 -> DayOfWeek.FRIDAY + else -> null + } + /** + * @output : {MONDAY=[09:00, 09:30], TUESDAY=[09:00, 09:30]} + */ + } + } + + fun doesMatchSearchQuery(query: String): Boolean { + val matchingCombinations = listOf( + "$name", + "${name?.first()}" + ) + + return matchingCombinations.any { + it.contains(query, ignoreCase = true) + } + } + + fun doesMatchDepartmentSearchQuery(departments: List): Boolean { + val matchingCombination = department.toDepartmentString() + + return departments.any { + it.contains(matchingCombination, ignoreCase = true) + } + } + + /** + * 시간표 강의 중복 + * @example : 강의 시간 겹침 + 완전 준복 + */ + fun duplicate(lectures: List): Boolean { + var flag = false + classTime.forEach { time -> + if (lectures.filter { it.classTime.contains(time) }.isNotEmpty()) { + flag = true + } + } + return flag + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 187591a00..6c66cf2ad 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -1,7 +1,11 @@ package `in`.koreatech.koin.domain.repository +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester interface TimetableRepository { suspend fun loadSemesters(): List + suspend fun loadDepartments(): List + suspend fun loadLectures(semester: String): List } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetDepartmentsUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetDepartmentsUseCase.kt new file mode 100644 index 000000000..6a6448a43 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetDepartmentsUseCase.kt @@ -0,0 +1,16 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class GetDepartmentsUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, +) { + suspend operator fun invoke(): List = + try { + timetableRepository.loadDepartments() + } catch (e: Exception) { + emptyList() + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetLecturesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetLecturesUseCase.kt new file mode 100644 index 000000000..7af54ad2d --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetLecturesUseCase.kt @@ -0,0 +1,16 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class GetLecturesUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, +) { + suspend operator fun invoke(semester: String): List = + try { + timetableRepository.loadLectures(semester) + } catch (e: Exception) { + emptyList() + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/util/ext/StringExtensions.kt b/domain/src/main/java/in/koreatech/koin/domain/util/ext/StringExtensions.kt index 6e26cc07f..ffd28ed16 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/util/ext/StringExtensions.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/util/ext/StringExtensions.kt @@ -23,3 +23,18 @@ fun String.formatPhoneNumber(): String = fun String.formatBusinessNumber(): String = this.replace(Regex("(\\d{3})(\\d{2})(\\d{5})"), "$1-$2-$3") +fun String.toDepartmentString(): String = when(this) { + "HRD학과" -> "HRD" + "고용서비스정책학과" -> "고용서비스" + "교양학부" -> "교양" + "디자인ㆍ건축공학부" -> "디자인" + "메카트로닉스공학부" -> "메카트로닉스" + "산업경영학부" -> "산업경영" + "에너지신소재화학공학부" -> "에너지신소재" + "융합학과" -> "융합" + "전기ㆍ전자ㆍ통신공학부" -> "전기" + "컴퓨터공학부" -> "컴퓨터" + "안전공학과" -> "안전" + "기계공학부" -> "기계" + else -> "" +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt b/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt new file mode 100644 index 000000000..a48d7c15e --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt @@ -0,0 +1,14 @@ +package `in`.koreatech.koin.model.timetable + +import androidx.compose.ui.graphics.Color +import java.time.LocalTime + +data class TimeBlock( + val title: String = "", + val start: LocalTime = LocalTime.of(0, 0), + val end: LocalTime = LocalTime.of(0, 0), + val startDuration: Float = 0f, + val endDuration: Float = 0f, + val duration: Float = 0f, + val color: Color? = null, +) \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt b/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt new file mode 100644 index 000000000..98cb79471 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt @@ -0,0 +1,39 @@ +package `in`.koreatech.koin.model.timetable + +import androidx.compose.ui.graphics.Color +import java.time.DayOfWeek +import java.time.Duration +import java.time.LocalTime +import kotlin.math.ceil + +data class TimetableEvent( + val id: Int, + val name: String, + val color: Color, + val dayOfWeek: DayOfWeek? = null, + val start: LocalTime, + val end: LocalTime, + val description: String? = null, +) { + fun convertToTimeBlock(endTime: LocalTime): TimeBlock { + val startDuration = Duration.between(LocalTime.of(start.hour, 0), start).run { + this.toMinutes() / 60f + } + val endDuration = Duration.between(start, endTime).run { + this.toMinutes() / 60f + } + val duration = ceil(startDuration + endDuration) + + return TimeBlock( + title = this.name, + start = start, + end = endTime, + startDuration = startDuration, + endDuration = endDuration, + duration = duration, + color = this.color + ) + } + + fun convertToEmptyTimeBlock() = TimeBlock() +} diff --git a/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEventType.kt b/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEventType.kt new file mode 100644 index 000000000..d574f0497 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEventType.kt @@ -0,0 +1,5 @@ +package `in`.koreatech.koin.model.timetable + +enum class TimetableEventType { + BASIC, SELECTED +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index 8bf6ad2c6..c25714cc2 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -1,13 +1,23 @@ package `in`.koreatech.koin.ui.timetablev2 import android.os.Bundle +import androidx.compose.material.BottomSheetState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.viewinterop.AndroidView import `in`.koreatech.koin.R import `in`.koreatech.koin.compose.ui.TimetableTheme import `in`.koreatech.koin.core.appbar.AppBarBase import `in`.koreatech.koin.databinding.ActivityTimetableBinding +import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity import `in`.koreatech.koin.ui.navigation.state.MenuState import `in`.koreatech.koin.ui.timetablev2.view.TimetableScreen +import `in`.koreatech.koin.util.BitmapUtils +import `in`.koreatech.koin.util.ext.showToast class TimetableActivity : KoinNavigationDrawerActivity() { private lateinit var binding: ActivityTimetableBinding @@ -17,18 +27,32 @@ class TimetableActivity : KoinNavigationDrawerActivity() { override val menuState: MenuState get() = MenuState.Timetable + private var timetableView: MutableState? = null + + @OptIn(ExperimentalMaterialApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityTimetableBinding.inflate(layoutInflater) setContentView(binding.root) initEvent() - binding.composeView.setContent { TimetableTheme { TimetableScreen( - content = { - + onSavedImage = { + BitmapUtils(this).apply { + timetableView?.value?.let { view -> + capture(view) { bitmap -> + saveBitmapImage(bitmap) + } + } ?: showToast("retry saved image..") + } + }, + content = { bottomSheetState, onEventClick -> + TimetableUI( + sheetState = bottomSheetState, + onEventClick = onEventClick + ) }, sheetContent = { @@ -38,6 +62,36 @@ class TimetableActivity : KoinNavigationDrawerActivity() { } } + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun TimetableUI( + sheetState: BottomSheetState, + onEventClick: (TimetableEvent) -> Unit, + ) { + timetableView = remember { + mutableStateOf( + TimetableView( + context = this@TimetableActivity, + sheetState = sheetState, + ) + ) + } + + AndroidView(factory = { + TimetableView( + context = it, + sheetState = sheetState, + ).apply { + post { + timetableView?.value = this + } + setOnTimetableEventClickListener { timetableEvent -> + onEventClick(timetableEvent) + } + } + }) + } + private fun initEvent() { handleAppBarEvent() } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt index 87b16dfdc..1dddc36c9 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt @@ -1,7 +1,11 @@ package `in`.koreatech.koin.ui.timetablev2 +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester data class TimetableState( - val semesters: List = emptyList() + val semesters: List = emptyList(), + val lectures: List = emptyList(), + val departments: List = emptyList() ) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt new file mode 100644 index 000000000..1f8d6e61a --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt @@ -0,0 +1,47 @@ +package `in`.koreatech.koin.ui.timetablev2 + +import android.content.Context +import android.util.AttributeSet +import androidx.compose.material.BottomSheetState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.AbstractComposeView +import androidx.lifecycle.viewmodel.compose.viewModel +import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.ui.timetablev2.view.Timetable +import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel + +class TimetableView @OptIn(ExperimentalMaterialApi::class) +@JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + private val sheetState: BottomSheetState, +) : AbstractComposeView(context, attrs, defStyleAttr) { + lateinit var onTimetableEventClickListener: OnTimetableEventClickListener + + @Composable + override fun Content() { + val viewModel = viewModel() + +// Timetable( +// events = , +// sheetState = sheetState, +// clickEvent = , +// onEventClick = onTimetableEventClickListener::onEventClick +// ) + } + + interface OnTimetableEventClickListener { + fun onEventClick(event: TimetableEvent) + } + + inline fun setOnTimetableEventClickListener(crossinline onEventClick: (TimetableEvent) -> Unit) { + this.onTimetableEventClickListener = object : OnTimetableEventClickListener { + override fun onEventClick(event: TimetableEvent) { + onEventClick(event) + } + } + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DayHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DayHeader.kt new file mode 100644 index 000000000..c9f525161 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DayHeader.kt @@ -0,0 +1,30 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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 + +@Composable +fun DayHeader( + day: String, + modifier: Modifier = Modifier, +) { + Text( + text = day, + textAlign = TextAlign.Center, + modifier = modifier + .fillMaxWidth() + .padding(4.dp) + ) +} + +@Preview(showBackground = true) +@Composable +private fun DayHeaderPreview() { + DayHeader(day = "월") +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt new file mode 100644 index 000000000..b715b2ec8 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt @@ -0,0 +1,36 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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 java.time.LocalTime +import java.time.format.DateTimeFormatter + +private val hourFormatter = DateTimeFormatter.ofPattern("HH") + +@Composable +fun SidebarLabel( + time: LocalTime, + modifier: Modifier = Modifier, +) { + Text( + text = time.format(hourFormatter), + modifier = modifier + .fillMaxSize() + .padding(4.dp), + textAlign = TextAlign.End + ) +} + +@Preview(showBackground = true) +@Composable +private fun SidebarLabelPreview() { + SidebarLabel( + time = LocalTime.now() + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt index 825ba8e33..f4433a84f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt @@ -41,7 +41,10 @@ fun SemesterDropdown( } selectedText.ifBlank { - if (semesters.isNotEmpty()) semesters[0].format() + if (semesters.isNotEmpty()) { + onSemesterTextChanged(semesters[0].semester) + semesters[0].format() + } else "" }.let { selectedText = it diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt new file mode 100644 index 000000000..cccd1c5ff --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt @@ -0,0 +1,96 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.BottomSheetState +import androidx.compose.material.BottomSheetValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.model.timetable.TimetableEventType +import `in`.koreatech.koin.util.ext.pxToDp + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun Timetable( + events: List, + modifier: Modifier = Modifier, + clickEvent: List = emptyList(), + sheetState: BottomSheetState = BottomSheetState( + BottomSheetValue.Collapsed, + density = Density(1f) + ), + onEventClick: (TimetableEvent) -> Unit, + eventContent: @Composable + (event: TimetableEvent, eventType: TimetableEventType?, onEventClick: (TimetableEvent) -> Unit) -> Unit = { event, eventType, onEventClick -> + TimetableEventTime(event = event, eventType = eventType, onEventClick = onEventClick) + }, +) { + val days = 5 + val dayWidth = 68.dp + val hourSidebarWidth = + (LocalContext.current.resources.displayMetrics.widthPixels / LocalContext.current.resources.displayMetrics.density).dp - (dayWidth * days) + val hourHeight = 64.dp + val verticalScrollState = rememberScrollState() + + Column( + modifier = modifier + .background(Color.White) + .padding( + /** + * 바텀 시트 올라올 때, + * 바텀 시트 크기 만큼 Bottom Padding 주기 + */ + bottom = if (sheetState.isExpanded) { + if (sheetState.currentValue == BottomSheetValue.Expanded) { + if (sheetState.targetValue == BottomSheetValue.Expanded && sheetState.progress == 1f) { + sheetState.requireOffset().pxToDp + } else { + 0.dp + } + } else { + 0.dp + } + } else { + 0.dp + } + ) + ) { + TimetableHeader( + modifier = Modifier.fillMaxWidth(), + dayStartPadding = hourSidebarWidth, + ) + Row( + modifier = Modifier + .weight(1f) + ) { + TimetableSidebar( + modifier = Modifier + .verticalScroll(verticalScrollState), + hourHeight = hourHeight, + hourWidth = hourSidebarWidth, + ) + TimetableContent( + modifier = Modifier + .weight(1f) + .verticalScroll(verticalScrollState), + clickEvent = clickEvent, + eventContent = eventContent, + events = events, + dayWidth = dayWidth, + hourHeight = hourHeight, + onEventClick = onEventClick + ) + } + } +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt new file mode 100644 index 000000000..1f1ad99a4 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt @@ -0,0 +1,45 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.model.timetable.TimetableEvent + +@Composable +fun TimetableBottomSheetContent( + colors: List, + lectures: List, + selectedLectures: Lecture, + currentDepartments: List, + searchText: String, + onSearchTextChanged: (String) -> Unit, + modifier: Modifier = Modifier, + onClickLecture: (List) -> Unit, + onSelectedLecture: (Lecture) -> Unit, + onAddLecture: (Lecture) -> Unit, + onSetting: () -> Unit, + onCancel: (Department) -> Unit, +) { + +} + +@Preview(showBackground = true) +@Composable +private fun TimetableBottomSheetContentPreview() { + TimetableBottomSheetContent( + colors = emptyList(), + lectures = emptyList(), + selectedLectures = Lecture(), + currentDepartments = emptyList(), + searchText = "", + onSearchTextChanged = {}, + onClickLecture = {}, + onSelectedLecture = {}, + onAddLecture = {}, + onSetting = {}, + onCancel = {} + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt new file mode 100644 index 000000000..17e1ff0a2 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt @@ -0,0 +1,169 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.layout.Box +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.ParentDataModifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.model.timetable.TimetableEventType +import java.time.DayOfWeek +import java.time.LocalTime +import java.time.temporal.ChronoUnit +import kotlin.math.roundToInt + +@Composable +fun TimetableContent( + dayWidth: Dp, + hourHeight: Dp, + events: List, + modifier: Modifier = Modifier, + clickEvent: List = emptyList(), + onEventClick: (event: TimetableEvent) -> Unit, + eventContent: @Composable (event: TimetableEvent, eventType: TimetableEventType, onEventClick: (TimetableEvent) -> Unit) -> Unit = { event, eventType, onClick -> + TimetableEventTime(event = event, eventType = eventType, onEventClick = onClick) + }, +) { + val days = 5 + val dividerColor = if (MaterialTheme.colors.isLight) Color.LightGray else Color.DarkGray + val times = 15 + + Layout( + content = { + events.sortedBy(TimetableEvent::start).forEach { event -> + Box(modifier = Modifier.eventData(event)) { + eventContent(event, TimetableEventType.BASIC, onEventClick = onEventClick) + } + } + if (clickEvent.isNotEmpty()) { + clickEvent.sortedBy(TimetableEvent::start).forEach { event -> + Box(modifier = Modifier.eventData(event)) { + eventContent( + event, + TimetableEventType.SELECTED, + onEventClick = onEventClick + ) + } + } + } + }, + modifier = modifier.drawBehind { + drawLine( + dividerColor, + start = Offset(0f, 0f), + end = Offset(size.width, 0f), + strokeWidth = 1.dp.toPx() + ) + drawLine( + dividerColor, + start = Offset(0f, 0f), + end = Offset(0f, size.height), + strokeWidth = 1.dp.toPx() + ) + + repeat(times * 2) { + drawLine( + dividerColor, + start = Offset(0f, (it + 1) * (hourHeight / 2).toPx()), + end = Offset(size.width, (it + 1) * (hourHeight / 2).toPx()), + strokeWidth = 1.dp.toPx() + ) + } + repeat(days - 1) { + drawLine( + dividerColor, + start = Offset((it + 1) * dayWidth.toPx(), 0f), + end = Offset((it + 1) * dayWidth.toPx(), size.height), + strokeWidth = 1.dp.toPx() + ) + } + } + ) { measureables, constraints -> + val height = hourHeight.roundToPx() * 15 + val width = dayWidth.roundToPx() * days + val placeablesWithEvents = measureables.map { measurable -> + val event = measurable.parentData as TimetableEvent + val eventDurationMinutes = ChronoUnit.MINUTES.between(event.start, event.end) + val eventHeight = ((eventDurationMinutes / 60f) * hourHeight.toPx()).roundToInt() + val placeable = measurable.measure( + constraints.copy( + minWidth = dayWidth.roundToPx(), maxWidth = dayWidth.roundToPx(), + minHeight = eventHeight, maxHeight = eventHeight + ) + ) + Pair(placeable, event) + } + + layout(width, height) { + placeablesWithEvents.forEach { (placeable, event) -> + val initStartTime = LocalTime.of(9, 0) + val eventOffsetMinutes = + ChronoUnit.MINUTES.between(initStartTime, event.start) + val eventY = ((eventOffsetMinutes / 60f) * hourHeight.toPx()).roundToInt() + + val eventOffsetDays: Int = when (event.dayOfWeek) { + DayOfWeek.MONDAY -> 0 + DayOfWeek.TUESDAY -> 1 + DayOfWeek.WEDNESDAY -> 2 + DayOfWeek.THURSDAY -> 3 + DayOfWeek.FRIDAY -> 4 + else -> -1 + } + val eventX = eventOffsetDays * dayWidth.roundToPx() + placeable.place(eventX, eventY) + } + } + } +} + + +private class EventDataModifier( + val event: TimetableEvent, +) : ParentDataModifier { + override fun Density.modifyParentData(parentData: Any?) = event +} + +private fun Modifier.eventData(event: TimetableEvent) = + this.then(EventDataModifier(event)) + +@Preview(showBackground = true) +@Composable +private fun TimetableContentPreview() { + val samples = listOf( + TimetableEvent( + id = 1, + name = "관희의 수업1", + color = Color(0xFFAFBBF2), + dayOfWeek = DayOfWeek.FRIDAY, + start = LocalTime.of(16, 0), + end = LocalTime.of(18, 0), + description = "공학2관 101호", + ), + TimetableEvent( + id = 2, + name = "관희의 수업2", + color = Color(0xFFDEE4FF), + dayOfWeek = DayOfWeek.THURSDAY, + start = LocalTime.of(14, 0), + end = LocalTime.of(16, 0), + description = "공학2관 105호", + ) + ) + + TimetableContent( + events = samples, + dayWidth = 68.dp, + hourHeight = 64.dp, + onEventClick = { + + } + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt new file mode 100644 index 000000000..47692f84b --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt @@ -0,0 +1,109 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.model.timetable.TimetableEventType +import java.time.DayOfWeek +import java.time.LocalTime +import java.time.format.DateTimeFormatter + +val timetableEventTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") + +@Composable +fun TimetableEventTime( + event: TimetableEvent, + modifier: Modifier = Modifier, + eventType: TimetableEventType? = null, + onEventClick: (event: TimetableEvent) -> Unit, +) { + Column( + modifier = modifier + .fillMaxSize() + .padding(end = 2.dp, bottom = 2.dp) + .background( + color = if (eventType == TimetableEventType.SELECTED) Color.Transparent else event.color, + shape = RoundedCornerShape(4.dp) + ) + .border( + color = if (eventType == TimetableEventType.SELECTED) Color.Red else Color.Transparent, + width = if (eventType == TimetableEventType.SELECTED) 1.dp else 0.dp, + shape = RoundedCornerShape(4.dp) + ) + .padding(4.dp) + .clickable { onEventClick(event) } + ) { + Divider(color = Color.White, thickness = 1.dp) + Spacer(modifier = Modifier.height(2.dp)) + when (eventType) { + TimetableEventType.BASIC -> { + Text( + text = event.start.format(timetableEventTimeFormatter) + + " - " + + event.end.format(timetableEventTimeFormatter), + fontSize = 8.sp, + color = Color.White + ) + Text( + text = event.name, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = Color.White + ) + + if (event.description != null) { + Text( + text = event.description, + fontSize = 8.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = Color.White + ) + } + } + + TimetableEventType.SELECTED -> {} + null -> {} + } + + } +} + +@Preview(showBackground = true) +@Composable +private fun TimetableEventTimePreview() { + val sample = TimetableEvent( + id = 1, + name = "관희의 수업", + color = Color(0xFFAFBBF2), + dayOfWeek = DayOfWeek.FRIDAY, + start = LocalTime.of(16, 0), + end = LocalTime.of(18, 0), + description = "공학2관 105호", + ) + + TimetableEventTime( + event = sample, + eventType = TimetableEventType.BASIC, + modifier = Modifier.sizeIn(maxHeight = 64.dp), + onEventClick = {} + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt new file mode 100644 index 000000000..d569a5df1 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt @@ -0,0 +1,41 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.ui.timetablev2.component.DayHeader + +@Composable +fun TimetableHeader( + dayStartPadding: Dp, + modifier: Modifier = Modifier, + dayHeader: @Composable (day: String) -> Unit = { DayHeader(day = it) }, +) { + Row( + modifier = modifier + .background(Color.LightGray) + .padding(start = dayStartPadding) + ) { + val days = listOf("월", "화", "수", "목", "금") + repeat(days.size) { + Box(modifier = Modifier.weight(1f)) { + dayHeader(day = days[it]) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun TimetableHeaderPreview() { + TimetableHeader( + dayStartPadding = 10.dp + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 4fce2edc6..fe9b9f4f5 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -1,11 +1,13 @@ package `in`.koreatech.koin.ui.timetablev2.view import android.content.Context +import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.BottomSheetScaffold +import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState @@ -17,6 +19,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel import org.orbitmvi.orbit.compose.collectAsState @@ -28,11 +31,10 @@ fun TimetableScreen( modifier: Modifier = Modifier, context: Context = LocalContext.current, timetableViewModel: TimetableViewModel = viewModel(), - content: @Composable ColumnScope.() -> Unit, + onSavedImage: () -> Unit, + content: @Composable ColumnScope.(BottomSheetState, onEventClick: (TimetableEvent) -> Unit) -> Unit, sheetContent: @Composable ColumnScope.() -> Unit, ) { - timetableViewModel.loadSemesters() - val state by timetableViewModel.collectAsState() timetableViewModel.collectSideEffect { @@ -41,12 +43,17 @@ fun TimetableScreen( } } + timetableViewModel.loadSemesters() + timetableViewModel.loadDepartments() + val sheetState = rememberBottomSheetState( initialValue = BottomSheetValue.Collapsed ) val scaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = sheetState ) + Log.e("aaa", "lectures : ${state.lectures}") + Log.e("aaa", "departments : ${state.departments}") BottomSheetScaffold( modifier = modifier, @@ -58,12 +65,15 @@ fun TimetableScreen( Column( modifier = Modifier.fillMaxHeight() ) { - content() TimetableContentHeader( semesters = state.semesters, modifier = Modifier.fillMaxWidth(), - onSavedImage = {}, - onSemesterTextChanged = {} + onSavedImage = onSavedImage, + onSemesterTextChanged = timetableViewModel::loadLectures + ) + content( + sheetState, + {} ) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSidebar.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSidebar.kt new file mode 100644 index 000000000..2bfde7638 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableSidebar.kt @@ -0,0 +1,55 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.ui.timetablev2.component.SidebarLabel +import java.time.LocalTime + +@Composable +fun TimetableSidebar( + hourHeight: Dp, + hourWidth: Dp, + modifier: Modifier = Modifier, + label: @Composable (time: LocalTime) -> Unit = { SidebarLabel(time = it) }, +) { + val dividerColor = if (MaterialTheme.colors.isLight) Color.LightGray else Color.DarkGray + val startTime = LocalTime.of(9, 0) + val times = 15 + + Column( + modifier = modifier, + ) { + repeat(times) { + Box( + modifier = Modifier + .size(height = hourHeight, width = hourWidth) + .drawBehind { + drawLine( + dividerColor, + start = Offset(0f, 0f), + end = Offset(size.width, 0f), + strokeWidth = 1.dp.toPx() + ) + } + ) { + label(startTime.plusHours(it.toLong())) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun TimetableSidebarPreview() { + TimetableSidebar(hourHeight = 64.dp, hourWidth = 68.dp) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index c36830485..79b1801b0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -3,6 +3,9 @@ package `in`.koreatech.koin.ui.timetablev2.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.domain.model.timetable.Semester +import `in`.koreatech.koin.domain.usecase.timetable.GetDepartmentsUseCase +import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.TimetableState @@ -17,6 +20,8 @@ import javax.inject.Inject @HiltViewModel class TimetableViewModel @Inject constructor( private val getSemesterUseCase: GetSemesterUseCase, + private val getLecturesUseCase: GetLecturesUseCase, + private val getDepartmentsUseCase: GetDepartmentsUseCase, ) : ContainerHost, ViewModel() { override val container: Container = container(TimetableState()) @@ -28,4 +33,20 @@ class TimetableViewModel @Inject constructor( } } } + + fun loadLectures(semester: String) = intent { + viewModelScope.launch { + getLecturesUseCase.invoke(semester).let { + reduce { state.copy(lectures = it) } + } + } + } + + fun loadDepartments() = intent { + viewModelScope.launch { + getDepartmentsUseCase.invoke().let { + reduce { state.copy(departments = it) } + } + } + } } diff --git a/koin/src/main/java/in/koreatech/koin/util/BitmapUtils.kt b/koin/src/main/java/in/koreatech/koin/util/BitmapUtils.kt new file mode 100644 index 000000000..17431c715 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/util/BitmapUtils.kt @@ -0,0 +1,101 @@ +package `in`.koreatech.koin.util + +import android.content.ContentValues +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import android.view.View +import `in`.koreatech.koin.util.ext.showToast +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream + +class BitmapUtils ( + private val context: Context +) { + fun capture(view: View, onSavedTimeTable: (Bitmap) -> Unit) { + val bitmap = generateBitmap(view) + onSavedTimeTable(bitmap) + } + + fun saveBitmapImage( + bitmap: Bitmap, + ) { + val timeStamp = System.currentTimeMillis() + + val values = ContentValues() + values.put(MediaStore.Images.Media.MIME_TYPE, "image/png") + values.put(MediaStore.Images.Media.DATE_ADDED, timeStamp) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + values.put(MediaStore.Images.Media.DATE_TAKEN, timeStamp) + // Pictures/앱 이름 + values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + "koin") + values.put(MediaStore.Images.Media.IS_PENDING, true) + val uri = + context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) + if (uri != null) { + try { + val outputStream = context.contentResolver.openOutputStream(uri) + if (outputStream != null) { + try { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + outputStream.close() + } catch (e: Exception) { + e.message + } + } + values.put(MediaStore.Images.Media.IS_PENDING, false) + context.contentResolver.update(uri, values, null, null) + + context.showToast("Saved...") + } catch (e: Exception) { + e.message + } + } else { + val imageFileFolder = + File(Environment.getExternalStorageDirectory().toString() + "/" + "koin") + if (!imageFileFolder.exists()) { + imageFileFolder.mkdirs() + } + val mImageName = "$timeStamp.png" + val imageFile = File(imageFileFolder, mImageName) + try { + val outputStream: OutputStream = FileOutputStream(imageFile) + try { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + outputStream.close() + } catch (e: Exception) { + e.message + } + values.put(MediaStore.Images.Media.DATA, imageFile.absolutePath) + context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) + + context.showToast("Saved...") + } catch (e: Exception) { + e.message + } + } + } + } + + fun generateBitmap(view: View): Bitmap { + val bitmap = Bitmap.createBitmap( + view.width, + view.height, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + view.layout( + view.left, + view.top, + view.right, + view.bottom + ) + view.draw(canvas) + return bitmap + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt new file mode 100644 index 000000000..8ce1d7060 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt @@ -0,0 +1,33 @@ +package `in`.koreatech.koin.util.ext + +import androidx.compose.ui.graphics.Color +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.model.timetable.TimetableEvent +import java.time.LocalTime + +fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List { + val events = mutableListOf() + /** + * @input : {MONDAY=[09:00, 09:30], TUESDAY=[09:00, 09:30]} + */ + findDayOfWeekAndTime().forEach { (key, value) -> + val timetableEvent = TimetableEvent( + id = id, + name = name, + color = colors[index ?: 0], + dayOfWeek = key, + start = value.firstOrNull() ?: LocalTime.of(0, 0), + end = value.lastOrNull()?.plusMinutes(30) ?: LocalTime.of(0, 0), + description = null + ) + events.add(timetableEvent) + } + /** + * @output : + * [ + * TimetableEvent(0, "강의이름1", 색상1, MONDAY, 09:00, 09:30, null), + * TimetableEvent(0, "강의이름2", 색상2, TUESDAY, 09:00, 09:30, null), + * ] + */ + return events +} diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TypeExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TypeExtensions.kt new file mode 100644 index 000000000..615aaef5e --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TypeExtensions.kt @@ -0,0 +1,8 @@ +package `in`.koreatech.koin.util.ext + +import android.content.res.Resources +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +val Float.pxToDp: Dp + get() = (this / Resources.getSystem().displayMetrics.density).dp From f665100ee3325d096f5178401bc738ec6f2a1ae0 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 14 Jun 2024 20:03:14 +0900 Subject: [PATCH 04/28] [ADD] timetable bottomsheet --- .../koin/ui/timetablev2/TimetableState.kt | 6 +- .../component/DepartmentCarouselCard.kt | 77 +++++++++++ .../ui/timetablev2/component/LectureItem.kt | 123 ++++++++++++++++++ .../ui/timetablev2/component/SearchBox.kt | 86 ++++++++++++ .../view/TimetableBottomSheetContent.kt | 58 ++++++++- .../view/TimetableContentHeader.kt | 14 ++ .../ui/timetablev2/view/TimetableScreen.kt | 25 +++- .../viewmodel/TimetableViewModel.kt | 13 ++ 8 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt index 1dddc36c9..f55c7aba8 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt @@ -5,7 +5,11 @@ import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester data class TimetableState( + val searchText: String = "", val semesters: List = emptyList(), val lectures: List = emptyList(), - val departments: List = emptyList() + val departments: List = emptyList(), + val selectedLecture: Lecture = Lecture(), + val currentDepartments: List = emptyList(), + ) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt new file mode 100644 index 000000000..d833e45e4 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt @@ -0,0 +1,77 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.domain.model.timetable.Department + +@Composable +fun DepartmentCarouselCard( + department: Department, + modifier: Modifier = Modifier, + onCancel: (Department) -> Unit, +) { + Card( + elevation = 2.dp, + backgroundColor = Color.White, + shape = RoundedCornerShape(16.dp), + modifier = modifier.border(1.dp, Color.Blue, RoundedCornerShape(16.dp)), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding( + vertical = 2.dp, + horizontal = 6.dp + ) + ) { + Text( + text = department.name, + fontSize = 14.sp, + color = Color.Blue + ) + Spacer(modifier = Modifier.width(2.dp)) + Box( + modifier = Modifier.clickable { + onCancel(department) + } + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = null, + modifier = Modifier.size(12.dp) + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun DepartmentCarouselBoxPreview() { + DepartmentCarouselCard( + Department( + 1, + "컴퓨터공학과" + ), + onCancel = {} + ) +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt new file mode 100644 index 000000000..27aba91bc --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt @@ -0,0 +1,123 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.selection.selectable +import androidx.compose.material.Card +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.util.ext.toTimetableEvents + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun LectureItem( + lecture: Lecture, + colors: List, + selectedLecture: Lecture, + modifier: Modifier = Modifier, + onSelect: (Lecture) -> Unit, + onAddLecture: (Lecture) -> Unit, + onClick: (List) -> Unit, +) { + val isSelected = selectedLecture == lecture + + Column( + modifier = modifier + .fillMaxWidth() + .background( + if (isSelected) { + Color.LightGray + } else { + Color.White + } + ) + .selectable( + selected = isSelected, + onClick = { + onClick(lecture.toTimetableEvents(colors = colors)) + if (isSelected) { + onSelect(Lecture()) + } else { + onSelect(lecture) + } + } + ) + .padding(12.dp) + ) { + Text( + text = lecture.name.toString(), + style = MaterialTheme.typography.body1, + color = Color.Black + ) + Row { + Text(text = lecture.professor.toString()) + Spacer(modifier = Modifier.width(2.dp)) + Text(text = lecture.department.toString()) + } + Row { + Text(text = lecture.grades + "학점" + "/") + Spacer(modifier = Modifier.width(2.dp)) + Text(text = lecture.target + "/") + Spacer(modifier = Modifier.width(2.dp)) + Text(text = lecture.lectureClass + "분반" + "/") + Spacer(modifier = Modifier.width(2.dp)) + Text(text = lecture.regularNumber + "명" + "/") + } + if (isSelected) { + Card( + modifier = Modifier.background(Color.White), + onClick = { onAddLecture(lecture) } + ) { + Text(text = "추가하기") + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun LectureItemPreview() { + Box(modifier = Modifier.fillMaxSize()) { + LectureItem( + colors = emptyList(), + lecture = Lecture( + name = "직업능력개발훈련평가", + professor = "우성민", + code = "HRD011", + grades = "2", + lectureClass = "01", + regularNumber = "40", + department = "HRD학과", + target = "전기3", + isEnglish = "", + isElearning = "", + designScore = "0", + classTime = listOf( + 310, + 311, + 312, + 313 + ) + ), + selectedLecture = Lecture(), + onClick = {}, + onSelect = {}, + onAddLecture = {} + ) + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt new file mode 100644 index 000000000..f0639ec91 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt @@ -0,0 +1,86 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SearchBox( + searchText: String, + modifier: Modifier = Modifier, + onSetting: () -> Unit, + onSearchTextChanged: (String) -> Unit, +) { + Column( + modifier = modifier + .padding(end = 24.dp) + .fillMaxWidth() + .wrapContentHeight() + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + modifier = Modifier.clickable(onClick = onSetting) + ) + Spacer(modifier = Modifier.width(5.dp)) + TextField( + value = searchText, + onValueChange = onSearchTextChanged, + placeholder = { + Text( + text = "입력해주세요.", + fontSize = 15.sp, + color = Color.LightGray + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.Search, + contentDescription = null + ) + }, + colors = TextFieldDefaults.textFieldColors( + unfocusedIndicatorColor = Color.DarkGray, + focusedIndicatorColor = Color.Black, + cursorColor = Color.Black, + backgroundColor = Color.White + ), + shape = RoundedCornerShape(4.dp), + modifier = Modifier.fillMaxWidth(), + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun SearchBoxPreview() { + SearchBox( + searchText = "", + onSearchTextChanged = {}, + onSetting = {} + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt index 1f1ad99a4..a6d0152cf 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt @@ -1,29 +1,75 @@ package `in`.koreatech.koin.ui.timetablev2.view +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.ui.timetablev2.component.DepartmentCarouselCard +import `in`.koreatech.koin.ui.timetablev2.component.LectureItem +import `in`.koreatech.koin.ui.timetablev2.component.SearchBox @Composable fun TimetableBottomSheetContent( + searchText: String, colors: List, lectures: List, selectedLectures: Lecture, currentDepartments: List, - searchText: String, - onSearchTextChanged: (String) -> Unit, modifier: Modifier = Modifier, - onClickLecture: (List) -> Unit, - onSelectedLecture: (Lecture) -> Unit, - onAddLecture: (Lecture) -> Unit, onSetting: () -> Unit, onCancel: (Department) -> Unit, + onAddLecture: (Lecture) -> Unit, + onSelectedLecture: (Lecture) -> Unit, + onSearchTextChanged: (String) -> Unit, + onClickLecture: (List) -> Unit, ) { - + Column( + modifier = modifier + .fillMaxWidth() + .fillMaxHeight(0.5f), + ) { + SearchBox( + searchText = searchText, + onSearchTextChanged = onSearchTextChanged, + onSetting = onSetting + ) + LazyRow( + modifier = Modifier.padding(horizontal = 4.dp, vertical = 6.dp) + ) { + itemsIndexed(currentDepartments) { index, department -> + DepartmentCarouselCard( + modifier = Modifier.padding( + end = if (index == currentDepartments.size) 0.dp else 4.dp + ), + department = department, + onCancel = onCancel + ) + } + } + LazyColumn { + itemsIndexed(lectures) { _, lecture -> + LectureItem( + colors = colors, + lecture = lecture, + selectedLecture = selectedLectures, + onClick = onClickLecture, + onSelect = onSelectedLecture, + onAddLecture = onAddLecture + ) + } + } + } } @Preview(showBackground = true) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt index 8e35eb55e..8c7198682 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.ui.timetablev2.view import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row @@ -9,6 +10,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircle import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,6 +26,7 @@ fun TimetableContentHeader( semesters: List, modifier: Modifier = Modifier, onSavedImage: () -> Unit, + onVisibleBottomSheet: () -> Unit, onSemesterTextChanged: (semester: String) -> Unit, ) { Row( @@ -45,6 +50,14 @@ fun TimetableContentHeader( .padding(8.dp), onClick = onSavedImage ) + Icon( + imageVector = Icons.Default.AddCircle, + contentDescription = null, + tint = ColorPrimary, + modifier = Modifier.clickable { + onVisibleBottomSheet() + } + ) } } @@ -59,6 +72,7 @@ fun TimetableContentHeaderPreview() { TimetableContentHeader( semesters = semesters, onSavedImage = {}, + onVisibleBottomSheet = {}, onSemesterTextChanged = {} ) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index fe9b9f4f5..6f13d3a42 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -22,6 +23,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel +import kotlinx.coroutines.launch import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @@ -52,13 +54,28 @@ fun TimetableScreen( val scaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = sheetState ) + val scope = rememberCoroutineScope() Log.e("aaa", "lectures : ${state.lectures}") Log.e("aaa", "departments : ${state.departments}") BottomSheetScaffold( modifier = modifier, scaffoldState = scaffoldState, - sheetContent = sheetContent, + sheetContent = { + TimetableBottomSheetContent( + searchText = state.searchText, + colors = emptyList(), + lectures = state.lectures, + selectedLectures = state.selectedLecture, + currentDepartments = state.currentDepartments, + onSetting = { }, + onCancel = {}, + onAddLecture = {}, + onSelectedLecture = {}, + onSearchTextChanged = {}, + onClickLecture = {} + ) + }, sheetBackgroundColor = Color.White, sheetPeekHeight = 0.dp, ) { @@ -69,6 +86,12 @@ fun TimetableScreen( semesters = state.semesters, modifier = Modifier.fillMaxWidth(), onSavedImage = onSavedImage, + onVisibleBottomSheet = { + scope.launch { + if (sheetState.isCollapsed) sheetState.expand() + else sheetState.collapse() + } + }, onSemesterTextChanged = timetableViewModel::loadLectures ) content( diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index 79b1801b0..fc7061b19 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -3,6 +3,7 @@ package `in`.koreatech.koin.ui.timetablev2.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.domain.usecase.timetable.GetDepartmentsUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase @@ -49,4 +50,16 @@ class TimetableViewModel @Inject constructor( } } } + + fun updateSearchText(text: String) = intent { + reduce { state.copy(searchText = text) } + } + + fun updateLectureEvent() { + + } + + fun updateSelectedLecture(lecture: Lecture) = intent { + reduce { state.copy(selectedLecture = lecture) } + } } From 0982dfe659e63414b73750a0ac09fa3441d5ca99 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 00:47:14 +0900 Subject: [PATCH 05/28] [ADD] anoymous timetables --- data/build.gradle | 2 + .../koin/data/di/datastore/DataStoreModule.kt | 36 +++ .../data/di/source/LocalDataSourceModule.kt | 7 +- .../repository/TimetableRepositoryImpl.kt | 23 ++ .../source/local/TimetableLocalDataSource.kt | 18 ++ .../koin/domain/model/timetable/Lecture.kt | 22 ++ .../domain/repository/TimetableRepository.kt | 3 + .../usecase/timetable/GetTimetablesUseCase.kt | 14 ++ .../usecase/timetable/UpdateLectureUseCase.kt | 16 ++ gradle/libs.versions.toml | 2 + koin/src/main/AndroidManifest.xml | 1 + .../java/in/koreatech/koin/common/UiStatus.kt | 6 +- .../in/koreatech/koin/compose/ui/Color.kt | 2 + .../koin/model/timetable/TimetableEvent.kt | 12 + .../KoinNavigationDrawerActivity.kt | 26 ++- .../koin/ui/timetablev2/TimetableActivity.kt | 6 +- .../koin/ui/timetablev2/TimetableState.kt | 16 +- .../koin/ui/timetablev2/TimetableView.kt | 35 ++- .../component/CustomAlertDialog.kt | 19 ++ .../ui/timetablev2/component/DepartmentBox.kt | 77 +++++++ .../timetablev2/component/DepartmentButton.kt | 49 ++++ .../component/DepartmentCarouselCard.kt | 13 +- .../ui/timetablev2/component/LectureItem.kt | 89 +++++--- .../ui/timetablev2/component/SearchBox.kt | 91 ++++---- .../ui/timetablev2/view/DepartmentDialog.kt | 84 +++++++ .../ui/timetablev2/view/LectureAddDialog.kt | 66 ++++++ .../timetablev2/view/LectureRemoveDialog.kt | 65 ++++++ .../ui/timetablev2/view/SemesterDropdown.kt | 6 +- .../koin/ui/timetablev2/view/Timetable.kt | 6 + .../view/TimetableBottomSheetContent.kt | 9 +- .../view/TimetableContentHeader.kt | 2 +- .../ui/timetablev2/view/TimetableScreen.kt | 122 ++++++++-- .../viewmodel/TimetableViewModel.kt | 214 +++++++++++++++++- .../koin/util/ext/TimetableExtensions.kt | 4 +- 34 files changed, 1026 insertions(+), 137 deletions(-) create mode 100644 data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/CustomAlertDialog.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentBox.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentButton.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/DepartmentDialog.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureAddDialog.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureRemoveDialog.kt diff --git a/data/build.gradle b/data/build.gradle index efca3e0ba..01726b55e 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -61,4 +61,6 @@ dependencies { testImplementation libs.junit androidTestImplementation libs.ext.junit + + implementation(libs.androidx.datastore) } diff --git a/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt b/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt new file mode 100644 index 000000000..640de8945 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt @@ -0,0 +1,36 @@ +package `in`.koreatech.koin.data.di.datastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.preferencesDataStoreFile +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import javax.inject.Singleton + +private const val KOIN_PREFERENCES = "koin_preferences" + +@InstallIn(SingletonComponent::class) +@Module +object DataStoreModule { + @Singleton + @Provides + fun providesPreferencesDataStore(@ApplicationContext appContext: Context): DataStore { + return PreferenceDataStoreFactory.create( + corruptionHandler = ReplaceFileCorruptionHandler( + produceNewData = { emptyPreferences() } + ), + scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), + produceFile = { appContext.preferencesDataStoreFile(KOIN_PREFERENCES) } + ) + } +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt index cd4fa58b7..3e28528a3 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt @@ -1,6 +1,8 @@ package `in`.koreatech.koin.data.di.source import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -63,8 +65,9 @@ object LocalDataSourceModule { @Provides @Singleton fun providesTimetableLocalDataSource( - @ApplicationContext applicationContext: Context + @ApplicationContext applicationContext: Context, + dataStore: DataStore ): TimetableLocalDataSource { - return TimetableLocalDataSource(applicationContext) + return TimetableLocalDataSource(applicationContext, dataStore) } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 8984927b8..c1a4f0223 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -1,5 +1,7 @@ package `in`.koreatech.koin.data.repository +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import `in`.koreatech.koin.data.mapper.toDepartment import `in`.koreatech.koin.data.mapper.toLecture import `in`.koreatech.koin.data.mapper.toSemester @@ -9,12 +11,33 @@ import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.domain.repository.TimetableRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow import javax.inject.Inject class TimetableRepositoryImpl @Inject constructor( private val timetableRemoteDataSource: TimetableRemoteDataSource, private val timetableLocalDataSource: TimetableLocalDataSource, ) : TimetableRepository { + override suspend fun getTimetables(key: String): List = + try { + val lectureString = timetableLocalDataSource.getString(key).first() + val lectureType = object : TypeToken>() {}.type + val gson = Gson() + val updateLectures = + gson.fromJson>(lectureString, lectureType).orEmpty() + updateLectures + } catch (e: Exception) { + emptyList() + } + + override suspend fun putString(key: String, value: T) { + try { + timetableLocalDataSource.putString(key, Gson().toJson(value)) + } catch (e: Exception) { } + } + override suspend fun loadSemesters(): List = timetableRemoteDataSource.loadSemesters().map { it.toSemester() } diff --git a/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt index 6985791c8..b014564c3 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt @@ -1,15 +1,33 @@ package `in`.koreatech.koin.data.source.local import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey import dagger.hilt.android.qualifiers.ApplicationContext import `in`.koreatech.koin.data.response.timetable.DepartmentResponse import `in`.koreatech.koin.data.response.timetable.DepartmentsResponse import `in`.koreatech.koin.data.util.readData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class TimetableLocalDataSource @Inject constructor( @ApplicationContext private val context: Context, + private val dataStore: DataStore ) { + fun getString(key: String): Flow = + dataStore.data.map { preferences -> + preferences[stringPreferencesKey(key)] ?: "" + } + + suspend fun putString(key: String, value: String) { + dataStore.edit { preferences -> + preferences[stringPreferencesKey(key)] = value + } + } + fun loadDepartments(): List? = context.readData("department.json")?.departments } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt index 3904e92f6..c76d5129a 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/Lecture.kt @@ -51,6 +51,28 @@ data class Lecture( } } + fun formatDescription(): String { + val description = if (target.isEmpty()) { + "" + } else { + target + }.let { + if (code.isNotEmpty()) "$it / $code" + else it + }.let { + if (grades.isNotEmpty()) "$it / ${grades}학점" + else it + }.let { + if (professor.isNotEmpty()) "$it / $professor" + else "$it / 미정(교수)" + }.let { + if (regularNumber.isNotEmpty()) "$it / ${regularNumber}명" + else "$it / 미정(인원)" + } + + return description + } + fun doesMatchSearchQuery(query: String): Boolean { val matchingCombinations = listOf( "$name", diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 6c66cf2ad..7c7c223d5 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -3,8 +3,11 @@ package `in`.koreatech.koin.domain.repository import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester +import kotlinx.coroutines.flow.Flow interface TimetableRepository { + suspend fun getTimetables(key: String): List + suspend fun putString(key: String, value: T) suspend fun loadSemesters(): List suspend fun loadDepartments(): List suspend fun loadLectures(semester: String): List diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt new file mode 100644 index 000000000..7f8ea464e --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt @@ -0,0 +1,14 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.domain.repository.TimetableRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetTimetablesUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, +) { + suspend operator fun invoke( + semester: String, + ): List = timetableRepository.getTimetables(semester) +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt new file mode 100644 index 000000000..57e1b11fd --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt @@ -0,0 +1,16 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class UpdateLectureUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +) { + suspend operator fun invoke( + semester: String, + lectures: List + ) { + timetableRepository.putString(semester, lectures) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c30b620eb..8f8b8605f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,6 +53,7 @@ coilVersion = "2.6.0" napier = "2.6.1" powerSpinner = "1.2.7" firebaseCrashlyticsBuildtoolsVersion = "2.9.9" +datastore = "1.1.1" [libraries] androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtxVersion" } @@ -65,6 +66,7 @@ androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel- androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerviewVersion" } androidx-runner = { module = "androidx.test:runner", version.ref = "runnerVersion" } androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCryptoVersion" } +androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityComposeVersion" } napier = {module = "io.github.aakira:napier", version.ref="napier"} diff --git a/koin/src/main/AndroidManifest.xml b/koin/src/main/AndroidManifest.xml index d1a3a6243..eaf18371e 100644 --- a/koin/src/main/AndroidManifest.xml +++ b/koin/src/main/AndroidManifest.xml @@ -47,6 +47,7 @@ android:resource="@color/black" /> "월" + DayOfWeek.TUESDAY -> "화" + DayOfWeek.WEDNESDAY -> "수" + DayOfWeek.THURSDAY -> "목" + DayOfWeek.FRIDAY -> "금" + DayOfWeek.SATURDAY -> "토" + DayOfWeek.SUNDAY -> "일" + else -> "" + } + fun convertToTimeBlock(endTime: LocalTime): TimeBlock { val startDuration = Duration.between(LocalTime.of(start.hour, 0), start).run { this.toMinutes() / 60f diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index ec19c7219..d8cfac7f9 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -6,6 +6,7 @@ import android.content.pm.PackageManager import android.graphics.Typeface import android.os.Build import android.os.Bundle +import android.util.Log import android.view.MenuItem import android.view.View import android.widget.Button @@ -58,8 +59,8 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), val drawerLayoutId get() = R.id.drawer_layout private var pressTime = System.currentTimeMillis() - private val koinNavigationDrawerViewModel by viewModels() + private val koinNavigationDrawerViewModel by viewModels() private val gotoAskForm = registerForActivityResult(GotoAskFormContract()) {} private val drawerLayout by lazy { @@ -136,7 +137,6 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - drawerLayout.setScrimColor(ContextCompat.getColor(this, R.color.black_alpha20)) drawerLayout.addDrawerListener { _, slideOffset -> if (slideOffset < 0.5f) window.blueStatusBar() else window.whiteStatusBar() @@ -287,12 +287,13 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), MenuState.Main -> goToMainActivity() MenuState.Store -> goToStoreActivity() MenuState.Timetable -> { - if (userState.value == null || userState.value?.isAnonymous == true) { - goToTimetableActivityV2() + goToTimetableActivityV2(userState.value, userState.value?.isAnonymous == true) +// if (userState.value == null || userState.value?.isAnonymous == true) { // goToAnonymousTimeTableActivity() - } else { - goToTimetableActivty() - } +// } else { +// goToTimetableActivityV2(userState.value, userState.value?.isAnonymous == true) +// goToTimetableActivty() +// } } MenuState.UserInfo -> { @@ -424,11 +425,18 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), /** * @TEST */ - private fun goToTimetableActivityV2() { + private fun goToTimetableActivityV2(user: User?, isAnonymous: Boolean) { if (menuState != MenuState.Main) { goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) } else { - startActivity(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) + val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { + if (user == null || isAnonymous) { + putExtra("isAnonymous", true) + } else { + putExtra("isAnonymous", false) + } + } + startActivity(intent) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index c25714cc2..c983f6083 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.ui.timetablev2 import android.os.Bundle +import android.util.Log import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable @@ -35,10 +36,12 @@ class TimetableActivity : KoinNavigationDrawerActivity() { binding = ActivityTimetableBinding.inflate(layoutInflater) setContentView(binding.root) initEvent() + val isAnonymous = intent.getBooleanExtra("isAnonymous", true) binding.composeView.setContent { TimetableTheme { TimetableScreen( + isAnonymous = isAnonymous, onSavedImage = { BitmapUtils(this).apply { timetableView?.value?.let { view -> @@ -53,9 +56,6 @@ class TimetableActivity : KoinNavigationDrawerActivity() { sheetState = bottomSheetState, onEventClick = onEventClick ) - }, - sheetContent = { - } ) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt index f55c7aba8..ace104950 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt @@ -1,15 +1,27 @@ package `in`.koreatech.koin.ui.timetablev2 +import `in`.koreatech.koin.common.UiStatus import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester +import `in`.koreatech.koin.model.timetable.TimetableEvent data class TimetableState( + val uiStatus: UiStatus = UiStatus.Init, + val isAnonymous: Boolean = true, val searchText: String = "", + val isDepartmentDialogVisible: Boolean = false, + val isAddLectureDialogVisible: Boolean = false, + val isRemoveLectureDialogVisible: Boolean = false, + val clickLecture: Lecture = Lecture(), + val selectedLecture: Lecture = Lecture(), + val selectedDepartments: List = emptyList(), + val currentSemester: Semester = Semester(), val semesters: List = emptyList(), + val _lectures: List = emptyList(), val lectures: List = emptyList(), val departments: List = emptyList(), - val selectedLecture: Lecture = Lecture(), + val timetableEvents: List = emptyList(), + val lectureEvents: List = emptyList(), val currentDepartments: List = emptyList(), - ) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt index 1f8d6e61a..846d7e816 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt @@ -5,12 +5,16 @@ import android.util.AttributeSet import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.AbstractComposeView import androidx.lifecycle.viewmodel.compose.viewModel +import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.view.Timetable import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel +import `in`.koreatech.koin.util.ext.toTimetableEvents +import org.orbitmvi.orbit.compose.collectAsState class TimetableView @OptIn(ExperimentalMaterialApi::class) @JvmOverloads constructor( @@ -21,16 +25,31 @@ class TimetableView @OptIn(ExperimentalMaterialApi::class) ) : AbstractComposeView(context, attrs, defStyleAttr) { lateinit var onTimetableEventClickListener: OnTimetableEventClickListener + @OptIn(ExperimentalMaterialApi::class) @Composable override fun Content() { - val viewModel = viewModel() - -// Timetable( -// events = , -// sheetState = sheetState, -// clickEvent = , -// onEventClick = onTimetableEventClickListener::onEventClick -// ) + val viewModel: TimetableViewModel = viewModel() + val state by viewModel.collectAsState() + + Timetable( + events = generateTimetableEvents(state.timetableEvents, emptyList()) , + sheetState = sheetState, + clickEvent = state.lectureEvents, + onEventClick = onTimetableEventClickListener::onEventClick + ) + } + + private fun generateTimetableEvents(timetableEvents: List, colors: List): List { + val updateTimetableEvents = mutableListOf() + timetableEvents.mapIndexed { index, lecture -> + lecture.toTimetableEvents(index, colors) + }.map { + it.forEach { + updateTimetableEvents.add(it) + } + } + + return updateTimetableEvents } interface OnTimetableEventClickListener { diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/CustomAlertDialog.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/CustomAlertDialog.kt new file mode 100644 index 000000000..88790c7a6 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/CustomAlertDialog.kt @@ -0,0 +1,19 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties + +@Composable +fun CustomAlertDialog( + properties: DialogProperties = DialogProperties(), + onDismissRequest: () -> Unit, + content: @Composable () -> Unit, +) { + Dialog( + onDismissRequest = onDismissRequest, + properties = properties, + ) { + content() + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentBox.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentBox.kt new file mode 100644 index 000000000..7c0eed151 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentBox.kt @@ -0,0 +1,77 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.compose.ui.ColorMain400 +import `in`.koreatech.koin.compose.ui.ColorPrimaryMain400_ALPAH10 +import `in`.koreatech.koin.domain.model.timetable.Department + +@Composable +fun DepartmentBox( + department: Department, + selected: Boolean, + modifier: Modifier = Modifier, + onClick: (Department, Boolean) -> Unit, +) { + Box( + modifier = modifier + .clickable { onClick(department, selected) } + .border( + width = 1.dp, + color = if (selected) ColorMain400 else Color.LightGray, + shape = RoundedCornerShape(4.dp) + ) + .background( + color = if (selected) ColorPrimaryMain400_ALPAH10 + else Color.White, + shape = RoundedCornerShape(4.dp) + ) + .padding( + vertical = 6.dp, + ), + contentAlignment = Alignment.Center + ) { + Text( + text = department.name, + fontSize = 10.sp, + color = if (selected) ColorMain400 else Color.Gray, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun DepartmentBoxPreviewSelected() { + DepartmentBox( + department = Department(1, "컴퓨터공학과"), + selected = true + ) { _, _ -> + + } +} + +@Preview(showBackground = true) +@Composable +private fun DepartmentBoxPreview() { + DepartmentBox( + department = Department(1, "컴퓨터공학과"), + selected = false + ) { _, _ -> + + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentButton.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentButton.kt new file mode 100644 index 000000000..182c91279 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentButton.kt @@ -0,0 +1,49 @@ +package `in`.koreatech.koin.ui.timetablev2.component + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.compose.ui.ColorPrimary + +@Composable +fun DepartmentButton( + modifier: Modifier = Modifier, + onCompleted: () -> Unit, +) { + Box(modifier = modifier) { + Button( + onClick = onCompleted, + shape = RoundedCornerShape(4.dp), + colors = ButtonDefaults.buttonColors(ColorPrimary), + contentPadding = PaddingValues( + horizontal = 14.dp + ), + modifier = Modifier + .height(26.dp) + ) { + Text( + text = "선택완료", + color = Color.White, + fontSize = 15.sp + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun DepartmentPreview() { + DepartmentButton( + onCompleted = {} + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt index d833e45e4..982176ba4 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/DepartmentCarouselCard.kt @@ -1,5 +1,6 @@ package `in`.koreatech.koin.ui.timetablev2.component +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.material.Icon @@ -21,6 +23,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.compose.ui.ColorMain400 +import `in`.koreatech.koin.compose.ui.ColorPrimary +import `in`.koreatech.koin.compose.ui.ColorPrimaryMain400_ALPAH10 import `in`.koreatech.koin.domain.model.timetable.Department @Composable @@ -33,11 +38,13 @@ fun DepartmentCarouselCard( elevation = 2.dp, backgroundColor = Color.White, shape = RoundedCornerShape(16.dp), - modifier = modifier.border(1.dp, Color.Blue, RoundedCornerShape(16.dp)), + modifier = modifier.border(1.dp, ColorMain400, RoundedCornerShape(16.dp)), ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier + .background(Color.White) + .wrapContentSize() .padding( vertical = 2.dp, horizontal = 6.dp @@ -45,8 +52,8 @@ fun DepartmentCarouselCard( ) { Text( text = department.name, - fontSize = 14.sp, - color = Color.Blue + fontSize = 12.sp, + color = ColorMain400 ) Spacer(modifier = Modifier.width(2.dp)) Box( diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt index 27aba91bc..8a72912ba 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt @@ -1,5 +1,6 @@ package `in`.koreatech.koin.ui.timetablev2.component +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -9,16 +10,21 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +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 `in`.koreatech.koin.compose.ui.ColorMain400 +import `in`.koreatech.koin.compose.ui.ColorPrimaryMain400_ALPAH10 import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.util.ext.toTimetableEvents @@ -31,25 +37,19 @@ fun LectureItem( selectedLecture: Lecture, modifier: Modifier = Modifier, onSelect: (Lecture) -> Unit, - onAddLecture: (Lecture) -> Unit, + onAddLecture: () -> Unit, onClick: (List) -> Unit, ) { val isSelected = selectedLecture == lecture + val events = lecture.toTimetableEvents(colors = colors) Column( modifier = modifier .fillMaxWidth() - .background( - if (isSelected) { - Color.LightGray - } else { - Color.White - } - ) .selectable( selected = isSelected, onClick = { - onClick(lecture.toTimetableEvents(colors = colors)) + onClick(events) if (isSelected) { onSelect(Lecture()) } else { @@ -57,33 +57,64 @@ fun LectureItem( } } ) + .padding( + horizontal = 12.dp, + vertical = 4.dp + ) + .background( + color = if (isSelected) { + ColorPrimaryMain400_ALPAH10 + } else { + Color.White + }, + shape = RoundedCornerShape(4.dp) + ) .padding(12.dp) ) { Text( - text = lecture.name.toString(), - style = MaterialTheme.typography.body1, - color = Color.Black + text = lecture.name, + color = Color.Black, + fontWeight = FontWeight.Bold, + fontSize = 14.sp ) Row { - Text(text = lecture.professor.toString()) - Spacer(modifier = Modifier.width(2.dp)) - Text(text = lecture.department.toString()) - } - Row { - Text(text = lecture.grades + "학점" + "/") - Spacer(modifier = Modifier.width(2.dp)) - Text(text = lecture.target + "/") - Spacer(modifier = Modifier.width(2.dp)) - Text(text = lecture.lectureClass + "분반" + "/") - Spacer(modifier = Modifier.width(2.dp)) - Text(text = lecture.regularNumber + "명" + "/") + events.forEachIndexed { index, event -> + Text( + text = (if (index != 0) "/" else "") + event.dayOfWeekToKorean(), + fontSize = 12.sp, + color = Color.Black + ) + } + Spacer(modifier = Modifier.width(6.dp)) + events.forEachIndexed { index, event -> + Text( + text = (if (index != 0) "/" else "") + "${event.start} ~ ${event.end}", + fontSize = 12.sp, + color = Color.Black + ) + } } + Text( + text = lecture.formatDescription(), + fontSize = 12.sp, + color = Color.Black + ) if (isSelected) { Card( - modifier = Modifier.background(Color.White), - onClick = { onAddLecture(lecture) } + shape = RoundedCornerShape(4.dp), + border = BorderStroke(1.dp, ColorMain400), + modifier = Modifier + .background(color = Color.Transparent) + .wrapContentSize(), + onClick = onAddLecture ) { - Text(text = "추가하기") + Text( + text = "추가", + color = ColorMain400, + fontSize = 14.sp, + modifier = Modifier + .padding(horizontal = 15.dp, vertical = 4.dp), + ) } } } @@ -91,7 +122,7 @@ fun LectureItem( @Preview(showBackground = true) @Composable -fun LectureItemPreview() { +private fun LectureItemPreview() { Box(modifier = Modifier.fillMaxSize()) { LectureItem( colors = emptyList(), diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt index f0639ec91..7de88659f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SearchBox.kt @@ -1,9 +1,12 @@ package `in`.koreatech.koin.ui.timetablev2.component 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.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -31,56 +34,54 @@ fun SearchBox( onSetting: () -> Unit, onSearchTextChanged: (String) -> Unit, ) { - Column( - modifier = modifier - .padding(end = 24.dp) - .fillMaxWidth() - .wrapContentHeight() + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Settings, - contentDescription = null, - modifier = Modifier.clickable(onClick = onSetting) - ) - Spacer(modifier = Modifier.width(5.dp)) - TextField( - value = searchText, - onValueChange = onSearchTextChanged, - placeholder = { - Text( - text = "입력해주세요.", - fontSize = 15.sp, - color = Color.LightGray - ) - }, - trailingIcon = { - Icon( - imageVector = Icons.Default.Search, - contentDescription = null - ) - }, - colors = TextFieldDefaults.textFieldColors( - unfocusedIndicatorColor = Color.DarkGray, - focusedIndicatorColor = Color.Black, - cursorColor = Color.Black, - backgroundColor = Color.White - ), - shape = RoundedCornerShape(4.dp), - modifier = Modifier.fillMaxWidth(), - ) - } + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + modifier = Modifier.clickable(onClick = onSetting) + ) + Spacer(modifier = Modifier.width(5.dp)) + TextField( + value = searchText, + onValueChange = onSearchTextChanged, + placeholder = { + Text( + text = "입력해주세요.", + fontSize = 15.sp, + color = Color.LightGray + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.Search, + contentDescription = null + ) + }, + colors = TextFieldDefaults.textFieldColors( + unfocusedIndicatorColor = Color.DarkGray, + focusedIndicatorColor = Color.Black, + cursorColor = Color.Black, + backgroundColor = Color.White + ), + shape = RoundedCornerShape(4.dp), + modifier = Modifier.fillMaxWidth(), + ) } } @Preview(showBackground = true) @Composable private fun SearchBoxPreview() { - SearchBox( - searchText = "", - onSearchTextChanged = {}, - onSetting = {} - ) + Box(modifier = Modifier.fillMaxSize()) { + SearchBox( + searchText = "", + onSearchTextChanged = {}, + onSetting = {}, + modifier = Modifier.padding(8.dp) + ) + } } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/DepartmentDialog.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/DepartmentDialog.kt new file mode 100644 index 000000000..6111acfb8 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/DepartmentDialog.kt @@ -0,0 +1,84 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.domain.model.timetable.Department +import `in`.koreatech.koin.ui.timetablev2.component.CustomAlertDialog +import `in`.koreatech.koin.ui.timetablev2.component.DepartmentBox +import `in`.koreatech.koin.ui.timetablev2.component.DepartmentButton +import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel + +@Composable +fun DepartmentDialog( + visible: Boolean, + selectedDepartments: List, + departments: List, + modifier: Modifier = Modifier, + onDismissRequest: () -> Unit, + onClick: (Department) -> Unit, + onCompleted: (List) -> Unit, +) { + if (visible) { + CustomAlertDialog( + onDismissRequest = onDismissRequest + ) { + Column( + modifier = modifier + .fillMaxWidth() + .background(Color.White) + .padding( + vertical = 20.dp, + horizontal = 16.dp + ) + ) { + Text( + text = "전공선택", + fontSize = 18.sp, + color = Color.Black + ) + Spacer(modifier = Modifier.height(12.dp)) + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = modifier + .background(Color.White) + ) { + itemsIndexed(departments) { index, department -> + DepartmentBox( + modifier = Modifier.padding( + start = if (index % 2 == 0) 0.dp else 2.dp, + end = if (index % 2 == 0) 2.dp else 0.dp, + bottom = 4.dp + ), + department = department, + selected = selectedDepartments.contains(department), + onClick = { selectedDepartment, _ -> + onClick(selectedDepartment) + }, + ) + } + } + Spacer(modifier = Modifier.height(12.dp)) + DepartmentButton( + modifier = Modifier.align(Alignment.End), + onCompleted = { + onCompleted(selectedDepartments) + } + ) + } + } + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureAddDialog.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureAddDialog.kt new file mode 100644 index 000000000..e7cad9941 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureAddDialog.kt @@ -0,0 +1,66 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import android.content.Context +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.ui.timetablev2.component.CustomAlertDialog + +@Composable +fun LectureAddDialog( + context: Context, + visible: Boolean, + lecture: Lecture, + duplication: Boolean, + modifier: Modifier = Modifier, + onDismissRequest: () -> Unit, + onAddLecture: (Lecture) -> Unit, +) { + if (visible) { + CustomAlertDialog( + onDismissRequest = onDismissRequest, + content = { + Column( + modifier = modifier + .background(Color.White) + .fillMaxWidth() + .padding(10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "${lecture.name}(${lecture.lectureClass})", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Text( + text = if (duplication) "기존 강의 대신\n새로운 강의를 추가하시겠습니까?" else "강의를 추가하시겠습니까?", + fontSize = 12.sp, + color = Color.Black, + textAlign = TextAlign.Center + ) + + Button( + onClick = { onAddLecture(lecture) } + ) { + Text(text = "추가하기") + } + } + } + ) + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureRemoveDialog.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureRemoveDialog.kt new file mode 100644 index 000000000..3d64636e2 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/LectureRemoveDialog.kt @@ -0,0 +1,65 @@ +package `in`.koreatech.koin.ui.timetablev2.view + +import android.content.Context +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.domain.model.timetable.Semester +import `in`.koreatech.koin.ui.timetablev2.component.CustomAlertDialog + +@Composable +fun LectureRemoveDialog( + modifier: Modifier = Modifier, + context: Context, + visible: Boolean, + lecture: Lecture, + semester: Semester, + onDismissRequest: () -> Unit, + onRemoveLecture: (Semester, Lecture) -> Unit, +) { + if (visible) { + CustomAlertDialog( + onDismissRequest = onDismissRequest, + content = { + Column( + modifier = modifier + .background(Color.White) + .fillMaxWidth() + .padding(10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "${lecture.name}(${lecture.lectureClass})", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = Color.Black + ) + Text( + text = "강의를 삭제하시겠습니까?", + fontSize = 12.sp, + color = Color.Black + ) + + Button( + onClick = { onRemoveLecture(semester, lecture) } + ) { + Text(text = "삭제하기") + } + } + } + ) + } +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt index f4433a84f..dc547962f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/SemesterDropdown.kt @@ -31,7 +31,7 @@ import `in`.koreatech.koin.domain.model.timetable.Semester fun SemesterDropdown( semesters: List, modifier: Modifier = Modifier, - onSemesterTextChanged: (semester: String) -> Unit, + onSemesterTextChanged: (semester: Semester) -> Unit, ) { var expanded by rememberSaveable { mutableStateOf(false) @@ -42,7 +42,7 @@ fun SemesterDropdown( selectedText.ifBlank { if (semesters.isNotEmpty()) { - onSemesterTextChanged(semesters[0].semester) + onSemesterTextChanged(semesters[0]) semesters[0].format() } else "" @@ -86,7 +86,7 @@ fun SemesterDropdown( semesters.forEach { semester -> DropdownMenuItem( onClick = { - onSemesterTextChanged(semester.semester) + onSemesterTextChanged(semester) selectedText = semester.format() expanded = false } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt index cccd1c5ff..ddd7797c4 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt @@ -1,8 +1,10 @@ package `in`.koreatech.koin.ui.timetablev2.view +import android.util.Log import androidx.compose.foundation.background 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.padding import androidx.compose.foundation.rememberScrollState @@ -11,6 +13,9 @@ import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -46,6 +51,7 @@ fun Timetable( Column( modifier = modifier .background(Color.White) + .fillMaxSize() .padding( /** * 바텀 시트 올라올 때, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt index a6d0152cf..53de56317 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt @@ -1,8 +1,10 @@ package `in`.koreatech.koin.ui.timetablev2.view import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow @@ -29,7 +31,7 @@ fun TimetableBottomSheetContent( modifier: Modifier = Modifier, onSetting: () -> Unit, onCancel: (Department) -> Unit, - onAddLecture: (Lecture) -> Unit, + onAddLecture: () -> Unit, onSelectedLecture: (Lecture) -> Unit, onSearchTextChanged: (String) -> Unit, onClickLecture: (List) -> Unit, @@ -40,12 +42,14 @@ fun TimetableBottomSheetContent( .fillMaxHeight(0.5f), ) { SearchBox( + modifier = Modifier.padding(8.dp), searchText = searchText, onSearchTextChanged = onSearchTextChanged, onSetting = onSetting ) LazyRow( - modifier = Modifier.padding(horizontal = 4.dp, vertical = 6.dp) + modifier = Modifier + .padding(horizontal = 4.dp, vertical = 6.dp) ) { itemsIndexed(currentDepartments) { index, department -> DepartmentCarouselCard( @@ -57,6 +61,7 @@ fun TimetableBottomSheetContent( ) } } + Spacer(modifier = Modifier.height(4.dp)) LazyColumn { itemsIndexed(lectures) { _, lecture -> LectureItem( diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt index 8c7198682..2f705d18c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt @@ -27,7 +27,7 @@ fun TimetableContentHeader( modifier: Modifier = Modifier, onSavedImage: () -> Unit, onVisibleBottomSheet: () -> Unit, - onSemesterTextChanged: (semester: String) -> Unit, + onSemesterTextChanged: (semester: Semester) -> Unit, ) { Row( modifier = modifier diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 6f13d3a42..853c4a341 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -2,24 +2,32 @@ package `in`.koreatech.koin.ui.timetablev2.view import android.content.Context import android.util.Log +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material.rememberBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import `in`.koreatech.koin.common.UiStatus import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel @@ -30,12 +38,12 @@ import org.orbitmvi.orbit.compose.collectSideEffect @OptIn(ExperimentalMaterialApi::class) @Composable fun TimetableScreen( + isAnonymous: Boolean, modifier: Modifier = Modifier, context: Context = LocalContext.current, timetableViewModel: TimetableViewModel = viewModel(), onSavedImage: () -> Unit, - content: @Composable ColumnScope.(BottomSheetState, onEventClick: (TimetableEvent) -> Unit) -> Unit, - sheetContent: @Composable ColumnScope.() -> Unit, + content: @Composable ColumnScope.(sheetState: BottomSheetState, onEventClick: (TimetableEvent) -> Unit) -> Unit, ) { val state by timetableViewModel.collectAsState() @@ -45,18 +53,76 @@ fun TimetableScreen( } } - timetableViewModel.loadSemesters() - timetableViewModel.loadDepartments() + LaunchedEffect(key1 = state.semesters) { + if (state.semesters.isEmpty()) { + timetableViewModel.loadSemesters() + } + if (state.departments.isEmpty()) { + timetableViewModel.loadDepartments() + } + } + + LaunchedEffect(key1 = isAnonymous) { + timetableViewModel.updateIsAnonymous(isAnonymous) + } + + if (state.currentSemester.semester.isBlank() && state.semesters.isNotEmpty()) { + timetableViewModel.updateCurrentSemester(state.semesters[0]) + } + + val scope = rememberCoroutineScope() val sheetState = rememberBottomSheetState( initialValue = BottomSheetValue.Collapsed ) val scaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = sheetState ) - val scope = rememberCoroutineScope() - Log.e("aaa", "lectures : ${state.lectures}") - Log.e("aaa", "departments : ${state.departments}") + + BackHandler(sheetState = sheetState) + + DepartmentDialog( + visible = state.isDepartmentDialogVisible, + departments = state.departments, + selectedDepartments = state.selectedDepartments, + onDismissRequest = { + if (state.currentDepartments.isEmpty()) { + timetableViewModel.clearSelectedDepartments() + } + timetableViewModel.closeDepartmentDialog() + }, + onClick = timetableViewModel::updateSelectedDepartment, + onCompleted = timetableViewModel::updateCurrentDepartment + ) + + LectureAddDialog( + context = context, + visible = state.isAddLectureDialogVisible, + lecture = state.selectedLecture, + duplication = state.selectedLecture.duplicate(state.timetableEvents), + onDismissRequest = timetableViewModel::closeAddLectureDialog, + onAddLecture = { lecture -> + if (state.selectedLecture.duplicate(state.timetableEvents)) { + timetableViewModel.duplicateLecture(lecture) + } else { + timetableViewModel.addLecture(state.currentSemester, lecture) + } + timetableViewModel.closeAddLectureDialog() +// sendBroadcastReceiver(currentSemester) + } + ) + + /** + * 강의 삭제 모달 + */ + LectureRemoveDialog( + context = context, + visible = state.isRemoveLectureDialogVisible, + lecture = state.clickLecture, + semester = state.currentSemester, + onDismissRequest = timetableViewModel::closeRemoveLectureDialog, + onRemoveLecture = timetableViewModel::removeLecture + ) BottomSheetScaffold( modifier = modifier, @@ -68,12 +134,12 @@ fun TimetableScreen( lectures = state.lectures, selectedLectures = state.selectedLecture, currentDepartments = state.currentDepartments, - onSetting = { }, - onCancel = {}, - onAddLecture = {}, - onSelectedLecture = {}, - onSearchTextChanged = {}, - onClickLecture = {} + onSetting = timetableViewModel::openDepartmentDialog, + onCancel = timetableViewModel::removeDepartment, + onAddLecture = timetableViewModel::openAddLectureDialog, + onSelectedLecture = timetableViewModel::updateSelectedLecture, + onSearchTextChanged = timetableViewModel::updateSearchText, + onClickLecture = timetableViewModel::updateLectureEvent ) }, sheetBackgroundColor = Color.White, @@ -92,13 +158,37 @@ fun TimetableScreen( else sheetState.collapse() } }, - onSemesterTextChanged = timetableViewModel::loadLectures + onSemesterTextChanged = timetableViewModel::updateCurrentSemester ) content( - sheetState, - {} + sheetState = sheetState, + onEventClick = timetableViewModel::updateClickLecture ) } } + + when (state.uiStatus) { + is UiStatus.Failed -> Unit + UiStatus.Init -> Unit + UiStatus.Loading -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator(modifier = Modifier.size(50.dp)) + } + } + + UiStatus.Success -> Unit + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun BackHandler(sheetState: BottomSheetState) { + val scope = rememberCoroutineScope() + + BackHandler(enabled = sheetState.isExpanded) { + scope.launch { + sheetState.collapse() + } + } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index fc7061b19..8db533e1a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -3,13 +3,20 @@ package `in`.koreatech.koin.ui.timetablev2.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.common.UiStatus +import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.domain.usecase.timetable.GetDepartmentsUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase +import `in`.koreatech.koin.domain.usecase.timetable.GetTimetablesUseCase +import `in`.koreatech.koin.domain.usecase.timetable.UpdateLectureUseCase +import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.TimetableState +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost @@ -23,43 +30,230 @@ class TimetableViewModel @Inject constructor( private val getSemesterUseCase: GetSemesterUseCase, private val getLecturesUseCase: GetLecturesUseCase, private val getDepartmentsUseCase: GetDepartmentsUseCase, + private val getTimetablesUseCase: GetTimetablesUseCase, + private val updateLectureUseCase: UpdateLectureUseCase, ) : ContainerHost, ViewModel() { override val container: Container = container(TimetableState()) - fun loadSemesters() = intent { - viewModelScope.launch { - getSemesterUseCase.invoke().let { - reduce { state.copy(semesters = it) } + init { + observeSearchTextAndDepartments() + } + + private fun observeSearchTextAndDepartments() { + intent { + combine( + container.stateFlow.map { it.searchText }, + container.stateFlow.map { it.currentDepartments } + ) { searchText, currentDepartments -> + Pair(searchText, currentDepartments) + }.collect { (searchText, currentDepartments) -> + updateLectures(searchText, currentDepartments) + } + } + } + + private fun updateLectures(searchText: String, currentDepartments: List) = intent { + reduce { + if (searchText.isBlank() && currentDepartments.isEmpty()) { + state.copy(lectures = state._lectures) + } else if (currentDepartments.isEmpty()) { + state.copy( + lectures = state._lectures.filter { lecture -> + lecture.doesMatchSearchQuery(searchText) + } + ) + } else { + state.copy( + lectures = state._lectures.filter { lecture -> + lecture.doesMatchDepartmentSearchQuery(currentDepartments.map { it.name }) && + (searchText.isBlank() || lecture.doesMatchSearchQuery(searchText)) + } + ) } } } - fun loadLectures(semester: String) = intent { + fun clear() = intent { + reduce { + state.copy( + selectedLecture = Lecture(), + lectureEvents = emptyList(), + clickLecture = Lecture() + ) + } + } + + fun clearSelectedDepartments() = intent { + reduce { state.copy(selectedDepartments = emptyList()) } + } + + fun loadSemesters() = intent { viewModelScope.launch { - getLecturesUseCase.invoke(semester).let { - reduce { state.copy(lectures = it) } + getSemesterUseCase().let { + reduce { state.copy(semesters = it) } } } } fun loadDepartments() = intent { viewModelScope.launch { - getDepartmentsUseCase.invoke().let { + getDepartmentsUseCase().let { reduce { state.copy(departments = it) } } } } + fun openAddLectureDialog() = intent { + reduce { state.copy(isAddLectureDialogVisible = true) } + } + + fun openRemoveLectureDialog() = intent { + reduce { state.copy(isRemoveLectureDialogVisible = true) } + } + + fun openDepartmentDialog() = intent { + reduce { state.copy(isDepartmentDialogVisible = true) } + } + + fun closeAddLectureDialog() = intent { + reduce { state.copy(isAddLectureDialogVisible = false) } + } + + fun closeRemoveLectureDialog() = intent { + reduce { state.copy(isRemoveLectureDialogVisible = false) } + } + + fun closeDepartmentDialog() = intent { + reduce { state.copy(isDepartmentDialogVisible = false) } + } + + fun updateIsAnonymous(isAnonymous: Boolean) = intent { + reduce { state.copy(isAnonymous = isAnonymous) } + } + fun updateSearchText(text: String) = intent { reduce { state.copy(searchText = text) } } - fun updateLectureEvent() { + fun updateLectureEvent(timetableEvents: List) = intent { + reduce { state.copy(lectureEvents = timetableEvents) } + } + fun updateClickLecture(event: TimetableEvent) = intent { + val updatedLecture = + state.timetableEvents.filter { it.id == event.id }.getOrElse(0) { Lecture() } + reduce { + state.copy( + clickLecture = updatedLecture, + isRemoveLectureDialogVisible = true + ) + } } fun updateSelectedLecture(lecture: Lecture) = intent { - reduce { state.copy(selectedLecture = lecture) } + if (lecture == Lecture()) { + updateLectureEvent(emptyList()) + } + reduce { state.copy(selectedLecture = lecture) } + } + + fun updateSelectedDepartment(department: Department) = intent { + val updatedDepartments = state.selectedDepartments.toMutableList() + if (state.selectedDepartments.contains(department)) { + updatedDepartments.remove(department) + } else { + updatedDepartments.add(department) + } + + reduce { state.copy(selectedDepartments = updatedDepartments) } + } + + fun updateCurrentDepartment(departments: List) = intent { + reduce { + state.copy( + currentDepartments = departments, + isDepartmentDialogVisible = false + ) + } + } + + fun updateCurrentSemester(semester: Semester) = intent { + clear() + reduce { state.copy(uiStatus = UiStatus.Loading) } + viewModelScope.launch { + if (state.isAnonymous) { + val lectures = getLecturesUseCase(semester.semester) + val events = getTimetablesUseCase(semester.semester) + + reduce { + state.copy( + uiStatus = UiStatus.Success, + lectures = lectures, + _lectures = lectures, + timetableEvents = events, + currentSemester = semester + ) + } + } + } + } + + fun addLecture(semester: Semester, lecture: Lecture) = intent { + val updateTimetableEvents = state.timetableEvents.toMutableList() + updateTimetableEvents.add(lecture) + reduce { + state.copy( + timetableEvents = updateTimetableEvents, + clickLecture = Lecture(), + selectedLecture = Lecture(), + lectureEvents = emptyList() + ) + } + + viewModelScope.launch { + if (state.isAnonymous) { + updateLectureUseCase(semester.semester, updateTimetableEvents) + } + } + } + + fun removeLecture(semester: Semester, lecture: Lecture) = intent { + clear() + val updateTimetableEvents = state.timetableEvents.toMutableList() + updateTimetableEvents.remove(lecture) + reduce { + state.copy( + timetableEvents = updateTimetableEvents, + isRemoveLectureDialogVisible = false + ) + } + + viewModelScope.launch { + if (state.isAnonymous) { + updateLectureUseCase(semester.semester, updateTimetableEvents) + } + } + } + + fun removeDepartment(department: Department) = intent { + val updateDepartments = state.currentDepartments.toMutableList() + updateDepartments.remove(department) + + reduce { + state.copy( + currentDepartments = updateDepartments, + selectedDepartments = updateDepartments + ) + } + } + + fun duplicateLecture(lecture: Lecture) = intent { + lecture.classTime.forEach { time -> + state.timetableEvents.filter { it.classTime.contains(time) }.forEach { lecture -> + removeLecture(state.currentSemester, lecture) + } + } + addLecture(state.currentSemester, lecture) } } diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt index 8ce1d7060..fc2e7700b 100644 --- a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.util.ext import androidx.compose.ui.graphics.Color +import `in`.koreatech.koin.compose.ui.ColorPrimary import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent import java.time.LocalTime @@ -14,7 +15,8 @@ fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List Date: Sun, 16 Jun 2024 00:54:01 +0900 Subject: [PATCH 06/28] [MOD] anonymous logic in datasource --- .../repository/TimetableRepositoryImpl.kt | 26 ++++++++++------ .../domain/repository/TimetableRepository.kt | 4 +-- .../usecase/timetable/GetTimetablesUseCase.kt | 4 +-- .../usecase/timetable/UpdateLectureUseCase.kt | 5 ++-- .../viewmodel/TimetableViewModel.kt | 30 ++++++++----------- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index c1a4f0223..2678fee7f 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -20,21 +20,29 @@ class TimetableRepositoryImpl @Inject constructor( private val timetableRemoteDataSource: TimetableRemoteDataSource, private val timetableLocalDataSource: TimetableLocalDataSource, ) : TimetableRepository { - override suspend fun getTimetables(key: String): List = + override suspend fun getTimetables(key: String, isAnonymous: Boolean): List = try { - val lectureString = timetableLocalDataSource.getString(key).first() - val lectureType = object : TypeToken>() {}.type - val gson = Gson() - val updateLectures = - gson.fromJson>(lectureString, lectureType).orEmpty() - updateLectures + if (isAnonymous) { + val lectureString = timetableLocalDataSource.getString(key).first() + val lectureType = object : TypeToken>() {}.type + val gson = Gson() + val updateLectures = + gson.fromJson>(lectureString, lectureType).orEmpty() + updateLectures + } else { + emptyList() + } } catch (e: Exception) { emptyList() } - override suspend fun putString(key: String, value: T) { + override suspend fun putString(key: String, isAnonymous: Boolean, value: T) { try { - timetableLocalDataSource.putString(key, Gson().toJson(value)) + if (isAnonymous) { + timetableLocalDataSource.putString(key, Gson().toJson(value)) + } else { + + } } catch (e: Exception) { } } diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 7c7c223d5..298dafa7f 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -6,8 +6,8 @@ import `in`.koreatech.koin.domain.model.timetable.Semester import kotlinx.coroutines.flow.Flow interface TimetableRepository { - suspend fun getTimetables(key: String): List - suspend fun putString(key: String, value: T) + suspend fun getTimetables(key: String, isAnonymous: Boolean): List + suspend fun putString(key: String, isAnonymous: Boolean, value: T) suspend fun loadSemesters(): List suspend fun loadDepartments(): List suspend fun loadLectures(semester: String): List diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt index 7f8ea464e..65b4b9528 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetTimetablesUseCase.kt @@ -2,7 +2,6 @@ package `in`.koreatech.koin.domain.usecase.timetable import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.repository.TimetableRepository -import kotlinx.coroutines.flow.Flow import javax.inject.Inject class GetTimetablesUseCase @Inject constructor( @@ -10,5 +9,6 @@ class GetTimetablesUseCase @Inject constructor( ) { suspend operator fun invoke( semester: String, - ): List = timetableRepository.getTimetables(semester) + isAnonymous: Boolean, + ): List = timetableRepository.getTimetables(semester, isAnonymous) } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt index 57e1b11fd..2eee4e062 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt @@ -9,8 +9,9 @@ class UpdateLectureUseCase @Inject constructor( ) { suspend operator fun invoke( semester: String, - lectures: List + isAnonymous: Boolean, + lectures: List, ) { - timetableRepository.putString(semester, lectures) + timetableRepository.putString(semester, isAnonymous, lectures) } } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index 8db533e1a..84cf9b1ec 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -182,19 +182,17 @@ class TimetableViewModel @Inject constructor( clear() reduce { state.copy(uiStatus = UiStatus.Loading) } viewModelScope.launch { - if (state.isAnonymous) { - val lectures = getLecturesUseCase(semester.semester) - val events = getTimetablesUseCase(semester.semester) - - reduce { - state.copy( - uiStatus = UiStatus.Success, - lectures = lectures, - _lectures = lectures, - timetableEvents = events, - currentSemester = semester - ) - } + val lectures = getLecturesUseCase(semester.semester) + val events = getTimetablesUseCase(semester.semester, state.isAnonymous) + + reduce { + state.copy( + uiStatus = UiStatus.Success, + lectures = lectures, + _lectures = lectures, + timetableEvents = events, + currentSemester = semester + ) } } } @@ -213,7 +211,7 @@ class TimetableViewModel @Inject constructor( viewModelScope.launch { if (state.isAnonymous) { - updateLectureUseCase(semester.semester, updateTimetableEvents) + updateLectureUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) } } } @@ -230,9 +228,7 @@ class TimetableViewModel @Inject constructor( } viewModelScope.launch { - if (state.isAnonymous) { - updateLectureUseCase(semester.semester, updateTimetableEvents) - } + updateLectureUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) } } From 09d2f95c595a213e1db8aa7ad027973ea84ab83e Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 02:10:25 +0900 Subject: [PATCH 07/28] [ADD] user timetables --- .../koin/data/api/auth/TimetableAuthApi.kt | 28 +++++++++++++ .../data/di/source/RemoteDataSourceModule.kt | 4 +- .../koin/data/mapper/TimetableMapper.kt | 41 +++++++++++++++++++ .../repository/TimetableRepositoryImpl.kt | 22 +++++++--- .../timetable/TimetablesLectureRequest.kt | 32 +++++++++++++++ .../request/timetable/TimetablesRequest.kt | 10 +++++ .../timetable/TimetablesLectureResponse.kt | 32 +++++++++++++++ .../response/timetable/TimetablesResponse.kt | 14 +++++++ .../remote/TimetableRemoteDataSource.kt | 15 +++++++ .../domain/repository/TimetableRepository.kt | 3 +- .../timetable/RemoveTimetablesUseCase.kt | 10 +++++ ...eUseCase.kt => UpdateTimetablesUseCase.kt} | 4 +- .../koin/di/network/AuthNetworkModule.kt | 9 ++++ .../viewmodel/TimetableViewModel.kt | 20 +++++---- 14 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesLectureRequest.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesRequest.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesLectureResponse.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesResponse.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/RemoveTimetablesUseCase.kt rename domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/{UpdateLectureUseCase.kt => UpdateTimetablesUseCase.kt} (75%) diff --git a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt new file mode 100644 index 000000000..2707130fe --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt @@ -0,0 +1,28 @@ +package `in`.koreatech.koin.data.api.auth + +import `in`.koreatech.koin.data.constant.URLConstant.TIMETABLE +import `in`.koreatech.koin.data.constant.URLConstant.TIMETABLES +import `in`.koreatech.koin.data.request.timetable.TimetablesRequest +import `in`.koreatech.koin.data.response.timetable.TimetablesResponse +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + +interface TimetableAuthApi { + @GET(TIMETABLES) + suspend fun getTimetables( + @Query("semester") semester: String, + ): TimetablesResponse + + @POST(TIMETABLES) + suspend fun postTimetables( + @Body timetables: TimetablesRequest + ) + + @DELETE(TIMETABLE) + suspend fun deleteTimetables( + @Query("id") id: Int + ) +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt index b592a304f..6e95eb796 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt @@ -5,6 +5,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import `in`.koreatech.koin.data.api.* +import `in`.koreatech.koin.data.api.auth.TimetableAuthApi import `in`.koreatech.koin.data.api.auth.UserAuthApi import `in`.koreatech.koin.data.source.remote.* import javax.inject.Singleton @@ -105,7 +106,8 @@ object RemoteDataSourceModule { @Singleton fun providesTimetableRemoteDataSource( timetableApi: TimetableApi, + timetableAuthApi: TimetableAuthApi ): TimetableRemoteDataSource { - return TimetableRemoteDataSource((timetableApi)) + return TimetableRemoteDataSource(timetableApi, timetableAuthApi) } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt index a1b0fdaff..5564f7686 100644 --- a/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt +++ b/data/src/main/java/in/koreatech/koin/data/mapper/TimetableMapper.kt @@ -1,8 +1,12 @@ package `in`.koreatech.koin.data.mapper +import com.google.gson.annotations.SerializedName +import `in`.koreatech.koin.data.request.timetable.TimetablesLectureRequest +import `in`.koreatech.koin.data.request.timetable.TimetablesRequest import `in`.koreatech.koin.data.response.timetable.DepartmentResponse import `in`.koreatech.koin.data.response.timetable.LectureResponse import `in`.koreatech.koin.data.response.timetable.SemesterResponse +import `in`.koreatech.koin.data.response.timetable.TimetablesLectureResponse import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester @@ -28,7 +32,44 @@ fun LectureResponse.toLecture() = Lecture( classTime = this.classTime, ) +fun TimetablesLectureResponse.toLecture() = Lecture( + id = this.id ?: 0, + code = this.code ?: "", + name = this.name ?: "", + grades = this.grades ?: "", + lectureClass = this.lectureClass ?: "", + regularNumber = this.regularNumber ?: "", + department = this.department ?: "", + target = this.target ?: "", + professor = this.professor ?: "", + isEnglish = "", + designScore = this.designScore ?: "", + isElearning = "", + classTime = this.classTime.orEmpty(), +) + fun DepartmentResponse.toDepartment() = Department( id = this.id, name = this.name +) + +fun List.toTimetablesRequest(semester: String) = TimetablesRequest( + timetable = this.map { it.toTimetablesLectureResponse() }, + semester = semester +) + +fun Lecture.toTimetablesLectureResponse() = TimetablesLectureRequest( + id = this.id, + code = this.code, + name = this.name, + grades = this.grades, + lectureClass = this.lectureClass, + regularNumber = this.regularNumber, + department = this.department, + target = this.target, + professor = this.professor, + designScore = this.designScore, + classPlace = "", + memo = "", + classTime = this.classTime ) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 2678fee7f..6d9c5d5e2 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -5,15 +5,14 @@ import com.google.gson.reflect.TypeToken import `in`.koreatech.koin.data.mapper.toDepartment import `in`.koreatech.koin.data.mapper.toLecture import `in`.koreatech.koin.data.mapper.toSemester +import `in`.koreatech.koin.data.mapper.toTimetablesRequest import `in`.koreatech.koin.data.source.local.TimetableLocalDataSource import `in`.koreatech.koin.data.source.remote.TimetableRemoteDataSource import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.domain.repository.TimetableRepository -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow import javax.inject.Inject class TimetableRepositoryImpl @Inject constructor( @@ -30,20 +29,31 @@ class TimetableRepositoryImpl @Inject constructor( gson.fromJson>(lectureString, lectureType).orEmpty() updateLectures } else { - emptyList() + timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() } + .orEmpty() } } catch (e: Exception) { emptyList() } - override suspend fun putString(key: String, isAnonymous: Boolean, value: T) { + override suspend fun updateTimetables(key: String, isAnonymous: Boolean, value: List) { try { if (isAnonymous) { timetableLocalDataSource.putString(key, Gson().toJson(value)) } else { - + timetableRemoteDataSource.updateTimetables(value.toTimetablesRequest(key)) } - } catch (e: Exception) { } + } catch (e: Exception) { + e.message + } + } + + override suspend fun removeTimetables(id: Int) { + try { + timetableRemoteDataSource.deleteTimetables(id) + } catch (e: Exception) { + e.message + } } override suspend fun loadSemesters(): List = diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesLectureRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesLectureRequest.kt new file mode 100644 index 000000000..20f17c81e --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesLectureRequest.kt @@ -0,0 +1,32 @@ +package `in`.koreatech.koin.data.request.timetable + +import com.google.gson.annotations.SerializedName + +data class TimetablesLectureRequest( + @SerializedName("id") + var id: Int? = 0, + @SerializedName("code") // 과목코드 : BSM314 + val code: String? = "", + @SerializedName("class_title") // 강의명 : 물리적 사고 + val name: String? = "", + @SerializedName("grades") // 학년 : 3 + val grades: String? = "", + @SerializedName("lecture_class") // 분반 : 01 + val lectureClass: String? = "", + @SerializedName("regular_number") // 수강인원 : 0~40 + val regularNumber: String? = "", + @SerializedName("department") // 학부 : 교양학부 + val department: String? = "", + @SerializedName("target") // 대상 : 기공1 + val target: String? = "", + @SerializedName("professor") // 교수 : 이미리 + val professor: String? = "", + @SerializedName("design_score") // 설계학점 : 0 + val designScore: String? = "", + @SerializedName("class_place") + val classPlace: String? = "", + @SerializedName("memo") // 설계학점 : 0 + val memo: String? = "", + @SerializedName("class_time") // 강의시간 : 0~429 + val classTime: List? = emptyList(), +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesRequest.kt new file mode 100644 index 000000000..013ba6ebb --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetablesRequest.kt @@ -0,0 +1,10 @@ +package `in`.koreatech.koin.data.request.timetable + +import com.google.gson.annotations.SerializedName + +data class TimetablesRequest( + @SerializedName("timetable") + val timetable: List, + @SerializedName("semester") + val semester: String, +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesLectureResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesLectureResponse.kt new file mode 100644 index 000000000..a7597fd06 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesLectureResponse.kt @@ -0,0 +1,32 @@ +package `in`.koreatech.koin.data.response.timetable + +import com.google.gson.annotations.SerializedName + +data class TimetablesLectureResponse( + @SerializedName("id") + var id: Int? = 0, + @SerializedName("code") // 과목코드 : BSM314 + val code: String? = "", + @SerializedName("class_title") // 강의명 : 물리적 사고 + val name: String? = "", + @SerializedName("grades") // 학년 : 3 + val grades: String? = "", + @SerializedName("lecture_class") // 분반 : 01 + val lectureClass: String? = "", + @SerializedName("regular_number") // 수강인원 : 0~40 + val regularNumber: String? = "", + @SerializedName("department") // 학부 : 교양학부 + val department: String? = "", + @SerializedName("target") // 대상 : 기공1 + val target: String? = "", + @SerializedName("professor") // 교수 : 이미리 + val professor: String? = "", + @SerializedName("design_score") // 설계학점 : 0 + val designScore: String? = "", + @SerializedName("class_place") + val classPlace: String? = "", + @SerializedName("memo") // 설계학점 : 0 + val memo: String? = "", + @SerializedName("class_time") // 강의시간 : 0~429 + val classTime: List? = emptyList(), +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesResponse.kt new file mode 100644 index 000000000..605425507 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/timetable/TimetablesResponse.kt @@ -0,0 +1,14 @@ +package `in`.koreatech.koin.data.response.timetable + +import com.google.gson.annotations.SerializedName + +data class TimetablesResponse( + @SerializedName("semester") + val semester: String? = "", + @SerializedName("timetable") + val timetables: List? = emptyList(), + @SerializedName("grades") + val grades: Int? = 0, + @SerializedName("total_grades") + val totalGrades: Int? = 0, +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt index 125d5d49b..c9a1122fa 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt @@ -1,16 +1,31 @@ package `in`.koreatech.koin.data.source.remote import `in`.koreatech.koin.data.api.TimetableApi +import `in`.koreatech.koin.data.api.auth.TimetableAuthApi +import `in`.koreatech.koin.data.request.timetable.TimetablesRequest import `in`.koreatech.koin.data.response.timetable.LectureResponse import `in`.koreatech.koin.data.response.timetable.SemesterResponse +import `in`.koreatech.koin.data.response.timetable.TimetablesResponse import javax.inject.Inject class TimetableRemoteDataSource @Inject constructor( private val timetableApi: TimetableApi, + private val timetableAuthApi: TimetableAuthApi, ) { + suspend fun loadTimetables(semester: String): TimetablesResponse = + timetableAuthApi.getTimetables(semester) + suspend fun loadSemesters(): List = timetableApi.getSemesters() suspend fun loadLectures(semester: String): List = timetableApi.getLectures(semester) + + suspend fun updateTimetables(timetablesRequest: TimetablesRequest) { + timetableAuthApi.postTimetables(timetablesRequest) + } + + suspend fun deleteTimetables(id: Int) { + timetableAuthApi.deleteTimetables(id) + } } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 298dafa7f..654bab6f8 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -7,7 +7,8 @@ import kotlinx.coroutines.flow.Flow interface TimetableRepository { suspend fun getTimetables(key: String, isAnonymous: Boolean): List - suspend fun putString(key: String, isAnonymous: Boolean, value: T) + suspend fun updateTimetables(key: String, isAnonymous: Boolean, value: List) + suspend fun removeTimetables(id: Int) suspend fun loadSemesters(): List suspend fun loadDepartments(): List suspend fun loadLectures(semester: String): List diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/RemoveTimetablesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/RemoveTimetablesUseCase.kt new file mode 100644 index 000000000..ab322b140 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/RemoveTimetablesUseCase.kt @@ -0,0 +1,10 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class RemoveTimetablesUseCase @Inject constructor( + private val timetableRepository: TimetableRepository, +) { + suspend operator fun invoke(id: Int) = timetableRepository.removeTimetables(id) +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetablesUseCase.kt similarity index 75% rename from domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt rename to domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetablesUseCase.kt index 2eee4e062..b058059a5 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateLectureUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetablesUseCase.kt @@ -4,7 +4,7 @@ import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.repository.TimetableRepository import javax.inject.Inject -class UpdateLectureUseCase @Inject constructor( +class UpdateTimetablesUseCase @Inject constructor( private val timetableRepository: TimetableRepository ) { suspend operator fun invoke( @@ -12,6 +12,6 @@ class UpdateLectureUseCase @Inject constructor( isAnonymous: Boolean, lectures: List, ) { - timetableRepository.putString(semester, isAnonymous, lectures) + timetableRepository.updateTimetables(semester, isAnonymous, lectures) } } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/di/network/AuthNetworkModule.kt b/koin/src/main/java/in/koreatech/koin/di/network/AuthNetworkModule.kt index def9c17e1..1045a318e 100644 --- a/koin/src/main/java/in/koreatech/koin/di/network/AuthNetworkModule.kt +++ b/koin/src/main/java/in/koreatech/koin/di/network/AuthNetworkModule.kt @@ -14,6 +14,7 @@ import `in`.koreatech.koin.core.qualifier.ServerUrl import `in`.koreatech.koin.data.api.PreSignedUrlApi import `in`.koreatech.koin.data.api.UploadUrlApi import `in`.koreatech.koin.data.api.UserApi +import `in`.koreatech.koin.data.api.auth.TimetableAuthApi import `in`.koreatech.koin.data.api.auth.UserAuthApi import `in`.koreatech.koin.data.source.local.TokenLocalDataSource import `in`.koreatech.koin.domain.usecase.user.DeleteUserRefreshTokenUseCase @@ -101,6 +102,14 @@ object AuthNetworkModule { ) : UserAuthApi { return retrofit.create(UserAuthApi::class.java) } + + @Provides + @Singleton + fun providesTimetableAuthApi( + @Auth retrofit: Retrofit + ): TimetableAuthApi { + return retrofit.create(TimetableAuthApi::class.java) + } } @Module diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index 84cf9b1ec..b18f11073 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -11,7 +11,8 @@ import `in`.koreatech.koin.domain.usecase.timetable.GetDepartmentsUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetTimetablesUseCase -import `in`.koreatech.koin.domain.usecase.timetable.UpdateLectureUseCase +import `in`.koreatech.koin.domain.usecase.timetable.RemoveTimetablesUseCase +import `in`.koreatech.koin.domain.usecase.timetable.UpdateTimetablesUseCase import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.TimetableState @@ -31,7 +32,8 @@ class TimetableViewModel @Inject constructor( private val getLecturesUseCase: GetLecturesUseCase, private val getDepartmentsUseCase: GetDepartmentsUseCase, private val getTimetablesUseCase: GetTimetablesUseCase, - private val updateLectureUseCase: UpdateLectureUseCase, + private val updateTimetablesUseCase: UpdateTimetablesUseCase, + private val removeTimetablesUseCase: RemoveTimetablesUseCase, ) : ContainerHost, ViewModel() { override val container: Container = container(TimetableState()) @@ -183,14 +185,14 @@ class TimetableViewModel @Inject constructor( reduce { state.copy(uiStatus = UiStatus.Loading) } viewModelScope.launch { val lectures = getLecturesUseCase(semester.semester) - val events = getTimetablesUseCase(semester.semester, state.isAnonymous) + val timetables = getTimetablesUseCase(semester.semester, state.isAnonymous) reduce { state.copy( uiStatus = UiStatus.Success, lectures = lectures, _lectures = lectures, - timetableEvents = events, + timetableEvents = timetables, currentSemester = semester ) } @@ -210,9 +212,7 @@ class TimetableViewModel @Inject constructor( } viewModelScope.launch { - if (state.isAnonymous) { - updateLectureUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) - } + updateTimetablesUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) } } @@ -228,7 +228,11 @@ class TimetableViewModel @Inject constructor( } viewModelScope.launch { - updateLectureUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) + if (state.isAnonymous) { + updateTimetablesUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) + } else { + removeTimetablesUseCase(lecture.id) + } } } From ccbaff52ed3f86674a743975bd14dab914f9ad19 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 02:16:52 +0900 Subject: [PATCH 08/28] [MOD] bottomsheet roundedshape --- .../in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 853c4a341..9dbde1fb4 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue @@ -144,6 +145,7 @@ fun TimetableScreen( }, sheetBackgroundColor = Color.White, sheetPeekHeight = 0.dp, + sheetShape = RoundedCornerShape(16.dp) ) { Column( modifier = Modifier.fillMaxHeight() From 6ae63abc6f17c19407117544f79f4ede42d76fb2 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 02:23:32 +0900 Subject: [PATCH 09/28] [ADD] lecture color palette --- .../in/koreatech/koin/compose/ui/Color.kt | 45 +++++++++++++++++++ .../koin/util/ext/TimetableExtensions.kt | 4 +- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt b/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt index 94c0825b6..cf1904e8b 100644 --- a/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt +++ b/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt @@ -6,3 +6,48 @@ val ColorPrimary = Color(0xFF175c8e) val ColorSecondary = Color(0xFFF7941E) val ColorMain400 = Color(0xFF4590BB) val ColorPrimaryMain400_ALPAH10 = Color(0x1a4590BB) + +val basicColors = listOf( + Color(0xFFBFC8D7), + Color(0xFFE2D2D2), + Color(0xFFE3E3B4), + Color(0xFFA2B59F), + Color(0xFFA8B0BD), + Color(0xFFC9BBBB), + Color(0xFFC9C9A0), + Color(0xFF8B9C89), + Color(0xFF9198A3), + Color(0xFFB0A3A3), + Color(0xFFB0B08C), + Color(0xFF748272), +) + +val ceramicColors = listOf( + Color(0xFFE1E5E8), + Color(0xFFC1BFCD), + Color(0xFFB2D1E4), + Color(0xFF8ABAE0), + Color(0xFFA6AAAD), + Color(0xFFA8A6B2), + Color(0xFF9DB9C9), + Color(0xFF7BA5C7), + Color(0xFF8E9194), + Color(0xFF908F99), + Color(0xFF89A2B0), + Color(0xFF6B90AD), +) + +val macaroonColors = listOf( + Color(0xFFF8DE9D), + Color(0xFFFFCECA), + Color(0xFFFFD0A7), + Color(0xFFE098AE), + Color(0xFFDEC78C), + Color(0xFFE5B9B6), + Color(0xFFE5BB96), + Color(0xFFC7879B), + Color(0xFFC4B07C), + Color(0xFFCCA5A2), + Color(0xFFCCA786), + Color(0xFFAD7686), +) \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt index fc2e7700b..baf482fd7 100644 --- a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt @@ -1,7 +1,7 @@ package `in`.koreatech.koin.util.ext import androidx.compose.ui.graphics.Color -import `in`.koreatech.koin.compose.ui.ColorPrimary +import `in`.koreatech.koin.compose.ui.basicColors import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent import java.time.LocalTime @@ -16,7 +16,7 @@ fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List Date: Sun, 16 Jun 2024 02:39:53 +0900 Subject: [PATCH 10/28] [MOD] userState in timetable navi trail --- .../ui/navigation/KoinNavigationDrawerActivity.kt | 15 +++++++++------ .../koin/ui/timetablev2/TimetableActivity.kt | 1 - .../koin/ui/timetablev2/view/TimetableScreen.kt | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index d8cfac7f9..54f6ee3eb 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -6,7 +6,6 @@ import android.content.pm.PackageManager import android.graphics.Typeface import android.os.Build import android.os.Bundle -import android.util.Log import android.view.MenuItem import android.view.View import android.widget.Button @@ -260,9 +259,12 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), nameTextview.text = user.name when (menuState) { MenuState.Main, MenuState.Notification -> { - if (!checkMainPermission()) requestMainPermissionLauncher.launch(MAIN_REQUIRED_PERMISSION) + if (!checkMainPermission()) requestMainPermissionLauncher.launch( + MAIN_REQUIRED_PERMISSION + ) koinNavigationDrawerViewModel.updateDeviceToken() } + else -> Unit } } @@ -426,16 +428,17 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), * @TEST */ private fun goToTimetableActivityV2(user: User?, isAnonymous: Boolean) { - if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) - } else { - val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { + val intent = + Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { if (user == null || isAnonymous) { putExtra("isAnonymous", true) } else { putExtra("isAnonymous", false) } } + if (menuState != MenuState.Main) { + goToActivityFinish(intent) + } else { startActivity(intent) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index c983f6083..f8331e6cf 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -1,7 +1,6 @@ package `in`.koreatech.koin.ui.timetablev2 import android.os.Bundle -import android.util.Log import androidx.compose.material.BottomSheetState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 9dbde1fb4..851c09eac 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -109,7 +109,8 @@ fun TimetableScreen( timetableViewModel.addLecture(state.currentSemester, lecture) } timetableViewModel.closeAddLectureDialog() -// sendBroadcastReceiver(currentSemester) + // TODO : update widget + // sendBroadcastReceiver(currentSemester) } ) From fb584aa4916b88fed4d3d9c43ce1debd9f28fae4 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 11:12:14 +0900 Subject: [PATCH 11/28] [MOD] user add timetables --- .../viewmodel/TimetableViewModel.kt | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index b18f11073..a5cdfb9e0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -1,5 +1,6 @@ package `in`.koreatech.koin.ui.timetablev2.viewmodel +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -202,17 +203,26 @@ class TimetableViewModel @Inject constructor( fun addLecture(semester: Semester, lecture: Lecture) = intent { val updateTimetableEvents = state.timetableEvents.toMutableList() updateTimetableEvents.add(lecture) - reduce { - state.copy( - timetableEvents = updateTimetableEvents, - clickLecture = Lecture(), - selectedLecture = Lecture(), - lectureEvents = emptyList() - ) - } + reduce { state.copy(uiStatus = UiStatus.Loading) } viewModelScope.launch { - updateTimetablesUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) + if (state.isAnonymous) { + updateTimetablesUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) + } else { + updateTimetablesUseCase(semester.semester, state.isAnonymous, listOf(lecture)) + } + val timetables = getTimetablesUseCase(semester.semester, state.isAnonymous) + reduce { + Log.e("aaa", "add timetables : $timetables") + state.copy( + uiStatus = UiStatus.Success, + timetableEvents = timetables, + currentSemester = semester, + lectureEvents = emptyList(), + clickLecture = Lecture(), + selectedLecture = Lecture() + ) + } } } @@ -220,19 +230,22 @@ class TimetableViewModel @Inject constructor( clear() val updateTimetableEvents = state.timetableEvents.toMutableList() updateTimetableEvents.remove(lecture) - reduce { - state.copy( - timetableEvents = updateTimetableEvents, - isRemoveLectureDialogVisible = false - ) - } + reduce { state.copy(uiStatus = UiStatus.Loading, isRemoveLectureDialogVisible = false) } viewModelScope.launch { if (state.isAnonymous) { updateTimetablesUseCase(semester.semester, state.isAnonymous, updateTimetableEvents) } else { removeTimetablesUseCase(lecture.id) } + + val timetables = getTimetablesUseCase(semester.semester, state.isAnonymous) + reduce { + state.copy( + uiStatus = UiStatus.Success, + timetableEvents = timetables, + ) + } } } @@ -242,6 +255,7 @@ class TimetableViewModel @Inject constructor( reduce { state.copy( + uiStatus = UiStatus.Success, currentDepartments = updateDepartments, selectedDepartments = updateDepartments ) From 431b509723dd42c71adb9460694bbef31b50d1f5 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 12:08:44 +0900 Subject: [PATCH 12/28] [UI] timetable --- .../koin/data/repository/TimetableRepositoryImpl.kt | 3 +-- .../koin/ui/timetablev2/component/LectureItem.kt | 13 +++++++++---- .../koin/ui/timetablev2/view/TimetableEventTime.kt | 9 +++++---- .../koin/ui/timetablev2/view/TimetableScreen.kt | 4 +--- .../ui/timetablev2/viewmodel/TimetableViewModel.kt | 2 -- .../koreatech/koin/util/ext/TimetableExtensions.kt | 3 ++- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 6d9c5d5e2..0a4f086c1 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -29,8 +29,7 @@ class TimetableRepositoryImpl @Inject constructor( gson.fromJson>(lectureString, lectureType).orEmpty() updateLectures } else { - timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() } - .orEmpty() + timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() }.orEmpty() } } catch (e: Exception) { emptyList() diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt index 8a72912ba..59b106a0f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/LectureItem.kt @@ -8,12 +8,14 @@ import androidx.compose.foundation.layout.Row 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.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card +import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -59,7 +61,6 @@ fun LectureItem( ) .padding( horizontal = 12.dp, - vertical = 4.dp ) .background( color = if (isSelected) { @@ -88,7 +89,7 @@ fun LectureItem( Spacer(modifier = Modifier.width(6.dp)) events.forEachIndexed { index, event -> Text( - text = (if (index != 0) "/" else "") + "${event.start} ~ ${event.end}", + text = (if (index != 0) "/" else "") + "${event.start}-${event.end}", fontSize = 12.sp, color = Color.Black ) @@ -97,7 +98,8 @@ fun LectureItem( Text( text = lecture.formatDescription(), fontSize = 12.sp, - color = Color.Black + color = Color.Black, + lineHeight = 14.sp ) if (isSelected) { Card( @@ -117,6 +119,9 @@ fun LectureItem( ) } } + + Spacer(modifier = Modifier.height(4.dp)) + Divider(modifier = Modifier.height(1.dp)) } } @@ -128,7 +133,7 @@ private fun LectureItemPreview() { colors = emptyList(), lecture = Lecture( name = "직업능력개발훈련평가", - professor = "우성민", + professor = "우성민 우성민우성민우성민우성민우성민우성민우성민우성민우성민우성민우성민우성민우성민", code = "HRD011", grades = "2", lectureClass = "01", diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt index 47692f84b..4df42c051 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt @@ -51,7 +51,7 @@ fun TimetableEventTime( .padding(4.dp) .clickable { onEventClick(event) } ) { - Divider(color = Color.White, thickness = 1.dp) + if (eventType == TimetableEventType.BASIC) Divider(color = Color.White, thickness = 1.dp) Spacer(modifier = Modifier.height(2.dp)) when (eventType) { TimetableEventType.BASIC -> { @@ -60,13 +60,14 @@ fun TimetableEventTime( + " - " + event.end.format(timetableEventTimeFormatter), fontSize = 8.sp, - color = Color.White + color = Color.White, ) Text( text = event.name, fontSize = 12.sp, fontWeight = FontWeight.Bold, - color = Color.White + color = Color.White, + lineHeight = 12.sp, ) if (event.description != null) { @@ -92,7 +93,7 @@ fun TimetableEventTime( private fun TimetableEventTimePreview() { val sample = TimetableEvent( id = 1, - name = "관희의 수업", + name = "관희의 수업 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", color = Color(0xFFAFBBF2), dayOfWeek = DayOfWeek.FRIDAY, start = LocalTime.of(16, 0), diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 851c09eac..9bb1d7b03 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Text import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material.rememberBottomSheetState import androidx.compose.runtime.Composable @@ -114,9 +115,6 @@ fun TimetableScreen( } ) - /** - * 강의 삭제 모달 - */ LectureRemoveDialog( context = context, visible = state.isRemoveLectureDialogVisible, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index a5cdfb9e0..889bb48c3 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -1,6 +1,5 @@ package `in`.koreatech.koin.ui.timetablev2.viewmodel -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -213,7 +212,6 @@ class TimetableViewModel @Inject constructor( } val timetables = getTimetablesUseCase(semester.semester, state.isAnonymous) reduce { - Log.e("aaa", "add timetables : $timetables") state.copy( uiStatus = UiStatus.Success, timetableEvents = timetables, diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt index baf482fd7..e7814e252 100644 --- a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.util.ext import androidx.compose.ui.graphics.Color +import `in`.koreatech.koin.compose.ui.ColorPrimary import `in`.koreatech.koin.compose.ui.basicColors import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent @@ -16,7 +17,7 @@ fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List= basicColors.size) ColorPrimary else basicColors[index ?: 0], dayOfWeek = key, start = value.firstOrNull() ?: LocalTime.of(0, 0), end = value.lastOrNull()?.plusMinutes(30) ?: LocalTime.of(0, 0), From 5a793cb05d80c0f951f16b3c53ef2f9ba6df469b Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 16 Jun 2024 12:38:57 +0900 Subject: [PATCH 13/28] [MOD] bottomsheetshape padding position & darkmode false --- .../in/koreatech/koin/ui/timetablev2/TimetableActivity.kt | 4 +++- .../in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index f8331e6cf..0f785cb10 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -38,7 +38,9 @@ class TimetableActivity : KoinNavigationDrawerActivity() { val isAnonymous = intent.getBooleanExtra("isAnonymous", true) binding.composeView.setContent { - TimetableTheme { + TimetableTheme( + darkTheme = false + ) { TimetableScreen( isAnonymous = isAnonymous, onSavedImage = { diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 9bb1d7b03..c8d9733f9 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -144,7 +144,7 @@ fun TimetableScreen( }, sheetBackgroundColor = Color.White, sheetPeekHeight = 0.dp, - sheetShape = RoundedCornerShape(16.dp) + sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) ) { Column( modifier = Modifier.fillMaxHeight() From 19fd67ce98102167a7fc80a5174bcd3b2c559650 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Mon, 17 Jun 2024 00:17:49 +0900 Subject: [PATCH 14/28] [ADD] unregister listener solve memory leak --- .../main/java/in/koreatech/koin/ui/splash/SplashActivity.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt index b29b258fd..a1eff8f8d 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt @@ -114,4 +114,9 @@ class SplashActivity : ActivityBase() { firebasePerformanceUtil.stop() } } + + override fun onDestroy() { + loginActivityLauncher.unregister() + super.onDestroy() + } } \ No newline at end of file From 8c72345d1a6513f8b94cecc084c23fc3b36cc9ae Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Mon, 17 Jun 2024 02:23:07 +0900 Subject: [PATCH 15/28] [MOD] bottomsheet height with ime --- koin/src/main/AndroidManifest.xml | 2 +- .../koin/ui/timetablev2/TimetableActivity.kt | 7 +++++++ .../koin/ui/timetablev2/TimetableState.kt | 1 + .../koreatech/koin/ui/timetablev2/TimetableView.kt | 1 + .../koin/ui/timetablev2/view/Timetable.kt | 14 ++++++++++---- .../view/TimetableBottomSheetContent.kt | 7 +++++-- .../koin/ui/timetablev2/view/TimetableScreen.kt | 11 ++++++++++- .../ui/timetablev2/viewmodel/TimetableViewModel.kt | 4 ++++ 8 files changed, 39 insertions(+), 8 deletions(-) diff --git a/koin/src/main/AndroidManifest.xml b/koin/src/main/AndroidManifest.xml index eaf18371e..502020a88 100644 --- a/koin/src/main/AndroidManifest.xml +++ b/koin/src/main/AndroidManifest.xml @@ -47,7 +47,7 @@ android:resource="@color/black" /> 0 + TimetableScreen( isAnonymous = isAnonymous, + isKeyboardVisible = isKeyboardVisible, onSavedImage = { BitmapUtils(this).apply { timetableView?.value?.let { view -> diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt index ace104950..6c1184eab 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableState.kt @@ -8,6 +8,7 @@ import `in`.koreatech.koin.model.timetable.TimetableEvent data class TimetableState( val uiStatus: UiStatus = UiStatus.Init, + val isKeyboardVisible: Boolean = false, val isAnonymous: Boolean = true, val searchText: String = "", val isDepartmentDialogVisible: Boolean = false, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt index 846d7e816..5c2c5b137 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt @@ -32,6 +32,7 @@ class TimetableView @OptIn(ExperimentalMaterialApi::class) val state by viewModel.collectAsState() Timetable( + isKeyboardVisible = state.isKeyboardVisible, events = generateTimetableEvents(state.timetableEvents, emptyList()) , sheetState = sheetState, clickEvent = state.lectureEvents, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt index ddd7797c4..6cf542635 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt @@ -4,8 +4,10 @@ import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -13,12 +15,10 @@ import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import `in`.koreatech.koin.model.timetable.TimetableEvent @@ -28,6 +28,7 @@ import `in`.koreatech.koin.util.ext.pxToDp @OptIn(ExperimentalMaterialApi::class) @Composable fun Timetable( + isKeyboardVisible: Boolean, events: List, modifier: Modifier = Modifier, clickEvent: List = emptyList(), @@ -60,7 +61,12 @@ fun Timetable( bottom = if (sheetState.isExpanded) { if (sheetState.currentValue == BottomSheetValue.Expanded) { if (sheetState.targetValue == BottomSheetValue.Expanded && sheetState.progress == 1f) { - sheetState.requireOffset().pxToDp + if (isKeyboardVisible) { + 500.dp + } else { + 350.dp + } +// sheetState.requireOffset().pxToDp } else { 0.dp } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt index 53de56317..3723c8e01 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt @@ -2,7 +2,6 @@ package `in`.koreatech.koin.ui.timetablev2.view import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -24,6 +23,7 @@ import `in`.koreatech.koin.ui.timetablev2.component.SearchBox @Composable fun TimetableBottomSheetContent( searchText: String, + isKeyboardVisible: Boolean, colors: List, lectures: List, selectedLectures: Lecture, @@ -39,7 +39,9 @@ fun TimetableBottomSheetContent( Column( modifier = modifier .fillMaxWidth() - .fillMaxHeight(0.5f), + .height( + if (isKeyboardVisible) 500.dp else 350.dp + ), ) { SearchBox( modifier = Modifier.padding(8.dp), @@ -81,6 +83,7 @@ fun TimetableBottomSheetContent( @Composable private fun TimetableBottomSheetContentPreview() { TimetableBottomSheetContent( + isKeyboardVisible = false, colors = emptyList(), lectures = emptyList(), selectedLectures = Lecture(), diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index c8d9733f9..9c68aaa44 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -6,9 +6,11 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetScaffold @@ -27,6 +29,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import `in`.koreatech.koin.common.UiStatus @@ -41,6 +45,7 @@ import org.orbitmvi.orbit.compose.collectSideEffect @Composable fun TimetableScreen( isAnonymous: Boolean, + isKeyboardVisible: Boolean, modifier: Modifier = Modifier, context: Context = LocalContext.current, timetableViewModel: TimetableViewModel = viewModel(), @@ -68,11 +73,14 @@ fun TimetableScreen( timetableViewModel.updateIsAnonymous(isAnonymous) } + LaunchedEffect(key1 = isKeyboardVisible) { + timetableViewModel.updateIsKeyboardVisible(isKeyboardVisible) + } + if (state.currentSemester.semester.isBlank() && state.semesters.isNotEmpty()) { timetableViewModel.updateCurrentSemester(state.semesters[0]) } - val scope = rememberCoroutineScope() val sheetState = rememberBottomSheetState( initialValue = BottomSheetValue.Collapsed @@ -130,6 +138,7 @@ fun TimetableScreen( sheetContent = { TimetableBottomSheetContent( searchText = state.searchText, + isKeyboardVisible = state.isKeyboardVisible, colors = emptyList(), lectures = state.lectures, selectedLectures = state.selectedLecture, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index 889bb48c3..3a760edf9 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -130,6 +130,10 @@ class TimetableViewModel @Inject constructor( reduce { state.copy(isDepartmentDialogVisible = false) } } + fun updateIsKeyboardVisible(visible: Boolean) = intent { + reduce { state.copy(isKeyboardVisible = visible) } + } + fun updateIsAnonymous(isAnonymous: Boolean) = intent { reduce { state.copy(isAnonymous = isAnonymous) } } From a082078ae1e109d3f0eb93b7a8c447918f7dd4af Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 20 Jun 2024 00:57:27 +0900 Subject: [PATCH 16/28] [ADD] glance project settings --- gradle/libs.versions.toml | 3 +++ koin/build.gradle | 2 ++ koin/src/main/AndroidManifest.xml | 10 +++++++ .../timetablev2/widget/TimetableAppWidget.kt | 26 +++++++++++++++++++ .../widget/TimetableWidgetReceiver.kt | 23 ++++++++++++++++ .../res/xml/timetablev2_app_widget_info.xml | 10 +++++++ 6 files changed, 74 insertions(+) create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetReceiver.kt create mode 100644 koin/src/main/res/xml/timetablev2_app_widget_info.xml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f8b8605f..279c616e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,6 +54,7 @@ napier = "2.6.1" powerSpinner = "1.2.7" firebaseCrashlyticsBuildtoolsVersion = "2.9.9" datastore = "1.1.1" +glance = "1.0.0" [libraries] androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtxVersion" } @@ -148,6 +149,8 @@ coil-svg = {module = "io.coil-kt:coil-svg", version.ref ="coilVersion"} powerSpinner = {module = "com.github.skydoves:powerspinner", version.ref = "powerSpinner"} +glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" } +glance-material = { group = "androidx.glance", name = "glance-material", version.ref = "glance" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradleVersion" } diff --git a/koin/build.gradle b/koin/build.gradle index cf78c3fc1..414f7c798 100644 --- a/koin/build.gradle +++ b/koin/build.gradle @@ -133,4 +133,6 @@ dependencies { implementation(libs.coil.compose) implementation(libs.androidx.lifecycle.compose) + implementation(libs.glance.appwidget) + implementation(libs.glance.material) } diff --git a/koin/src/main/AndroidManifest.xml b/koin/src/main/AndroidManifest.xml index 502020a88..9dea477a6 100644 --- a/koin/src/main/AndroidManifest.xml +++ b/koin/src/main/AndroidManifest.xml @@ -101,6 +101,16 @@ android:name="firebase_crashlytics_collection_enabled" android:value="false" /> + + + + + + + { + + } + } + } +} \ No newline at end of file diff --git a/koin/src/main/res/xml/timetablev2_app_widget_info.xml b/koin/src/main/res/xml/timetablev2_app_widget_info.xml new file mode 100644 index 000000000..e531ea0dd --- /dev/null +++ b/koin/src/main/res/xml/timetablev2_app_widget_info.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file From 035d9f5a284ad1ea765eec79ce98acf9806dcf23 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 20 Jun 2024 10:33:24 +0900 Subject: [PATCH 17/28] [ADD] update widget --- .../repository/TimetableRepositoryImpl.kt | 11 ++++++- .../source/local/TimetableLocalDataSource.kt | 6 ++++ .../domain/repository/TimetableRepository.kt | 1 + .../timetable/UpdateSemesterUseCase.kt | 12 +++++++ .../koin/ui/timetablev2/TimetableActivity.kt | 18 +++++++++- .../ui/timetablev2/view/TimetableScreen.kt | 22 +++++++------ .../viewmodel/TimetableViewModel.kt | 3 ++ .../timetablev2/widget/TimetableAppWidget.kt | 33 +++++++++++++++++-- .../widget/TimetableWidgetReceiver.kt | 31 +++++++++++++++-- .../widget/TimetableWidgetScreen.kt | 13 ++++++++ 10 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateSemesterUseCase.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 0a4f086c1..5d46c9185 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -29,7 +29,8 @@ class TimetableRepositoryImpl @Inject constructor( gson.fromJson>(lectureString, lectureType).orEmpty() updateLectures } else { - timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() }.orEmpty() + timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() } + .orEmpty() } } catch (e: Exception) { emptyList() @@ -47,6 +48,14 @@ class TimetableRepositoryImpl @Inject constructor( } } + override suspend fun updateCurrentSemester(semester: String) { + try { + timetableLocalDataSource.putSemester(semester) + } catch (e: Exception) { + e.message + } + } + override suspend fun removeTimetables(id: Int) { try { timetableRemoteDataSource.deleteTimetables(id) diff --git a/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt index b014564c3..944039d92 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/local/TimetableLocalDataSource.kt @@ -28,6 +28,12 @@ class TimetableLocalDataSource @Inject constructor( } } + suspend fun putSemester(value: String) { + dataStore.edit { preferences -> + preferences[stringPreferencesKey("semester")] = value + } + } + fun loadDepartments(): List? = context.readData("department.json")?.departments } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 654bab6f8..1b6acd093 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.Flow interface TimetableRepository { suspend fun getTimetables(key: String, isAnonymous: Boolean): List suspend fun updateTimetables(key: String, isAnonymous: Boolean, value: List) + suspend fun updateCurrentSemester(semester: String) suspend fun removeTimetables(id: Int) suspend fun loadSemesters(): List suspend fun loadDepartments(): List diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateSemesterUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateSemesterUseCase.kt new file mode 100644 index 000000000..b2072f94e --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateSemesterUseCase.kt @@ -0,0 +1,12 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class UpdateSemesterUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +) { + suspend operator fun invoke(semester: String) { + timetableRepository.updateCurrentSemester(semester) + } +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index 6da7b4b74..1bb97c647 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -1,5 +1,7 @@ package `in`.koreatech.koin.ui.timetablev2 +import android.appwidget.AppWidgetManager +import android.content.Intent import android.os.Bundle import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.ime @@ -9,17 +11,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.viewinterop.AndroidView import `in`.koreatech.koin.R import `in`.koreatech.koin.compose.ui.TimetableTheme import `in`.koreatech.koin.core.appbar.AppBarBase import `in`.koreatech.koin.databinding.ActivityTimetableBinding +import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity import `in`.koreatech.koin.ui.navigation.state.MenuState import `in`.koreatech.koin.ui.timetablev2.view.TimetableScreen +import `in`.koreatech.koin.ui.timetablev2.widget.TimetableAppWidget +import `in`.koreatech.koin.ui.timetablev2.widget.TimetableWidgetReceiver import `in`.koreatech.koin.util.BitmapUtils import `in`.koreatech.koin.util.ext.showToast @@ -41,6 +45,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { initEvent() val isAnonymous = intent.getBooleanExtra("isAnonymous", true) + binding.composeView.setContent { TimetableTheme( darkTheme = false @@ -59,6 +64,9 @@ class TimetableActivity : KoinNavigationDrawerActivity() { } ?: showToast("retry saved image..") } }, + onSendBroadcastReceiver = { semester -> + sendTimetableWidgetReceiver(semester) + }, content = { bottomSheetState, onEventClick -> TimetableUI( sheetState = bottomSheetState, @@ -112,4 +120,12 @@ class TimetableActivity : KoinNavigationDrawerActivity() { } } } + + private fun sendTimetableWidgetReceiver(semester: Semester) { + val intent = Intent(this, TimetableWidgetReceiver::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(TimetableAppWidget.SEMESTER, semester.semester) + } + sendBroadcast(intent) + } } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index 9c68aaa44..df109e530 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -6,11 +6,9 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomSheetScaffold @@ -18,7 +16,6 @@ import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Text import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material.rememberBottomSheetState import androidx.compose.runtime.Composable @@ -29,11 +26,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import `in`.koreatech.koin.common.UiStatus +import `in`.koreatech.koin.domain.model.timetable.Semester import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect import `in`.koreatech.koin.ui.timetablev2.viewmodel.TimetableViewModel @@ -50,7 +46,8 @@ fun TimetableScreen( context: Context = LocalContext.current, timetableViewModel: TimetableViewModel = viewModel(), onSavedImage: () -> Unit, - content: @Composable ColumnScope.(sheetState: BottomSheetState, onEventClick: (TimetableEvent) -> Unit) -> Unit, + onSendBroadcastReceiver: (semester: Semester) -> Unit, + content: @Composable ColumnScope.(sheetState: BottomSheetState, onEventClick: (TimetableEvent) -> Unit) -> Unit ) { val state by timetableViewModel.collectAsState() @@ -69,6 +66,10 @@ fun TimetableScreen( } } + if (state.currentSemester.semester.isNotEmpty()) { + onSendBroadcastReceiver(state.currentSemester) + } + LaunchedEffect(key1 = isAnonymous) { timetableViewModel.updateIsAnonymous(isAnonymous) } @@ -118,8 +119,7 @@ fun TimetableScreen( timetableViewModel.addLecture(state.currentSemester, lecture) } timetableViewModel.closeAddLectureDialog() - // TODO : update widget - // sendBroadcastReceiver(currentSemester) + onSendBroadcastReceiver(state.currentSemester) } ) @@ -129,7 +129,10 @@ fun TimetableScreen( lecture = state.clickLecture, semester = state.currentSemester, onDismissRequest = timetableViewModel::closeRemoveLectureDialog, - onRemoveLecture = timetableViewModel::removeLecture + onRemoveLecture = { semester, lecture -> + timetableViewModel.removeLecture(semester, lecture) + onSendBroadcastReceiver(state.currentSemester) + } ) BottomSheetScaffold( @@ -201,4 +204,3 @@ private fun BackHandler(sheetState: BottomSheetState) { } } } - diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt index 3a760edf9..08d00df7e 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/viewmodel/TimetableViewModel.kt @@ -12,6 +12,7 @@ import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetTimetablesUseCase import `in`.koreatech.koin.domain.usecase.timetable.RemoveTimetablesUseCase +import `in`.koreatech.koin.domain.usecase.timetable.UpdateSemesterUseCase import `in`.koreatech.koin.domain.usecase.timetable.UpdateTimetablesUseCase import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.TimetableSideEffect @@ -33,6 +34,7 @@ class TimetableViewModel @Inject constructor( private val getDepartmentsUseCase: GetDepartmentsUseCase, private val getTimetablesUseCase: GetTimetablesUseCase, private val updateTimetablesUseCase: UpdateTimetablesUseCase, + private val updateSemesterUseCase: UpdateSemesterUseCase, private val removeTimetablesUseCase: RemoveTimetablesUseCase, ) : ContainerHost, ViewModel() { override val container: Container = @@ -185,6 +187,7 @@ class TimetableViewModel @Inject constructor( } fun updateCurrentSemester(semester: Semester) = intent { + updateSemesterUseCase(semester.semester) clear() reduce { state.copy(uiStatus = UiStatus.Loading) } viewModelScope.launch { diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt index b048d917b..1c2b2724a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt @@ -1,16 +1,45 @@ package `in`.koreatech.koin.ui.timetablev2.widget import android.content.Context +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.stringPreferencesKey import androidx.glance.GlanceId import androidx.glance.action.ActionParameters import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.action.ActionCallback import androidx.glance.appwidget.provideContent +import androidx.glance.currentState +import `in`.koreatech.koin.model.timetable.TimetableEvent + +object TimetableAppWidget : GlanceAppWidget() { + const val SEMESTER = "semester" + const val LAST_UPDATED = "lastUpdated" -object TimetableAppWidget: GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { + val appContext = context.applicationContext + provideContent { + val state = currentState() + val semester = state[stringPreferencesKey(SEMESTER)] ?: "" + val lastUpdated = state[stringPreferencesKey(LAST_UPDATED)] ?: "" + val lecture by remember { + mutableStateOf("") + } + val timetableEvents = remember { + mutableStateOf>(emptyList()) + } + + LaunchedEffect(key1 = lastUpdated) { + } + + TimetableWidgetScreen( + state = state + ) } } } @@ -23,4 +52,4 @@ class RefreshAction : ActionCallback { ) { TimetableAppWidget.update(context, glanceId) } -} \ No newline at end of file +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetReceiver.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetReceiver.kt index d5bd01e2e..7814521a6 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetReceiver.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetReceiver.kt @@ -3,10 +3,17 @@ package `in`.koreatech.koin.ui.timetablev2.widget import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent +import androidx.datastore.preferences.core.stringPreferencesKey import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager import androidx.glance.appwidget.GlanceAppWidgetReceiver +import androidx.glance.appwidget.state.updateAppWidgetState +import androidx.glance.appwidget.updateAll +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch -class TimetableWidgetReceiver: GlanceAppWidgetReceiver() { +class TimetableWidgetReceiver : GlanceAppWidgetReceiver() { override val glanceAppWidget: GlanceAppWidget = TimetableAppWidget override fun onReceive(context: Context, intent: Intent) { @@ -16,8 +23,26 @@ class TimetableWidgetReceiver: GlanceAppWidgetReceiver() { when (action) { AppWidgetManager.ACTION_APPWIDGET_UPDATE -> { - + val semester = intent.getStringExtra(TimetableAppWidget.SEMESTER) ?: "" + CoroutineScope(Dispatchers.IO).launch { + updateWidget(context, semester) + } } } } -} \ No newline at end of file + + suspend fun updateWidget( + context: Context, + semester: String + ) { + val updatedTime = System.currentTimeMillis().toString() + GlanceAppWidgetManager(context).getGlanceIds(TimetableAppWidget.javaClass) + .forEach { glanceId -> + updateAppWidgetState(context, glanceId) { prefs -> + prefs[stringPreferencesKey(TimetableAppWidget.SEMESTER)] = semester + prefs[stringPreferencesKey(TimetableAppWidget.LAST_UPDATED)] = updatedTime + } + } + TimetableAppWidget.updateAll(context) + } +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt new file mode 100644 index 000000000..ccb612fa1 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt @@ -0,0 +1,13 @@ +package `in`.koreatech.koin.ui.timetablev2.widget + +import androidx.compose.runtime.Composable +import androidx.datastore.preferences.core.Preferences +import androidx.glance.GlanceModifier + +@Composable +fun TimetableWidgetScreen( + state: Preferences, + modifier: GlanceModifier = GlanceModifier +) { + +} \ No newline at end of file From 46ab5deead20665e0d8420ec07be2e6623bc6305 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 20 Jun 2024 20:42:16 +0900 Subject: [PATCH 18/28] [ADD] glance widget --- .../data/di/repository/RepositoryModule.kt | 5 +- .../repository/TimetableRepositoryImpl.kt | 7 +- .../timetablev2/widget/TimetableAppWidget.kt | 107 ++++++++++++-- .../widget/TimetableWidgetScreen.kt | 13 -- .../widget/view/TimetableWidgetContent.kt | 132 ++++++++++++++++++ .../widget/view/TimetableWidgetHeader.kt | 75 ++++++++++ .../widget/view/TimetableWidgetScreen.kt | 40 ++++++ .../koin/util/mapper/TimetableMapperUtils.kt | 36 +++++ koin/src/main/res/drawable/dragon.jpg | Bin 0 -> 7009 bytes koin/src/main/res/drawable/icon_refresh.xml | 12 ++ .../main/res/drawable/shape_timetable_row.xml | 6 + koin/src/main/res/values/colors.xml | 2 + 12 files changed, 409 insertions(+), 26 deletions(-) delete mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetHeader.kt create mode 100644 koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt create mode 100644 koin/src/main/java/in/koreatech/koin/util/mapper/TimetableMapperUtils.kt create mode 100644 koin/src/main/res/drawable/dragon.jpg create mode 100644 koin/src/main/res/drawable/icon_refresh.xml create mode 100644 koin/src/main/res/drawable/shape_timetable_row.xml diff --git a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt index c49db953c..2e2bc6b3f 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt @@ -207,8 +207,9 @@ object RepositoryModule { @Singleton fun providesTimetableRepository( timetableRemoteDataSource: TimetableRemoteDataSource, - timetableLocalDataSource: TimetableLocalDataSource + timetableLocalDataSource: TimetableLocalDataSource, + tokenLocalDataSource: TokenLocalDataSource ): TimetableRepository { - return TimetableRepositoryImpl(timetableRemoteDataSource, timetableLocalDataSource) + return TimetableRepositoryImpl(timetableRemoteDataSource, timetableLocalDataSource, tokenLocalDataSource) } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 5d46c9185..530df8ea5 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -7,6 +7,7 @@ import `in`.koreatech.koin.data.mapper.toLecture import `in`.koreatech.koin.data.mapper.toSemester import `in`.koreatech.koin.data.mapper.toTimetablesRequest import `in`.koreatech.koin.data.source.local.TimetableLocalDataSource +import `in`.koreatech.koin.data.source.local.TokenLocalDataSource import `in`.koreatech.koin.data.source.remote.TimetableRemoteDataSource import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture @@ -18,10 +19,11 @@ import javax.inject.Inject class TimetableRepositoryImpl @Inject constructor( private val timetableRemoteDataSource: TimetableRemoteDataSource, private val timetableLocalDataSource: TimetableLocalDataSource, + private val tokenLocalDataSource: TokenLocalDataSource ) : TimetableRepository { override suspend fun getTimetables(key: String, isAnonymous: Boolean): List = try { - if (isAnonymous) { + if (tokenLocalDataSource.getAccessToken().isNullOrEmpty() && isAnonymous) { val lectureString = timetableLocalDataSource.getString(key).first() val lectureType = object : TypeToken>() {}.type val gson = Gson() @@ -29,8 +31,7 @@ class TimetableRepositoryImpl @Inject constructor( gson.fromJson>(lectureString, lectureType).orEmpty() updateLectures } else { - timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() } - .orEmpty() + timetableRemoteDataSource.loadTimetables(key).timetables?.map { it.toLecture() }.orEmpty() } } catch (e: Exception) { emptyList() diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt index 1c2b2724a..156d23ff5 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt @@ -2,18 +2,36 @@ package `in`.koreatech.koin.ui.timetablev2.widget import android.content.Context import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.stringPreferencesKey import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.ImageProvider import androidx.glance.action.ActionParameters import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.action.ActionCallback import androidx.glance.appwidget.provideContent +import androidx.glance.background import androidx.glance.currentState +import androidx.glance.layout.fillMaxSize +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent +import `in`.koreatech.koin.R +import `in`.koreatech.koin.compose.ui.basicColors +import `in`.koreatech.koin.data.source.local.TokenLocalDataSource +import `in`.koreatech.koin.domain.model.timetable.Lecture +import `in`.koreatech.koin.domain.repository.TimetableRepository +import `in`.koreatech.koin.model.timetable.TimeBlock import `in`.koreatech.koin.model.timetable.TimetableEvent +import `in`.koreatech.koin.ui.timetablev2.widget.view.TimetableWidgetScreen +import `in`.koreatech.koin.util.ext.toTimetableEvents +import `in`.koreatech.koin.util.mapper.toTimeBlocks +import java.time.DayOfWeek object TimetableAppWidget : GlanceAppWidget() { const val SEMESTER = "semester" @@ -21,27 +39,94 @@ object TimetableAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { val appContext = context.applicationContext + val timetableRepository = timetableRepository(context) provideContent { val state = currentState() val semester = state[stringPreferencesKey(SEMESTER)] ?: "" val lastUpdated = state[stringPreferencesKey(LAST_UPDATED)] ?: "" - val lecture by remember { - mutableStateOf("") - } - val timetableEvents = remember { - mutableStateOf>(emptyList()) + val timeBlocks = remember { + mutableStateOf>>(emptyList()) } LaunchedEffect(key1 = lastUpdated) { - + timetableRepository.getTimetables(semester, true).let { timetables -> + timeBlocks.value = generateDayOfWeekTimetables(timetables) + } } TimetableWidgetScreen( - state = state + timeBlocks = timeBlocks.value, + modifier = GlanceModifier.fillMaxSize() + .background(imageProvider = ImageProvider(R.drawable.dragon)) ) } } + + private fun timetableRepository(context: Context): TimetableRepository { + val hiltEntryPoint = EntryPointAccessors.fromApplication( + context, TimetableWidgetEntryPoint::class.java + ) + return hiltEntryPoint.timetableRepository() + } + + private fun generateTimetableEvents( + lectures: List, + colors: List + ): List { + val updatedTimetableEvents = mutableListOf() + lectures.mapIndexed { index, lecture -> + lecture.toTimetableEvents(index, colors) + }.map { + it.forEach { + updatedTimetableEvents.add(it) + } + } + return updatedTimetableEvents + } + + private fun generateDayOfWeekTimetables(timetableEvents: List): List> { + val timeBlocks = MutableList>(5) { emptyList() } + + val mondays = + generateTimetableEvents( + timetableEvents, + basicColors + ).filter { it.dayOfWeek == DayOfWeek.MONDAY } + .sortedBy { it.start }.toTimeBlocks() + val tuesdays = + generateTimetableEvents( + timetableEvents, + basicColors + ).filter { it.dayOfWeek == DayOfWeek.TUESDAY } + .sortedBy { it.start }.toTimeBlocks() + val wednesdays = + generateTimetableEvents( + timetableEvents, + basicColors + ).filter { it.dayOfWeek == DayOfWeek.WEDNESDAY } + .sortedBy { it.start }.toTimeBlocks() + val thursday = + generateTimetableEvents( + timetableEvents, + basicColors + ).filter { it.dayOfWeek == DayOfWeek.THURSDAY } + .sortedBy { it.start }.toTimeBlocks() + val fridays = + generateTimetableEvents( + timetableEvents, + basicColors + ).filter { it.dayOfWeek == DayOfWeek.FRIDAY } + .sortedBy { it.start }.toTimeBlocks() + + timeBlocks[0] = mondays + timeBlocks[1] = tuesdays + timeBlocks[2] = wednesdays + timeBlocks[3] = thursday + timeBlocks[4] = fridays + + return timeBlocks + } } class RefreshAction : ActionCallback { @@ -53,3 +138,9 @@ class RefreshAction : ActionCallback { TimetableAppWidget.update(context, glanceId) } } + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface TimetableWidgetEntryPoint { + fun timetableRepository(): TimetableRepository +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt deleted file mode 100644 index ccb612fa1..000000000 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableWidgetScreen.kt +++ /dev/null @@ -1,13 +0,0 @@ -package `in`.koreatech.koin.ui.timetablev2.widget - -import androidx.compose.runtime.Composable -import androidx.datastore.preferences.core.Preferences -import androidx.glance.GlanceModifier - -@Composable -fun TimetableWidgetScreen( - state: Preferences, - modifier: GlanceModifier = GlanceModifier -) { - -} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt new file mode 100644 index 000000000..ccb2e7455 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt @@ -0,0 +1,132 @@ +package `in`.koreatech.koin.ui.timetablev2.widget.view + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceModifier +import androidx.glance.ImageProvider +import androidx.glance.background +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.Column +import androidx.glance.layout.Row +import androidx.glance.layout.fillMaxHeight +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import androidx.glance.layout.padding +import androidx.glance.layout.width +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import `in`.koreatech.koin.R +import `in`.koreatech.koin.model.timetable.TimeBlock + +@Composable +fun TimetableWidgetContent( + timeWidth: Float, + timeBlocks: List>, + modifier: GlanceModifier = GlanceModifier +) { + Row( + modifier = modifier + ) { + if (timeBlocks.isNotEmpty()) { + repeat(timeBlocks.size + 2) { index -> + Column( + modifier = GlanceModifier + .width((timeWidth / 6f).dp) + .fillMaxHeight() + ) { + for (i in 9..18) { + if (index == 0) { + Box( + modifier = GlanceModifier + .fillMaxWidth() + .height(60.dp) + .background(imageProvider = ImageProvider(R.drawable.shape_timetable_row)), + contentAlignment = Alignment.TopEnd + ) { + Text( + text = i.toString(), + style = TextStyle( + fontSize = 14.sp, + color = ColorProvider(Color.Gray) + ), + modifier = GlanceModifier + .padding(top = 2.dp, end = 2.dp) + ) + } + } else if (index in 1..5) { + val timeBlock = timeBlocks[index - 1][i - 9] + Box( + modifier = GlanceModifier + .fillMaxWidth() + .height( + if (timeBlock == null) { + 60.dp + } else { + (timeBlock.duration * 60).dp + } + ) + .padding( + top = ((timeBlock?.startDuration ?: 0f) * 60).dp, + end = 2.dp + ) + .background(imageProvider = ImageProvider(R.drawable.shape_timetable_row)) + ) { + Column( + modifier = GlanceModifier + .fillMaxWidth() + .height( + ((timeBlock?.endDuration ?: 0f) * 60).dp + ) + .padding(2.dp) + .background( + timeBlock?.color ?: Color.Transparent + ) + ) { + Box( + modifier = GlanceModifier + .fillMaxWidth() + .height(2.dp) + .background(Color.White) + ) { + + } + Text( + text = timeBlock?.title ?: "", + style = TextStyle( + fontSize = 12.sp, + color = ColorProvider(Color.White), + fontWeight = FontWeight.Bold + ) + ) + Text( + text = if (timeBlock?.start == null || timeBlock?.end == null) "" else "${timeBlock.start} - ${timeBlock.end}", + style = TextStyle( + fontSize = 8.sp, + color = ColorProvider(Color.White) + ) + ) + } + } + } + } + } + } + } + } +} + +@Preview +@Composable +fun TimetableWidgetContentPreview() { + TimetableWidgetContent( + timeWidth = 1f, + timeBlocks = emptyList(), + modifier = GlanceModifier.fillMaxWidth() + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetHeader.kt new file mode 100644 index 000000000..e6c0e5b3d --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetHeader.kt @@ -0,0 +1,75 @@ +package `in`.koreatech.koin.ui.timetablev2.widget.view + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.action.clickable +import androidx.glance.appwidget.action.actionRunCallback +import androidx.glance.background +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.Row +import androidx.glance.layout.fillMaxHeight +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextAlign +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import `in`.koreatech.koin.R +import `in`.koreatech.koin.ui.timetablev2.widget.RefreshAction + +@Composable +fun TimetableWidgetHeader( + modifier: GlanceModifier = GlanceModifier +) { + val headerTitle = listOf("", "월", "화", "수", "목", "금") + Row( + modifier = modifier + ) { + headerTitle.forEachIndexed { index, title -> + Box( + modifier = GlanceModifier + .defaultWeight() + .fillMaxHeight(), + contentAlignment = Alignment.Center + ) { + if (title.isEmpty()) { + Image( + provider = ImageProvider(R.drawable.icon_refresh), + contentDescription = null, + modifier = GlanceModifier.clickable(onClick = actionRunCallback()) + ) + } else { + Text( + text = title, + style = TextStyle( + color = ColorProvider(Color.Black), + fontSize = 14.sp, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold + ) + ) + } + } + } + } + +} + +@Preview(showBackground = true) +@Composable +fun TimetableWidgetHeaderPreview() { + TimetableWidgetHeader( + modifier = GlanceModifier + .fillMaxWidth() + .height(40.dp) + .background(Color.Transparent) + ) +} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt new file mode 100644 index 000000000..34d525e9e --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt @@ -0,0 +1,40 @@ +package `in`.koreatech.koin.ui.timetablev2.widget.view + +import android.appwidget.AppWidgetManager +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.glance.GlanceModifier +import androidx.glance.appwidget.LocalAppWidgetOptions +import androidx.glance.appwidget.lazy.LazyColumn +import androidx.glance.background +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import `in`.koreatech.koin.model.timetable.TimeBlock + +@Composable +fun TimetableWidgetScreen( + timeBlocks: List>, + modifier: GlanceModifier = GlanceModifier, + timeWidth: Int = LocalAppWidgetOptions.current.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) +) { + LazyColumn( + modifier = modifier + ) { + item { + TimetableWidgetHeader( + modifier = GlanceModifier + .fillMaxWidth() + .height(40.dp) + .background(Color.Transparent) + ) + } + item { + TimetableWidgetContent( + timeWidth = timeWidth.toFloat(), + timeBlocks = timeBlocks + ) + } + } +} + diff --git a/koin/src/main/java/in/koreatech/koin/util/mapper/TimetableMapperUtils.kt b/koin/src/main/java/in/koreatech/koin/util/mapper/TimetableMapperUtils.kt new file mode 100644 index 000000000..e2f36c5b9 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/util/mapper/TimetableMapperUtils.kt @@ -0,0 +1,36 @@ +package `in`.koreatech.koin.util.mapper + +import `in`.koreatech.koin.model.timetable.TimeBlock +import `in`.koreatech.koin.model.timetable.TimetableEvent +import java.time.LocalTime + +fun List.toTimeBlocks(): List { + val updateList = Array(10) { null } + + this.forEach { + if (it.start.hour < 9) return@forEach + + if (it.end.hour > 18) { + for (i in it.start.hour until 19) { + if (i == it.start.hour) { + updateList[i - 9] = it.convertToTimeBlock(LocalTime.of(19, 0)) + } else { + updateList[i - 9] = it.convertToEmptyTimeBlock() + } + } + } else { + val endHour: Int = if (it.end.minute == 0) it.end.hour + else it.end.hour + 1 + + for (i in it.start.hour until endHour) { + if (i == it.start.hour) { + updateList[i - 9] = it.convertToTimeBlock(it.end) + } else { + updateList[i - 9] = it.convertToEmptyTimeBlock() + } + } + } + } + + return updateList.toList() +} \ No newline at end of file diff --git a/koin/src/main/res/drawable/dragon.jpg b/koin/src/main/res/drawable/dragon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e7abf32495a26f6f9d305e713072f05340fc4df GIT binary patch literal 7009 zcmb7IXEYpa)85sI9z++c-eUDGQNoHSiQcnH)aY#^N(7PBRuG~-dRT1qPL$O>qA#L% ztE?J?&sW}ap7)&h&v)Oyt}}D4nKS2}nS17T=5`4{4bjxr1OR~m0PyYtZs!1z08%1i z5@I4!5@HfEGSYh#3{({4?Euh_09pb4_&_cI9t{wm26)>IV7+q? z4f_pg-;~9iaeCnw> zwVNIM)ady#cF0Xh%`>!bQWIn5KJ;KJ)_iALjJYz%9G-iId!%9iMI2_UmqLO$YIJv;uS!6Ab37pY=B z+sqN(Wc8Fj4S6-F``zN}T&F9IXJHF?a|=Lx+F2QyVpz#-dJqyU8A9GPJ^X6wDj8F0 zpdB*)ppN-Ne%#|_2je<;jbRR#;_qrTrA6v**WC-h{p;7d z=eSpE(--~?c@V>jL^8wAc%KUag#xMq5dD7vXhSxODLX738pYVViO?kbrlhU(Ev@|Y z?K!CDFOLZ7peBTP>n$M3qUzjkH+=TnJ)8?VK`KGL_JxAOAc3`@jP-dl^GX#rx1L5BXkkI@VC#v?p>7r46m)yxYXrg2f8 z*Gp|FTB$tx+VSePq=~>zG&cwimw_Wp=jY07w0$T#?S}@!Q&nZi>5Uj4s(39jqFqLP zQcXV6J!nYc4YMV;%KZWV9f;ch1Uh^I{ME+pm@zEs_I&$+Lrb{Lq zV%gUZD0BGLHAl|6zm)T}R_PNx?}Rt85|eMv;^@w^xXunMj9D7?N!Y*I8-^M`z7a$E zS_mQeN<@&%FzuS%PYqLAYGFmH9aS@{0-P99G>CG^NXFoSxzzkwQ@Qtoda@v?e;ATQ z$QBOl2dw!oM^CG3M^h@96+oj$ z1A6nZi~Q2wM2Xm$fR(0CIi((7(mXyL=7G0pWmOJBqq{YtL^;@glsFQcamQcC&p(La zVO*8{0|}Zr@K{L07}sv(IlKb4G(}M?`f4pPTvEwT*Gx+e_IFcTYdXAXic4|4#E0J) zom3d;S?oA^x4u!26M&p}{$IuY-}>>AL;qB&2u`j_dLSL~kgA4X=~Mk-!bG+wCBh}8&a&cA=G!0IO!#iYVGx`1d*zOF+=i6(kjyGc+ z9ICRVB@R{ZXwN2FBL_lvEvP?fI~>|IhOhVf3&&cV&_^r6uy2ol4VzjSxu){CtZ>^` z97Q~~B`PA&0RBsrgoJW3g%TV+Ue$@?{yTHdeylrE0|N!K3!GR(_A&;GWRHFc zA%{zPlAF{ri?qnU002+ll5a-n8mE@QcdbmGXD3XCBClbzPrbdq9-PmAshYguC@vg7 zWfNZ59+VhJ|I#DRsc_7^%K1KZ?pT1)5=u{Sd2MAw+)1D5k~){svw>m06kTU|Gq|#v zNKEh^ZXdp%$K|3Yn!`ll5q9mCtqQ$Vyy5Ote&6o(Qb#3Q!MVVU5*=$|x(H1y5GU(m zRP+q&tSny=462!BQRoqQ*LoBDXULkf+`>-&y3R%t>AK{`9knqPpfMWF1&k<8nvb9D z2`crgP0b;WS-7UCrc+mE8#tNM=1x{E|!DAOKJ(P>LQg$)t%bYM>9mU=n&xMQ3xKxnNQYxBq(a ze6nKScWSCuS6+leJ2X|mBeP}NKAu4~jIjC?+t)|bhLzIQ44N|#Tb>^vtTC=qkv_6T zSe@d~XklR~f!&+3BlH8YJzFy{lYH&i>|JNvK6hixg@;5| ze$*|X-R4<>e9=y%H7bAbXm0T&=+ky{_6<8(bIXSo8O?|lVu?{4!Bv~9dZtxaKthJ+%OCiA~%7B0m0__!0!^RkpcGkEtA z{I;RoX3o~5AbWHA2)HhbW#u7qFr8j?51{C^6LemgpS!KNH;!F~E-dMhwqJ=-X#H#~ z2yk2FsIihC%|9KySLQ65Up;|=N;r8%Xkffai9OwiAay~6(Q+kaU*nj!m(uhnMJgf? zoLXPV?{}&Qi0nQ0+7YTg<f#VS=ia~@^N4Nt;MJnqoS5&1y`;=c$U(xRG<}Vmt`}OEkKP)CV=I-{UA%%(zI?7+(~jZ<>RI zBJ0>$-q{DRqq#BPUt^HjwZ&?o#Sl=0LFeAf-(^%>R0KiBSSQy`@w~#Ben$`X6^Cr+ zOb%+^#Y6zTSMq+~s_=6nSC^Gv_e$U^sH|QwOR2tOx26To>M}>pqzEkDLKV5V6{!HNUwNN@MK%on!F!v1X zzTV{)+KpVyQQYJsmK(7z{pYA5V1ROM@s2sYOKxjbG7)IqP05f#jLzeLdto@@p&odYgiBL5e7IuQQK^K zQXKM5i?+|MP5zfE9!&&ezBi>;n9SKm<4}EmQ}FC-0%{UD;*r)0Q~B(lyn(*MB9&_| zQd&?8P`b9gd8b7ndb+C9tJIGnH;(NLB`iG@np^#&PKERf0KJ2kV>v0*5U(UOmu92K zlX19TyM}9T)Piax6vAH(WznNTIp<3Ke1o8jxhVeSCrP4{!b9&0 z5PCbh(1qUp^Ka(X$~QNTDVE^2aoDMx%n!XHtBZg!MPcvUs^6wp?C4teGHGqY{!S)h zg3VSxn2@RH0dl1w&WhQHoH2l@ba3&m!dv=}W9F2N*ONlW5{@fNmM!0zNvZTiztQ8B z4xos;Z;9CVf`)q4U{fYXygL%Wv$&hta{pAB=^SV4jwsW;)p!dyYf~8m;n4;WQ3$b8 zwN>Q1fu`4w6g+0AmcgT=m%Gx@#lE)yJwN#6Gv28w9i*I!caK4_@ctvYK~~OVzKrG@ z2mM>XDh0UuoA>#8b?R$2vl8ECOKaz!6=WW%q(soKY~R6;W|kgZABPcd4JyR!V>uf? zj;*HPmvIgxY6eOTUB1sPZ}MOFi(~G^FP{4=eKo__zs_i6Q}_3(bN?Dm^=JSy!)48- zXz|-b7Zu0r71Oa~zm}50em_N`#daP@a<8ua5xbCi|2VQ>>U=R&uhf-;;dyJR`z2dc zitg5ajnd<9+QszWGtnN7GX4?=&zUr*EZ;`%_pyBCwD?(J1ma`iDr6rz;%{gbhZweI z{@XYx6rwlPWbGzCLq-Zh7o z2s?#RPVF!iC?W7yP?v=L70vGBY$~sPT=Se0b4<%uZO-WV=nT4>U&R@O(AmB;STYjD zxuqOAUwrJ6G&s8a@GBqOAg*Ac`km7-3>qXDL{QIqL-WF~%-0>;lx!I_qQ?L%(K2hV zn+ae#?y9C5WH&kux$CtrtYk$|dG2KkqlxTmOH@d^jhCy5yVmmDh98R|sNXxIF@8*% zO{r(!;B|dGS)&T3NvL&j#78T>6g_WJ{?gY`dIV#br$8kkArL?vgGq+vG{L0qKkgOxX${&lJQ zYR=o`JJF#%YY!t3iq5D4$douL`%y_s`zHoA!IOj`orBM>JqTk0@ zYS87o&x=2Q^%a6r-cHdg!f(Kj6sy;t2g%9d^b4KeG%EBKGl`9Dd}k{; zR@iksE4hzXfw+C;=CILC>vpXz!bcfy`ZD<4^}Ku?dolvV?6c@v9gKyVlPd+X+?9^{ zFEp#`>`QWhir}D)=1M^=Pc@r#GqbsIic^6_4TCtHi4S+Rf`S4od9^s`5O4+ncF;FN zOboT1S%38iK(_wv!T%q4If@~EIb5VJnZ-1Xg{AZ-;zYOMfs4avGB7Z%Wd!_Qh$Sk2^K4@Av;@+-N>aj_EE3Kt0V%MPYUNm&0gY~a? z00F->F}h3vpCvjQI!<-*E;T#kS2yfGKP!3oRbTyHkAaFoC0R6|FipH)@(?ZBiWG1Abs64^Z zq7-60mhq3x$v@U8z-8G3(}I-f`Da%d!`8OJ`t%iLsHTaNeA)z>{Oo(-)CR{S>n0Cj zi3Q}B6YOPY<-vAWy25V0jfF-{+EgF8pKcu_#*qGoTrWigZ8VYUzb#u{j@Q6!m4his zM!Ay=AccJGpd?gbC9%v@dF5#{|GU94FCV&XVVU~TC3l+@vIenZ^3s7y0-B7J?LU;d z92)iC15A=+B;&t5Ned92IUA%_LYdf5Q2Ei(q7`{eOQ`vG&Nx>n7_b8&trl_Oz7BKKe$aFun&+0v*-=WGA8^wb9dNaEpt4j7>D?I2gr{k~1ISkt3y zLe1>lan=6xqd()JJsJ+DCI|guUzL6|jxJd62pEjJ8dHOX5mjQ0Nvd%P12Fc*WC^gF~J;z3<9?gK!5mP=q(>sf^OwP4Hg0-b)`KrcocQI&;JGdYRtECg4tEl#U(w ztEX*`=7#QEDy5r8@=x08!LekO%L^+Lg!=sT!yx0wEODYD(O1?+;>A{aqQ#QnJ_xc! zwY2f1HH<7{E_}W%iFCgwAke#RgiTiZCW%d3( z3I1s96F&V@f?)hbhh^3oA*vyy5LeS}+??KYCOC~={HC&7H zV$OFo*Q3#c%qc>2=Jy(kH}Hy8A=6a28V zgCLA_-v4-H3L6{SI_+BqYO2jZ8#z@62B`BHj?u=?awrqgupS0=oRtUJ{aSBmw-GzI zkmOD>5iMv{>(n&U0Y3G3)Tf&+~hiA8z?cQr`#)9gJ{>)NKL zUgYn)RcGQg7vlD1XlLat&wDJ=7#XoIUg*}=ZCt9vpJ@1{4D--WujgY7Q*0~#LlO}W zB^97*y!q`tjXs|}{A~m^{#jTnV>nmj&OK&5D>q0){1Cc*GwEEY;VzDHP9A$(3e#W~ z3aWeyb9JBqWzdmjr^}(G+)-8Wlkhnoh29u!4i6&$7J#e*>E3O^*hvK2H!#nj=y8@2S%Z0{M@A-L&@&r~SrEZWq2GPFqUrOvgyH}47 zXZ0_QHHvq9Ic7B_Z_K7Y+P0pk<6BPF+O_6BDOGEjVU%>TM-XDS5+xk#09Lgw+y0E;)Rz&!p~;xC8m+dymAf zV6Gvpy|2;?+~fu9*xeVOizQn%lkmkLrt^>KObq9TP)yQMpO>PNgD7%ZN95#2@G jrn}-^@YIEY(V#!BJ1M<$`vFfr7kd6{A1 + + + + diff --git a/koin/src/main/res/drawable/shape_timetable_row.xml b/koin/src/main/res/drawable/shape_timetable_row.xml new file mode 100644 index 000000000..501553e07 --- /dev/null +++ b/koin/src/main/res/drawable/shape_timetable_row.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/koin/src/main/res/values/colors.xml b/koin/src/main/res/values/colors.xml index 2d8eaefe1..2030ff5e9 100644 --- a/koin/src/main/res/values/colors.xml +++ b/koin/src/main/res/values/colors.xml @@ -16,4 +16,6 @@ #8E8E8E + + #BCBCBC \ No newline at end of file From 2580677fc32607105ff7bbc173d40ed538bf6848 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 21 Jun 2024 10:14:16 +0900 Subject: [PATCH 19/28] [MOD] timeblock ui --- gradle/libs.versions.toml | 2 -- .../koin/ui/timetablev2/view/TimetableEventTime.kt | 11 +++++------ .../timetablev2/widget/view/TimetableWidgetContent.kt | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c91cc94e..d1483fafa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,6 @@ napier = "2.6.1" composeNumberPickerVersion = "1.0.3" powerSpinner = "1.2.7" firebaseCrashlyticsBuildtoolsVersion = "2.9.9" -composeNumberPickerVersion = "1.0.3" datastore = "1.1.1" glance = "1.0.0" @@ -151,7 +150,6 @@ coil-svg = {module = "io.coil-kt:coil-svg", version.ref ="coilVersion"} compose-numberPicker = {module = "com.chargemap.compose:numberpicker", version.ref = "composeNumberPickerVersion"} powerSpinner = {module = "com.github.skydoves:powerspinner", version.ref = "powerSpinner"} -compose-numberPicker = {module = "com.chargemap.compose:numberpicker", version.ref = "composeNumberPickerVersion"} glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" } glance-material = { group = "androidx.glance", name = "glance-material", version.ref = "glance" } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt index 4df42c051..9149f181a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt @@ -38,7 +38,6 @@ fun TimetableEventTime( Column( modifier = modifier .fillMaxSize() - .padding(end = 2.dp, bottom = 2.dp) .background( color = if (eventType == TimetableEventType.SELECTED) Color.Transparent else event.color, shape = RoundedCornerShape(4.dp) @@ -60,14 +59,14 @@ fun TimetableEventTime( + " - " + event.end.format(timetableEventTimeFormatter), fontSize = 8.sp, - color = Color.White, + color = Color.Black, ) Text( text = event.name, fontSize = 12.sp, fontWeight = FontWeight.Bold, - color = Color.White, - lineHeight = 12.sp, + color = Color.Black, + lineHeight = 14.sp, ) if (event.description != null) { @@ -76,7 +75,7 @@ fun TimetableEventTime( fontSize = 8.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = Color.White + color = Color.Black ) } } @@ -93,7 +92,7 @@ fun TimetableEventTime( private fun TimetableEventTimePreview() { val sample = TimetableEvent( id = 1, - name = "관희의 수업 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + name = "관희의 수업", color = Color(0xFFAFBBF2), dayOfWeek = DayOfWeek.FRIDAY, start = LocalTime.of(16, 0), diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt index ccb2e7455..adb4fd3b8 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt @@ -100,7 +100,7 @@ fun TimetableWidgetContent( text = timeBlock?.title ?: "", style = TextStyle( fontSize = 12.sp, - color = ColorProvider(Color.White), + color = ColorProvider(Color.Black), fontWeight = FontWeight.Bold ) ) @@ -108,7 +108,7 @@ fun TimetableWidgetContent( text = if (timeBlock?.start == null || timeBlock?.end == null) "" else "${timeBlock.start} - ${timeBlock.end}", style = TextStyle( fontSize = 8.sp, - color = ColorProvider(Color.White) + color = ColorProvider(Color.Black) ) ) } From 4cc5c95ea0688eddf0eaffd1b14eff6191e8c8bd Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 21 Jun 2024 22:39:23 +0900 Subject: [PATCH 20/28] [MOD] timetable ui --- .../ui/timetablev2/view/TimetableContentHeader.kt | 14 +++++++++++--- .../koin/ui/timetablev2/view/TimetableEventTime.kt | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt index 2f705d18c..12206c993 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt @@ -5,10 +5,14 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.icons.Icons @@ -31,6 +35,7 @@ fun TimetableContentHeader( ) { Row( modifier = modifier + .fillMaxWidth() .height(IntrinsicSize.Max), verticalAlignment = Alignment.CenterVertically ) { @@ -50,13 +55,16 @@ fun TimetableContentHeader( .padding(8.dp), onClick = onSavedImage ) + Spacer(modifier = Modifier.width(10.dp)) Icon( imageVector = Icons.Default.AddCircle, contentDescription = null, tint = ColorPrimary, - modifier = Modifier.clickable { - onVisibleBottomSheet() - } + modifier = Modifier + .size(30.dp) + .clickable { + onVisibleBottomSheet() + } ) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt index 9149f181a..aaf74c678 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt @@ -37,6 +37,7 @@ fun TimetableEventTime( ) { Column( modifier = modifier + .padding(bottom = 2.dp, end = 2.dp) .fillMaxSize() .background( color = if (eventType == TimetableEventType.SELECTED) Color.Transparent else event.color, From 55d30409b080b27514d452f1ff04caa441a53a83 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sat, 22 Jun 2024 17:07:56 +0900 Subject: [PATCH 21/28] [DEL] dragon image --- .../ui/timetablev2/widget/TimetableAppWidget.kt | 2 +- .../widget/view/TimetableWidgetContent.kt | 2 +- koin/src/main/res/drawable/dragon.jpg | Bin 7009 -> 0 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 koin/src/main/res/drawable/dragon.jpg diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt index 156d23ff5..deefa5b42 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt @@ -58,7 +58,7 @@ object TimetableAppWidget : GlanceAppWidget() { TimetableWidgetScreen( timeBlocks = timeBlocks.value, modifier = GlanceModifier.fillMaxSize() - .background(imageProvider = ImageProvider(R.drawable.dragon)) + .background(Color.White) ) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt index adb4fd3b8..f9fc17882 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt @@ -53,7 +53,7 @@ fun TimetableWidgetContent( text = i.toString(), style = TextStyle( fontSize = 14.sp, - color = ColorProvider(Color.Gray) + color = ColorProvider(Color.Black) ), modifier = GlanceModifier .padding(top = 2.dp, end = 2.dp) diff --git a/koin/src/main/res/drawable/dragon.jpg b/koin/src/main/res/drawable/dragon.jpg deleted file mode 100644 index 8e7abf32495a26f6f9d305e713072f05340fc4df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7009 zcmb7IXEYpa)85sI9z++c-eUDGQNoHSiQcnH)aY#^N(7PBRuG~-dRT1qPL$O>qA#L% ztE?J?&sW}ap7)&h&v)Oyt}}D4nKS2}nS17T=5`4{4bjxr1OR~m0PyYtZs!1z08%1i z5@I4!5@HfEGSYh#3{({4?Euh_09pb4_&_cI9t{wm26)>IV7+q? z4f_pg-;~9iaeCnw> zwVNIM)ady#cF0Xh%`>!bQWIn5KJ;KJ)_iALjJYz%9G-iId!%9iMI2_UmqLO$YIJv;uS!6Ab37pY=B z+sqN(Wc8Fj4S6-F``zN}T&F9IXJHF?a|=Lx+F2QyVpz#-dJqyU8A9GPJ^X6wDj8F0 zpdB*)ppN-Ne%#|_2je<;jbRR#;_qrTrA6v**WC-h{p;7d z=eSpE(--~?c@V>jL^8wAc%KUag#xMq5dD7vXhSxODLX738pYVViO?kbrlhU(Ev@|Y z?K!CDFOLZ7peBTP>n$M3qUzjkH+=TnJ)8?VK`KGL_JxAOAc3`@jP-dl^GX#rx1L5BXkkI@VC#v?p>7r46m)yxYXrg2f8 z*Gp|FTB$tx+VSePq=~>zG&cwimw_Wp=jY07w0$T#?S}@!Q&nZi>5Uj4s(39jqFqLP zQcXV6J!nYc4YMV;%KZWV9f;ch1Uh^I{ME+pm@zEs_I&$+Lrb{Lq zV%gUZD0BGLHAl|6zm)T}R_PNx?}Rt85|eMv;^@w^xXunMj9D7?N!Y*I8-^M`z7a$E zS_mQeN<@&%FzuS%PYqLAYGFmH9aS@{0-P99G>CG^NXFoSxzzkwQ@Qtoda@v?e;ATQ z$QBOl2dw!oM^CG3M^h@96+oj$ z1A6nZi~Q2wM2Xm$fR(0CIi((7(mXyL=7G0pWmOJBqq{YtL^;@glsFQcamQcC&p(La zVO*8{0|}Zr@K{L07}sv(IlKb4G(}M?`f4pPTvEwT*Gx+e_IFcTYdXAXic4|4#E0J) zom3d;S?oA^x4u!26M&p}{$IuY-}>>AL;qB&2u`j_dLSL~kgA4X=~Mk-!bG+wCBh}8&a&cA=G!0IO!#iYVGx`1d*zOF+=i6(kjyGc+ z9ICRVB@R{ZXwN2FBL_lvEvP?fI~>|IhOhVf3&&cV&_^r6uy2ol4VzjSxu){CtZ>^` z97Q~~B`PA&0RBsrgoJW3g%TV+Ue$@?{yTHdeylrE0|N!K3!GR(_A&;GWRHFc zA%{zPlAF{ri?qnU002+ll5a-n8mE@QcdbmGXD3XCBClbzPrbdq9-PmAshYguC@vg7 zWfNZ59+VhJ|I#DRsc_7^%K1KZ?pT1)5=u{Sd2MAw+)1D5k~){svw>m06kTU|Gq|#v zNKEh^ZXdp%$K|3Yn!`ll5q9mCtqQ$Vyy5Ote&6o(Qb#3Q!MVVU5*=$|x(H1y5GU(m zRP+q&tSny=462!BQRoqQ*LoBDXULkf+`>-&y3R%t>AK{`9knqPpfMWF1&k<8nvb9D z2`crgP0b;WS-7UCrc+mE8#tNM=1x{E|!DAOKJ(P>LQg$)t%bYM>9mU=n&xMQ3xKxnNQYxBq(a ze6nKScWSCuS6+leJ2X|mBeP}NKAu4~jIjC?+t)|bhLzIQ44N|#Tb>^vtTC=qkv_6T zSe@d~XklR~f!&+3BlH8YJzFy{lYH&i>|JNvK6hixg@;5| ze$*|X-R4<>e9=y%H7bAbXm0T&=+ky{_6<8(bIXSo8O?|lVu?{4!Bv~9dZtxaKthJ+%OCiA~%7B0m0__!0!^RkpcGkEtA z{I;RoX3o~5AbWHA2)HhbW#u7qFr8j?51{C^6LemgpS!KNH;!F~E-dMhwqJ=-X#H#~ z2yk2FsIihC%|9KySLQ65Up;|=N;r8%Xkffai9OwiAay~6(Q+kaU*nj!m(uhnMJgf? zoLXPV?{}&Qi0nQ0+7YTg<f#VS=ia~@^N4Nt;MJnqoS5&1y`;=c$U(xRG<}Vmt`}OEkKP)CV=I-{UA%%(zI?7+(~jZ<>RI zBJ0>$-q{DRqq#BPUt^HjwZ&?o#Sl=0LFeAf-(^%>R0KiBSSQy`@w~#Ben$`X6^Cr+ zOb%+^#Y6zTSMq+~s_=6nSC^Gv_e$U^sH|QwOR2tOx26To>M}>pqzEkDLKV5V6{!HNUwNN@MK%on!F!v1X zzTV{)+KpVyQQYJsmK(7z{pYA5V1ROM@s2sYOKxjbG7)IqP05f#jLzeLdto@@p&odYgiBL5e7IuQQK^K zQXKM5i?+|MP5zfE9!&&ezBi>;n9SKm<4}EmQ}FC-0%{UD;*r)0Q~B(lyn(*MB9&_| zQd&?8P`b9gd8b7ndb+C9tJIGnH;(NLB`iG@np^#&PKERf0KJ2kV>v0*5U(UOmu92K zlX19TyM}9T)Piax6vAH(WznNTIp<3Ke1o8jxhVeSCrP4{!b9&0 z5PCbh(1qUp^Ka(X$~QNTDVE^2aoDMx%n!XHtBZg!MPcvUs^6wp?C4teGHGqY{!S)h zg3VSxn2@RH0dl1w&WhQHoH2l@ba3&m!dv=}W9F2N*ONlW5{@fNmM!0zNvZTiztQ8B z4xos;Z;9CVf`)q4U{fYXygL%Wv$&hta{pAB=^SV4jwsW;)p!dyYf~8m;n4;WQ3$b8 zwN>Q1fu`4w6g+0AmcgT=m%Gx@#lE)yJwN#6Gv28w9i*I!caK4_@ctvYK~~OVzKrG@ z2mM>XDh0UuoA>#8b?R$2vl8ECOKaz!6=WW%q(soKY~R6;W|kgZABPcd4JyR!V>uf? zj;*HPmvIgxY6eOTUB1sPZ}MOFi(~G^FP{4=eKo__zs_i6Q}_3(bN?Dm^=JSy!)48- zXz|-b7Zu0r71Oa~zm}50em_N`#daP@a<8ua5xbCi|2VQ>>U=R&uhf-;;dyJR`z2dc zitg5ajnd<9+QszWGtnN7GX4?=&zUr*EZ;`%_pyBCwD?(J1ma`iDr6rz;%{gbhZweI z{@XYx6rwlPWbGzCLq-Zh7o z2s?#RPVF!iC?W7yP?v=L70vGBY$~sPT=Se0b4<%uZO-WV=nT4>U&R@O(AmB;STYjD zxuqOAUwrJ6G&s8a@GBqOAg*Ac`km7-3>qXDL{QIqL-WF~%-0>;lx!I_qQ?L%(K2hV zn+ae#?y9C5WH&kux$CtrtYk$|dG2KkqlxTmOH@d^jhCy5yVmmDh98R|sNXxIF@8*% zO{r(!;B|dGS)&T3NvL&j#78T>6g_WJ{?gY`dIV#br$8kkArL?vgGq+vG{L0qKkgOxX${&lJQ zYR=o`JJF#%YY!t3iq5D4$douL`%y_s`zHoA!IOj`orBM>JqTk0@ zYS87o&x=2Q^%a6r-cHdg!f(Kj6sy;t2g%9d^b4KeG%EBKGl`9Dd}k{; zR@iksE4hzXfw+C;=CILC>vpXz!bcfy`ZD<4^}Ku?dolvV?6c@v9gKyVlPd+X+?9^{ zFEp#`>`QWhir}D)=1M^=Pc@r#GqbsIic^6_4TCtHi4S+Rf`S4od9^s`5O4+ncF;FN zOboT1S%38iK(_wv!T%q4If@~EIb5VJnZ-1Xg{AZ-;zYOMfs4avGB7Z%Wd!_Qh$Sk2^K4@Av;@+-N>aj_EE3Kt0V%MPYUNm&0gY~a? z00F->F}h3vpCvjQI!<-*E;T#kS2yfGKP!3oRbTyHkAaFoC0R6|FipH)@(?ZBiWG1Abs64^Z zq7-60mhq3x$v@U8z-8G3(}I-f`Da%d!`8OJ`t%iLsHTaNeA)z>{Oo(-)CR{S>n0Cj zi3Q}B6YOPY<-vAWy25V0jfF-{+EgF8pKcu_#*qGoTrWigZ8VYUzb#u{j@Q6!m4his zM!Ay=AccJGpd?gbC9%v@dF5#{|GU94FCV&XVVU~TC3l+@vIenZ^3s7y0-B7J?LU;d z92)iC15A=+B;&t5Ned92IUA%_LYdf5Q2Ei(q7`{eOQ`vG&Nx>n7_b8&trl_Oz7BKKe$aFun&+0v*-=WGA8^wb9dNaEpt4j7>D?I2gr{k~1ISkt3y zLe1>lan=6xqd()JJsJ+DCI|guUzL6|jxJd62pEjJ8dHOX5mjQ0Nvd%P12Fc*WC^gF~J;z3<9?gK!5mP=q(>sf^OwP4Hg0-b)`KrcocQI&;JGdYRtECg4tEl#U(w ztEX*`=7#QEDy5r8@=x08!LekO%L^+Lg!=sT!yx0wEODYD(O1?+;>A{aqQ#QnJ_xc! zwY2f1HH<7{E_}W%iFCgwAke#RgiTiZCW%d3( z3I1s96F&V@f?)hbhh^3oA*vyy5LeS}+??KYCOC~={HC&7H zV$OFo*Q3#c%qc>2=Jy(kH}Hy8A=6a28V zgCLA_-v4-H3L6{SI_+BqYO2jZ8#z@62B`BHj?u=?awrqgupS0=oRtUJ{aSBmw-GzI zkmOD>5iMv{>(n&U0Y3G3)Tf&+~hiA8z?cQr`#)9gJ{>)NKL zUgYn)RcGQg7vlD1XlLat&wDJ=7#XoIUg*}=ZCt9vpJ@1{4D--WujgY7Q*0~#LlO}W zB^97*y!q`tjXs|}{A~m^{#jTnV>nmj&OK&5D>q0){1Cc*GwEEY;VzDHP9A$(3e#W~ z3aWeyb9JBqWzdmjr^}(G+)-8Wlkhnoh29u!4i6&$7J#e*>E3O^*hvK2H!#nj=y8@2S%Z0{M@A-L&@&r~SrEZWq2GPFqUrOvgyH}47 zXZ0_QHHvq9Ic7B_Z_K7Y+P0pk<6BPF+O_6BDOGEjVU%>TM-XDS5+xk#09Lgw+y0E;)Rz&!p~;xC8m+dymAf zV6Gvpy|2;?+~fu9*xeVOizQn%lkmkLrt^>KObq9TP)yQMpO>PNgD7%ZN95#2@G jrn}-^@YIEY(V#!BJ1M<$`vFfr7kd6{A1 Date: Sat, 22 Jun 2024 18:22:49 +0900 Subject: [PATCH 22/28] [ADD] lecture default colors --- .../java/in/koreatech/koin/compose/ui/Color.kt | 14 ++++++++++++++ .../koreatech/koin/ui/timetablev2/TimetableView.kt | 8 ++++++-- .../view/TimetableBottomSheetContent.kt | 3 ++- .../koin/ui/timetablev2/view/TimetableScreen.kt | 1 - .../ui/timetablev2/widget/TimetableAppWidget.kt | 8 ++------ .../koreatech/koin/util/ext/TimetableExtensions.kt | 4 ++-- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt b/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt index cf1904e8b..c1f7e036f 100644 --- a/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt +++ b/koin/src/main/java/in/koreatech/koin/compose/ui/Color.kt @@ -7,6 +7,20 @@ val ColorSecondary = Color(0xFFF7941E) val ColorMain400 = Color(0xFF4590BB) val ColorPrimaryMain400_ALPAH10 = Color(0x1a4590BB) +val defaultColors = listOf( + Color(0xfffdbcf5), + Color(0xfffdbcf5), + Color(0xfffedb8f), + Color(0xffc2eead), + Color(0xffffb588), + Color(0xffffa9b7), + Color(0xff8ae9ff), + Color(0xff60e4c1), + Color(0xffb4bfff), + Color(0xff72b0ff), + Color(0xffe0e5eb) +) + val basicColors = listOf( Color(0xFFBFC8D7), Color(0xFFE2D2D2), diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt index 5c2c5b137..afb3097f7 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableView.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.AbstractComposeView import androidx.lifecycle.viewmodel.compose.viewModel +import `in`.koreatech.koin.compose.ui.defaultColors import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.ui.timetablev2.view.Timetable @@ -33,14 +34,17 @@ class TimetableView @OptIn(ExperimentalMaterialApi::class) Timetable( isKeyboardVisible = state.isKeyboardVisible, - events = generateTimetableEvents(state.timetableEvents, emptyList()) , + events = generateTimetableEvents(state.timetableEvents), sheetState = sheetState, clickEvent = state.lectureEvents, onEventClick = onTimetableEventClickListener::onEventClick ) } - private fun generateTimetableEvents(timetableEvents: List, colors: List): List { + private fun generateTimetableEvents( + timetableEvents: List, + colors: List = defaultColors + ): List { val updateTimetableEvents = mutableListOf() timetableEvents.mapIndexed { index, lecture -> lecture.toTimetableEvents(index, colors) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt index 3723c8e01..ff582d588 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableBottomSheetContent.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.compose.ui.defaultColors import `in`.koreatech.koin.domain.model.timetable.Department import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent @@ -24,7 +25,7 @@ import `in`.koreatech.koin.ui.timetablev2.component.SearchBox fun TimetableBottomSheetContent( searchText: String, isKeyboardVisible: Boolean, - colors: List, + colors: List = defaultColors, lectures: List, selectedLectures: Lecture, currentDepartments: List, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt index df109e530..60d31f1d2 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableScreen.kt @@ -142,7 +142,6 @@ fun TimetableScreen( TimetableBottomSheetContent( searchText = state.searchText, isKeyboardVisible = state.isKeyboardVisible, - colors = emptyList(), lectures = state.lectures, selectedLectures = state.selectedLecture, currentDepartments = state.currentDepartments, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt index deefa5b42..50b9708e8 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/TimetableAppWidget.kt @@ -23,6 +23,7 @@ import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent import `in`.koreatech.koin.R import `in`.koreatech.koin.compose.ui.basicColors +import `in`.koreatech.koin.compose.ui.defaultColors import `in`.koreatech.koin.data.source.local.TokenLocalDataSource import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.domain.repository.TimetableRepository @@ -72,7 +73,7 @@ object TimetableAppWidget : GlanceAppWidget() { private fun generateTimetableEvents( lectures: List, - colors: List + colors: List = defaultColors ): List { val updatedTimetableEvents = mutableListOf() lectures.mapIndexed { index, lecture -> @@ -91,31 +92,26 @@ object TimetableAppWidget : GlanceAppWidget() { val mondays = generateTimetableEvents( timetableEvents, - basicColors ).filter { it.dayOfWeek == DayOfWeek.MONDAY } .sortedBy { it.start }.toTimeBlocks() val tuesdays = generateTimetableEvents( timetableEvents, - basicColors ).filter { it.dayOfWeek == DayOfWeek.TUESDAY } .sortedBy { it.start }.toTimeBlocks() val wednesdays = generateTimetableEvents( timetableEvents, - basicColors ).filter { it.dayOfWeek == DayOfWeek.WEDNESDAY } .sortedBy { it.start }.toTimeBlocks() val thursday = generateTimetableEvents( timetableEvents, - basicColors ).filter { it.dayOfWeek == DayOfWeek.THURSDAY } .sortedBy { it.start }.toTimeBlocks() val fridays = generateTimetableEvents( timetableEvents, - basicColors ).filter { it.dayOfWeek == DayOfWeek.FRIDAY } .sortedBy { it.start }.toTimeBlocks() diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt index e7814e252..d1468daf8 100644 --- a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt @@ -3,6 +3,7 @@ package `in`.koreatech.koin.util.ext import androidx.compose.ui.graphics.Color import `in`.koreatech.koin.compose.ui.ColorPrimary import `in`.koreatech.koin.compose.ui.basicColors +import `in`.koreatech.koin.compose.ui.defaultColors import `in`.koreatech.koin.domain.model.timetable.Lecture import `in`.koreatech.koin.model.timetable.TimetableEvent import java.time.LocalTime @@ -16,8 +17,7 @@ fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List= basicColors.size) ColorPrimary else basicColors[index ?: 0], + color = colors[(if (index != null) index + 1 else 0) % colors.size], dayOfWeek = key, start = value.firstOrNull() ?: LocalTime.of(0, 0), end = value.lastOrNull()?.plusMinutes(30) ?: LocalTime.of(0, 0), From 8e77bda8d8af49f2aa994448e1db585aad134372 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sat, 22 Jun 2024 20:25:22 +0900 Subject: [PATCH 23/28] [MOD] timetable widget label & invisible before timetable widget --- koin/src/main/AndroidManifest.xml | 28 ++++++++++--------- koin/src/main/res/values/strings.xml | 1 + .../res/xml/timetablev2_app_widget_info.xml | 1 - 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/koin/src/main/AndroidManifest.xml b/koin/src/main/AndroidManifest.xml index 9dea477a6..27ee939c8 100644 --- a/koin/src/main/AndroidManifest.xml +++ b/koin/src/main/AndroidManifest.xml @@ -102,7 +102,8 @@ android:value="false" /> + android:exported="true" + android:label="@string/add_widget_timetable"> @@ -111,18 +112,19 @@ android:resource="@xml/timetablev2_app_widget_info"/> - - - - - - - + + + + + + + + + + + + + Go to Permissions to Grant Storage EXAMPLE Add widget + 코인 시간표 오류 diff --git a/koin/src/main/res/xml/timetablev2_app_widget_info.xml b/koin/src/main/res/xml/timetablev2_app_widget_info.xml index e531ea0dd..551f2320e 100644 --- a/koin/src/main/res/xml/timetablev2_app_widget_info.xml +++ b/koin/src/main/res/xml/timetablev2_app_widget_info.xml @@ -1,6 +1,5 @@ Date: Sat, 22 Jun 2024 20:36:36 +0900 Subject: [PATCH 24/28] [MOD] lecture description --- .../java/in/koreatech/koin/model/timetable/TimeBlock.kt | 1 + .../in/koreatech/koin/model/timetable/TimetableEvent.kt | 5 +++-- .../koin/ui/timetablev2/view/TimetableEventTime.kt | 7 ------- .../ui/timetablev2/widget/view/TimetableWidgetContent.kt | 2 +- .../java/in/koreatech/koin/util/ext/TimetableExtensions.kt | 3 ++- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt b/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt index a48d7c15e..843e02280 100644 --- a/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt +++ b/koin/src/main/java/in/koreatech/koin/model/timetable/TimeBlock.kt @@ -11,4 +11,5 @@ data class TimeBlock( val endDuration: Float = 0f, val duration: Float = 0f, val color: Color? = null, + val description: String = "" ) \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt b/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt index c32e29ec2..294afd49a 100644 --- a/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt +++ b/koin/src/main/java/in/koreatech/koin/model/timetable/TimetableEvent.kt @@ -38,12 +38,13 @@ data class TimetableEvent( return TimeBlock( title = this.name, - start = start, + start = this.start, end = endTime, startDuration = startDuration, endDuration = endDuration, duration = duration, - color = this.color + color = this.color, + description = this.description ?: "" ) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt index aaf74c678..9c94d0ab0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt @@ -55,13 +55,6 @@ fun TimetableEventTime( Spacer(modifier = Modifier.height(2.dp)) when (eventType) { TimetableEventType.BASIC -> { - Text( - text = event.start.format(timetableEventTimeFormatter) - + " - " + - event.end.format(timetableEventTimeFormatter), - fontSize = 8.sp, - color = Color.Black, - ) Text( text = event.name, fontSize = 12.sp, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt index f9fc17882..3c3b513a1 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt @@ -105,7 +105,7 @@ fun TimetableWidgetContent( ) ) Text( - text = if (timeBlock?.start == null || timeBlock?.end == null) "" else "${timeBlock.start} - ${timeBlock.end}", + text = timeBlock?.description ?: "", style = TextStyle( fontSize = 8.sp, color = ColorProvider(Color.Black) diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt index d1468daf8..2f3fcef41 100644 --- a/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt +++ b/koin/src/main/java/in/koreatech/koin/util/ext/TimetableExtensions.kt @@ -14,6 +14,7 @@ fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List + val description = if (grades.length == 1) "0${grades} ${this.professor}" else "$grades $professor" val timetableEvent = TimetableEvent( id = id, name = name, @@ -21,7 +22,7 @@ fun Lecture.toTimetableEvents(index: Int? = null, colors: List): List Date: Tue, 25 Jun 2024 15:53:33 +0900 Subject: [PATCH 25/28] [MOD] timetable header size --- .../koreatech/koin/ui/timetablev2/component/SidebarLabel.kt | 4 +++- .../java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt | 3 +-- .../koin/ui/timetablev2/view/TimetableContentHeader.kt | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt index b715b2ec8..021e4fdb6 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt @@ -8,6 +8,7 @@ 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 java.time.LocalTime import java.time.format.DateTimeFormatter @@ -23,7 +24,8 @@ fun SidebarLabel( modifier = modifier .fillMaxSize() .padding(4.dp), - textAlign = TextAlign.End + textAlign = TextAlign.End, + fontSize = 12.sp ) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt index 6cf542635..41827d305 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt @@ -84,7 +84,7 @@ fun Timetable( ) Row( modifier = Modifier - .weight(1f) + .fillMaxWidth() ) { TimetableSidebar( modifier = Modifier @@ -94,7 +94,6 @@ fun Timetable( ) TimetableContent( modifier = Modifier - .weight(1f) .verticalScroll(verticalScrollState), clickEvent = clickEvent, eventContent = eventContent, diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt index 12206c993..113d308e1 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContentHeader.kt @@ -49,18 +49,17 @@ fun TimetableContentHeader( TimetableSaveButton( modifier = Modifier .padding(4.dp) - .weight(1f, fill = false) .fillMaxHeight() .background(color = ColorPrimary, shape = RoundedCornerShape(4.dp)) .padding(8.dp), onClick = onSavedImage ) - Spacer(modifier = Modifier.width(10.dp)) Icon( imageVector = Icons.Default.AddCircle, contentDescription = null, tint = ColorPrimary, modifier = Modifier + .padding(start = 4.dp, end = 8.dp) .size(30.dp) .clickable { onVisibleBottomSheet() From a65a88a56c3fab4f718f73669ff9b4a21ef84c68 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Tue, 25 Jun 2024 16:39:55 +0900 Subject: [PATCH 26/28] [MOD] timetable widget ui --- .../ui/timetablev2/widget/view/TimetableWidgetContent.kt | 7 +++---- .../ui/timetablev2/widget/view/TimetableWidgetScreen.kt | 1 + koin/src/main/res/xml/timetablev2_app_widget_info.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt index 3c3b513a1..f2901f0fc 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetContent.kt @@ -52,7 +52,7 @@ fun TimetableWidgetContent( Text( text = i.toString(), style = TextStyle( - fontSize = 14.sp, + fontSize = 12.sp, color = ColorProvider(Color.Black) ), modifier = GlanceModifier @@ -73,7 +73,6 @@ fun TimetableWidgetContent( ) .padding( top = ((timeBlock?.startDuration ?: 0f) * 60).dp, - end = 2.dp ) .background(imageProvider = ImageProvider(R.drawable.shape_timetable_row)) ) { @@ -91,7 +90,7 @@ fun TimetableWidgetContent( Box( modifier = GlanceModifier .fillMaxWidth() - .height(2.dp) + .height(1.dp) .background(Color.White) ) { @@ -99,7 +98,7 @@ fun TimetableWidgetContent( Text( text = timeBlock?.title ?: "", style = TextStyle( - fontSize = 12.sp, + fontSize = 10.sp, color = ColorProvider(Color.Black), fontWeight = FontWeight.Bold ) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt index 34d525e9e..f589d9146 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/widget/view/TimetableWidgetScreen.kt @@ -31,6 +31,7 @@ fun TimetableWidgetScreen( } item { TimetableWidgetContent( + modifier = GlanceModifier.fillMaxWidth(), timeWidth = timeWidth.toFloat(), timeBlocks = timeBlocks ) diff --git a/koin/src/main/res/xml/timetablev2_app_widget_info.xml b/koin/src/main/res/xml/timetablev2_app_widget_info.xml index 551f2320e..05139ac6d 100644 --- a/koin/src/main/res/xml/timetablev2_app_widget_info.xml +++ b/koin/src/main/res/xml/timetablev2_app_widget_info.xml @@ -1,9 +1,9 @@ \ No newline at end of file From 079002a2f0b51d884f89363d587b9f375316306b Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Wed, 26 Jun 2024 09:19:14 +0900 Subject: [PATCH 27/28] [ADD] empty event time click enabled false --- .../java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt | 6 ------ .../koin/ui/timetablev2/view/TimetableEventTime.kt | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt index 41827d305..53cb13cec 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt @@ -1,13 +1,10 @@ package `in`.koreatech.koin.ui.timetablev2.view -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -18,12 +15,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import `in`.koreatech.koin.model.timetable.TimetableEvent import `in`.koreatech.koin.model.timetable.TimetableEventType -import `in`.koreatech.koin.util.ext.pxToDp @OptIn(ExperimentalMaterialApi::class) @Composable @@ -66,7 +61,6 @@ fun Timetable( } else { 350.dp } -// sheetState.requireOffset().pxToDp } else { 0.dp } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt index 9c94d0ab0..df2ad49c0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableEventTime.kt @@ -49,7 +49,11 @@ fun TimetableEventTime( shape = RoundedCornerShape(4.dp) ) .padding(4.dp) - .clickable { onEventClick(event) } + .clickable( + enabled = if (eventType == TimetableEventType.SELECTED) false else true + ) { + onEventClick(event) + } ) { if (eventType == TimetableEventType.BASIC) Divider(color = Color.White, thickness = 1.dp) Spacer(modifier = Modifier.height(2.dp)) From ee9437e06f509d2cf9e49cf043834c66e9709f99 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 27 Jun 2024 17:26:15 +0900 Subject: [PATCH 28/28] [ADD] lecture click event trigger --- .../ui/timetablev2/component/SidebarLabel.kt | 2 +- .../koin/ui/timetablev2/view/Timetable.kt | 30 ++++++++++++++++--- .../ui/timetablev2/view/TimetableContent.kt | 11 ++++--- .../ui/timetablev2/view/TimetableHeader.kt | 4 ++- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt index 021e4fdb6..032d893ba 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/component/SidebarLabel.kt @@ -25,7 +25,7 @@ fun SidebarLabel( .fillMaxSize() .padding(4.dp), textAlign = TextAlign.End, - fontSize = 12.sp + fontSize = 14.sp ) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt index 53cb13cec..bcb99f4b2 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/Timetable.kt @@ -12,6 +12,11 @@ import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -37,13 +42,25 @@ fun Timetable( TimetableEventTime(event = event, eventType = eventType, onEventClick = onEventClick) }, ) { - val days = 5 - val dayWidth = 68.dp - val hourSidebarWidth = - (LocalContext.current.resources.displayMetrics.widthPixels / LocalContext.current.resources.displayMetrics.density).dp - (dayWidth * days) + val screenWidth = (LocalContext.current.resources.displayMetrics.widthPixels / LocalContext.current.resources.displayMetrics.density).dp +// val days = 5 +// val dayWidth = 68.dp + val dayWidth = screenWidth / 6 +// val hourSidebarWidth = +// (LocalContext.current.resources.displayMetrics.widthPixels / LocalContext.current.resources.displayMetrics.density).dp - (dayWidth * days) + val hourSidebarWidth = dayWidth val hourHeight = 64.dp + var scrollValue by remember { + mutableStateOf(0) + } val verticalScrollState = rememberScrollState() + LaunchedEffect(key1 = scrollValue) { + if (clickEvent.isNotEmpty()) { + verticalScrollState.scrollTo(scrollValue) + } + } + Column( modifier = modifier .background(Color.White) @@ -94,6 +111,11 @@ fun Timetable( events = events, dayWidth = dayWidth, hourHeight = hourHeight, + onEventY = { eventY -> + if (scrollValue != eventY) { + scrollValue = eventY + } + }, onEventClick = onEventClick ) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt index 17e1ff0a2..1295bce42 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableContent.kt @@ -27,6 +27,7 @@ fun TimetableContent( events: List, modifier: Modifier = Modifier, clickEvent: List = emptyList(), + onEventY: (Int) -> Unit, onEventClick: (event: TimetableEvent) -> Unit, eventContent: @Composable (event: TimetableEvent, eventType: TimetableEventType, onEventClick: (TimetableEvent) -> Unit) -> Unit = { event, eventType, onClick -> TimetableEventTime(event = event, eventType = eventType, onEventClick = onClick) @@ -103,7 +104,7 @@ fun TimetableContent( } layout(width, height) { - placeablesWithEvents.forEach { (placeable, event) -> + placeablesWithEvents.forEachIndexed { index, (placeable, event) -> val initStartTime = LocalTime.of(9, 0) val eventOffsetMinutes = ChronoUnit.MINUTES.between(initStartTime, event.start) @@ -118,6 +119,9 @@ fun TimetableContent( else -> -1 } val eventX = eventOffsetDays * dayWidth.roundToPx() + if (index == placeablesWithEvents.size - 1) { + onEventY(eventY) + } placeable.place(eventX, eventY) } } @@ -162,8 +166,7 @@ private fun TimetableContentPreview() { events = samples, dayWidth = 68.dp, hourHeight = 64.dp, - onEventClick = { - - } + onEventY = {}, + onEventClick = {} ) } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt index d569a5df1..08f7f6b38 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/view/TimetableHeader.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.ui.timetablev2.view import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -20,7 +21,8 @@ fun TimetableHeader( ) { Row( modifier = modifier - .background(Color.LightGray) + .background(Color.White) + .border(width = (0.5).dp, color = Color.LightGray) .padding(start = dayStartPadding) ) { val days = listOf("월", "화", "수", "목", "금")