Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
RznNike committed Jan 23, 2024
2 parents cd2f608 + e87844a commit 4f6b43e
Show file tree
Hide file tree
Showing 171 changed files with 9,745 additions and 998 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ In addition to this, other useful features are also available:
* analysis of collected data (for visual acuity test);
* import and export of the journal to a file;
* setting the correct image scaling during tests;
* light and dark themes with auto/manual selection;
* support for Russian and English languages.
## Screenshots
<img src="/readme_files/en/screenshot_1.png" alt="icon" width="250"/> <img src="/readme_files/en/screenshot_2.png" alt="icon" width="250"/> <img src="/readme_files/en/screenshot_3.png" alt="icon" width="250"/>
Expand All @@ -54,4 +55,4 @@ This project uses:
* [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) (charts)
* [ViewBindingPropertyDelegate](https://github.com/kirich1409/ViewBindingPropertyDelegate) (view binding)
* [ObjectBox](https://github.com/objectbox/objectbox-java) (database)
* [JUnit 5](https://junit.org/junit5/) (testing)
* [JUnit 5](https://github.com/junit-team/junit5) + [Mockito](https://github.com/mockito/mockito) (testing)
3 changes: 2 additions & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* анализ собранных данных (для теста остроты зрения);
* импорт и экспорт журнала в файл;
* настройка правильного масштабирования изображения во время тестов;
* светлая и темная темы с авто/ручным выбором;
* поддержка русского и английского языков.
## Скриншоты
<img src="/readme_files/ru/screenshot_1.png" alt="icon" width="250"/> <img src="/readme_files/ru/screenshot_2.png" alt="icon" width="250"/> <img src="/readme_files/ru/screenshot_3.png" alt="icon" width="250"/>
Expand All @@ -54,4 +55,4 @@
* [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) (графики)
* [ViewBindingPropertyDelegate](https://github.com/kirich1409/ViewBindingPropertyDelegate) (view binding)
* [ObjectBox](https://github.com/objectbox/objectbox-java) (база данных)
* [JUnit 5](https://junit.org/junit5/) (тестирование)
* [JUnit 5](https://github.com/junit-team/junit5) + [Mockito](https://github.com/mockito/mockito) (тестирование)
23 changes: 22 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ android {
abi.enableSplit = false
language.enableSplit = false
}
@Suppress("UnstableApiUsage")
testOptions {
unitTests.all {
it.useJUnitPlatform()
}
}
}

dependencies {
Expand All @@ -125,7 +131,7 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
Expand All @@ -136,6 +142,7 @@ dependencies {
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:" + rootProject.extra["coroutinesVersion"])
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:" + rootProject.extra["coroutinesVersion"])
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:" + rootProject.extra["coroutinesVersion"])

// Material
implementation("com.google.android.material:material:1.11.0")
Expand All @@ -148,6 +155,8 @@ dependencies {
// https://github.com/InsertKoinIO/koin
implementation("io.insert-koin:koin-core:" + rootProject.extra["koinVersion"])
implementation("io.insert-koin:koin-android:" + rootProject.extra["koinVersion"])
testImplementation("io.insert-koin:koin-test:" + rootProject.extra["koinVersion"])
testImplementation("io.insert-koin:koin-test-junit5:" + rootProject.extra["koinVersion"])

// Moxy MVP
// https://github.com/moxy-community/Moxy
Expand Down Expand Up @@ -191,4 +200,16 @@ dependencies {
// FlexboxLayoutManager
// https://github.com/google/flexbox-layout
implementation("com.google.android.flexbox:flexbox:3.0.0")

// Testing
// https://github.com/junit-team/junit5/
testImplementation("org.junit.jupiter:junit-jupiter:" + rootProject.extra["junitVersion"])

// Mocks for testing
// https://github.com/mockito/mockito
val mockitoVersion = "5.9.0"
testImplementation("org.mockito:mockito-core:$mockitoVersion")
testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion")
// https://github.com/mockito/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
android:resource="@drawable/ic_notification"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorNotification"/>
android:resource="@color/globalColorAccent"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="EyeHealthManagerNotifications"/>
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/ru/rznnike/eyehealthmanager/app/Screens.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ import ru.rznnike.eyehealthmanager.app.ui.fragment.settings.testing.TestingSetti
import ru.rznnike.eyehealthmanager.app.ui.fragment.splash.SplashFlowFragment
import ru.rznnike.eyehealthmanager.app.ui.fragment.splash.SplashFragment
import ru.rznnike.eyehealthmanager.domain.model.AcuityTestResult
import ru.rznnike.eyehealthmanager.domain.model.AnalysisParameters
import ru.rznnike.eyehealthmanager.domain.model.AnalysisResult
import ru.rznnike.eyehealthmanager.domain.model.enums.AstigmatismAnswerType
import ru.rznnike.eyehealthmanager.domain.model.enums.DaltonismAnomalyType
import ru.rznnike.eyehealthmanager.domain.model.enums.DayPart
import ru.rznnike.eyehealthmanager.domain.model.enums.NearFarAnswerType

Expand Down Expand Up @@ -119,7 +119,7 @@ object Screens {

fun daltonismResult(
errorsCount: Int,
resultType: String
resultType: DaltonismAnomalyType
) = DaltonismResultFragment::class.getFragmentScreen(
DaltonismResultFragment.ERRORS_COUNT to errorsCount,
DaltonismResultFragment.RESULT_TYPE to resultType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ import ru.rznnike.eyehealthmanager.app.observer.AppLifeCycleObserver
import ru.rznnike.eyehealthmanager.device.notification.Notificator
import ru.rznnike.eyehealthmanager.domain.global.CoroutineProvider
import ru.rznnike.eyehealthmanager.domain.global.DispatcherProvider
import java.time.Clock

val appModule = module {
factory { androidContext().resources }

factory { AppLifeCycleObserver() }
single { Notifier(get()) }
single { ErrorHandler(get(), get()) }
single { EventDispatcher() }
single { EventDispatcher(get()) }
single { ExternalIntentDispatcher(get()) }
single { Notificator(androidContext()) }
single<CrashlyticsProvider> { CrashlyticsProviderImpl() }
single { Clock.systemUTC() }

single<CoroutineProvider> {
object : CoroutineProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ package ru.rznnike.eyehealthmanager.app.di

import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import ru.rznnike.eyehealthmanager.data.storage.dao.AcuityTestDAO
import ru.rznnike.eyehealthmanager.data.storage.dao.AstigmatismTestDAO
import ru.rznnike.eyehealthmanager.data.storage.dao.ColorPerceptionTestDAO
import ru.rznnike.eyehealthmanager.data.storage.dao.ContrastTestDAO
import ru.rznnike.eyehealthmanager.data.storage.dao.DaltonismTestDAO
import ru.rznnike.eyehealthmanager.data.storage.dao.NearFarTestDAO
import ru.rznnike.eyehealthmanager.data.storage.dao.TestDAO
import ru.rznnike.eyehealthmanager.data.storage.entity.MyObjectBox
import ru.rznnike.eyehealthmanager.domain.storage.repository.TestRepository
import ru.rznnike.eyehealthmanager.data.storage.repository.TestRepositoryImpl

val databaseModule = module {
single { MyObjectBox.builder().androidContext(androidContext()).build() }
single<TestRepository> { TestRepositoryImpl(get()) }
single { TestDAO(get()) }
single { AcuityTestDAO(get()) }
single { AstigmatismTestDAO(get()) }
single { ColorPerceptionTestDAO(get()) }
single { ContrastTestDAO(get()) }
single { DaltonismTestDAO(get()) }
single { NearFarTestDAO(get()) }
single<TestRepository> { TestRepositoryImpl(get(), get(), get(), get(), get(), get(), get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import ru.rznnike.eyehealthmanager.domain.gateway.UserGateway
val gatewayModule = module {
single<UserGateway> { UserGatewayImpl(get()) }
single<NotificationGateway> { NotificationGatewayImpl() }
single<TestGateway> { TestGatewayImpl(get(), get()) }
single<AnalysisGateway> { AnalysisGatewayImpl(get()) }
single<DevGateway> { DevGatewayImpl(get()) }
single<TestGateway> { TestGatewayImpl(get(), get(), get()) }
single<AnalysisGateway> { AnalysisGatewayImpl(get(), get()) }
single<DevGateway> { DevGatewayImpl(get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import ru.rznnike.eyehealthmanager.domain.interactor.notification.ObserveCancelN
import ru.rznnike.eyehealthmanager.domain.interactor.notification.ObserveShowNotificationUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.test.*
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetAcuityTestingSettingsUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetAppThemeUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetApplyDynamicCorrectionsUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.SetUserLanguageUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetDisplayedChangelogVersionUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetTestingSettingsUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetUserLanguageUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.GetWelcomeDialogShowedUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.SetAcuityTestingSettingsUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.SetAppThemeUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.SetApplyDynamicCorrectionsUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.SetDisplayedChangelogVersionUseCase
import ru.rznnike.eyehealthmanager.domain.interactor.user.SetTestingSettingsUseCase
Expand All @@ -33,6 +35,8 @@ val interactorModule = module {
single { SetAcuityTestingSettingsUseCase(get(), get()) }
single { GetApplyDynamicCorrectionsUseCase(get(), get()) }
single { SetApplyDynamicCorrectionsUseCase(get(), get()) }
single { GetAppThemeUseCase(get(), get()) }
single { SetAppThemeUseCase(get(), get()) }

single { GetTestResultsUseCase(get(), get()) }
single { AddTestResultUseCase(get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.os.Build
import android.view.*
import android.view.Gravity
import android.view.View
import android.view.Window
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
Expand All @@ -29,8 +33,12 @@ import ru.rznnike.eyehealthmanager.app.dialog.bottom.BottomDialogParameters
import ru.rznnike.eyehealthmanager.app.ui.view.EmptyDividerDecoration
import ru.rznnike.eyehealthmanager.app.utils.extensions.addSystemWindowInsetToPadding
import ru.rznnike.eyehealthmanager.app.utils.extensions.deviceSize
import ru.rznnike.eyehealthmanager.app.utils.extensions.isNightModeEnabled
import ru.rznnike.eyehealthmanager.app.utils.extensions.toHtmlSpanned
import java.util.*
import ru.rznnike.eyehealthmanager.domain.utils.currentTimeMillis
import ru.rznnike.eyehealthmanager.domain.utils.millis
import ru.rznnike.eyehealthmanager.domain.utils.toDateTime
import ru.rznnike.eyehealthmanager.domain.utils.toLocalDate

fun Context.showAlertDialog(
parameters: AlertDialogParameters,
Expand Down Expand Up @@ -222,43 +230,37 @@ fun Context.showDatePicker(
onCancel: (() -> Unit)? = null,
onSuccess: (date: Long) -> Unit
) {
val currentCalendar = Calendar.getInstance().apply {
timeInMillis = preselectedDate ?: System.currentTimeMillis()
}
val currentDate = (preselectedDate ?: currentTimeMillis()).toLocalDate()
DatePickerDialog(
this,
android.R.style.ThemeOverlay_Material_Dialog,
R.style.AppTheme_Dialog_DatePicker,
{ _, year, month, dayOfMonth ->
if (enableTimePicker) {
showTimePicker(
preselectedTime = preselectedDate,
onCancel = onCancel,
onSuccess = { time ->
val selectedTime = Calendar.getInstance().apply {
timeInMillis = time
set(Calendar.YEAR, year)
set(Calendar.MONTH, month)
set(Calendar.DAY_OF_MONTH, dayOfMonth)
}.timeInMillis
onSuccess = { timestamp ->
val selectedTime = timestamp.toDateTime()
.withYear(year)
.withMonth(month + 1)
.withDayOfMonth(dayOfMonth)
.millis()
onSuccess(selectedTime)
}
)
} else {
val selectedTime = Calendar.getInstance().apply {
set(Calendar.YEAR, year)
set(Calendar.MONTH, month)
set(Calendar.DAY_OF_MONTH, dayOfMonth)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
val selectedTime = currentTimeMillis().toDateTime().toLocalDate()
.withYear(year)
.withMonth(month + 1)
.withDayOfMonth(dayOfMonth)
.atStartOfDay()
.millis()
onSuccess(selectedTime)
}
},
currentCalendar.get(Calendar.YEAR),
currentCalendar.get(Calendar.MONTH),
currentCalendar.get(Calendar.DAY_OF_MONTH)
currentDate.year,
currentDate.monthValue - 1,
currentDate.dayOfMonth
).apply {
maxDate?.let { datePicker.maxDate = it }
minDate?.let { datePicker.minDate = it }
Expand All @@ -270,25 +272,23 @@ fun Context.showDatePicker(
fun Context.showTimePicker(
preselectedTime: Long? = null,
onCancel: (() -> Unit)? = null,
onSuccess: (date: Long) -> Unit
onSuccess: (timestamp: Long) -> Unit
) {
val currentCalendar = Calendar.getInstance().apply {
timeInMillis = preselectedTime ?: System.currentTimeMillis()
}
val currentDate = (preselectedTime ?: currentTimeMillis()).toDateTime()
TimePickerDialog(
this,
android.R.style.ThemeOverlay_Material_Dialog,
R.style.AppTheme_Dialog_DatePicker,
{ _, hourOfDay, minute ->
val selectedTime = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, hourOfDay)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
val selectedTime = currentTimeMillis().toDateTime()
.withHour(hourOfDay)
.withMinute(minute)
.withSecond(0)
.withNano(0)
.millis()
onSuccess(selectedTime)
},
currentCalendar.get(Calendar.HOUR_OF_DAY),
currentCalendar.get(Calendar.MINUTE),
currentDate.hour,
currentDate.minute,
true
).apply {
setOnCancelListener { onCancel?.invoke() }
Expand Down Expand Up @@ -365,6 +365,7 @@ fun Fragment.showCustomBottomDialog(
}

private fun Window.setLightNavigationBar() = when {
context.isNightModeEnabled -> Unit // disable for dark theme
Build.VERSION.SDK_INT >= 30 -> {
insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package ru.rznnike.eyehealthmanager.app.dispatcher.event

import android.os.Handler
import android.os.Looper
import kotlinx.coroutines.launch
import ru.rznnike.eyehealthmanager.domain.global.CoroutineProvider
import java.util.*
import kotlin.collections.ArrayList
import kotlin.reflect.KClass

class EventDispatcher {
class EventDispatcher(
private val coroutineProvider: CoroutineProvider
) {
private val eventListeners = HashMap<String, MutableList<EventListener>>()

fun addEventListener(appEventClass: KClass<out AppEvent>, listener: EventListener): EventListener {
Expand Down Expand Up @@ -44,10 +45,8 @@ class EventDispatcher {
.filter { it.key == key && it.value.size > 0 }
.forEach {
it.value.forEach { listener ->
Handler(Looper.getMainLooper()).post {
listener.onEvent(
appEvent
)
coroutineProvider.scopeMain.launch {
listener.onEvent(appEvent)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ru.rznnike.eyehealthmanager.app.dispatcher.external

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
Expand All @@ -14,9 +13,7 @@ class ExternalIntentDispatcher(
ExternalIntentData.App().apply { processed = true }
)

fun subscribe(): Flow<ExternalIntentData> {
return eventsFlow.asStateFlow()
}
fun subscribe() = eventsFlow.asStateFlow()

fun send(data: ExternalIntentData) {
coroutineProvider.scopeIo.launch {
Expand Down
Loading

0 comments on commit 4f6b43e

Please sign in to comment.