diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 11f1c65..82284de 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -1,7 +1,9 @@ object KotlinDependencies { const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinVersion}" - const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesAndroidVersion}" - const val jsonSerialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinSerializationJsonVersion}" + const val coroutines = + "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesAndroidVersion}" + const val jsonSerialization = + "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinSerializationJsonVersion}" const val dateTime = "org.jetbrains.kotlinx:kotlinx-datetime:${Versions.kotlinDateTimeVersion}" } @@ -10,22 +12,26 @@ object AndroidXDependencies { const val splashScreen = "androidx.core:core-splashscreen:${Versions.splashVersion}" const val appCompat = "androidx.appcompat:appcompat:${Versions.appCompatVersion}" - const val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.constraintLayoutVersion}" + const val constraintLayout = + "androidx.constraintlayout:constraintlayout:${Versions.constraintLayoutVersion}" const val startup = "androidx.startup:startup-runtime:${Versions.appStartUpVersion}" const val fragment = "androidx.fragment:fragment-ktx:${Versions.fragmentKtxVersion}" const val legacy = "androidx.legacy:legacy-support-v4:${Versions.legacySupportVersion}" const val security = "androidx.security:security-crypto:${Versions.securityVersion}" - const val navigationFragment = "androidx.navigation:navigation-fragment-ktx:${Versions.navigationVersion}" + const val navigationFragment = + "androidx.navigation:navigation-fragment-ktx:${Versions.navigationVersion}" const val navigationUi = "androidx.navigation:navigation-ui-ktx:${Versions.navigationVersion}" const val lifeCycleKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycleVersion}" - const val lifecycleJava8 = "androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycleVersion}" + const val lifecycleJava8 = + "androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycleVersion}" const val workManager = "androidx.work:work-runtime-ktx:${Versions.workManagerVersion}" const val hiltWorkManager = "androidx.hilt:hilt-work:1.0.0" const val hilt = "com.google.dagger:hilt-android:${Versions.hiltVersion}" - const val ossLicense = "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}" + const val ossLicense = + "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}" } object TestDependencies { @@ -35,7 +41,8 @@ object TestDependencies { } object MaterialDesignDependencies { - const val materialDesign = "com.google.android.material:material:${Versions.materialDesignVersion}" + const val materialDesign = + "com.google.android.material:material:${Versions.materialDesignVersion}" } object KaptDependencies { @@ -48,23 +55,30 @@ object ThirdPartyDependencies { const val coil = "io.coil-kt:coil:${Versions.coilVersion}" const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofitVersion}" - const val retrofitJsonConverter = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:${Versions.kotlinSerializationConverterVersion}" + const val retrofitJsonConverter = + "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:${Versions.kotlinSerializationConverterVersion}" const val okHttpBom = "com.squareup.okhttp3:okhttp-bom:${Versions.okHttpVersion}" const val okHttp = "com.squareup.okhttp3:okhttp" const val okHttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor" const val timber = "com.jakewharton.timber:timber:${Versions.timberVersion}" - const val ossLicense = "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}" + const val ossLicense = + "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}" const val progressView = "com.github.skydoves:progressview:${Versions.progressViewVersion}" const val balloon = "com.github.skydoves:balloon:${Versions.balloonVersion}" const val lottie = "com.airbnb.android:lottie:${Versions.lottieVersion}" - const val circularProgressBar = "com.mikhaellopez:circularprogressbar:${Versions.circularProgressBar}" + const val circularProgressBar = + "com.mikhaellopez:circularprogressbar:${Versions.circularProgressBar}" const val circleIndicator = "me.relex:circleindicator:${Versions.circleIndicatorVersion}" const val shimmer = "com.facebook.shimmer:shimmer:${Versions.shimmerVersion}" } +object JitPackDependencies { + const val mpChart = "com.github.PhilJay:MPAndroidChart:${Versions.mpChartVersion}" +} + object ClassPathPlugins { const val gradle = "com.android.tools.build:gradle:${Versions.gradleVersion}" const val kotlinGradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinVersion}" diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index f64faaf..66e803d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -34,6 +34,8 @@ object Versions { const val circleIndicatorVersion = "2.1.6" const val shimmerVersion = "0.5.0" + const val mpChartVersion = "v3.1.0" + const val junitVersion = "4.13.2" const val espressoVersion = "3.5.1" const val androidTestVersion = "1.1.2" diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 3698195..4d975c5 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -85,4 +85,8 @@ dependencies { implementation(circularProgressBar) implementation(circleIndicator) } + + JitPackDependencies.run { + implementation(mpChart) + } } diff --git a/presentation/src/main/java/com/kkkk/presentation/main/report/ReportFragment.kt b/presentation/src/main/java/com/kkkk/presentation/main/report/ReportFragment.kt index 9fbf9d9..ec06a87 100644 --- a/presentation/src/main/java/com/kkkk/presentation/main/report/ReportFragment.kt +++ b/presentation/src/main/java/com/kkkk/presentation/main/report/ReportFragment.kt @@ -2,18 +2,140 @@ package com.kkkk.presentation.main.report import android.os.Bundle import android.view.View +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.formatter.ValueFormatter import com.kkkk.core.base.BaseFragment +import com.kkkk.core.extension.colorOf +import com.kkkk.core.extension.stringOf import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kr.genti.presentation.R import kr.genti.presentation.databinding.FragmentReportBinding +import java.text.SimpleDateFormat +import java.util.Locale @AndroidEntryPoint class ReportFragment : BaseFragment(R.layout.fragment_report) { + + private val viewModel by activityViewModels() + override fun onViewCreated( view: View, savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) + binding.vm = viewModel + observeReportMonth() + observeChartEntry() + } + + private fun observeReportMonth() { + viewModel.reportMonth.observe(viewLifecycleOwner) { month -> + binding.tvReportMonth.text = when (month) { + 1 -> stringOf(R.string.report_tv_month_1) + 3 -> stringOf(R.string.report_tv_month_3) + 6 -> stringOf(R.string.report_tv_month_6) + else -> return@observe + } + viewModel.setGraphValue() + } + } + + private fun observeChartEntry() { + viewModel.chartEntry.flowWithLifecycle(lifecycle).distinctUntilChanged().onEach { + if (it.isNotEmpty()) { + binding.chartReport.apply { + data = LineData( + LineDataSet( + viewModel.chartEntry.value, + CHART_REPORT + ).setDataSettings() + ) + invalidate() + } + setGraphSettings() + } + }.launchIn(lifecycleScope) + } + + private fun LineDataSet.setDataSettings(): LineDataSet { + this.apply { + color = colorOf(R.color.purple_50) + circleRadius = 8f + setCircleColor(colorOf(R.color.purple_50)) + setDrawFilled(true) + setDrawValues(false) + lineWidth = 4F + setDrawCircleHole(false) + fillColor = colorOf(R.color.purple_10) + fillAlpha = 255 + isHighlightEnabled = false + mode = LineDataSet.Mode.LINEAR + } + return this + } + + private fun setGraphSettings() { + binding.chartReport.apply { + setXAxisSettings(this) + setYAxisSettings(this) + setCommonSettings(this) + } + } + + private fun setXAxisSettings(chart: LineChart) { + chart.xAxis.apply { + valueFormatter = object : ValueFormatter() { + override fun getFormattedValue(value: Float): String? { + return if (value.toInt() in viewModel.nowList.indices) { + DATE_FORMAT.parse(viewModel.nowList[value.toInt()].second) + ?.let { DISPLAY_DATE_FORMAT.format(it) } + } else { + "" + } + } + } + position = XAxis.XAxisPosition.BOTTOM + granularity = 1f + axisMinimum = 0.7f + axisMaximum = viewModel.chartEntry.value.size - 0.8f + setDrawGridLines(false) + setDrawAxisLine(false) + textSize = 15f + textColor = colorOf(R.color.gray_600) + } + } + + private fun setYAxisSettings(chart: LineChart) { + chart.axisLeft.apply { + isEnabled = false + axisMaximum = 100f + } + chart.axisRight.isEnabled = false + } + + private fun setCommonSettings(chart: LineChart) { + chart.apply { + legend.isEnabled = false + description.isEnabled = false + setScaleEnabled(false) + setDragEnabled(false) + setExtraOffsets(0f, 0f, 0f, 20f) + } + } + + companion object { + private const val CHART_REPORT = "CHART_REPORT" + val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val DISPLAY_DATE_FORMAT = SimpleDateFormat("MM/dd", Locale.getDefault()) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/kkkk/presentation/main/report/ReportViewModel.kt b/presentation/src/main/java/com/kkkk/presentation/main/report/ReportViewModel.kt new file mode 100644 index 0000000..b2491d1 --- /dev/null +++ b/presentation/src/main/java/com/kkkk/presentation/main/report/ReportViewModel.kt @@ -0,0 +1,65 @@ +package com.kkkk.presentation.main.report + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.github.mikephil.charting.data.Entry +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@HiltViewModel +class ReportViewModel +@Inject +constructor( + // private val authRepository: AuthRepository, +) : ViewModel() { + + val isChangingMonth = MutableLiveData(false) + val reportMonth = MutableLiveData(3) + + val mockList = listOf( + Pair(10f, "2024-02-28"), + Pair(20f, "2024-03-28"), + Pair(20f, "2024-04-02"), + Pair(30f, "2024-04-29"), + Pair(10f, "2024-05-08"), + Pair(20f, "2024-05-18"), + Pair(20f, "2024-05-22"), + Pair(30f, "2024-06-09"), + Pair(100f, "2024-06-13"), + Pair(90f, "2024-06-23") + ) + var nowList = listOf>() + + private val _chartEntry = MutableStateFlow>(mutableListOf()) + val chartEntry: StateFlow> = _chartEntry + + init { + setGraphValue() + } + + fun setIsChangingMonth() { + isChangingMonth.value = isChangingMonth.value?.not() ?: false + } + + fun setReportMonth(month: Int) { + reportMonth.value = month + isChangingMonth.value = false + } + + fun setGraphValue() { + // TODO 서버 붙이고 수정 + nowList = when (reportMonth.value) { + 1 -> mockList.subList(5, 10) + 3 -> mockList.subList(2, 10) + 6 -> mockList.subList(0, 10) + else -> return + } + val tempList = arrayListOf() + nowList.forEachIndexed { index, (value, _) -> + tempList.add(Entry(index.toFloat(), value)) + } + _chartEntry.value = tempList + } +} \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_drop_down.xml b/presentation/src/main/res/drawable/ic_drop_down.xml new file mode 100644 index 0000000..0703360 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_drop_down.xml @@ -0,0 +1,16 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_drop_up.xml b/presentation/src/main/res/drawable/ic_drop_up.xml new file mode 100644 index 0000000..949cb69 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_drop_up.xml @@ -0,0 +1,16 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_next_purple.xml b/presentation/src/main/res/drawable/ic_next_purple.xml new file mode 100644 index 0000000..e7a67b4 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_next_purple.xml @@ -0,0 +1,13 @@ + + + diff --git a/presentation/src/main/res/drawable/shape_gray100_fill_8_rect.xml b/presentation/src/main/res/drawable/shape_gray100_fill_8_rect.xml new file mode 100644 index 0000000..35de093 --- /dev/null +++ b/presentation/src/main/res/drawable/shape_gray100_fill_8_rect.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/shape_gray300_dash_line.xml b/presentation/src/main/res/drawable/shape_gray300_dash_line.xml new file mode 100644 index 0000000..83fa19f --- /dev/null +++ b/presentation/src/main/res/drawable/shape_gray300_dash_line.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_report.xml b/presentation/src/main/res/layout/fragment_report.xml index 71cbc96..2669a5e 100644 --- a/presentation/src/main/res/layout/fragment_report.xml +++ b/presentation/src/main/res/layout/fragment_report.xml @@ -1,9 +1,16 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/chart_report"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index d172eac..d6f9f97 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -23,6 +23,14 @@ 단계 변경하기 완료하기 + 동안의 성과에요! + 달성한 업적 + 전체보기 + + 1개월 + 3개월 + 6개월 + 리듬 추천을 위해\n보행속도 정보가 필요해요\n진행하시겠어요? 보행속도 측정하기 @@ -31,4 +39,5 @@ 측정이 완료되었습니다 스템포를 시작해볼까요? + \ No newline at end of file