├── .gitignore
├── F-Droid.svg
├── IzzyOnDroid.png
├── LICENSE
├── README.md
├── RuStore.svg
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── schemas
│ ├── ru.application.homemedkit.data.MedicineDatabase
│ │ ├── 10.json
│ │ ├── 11.json
│ │ ├── 12.json
│ │ ├── 13.json
│ │ ├── 14.json
│ │ ├── 15.json
│ │ ├── 16.json
│ │ ├── 17.json
│ │ ├── 18.json
│ │ ├── 19.json
│ │ ├── 20.json
│ │ ├── 21.json
│ │ ├── 22.json
│ │ ├── 23.json
│ │ ├── 24.json
│ │ ├── 25.json
│ │ ├── 26.json
│ │ ├── 27.json
│ │ ├── 28.json
│ │ ├── 29.json
│ │ ├── 30.json
│ │ ├── 31.json
│ │ └── 9.json
│ └── ru.application.homemedkit.databaseController.MedicineDatabase
│ │ ├── 1.json
│ │ ├── 4.json
│ │ ├── 5.json
│ │ ├── 6.json
│ │ ├── 7.json
│ │ └── 8.json
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── ru
│ │ └── application
│ │ └── homemedkit
│ │ ├── HomeMeds.kt
│ │ ├── IntakeDialogActivity.kt
│ │ ├── MainActivity.kt
│ │ ├── data
│ │ ├── MedicineDatabase.kt
│ │ ├── dao
│ │ │ ├── AlarmDAO.kt
│ │ │ ├── BaseDAO.kt
│ │ │ ├── IntakeDAO.kt
│ │ │ ├── IntakeDayDAO.kt
│ │ │ ├── KitDAO.kt
│ │ │ ├── MedicineDAO.kt
│ │ │ └── TakenDAO.kt
│ │ ├── dto
│ │ │ ├── Alarm.kt
│ │ │ ├── Image.kt
│ │ │ ├── Intake.kt
│ │ │ ├── IntakeDay.kt
│ │ │ ├── IntakeTaken.kt
│ │ │ ├── IntakeTime.kt
│ │ │ ├── Kit.kt
│ │ │ ├── Medicine.kt
│ │ │ ├── MedicineKit.kt
│ │ │ └── Technical.kt
│ │ └── model
│ │ │ ├── Intake.kt
│ │ │ ├── IntakeAmountTime.kt
│ │ │ ├── IntakeFull.kt
│ │ │ ├── IntakeList.kt
│ │ │ ├── IntakeListScheme.kt
│ │ │ ├── IntakeModel.kt
│ │ │ ├── IntakePast.kt
│ │ │ ├── IntakeSchedule.kt
│ │ │ ├── IntakeTakenFull.kt
│ │ │ ├── MedicineFull.kt
│ │ │ ├── MedicineGrouped.kt
│ │ │ ├── MedicineIntake.kt
│ │ │ ├── MedicineList.kt
│ │ │ ├── MedicineMain.kt
│ │ │ ├── Schedule.kt
│ │ │ ├── ScheduleModel.kt
│ │ │ └── TakenModel.kt
│ │ ├── dialogs
│ │ ├── DatePicker.kt
│ │ ├── DateRangePicker.kt
│ │ ├── DraggableLazyColumn.kt
│ │ ├── MonthYearPickerDialog.kt
│ │ └── TimePickerDialog.kt
│ │ ├── helpers
│ │ ├── AppUtils.kt
│ │ ├── Constants.kt
│ │ ├── DataMatrixAnalyzer.kt
│ │ ├── FileManager.kt
│ │ ├── ImageCompressor.kt
│ │ ├── PhotoCapture.kt
│ │ ├── Preferences.kt
│ │ ├── ResourceText.kt
│ │ ├── enums
│ │ │ ├── DoseType.kt
│ │ │ ├── DrugType.kt
│ │ │ ├── FoodType.kt
│ │ │ ├── IntakeExtra.kt
│ │ │ ├── IntakeTab.kt
│ │ │ ├── Interval.kt
│ │ │ ├── MedicineTab.kt
│ │ │ ├── Menu.kt
│ │ │ ├── Period.kt
│ │ │ ├── SchemaType.kt
│ │ │ ├── Sorting.kt
│ │ │ └── Theme.kt
│ │ ├── extensions
│ │ │ ├── Context.kt
│ │ │ ├── Intake.kt
│ │ │ ├── Locale.kt
│ │ │ ├── Medicine.kt
│ │ │ ├── Navigation.kt
│ │ │ ├── Notification.kt
│ │ │ └── SharedPreferences.kt
│ │ └── permissions
│ │ │ ├── Permission.kt
│ │ │ └── PermissionState.kt
│ │ ├── models
│ │ ├── events
│ │ │ ├── IntakeEvent.kt
│ │ │ ├── MedicineEvent.kt
│ │ │ ├── Response.kt
│ │ │ └── TakenEvent.kt
│ │ ├── states
│ │ │ ├── CameraState.kt
│ │ │ ├── IntakeState.kt
│ │ │ ├── IntakesState.kt
│ │ │ ├── MedicineState.kt
│ │ │ ├── MedicinesState.kt
│ │ │ ├── ScannerState.kt
│ │ │ ├── TakenState.kt
│ │ │ └── TechnicalState.kt
│ │ ├── validation
│ │ │ ├── Validation.kt
│ │ │ └── ValidationResult.kt
│ │ └── viewModels
│ │ │ ├── IntakeViewModel.kt
│ │ │ ├── IntakesViewModel.kt
│ │ │ ├── MedicineViewModel.kt
│ │ │ ├── MedicinesViewModel.kt
│ │ │ └── ScannerViewModel.kt
│ │ ├── network
│ │ ├── Network.kt
│ │ └── models
│ │ │ ├── MainModel.kt
│ │ │ ├── bio
│ │ │ ├── BioData.kt
│ │ │ └── ProductProperty.kt
│ │ │ └── medicine
│ │ │ ├── DrugsData.kt
│ │ │ ├── Foiv.kt
│ │ │ └── VidalData.kt
│ │ ├── receivers
│ │ ├── ActionReceiver.kt
│ │ ├── AlarmReceiver.kt
│ │ ├── AlarmSetter.kt
│ │ ├── BootReceiver.kt
│ │ ├── ExpirationReceiver.kt
│ │ ├── LocaleReceiver.kt
│ │ └── PreAlarmReceiver.kt
│ │ └── ui
│ │ ├── elements
│ │ └── Box.kt
│ │ ├── navigation
│ │ ├── Navigation.kt
│ │ └── Screen.kt
│ │ ├── screens
│ │ ├── CameraPreview.kt
│ │ ├── IntakeScreen.kt
│ │ ├── IntakesScreen.kt
│ │ ├── MedicineScreen.kt
│ │ ├── MedicinesScreen.kt
│ │ ├── ScannerScreen.kt
│ │ └── SettingsScreen.kt
│ │ └── theme
│ │ ├── Color.kt
│ │ └── Theme.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_foreground.xml
│ ├── ic_launcher_monochrome.xml
│ ├── ic_launcher_notification.xml
│ ├── vector_add_photo.xml
│ ├── vector_barcode.xml
│ ├── vector_bell.xml
│ ├── vector_camera.xml
│ ├── vector_check.xml
│ ├── vector_datamatrix.xml
│ ├── vector_filter.xml
│ ├── vector_flash.xml
│ ├── vector_medicine.xml
│ ├── vector_period.xml
│ ├── vector_remove.xml
│ ├── vector_scanner.xml
│ ├── vector_settings.xml
│ ├── vector_sort.xml
│ ├── vector_time.xml
│ ├── vector_type_aerosol.xml
│ ├── vector_type_bandage.xml
│ ├── vector_type_capsule.xml
│ ├── vector_type_decoction.xml
│ ├── vector_type_dragee.xml
│ ├── vector_type_drops.xml
│ ├── vector_type_emulsion.xml
│ ├── vector_type_extract.xml
│ ├── vector_type_gel.xml
│ ├── vector_type_granules.xml
│ ├── vector_type_mix.xml
│ ├── vector_type_mixture.xml
│ ├── vector_type_napkins.xml
│ ├── vector_type_nasal_spray.xml
│ ├── vector_type_ointment.xml
│ ├── vector_type_paste.xml
│ ├── vector_type_patch.xml
│ ├── vector_type_pills.xml
│ ├── vector_type_powder.xml
│ ├── vector_type_sachet.xml
│ ├── vector_type_solution.xml
│ ├── vector_type_suppository.xml
│ ├── vector_type_suspension.xml
│ ├── vector_type_syrup.xml
│ ├── vector_type_tablets.xml
│ ├── vector_type_tincture.xml
│ ├── vector_type_unknown.xml
│ └── vector_wrong.xml
│ ├── mipmap
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── resources.properties
│ ├── values-de
│ └── strings.xml
│ ├── values-es
│ └── strings.xml
│ ├── values-fr
│ └── strings.xml
│ ├── values-in
│ └── strings.xml
│ ├── values-it
│ └── strings.xml
│ ├── values-nl
│ └── strings.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-ru
│ └── strings.xml
│ ├── values-ta
│ └── strings.xml
│ ├── values-tr
│ └── strings.xml
│ ├── values-vi
│ └── strings.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values
│ └── strings.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ ├── de-DE
│ ├── changelogs
│ │ └── 54.txt
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
│ ├── en-US
│ ├── changelogs
│ │ └── 54.txt
│ ├── full_description.txt
│ ├── images
│ │ ├── featureGraphic.png
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── 01.jpg
│ │ │ ├── 02.jpg
│ │ │ ├── 03.jpg
│ │ │ ├── 04.jpg
│ │ │ ├── 05.jpg
│ │ │ ├── 06.jpg
│ │ │ ├── 07.jpg
│ │ │ ├── 08.jpg
│ │ │ ├── 09.jpg
│ │ │ └── 10.jpg
│ ├── short_description.txt
│ └── title.txt
│ ├── id
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
│ ├── pt-BR
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
│ └── ru
│ ├── changelogs
│ └── 54.txt
│ ├── full_description.txt
│ ├── images
│ └── phoneScreenshots
│ │ ├── 01.jpg
│ │ ├── 02.jpg
│ │ ├── 03.jpg
│ │ ├── 04.jpg
│ │ ├── 05.jpg
│ │ ├── 06.jpg
│ │ ├── 07.jpg
│ │ ├── 08.jpg
│ │ ├── 09.jpg
│ │ └── 10.jpg
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea/
5 | misc.xml
6 | deploymentTargetDropDown.xml
7 | .DS_Store
8 | /build
9 | /app/release
10 | /captures
11 | .externalNativeBuild
12 | .cxx
13 | local.properties
14 | *.apk
15 | output.json
--------------------------------------------------------------------------------
/IzzyOnDroid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/IzzyOnDroid.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | # Домашняя аптечка
6 |
7 | Домашняя аптечка — это приложение, предназначенное для удобного хранения и управления вашими лекарствами.
8 |
9 |
10 |
11 |
12 |
13 | [

](https://www.rustore.ru/catalog/app/ru.application.homemedkit)
14 | [

](https://apt.izzysoft.de/fdroid/index/apk/ru.application.homemedkit)
15 | [

](https://f-droid.org/packages/ru.application.homemedkit)
16 |
17 |
18 |
19 |
20 |
21 | # Описание
22 |
23 | С помощью приложения вы сможете отслеживать сроки годности лекарств, создавать график их приёма и получать уведомления о нём.
24 | Это надёжный и удобный помощник, который поможет вам следить за вашим здоровьем.
25 |
26 | # Основные функции
27 |
28 | :white_check_mark: Автоматическое добавление лекарства при помощи сканирования маркировки.\
29 | :white_check_mark: Возможность добавления лекарства вручную.\
30 | :white_check_mark: Обновление информации о лекарстве, если оно было отсканировано без подключения к интернету.\
31 | :white_check_mark: Редактирование информации о лекарствах, добавленных вручную.\
32 | :white_check_mark: Добавление изображений лекарств.\
33 | :white_check_mark: Сортировка лекарств по срокам годности.\
34 | :white_check_mark: Группировка лекарств по категориям.\
35 | :white_check_mark: Звуковое уведомление о необходимости приема лекарства.\
36 | :white_check_mark: Отображение ближайшего расписания приёма лекарств.\
37 | :white_check_mark: Напоминание об истечении срока годности лекарств.\
38 | :white_check_mark: Экспорт и импорт базы данных на другие устройства.
39 |
40 | # Medkit (English)
41 |
42 | An application that allows you to easily and quickly register and store information about medicine.
43 | With it, you can track the expiration dates of medications, create a schedule for their intake and receive notifications about it.
44 | It is a reliable and convenient assistant that will help you monitor your health.
45 |
46 | # Main features
47 |
48 | :white_check_mark: Automatic addition of medications by scanning the label.\
49 | :white_check_mark: The ability to add medications manually.\
50 | :white_check_mark: Update information about the medication if it was scanned without an internet connection.\
51 | :white_check_mark: Edit information about medications added manually.\
52 | :white_check_mark: Adding medications images.\
53 | :white_check_mark: Sorting of medications by expiration date.\
54 | :white_check_mark: Grouping of medications by categories.\
55 | :white_check_mark: Notifications for when to take medications.\
56 | :white_check_mark: Display of the nearest medications to be taken.\
57 | :white_check_mark: Reminder of the expiration date of medications.\
58 | :white_check_mark: Export and import the database to other devices.
59 |
60 |
61 |
62 | # Translations
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | local.properties
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android)
3 | alias(libs.plugins.compose)
4 | alias(libs.plugins.kotlin)
5 | alias(libs.plugins.kotlin.serialization)
6 | alias(libs.plugins.ksp)
7 | alias(libs.plugins.room)
8 | }
9 |
10 | android {
11 | namespace = "ru.application.homemedkit"
12 | compileSdk = 35
13 |
14 | defaultConfig {
15 | applicationId = "ru.application.homemedkit"
16 | minSdk = 26
17 | targetSdk = 35
18 | versionCode = 54
19 | versionName = "1.7.9"
20 | }
21 |
22 | dependenciesInfo {
23 | includeInApk = false
24 | includeInBundle = false
25 | }
26 |
27 | androidResources {
28 | generateLocaleConfig = true
29 | }
30 |
31 | room {
32 | schemaDirectory("$projectDir/schemas")
33 | }
34 |
35 | buildTypes {
36 | release {
37 | isMinifyEnabled = false
38 | proguardFiles(
39 | getDefaultProguardFile("proguard-android-optimize.txt"),
40 | "proguard-rules.pro"
41 | )
42 | }
43 | }
44 | compileOptions {
45 | sourceCompatibility = JavaVersion.VERSION_17
46 | targetCompatibility = JavaVersion.VERSION_17
47 | }
48 | kotlinOptions {
49 | jvmTarget = "17"
50 | }
51 | buildFeatures {
52 | compose = true
53 | }
54 | }
55 |
56 | dependencies {
57 | // ==================== Android ====================
58 | implementation(libs.androidx.lifecycle.viewmodel.compose)
59 | implementation(libs.androidx.lifecycle.runtime.compose)
60 | implementation(libs.androidx.material3)
61 |
62 | // ==================== Room ====================
63 | ksp(libs.androidx.room.compiler)
64 | implementation(libs.androidx.room.runtime)
65 | implementation(libs.androidx.room.ktx)
66 |
67 | // ==================== Network ====================
68 | implementation(libs.bundles.ktor)
69 |
70 | // ==================== Navigation ====================
71 | implementation(libs.androidx.navigation.compose)
72 | implementation(libs.kotlinx.serialization.json)
73 |
74 | // ==================== Scanner ====================
75 | implementation(libs.bundles.camera)
76 | implementation(libs.zxing)
77 |
78 | // ==================== Settings ====================
79 | implementation(libs.material.preferences)
80 |
81 | // ==================== Coil ====================
82 | implementation(libs.coil.compose)
83 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
55 |
56 |
59 |
60 |
63 |
64 |
67 |
68 |
72 |
73 |
74 |
75 |
76 |
77 |
80 |
81 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/HomeMeds.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit
2 |
3 | import android.app.Application
4 | import coil3.ImageLoader
5 | import coil3.PlatformContext
6 | import coil3.SingletonImageLoader
7 | import coil3.disk.DiskCache
8 | import coil3.disk.directory
9 | import coil3.memory.MemoryCache
10 | import coil3.request.CachePolicy
11 | import coil3.request.crossfade
12 | import ru.application.homemedkit.R.string.channel_exp_desc
13 | import ru.application.homemedkit.R.string.channel_intakes_desc
14 | import ru.application.homemedkit.R.string.channel_pre_desc
15 | import ru.application.homemedkit.data.MedicineDatabase
16 | import ru.application.homemedkit.helpers.CHANNEL_ID_EXP
17 | import ru.application.homemedkit.helpers.CHANNEL_ID_INTAKES
18 | import ru.application.homemedkit.helpers.CHANNEL_ID_PRE
19 | import ru.application.homemedkit.helpers.Preferences
20 | import ru.application.homemedkit.helpers.extensions.createNotificationChannel
21 |
22 |
23 | class HomeMeds : Application(), SingletonImageLoader.Factory {
24 |
25 | companion object {
26 | lateinit var database: MedicineDatabase
27 | }
28 |
29 | override fun onCreate() {
30 | super.onCreate()
31 |
32 | Preferences.getInstance(this)
33 | database = MedicineDatabase.getInstance(this)
34 | mapOf(
35 | CHANNEL_ID_INTAKES to channel_intakes_desc,
36 | CHANNEL_ID_PRE to channel_pre_desc,
37 | CHANNEL_ID_EXP to channel_exp_desc
38 | ).forEach { (id, name) -> createNotificationChannel(id, name) }
39 | }
40 |
41 | override fun newImageLoader(context: PlatformContext) = ImageLoader(context).newBuilder()
42 | .crossfade(200)
43 | .memoryCachePolicy(CachePolicy.ENABLED)
44 | .memoryCache {
45 | MemoryCache.Builder()
46 | .maxSizePercent(context, 0.3)
47 | .strongReferencesEnabled(true)
48 | .build()
49 | }
50 | .diskCachePolicy(CachePolicy.ENABLED)
51 | .diskCache {
52 | DiskCache.Builder()
53 | .maxSizeBytes(32 * 1024 * 1024L)
54 | .directory(context.cacheDir)
55 | .build()
56 | }.build()
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/IntakeDialogActivity.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit
2 |
3 | import android.os.Bundle
4 | import android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
5 | import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
6 | import androidx.activity.ComponentActivity
7 | import androidx.activity.compose.setContent
8 | import androidx.compose.material3.AlertDialog
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Text
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.core.app.NotificationManagerCompat
14 | import ru.application.homemedkit.R.string.intake_text_not_taken
15 | import ru.application.homemedkit.R.string.intake_text_taken
16 | import ru.application.homemedkit.R.string.text_do_intake
17 | import ru.application.homemedkit.R.string.text_intake_time
18 | import ru.application.homemedkit.data.MedicineDatabase
19 | import ru.application.homemedkit.helpers.BLANK
20 | import ru.application.homemedkit.helpers.ID
21 | import ru.application.homemedkit.helpers.TAKEN_ID
22 | import ru.application.homemedkit.helpers.decimalFormat
23 | import ru.application.homemedkit.ui.theme.AppTheme
24 |
25 |
26 | class IntakeDialogActivity : ComponentActivity() {
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 |
30 | window.addFlags(FLAG_KEEP_SCREEN_ON or FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
31 |
32 | val database = MedicineDatabase.getInstance(this)
33 | val takenDAO = database.takenDAO()
34 |
35 | val medicineId = intent.getLongExtra(ID, 0L)
36 | val takenId = intent.getLongExtra(TAKEN_ID, 0L)
37 | val amount = intent.getDoubleExtra(BLANK, 0.0)
38 |
39 | val medicine = database.medicineDAO().getById(medicineId) ?: return
40 |
41 | setContent {
42 | AppTheme {
43 | AlertDialog(
44 | title = { Text(stringResource(text_do_intake)) },
45 | onDismissRequest = {
46 | takenDAO.setNotified(takenId)
47 | NotificationManagerCompat.from(this).cancel(takenId.toInt())
48 | finishAndRemoveTask()
49 | },
50 | dismissButton = {
51 | Button(
52 | onClick = {
53 | takenDAO.setNotified(takenId)
54 | NotificationManagerCompat.from(this).cancel(takenId.toInt())
55 | finishAndRemoveTask()
56 | }
57 | ) { Text(stringResource(intake_text_not_taken)) }
58 | },
59 | confirmButton = {
60 | Button(
61 | onClick = {
62 | takenDAO.setNotified(takenId)
63 | takenDAO.setTaken(takenId, true, System.currentTimeMillis())
64 | database.medicineDAO().intakeMedicine(medicineId, amount)
65 | NotificationManagerCompat.from(this).cancel(takenId.toInt())
66 | finishAndRemoveTask()
67 | }
68 | ) { Text(stringResource(intake_text_taken)) }
69 | },
70 | text = {
71 | Text(
72 | style = MaterialTheme.typography.bodyLarge,
73 | text = stringResource(
74 | text_intake_time,
75 | medicine.nameAlias.ifEmpty(medicine::productName),
76 | decimalFormat(amount),
77 | stringResource(medicine.doseType.title),
78 | decimalFormat(medicine.prodAmount.minus(amount))
79 | )
80 | )
81 | }
82 | )
83 | }
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.Scaffold
9 | import androidx.compose.runtime.LaunchedEffect
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.navigation.compose.currentBackStackEntryAsState
15 | import androidx.navigation.compose.rememberNavController
16 | import ru.application.homemedkit.helpers.KEY_EXP_IMP
17 | import ru.application.homemedkit.helpers.Preferences
18 | import ru.application.homemedkit.helpers.extensions.showToast
19 | import ru.application.homemedkit.helpers.extensions.toBottomBarItem
20 | import ru.application.homemedkit.receivers.AlarmSetter
21 | import ru.application.homemedkit.ui.navigation.BottomNavigationBar
22 | import ru.application.homemedkit.ui.navigation.Navigation
23 | import ru.application.homemedkit.ui.theme.AppTheme
24 |
25 | class MainActivity : ComponentActivity() {
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 |
29 | setContent {
30 | val navigator = rememberNavController()
31 | val backStack by navigator.currentBackStackEntryAsState()
32 |
33 | AppTheme {
34 | Scaffold(
35 | bottomBar = { BottomNavigationBar(backStack, navigator::toBottomBarItem) },
36 | content = { Navigation(navigator, Modifier.padding(it)) }
37 | )
38 | }
39 |
40 | val imported by remember {
41 | mutableStateOf(intent.getBooleanExtra(KEY_EXP_IMP, false))
42 | }
43 |
44 | LaunchedEffect(imported) {
45 | if (imported) {
46 | showToast(R.string.text_success)
47 | AlarmSetter(this@MainActivity).resetAll()
48 | }
49 | }
50 | }
51 | }
52 |
53 | override fun attachBaseContext(newBase: Context?) {
54 | super.attachBaseContext(Preferences.changeLanguage(newBase))
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/AlarmDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import androidx.room.Transaction
6 | import kotlinx.coroutines.flow.Flow
7 | import ru.application.homemedkit.data.dto.Alarm
8 | import ru.application.homemedkit.data.model.Schedule
9 |
10 | @Dao
11 | interface AlarmDAO : BaseDAO {
12 | @Transaction
13 | @Query(
14 | """
15 | SELECT alarms.alarmId, alarms.`trigger`, alarms.amount, images.image,
16 | medicines.nameAlias, medicines.productName, medicines.prodFormNormName, medicines.doseType
17 | FROM alarms
18 | JOIN intakes ON intakes.intakeId = alarms.intakeId
19 | JOIN medicines ON medicines.id = intakes.medicineId
20 | JOIN images ON images.medicineId = medicines.id
21 | WHERE (:search = '' OR LOWER(medicines.productName) LIKE '%' || LOWER(:search) || '%')
22 | GROUP BY alarms.alarmId
23 | """
24 | )
25 | fun getFlow(search: String): Flow>
26 |
27 | @Query("SELECT * FROM alarms")
28 | fun getAll(): List
29 |
30 | @Query("SELECT * FROM alarms WHERE alarmId = :alarmId")
31 | fun getById(alarmId: Long): Alarm?
32 |
33 | @Query(
34 | """
35 | SELECT * FROM alarms
36 | WHERE intakeId = :intakeId
37 | ORDER BY `trigger`
38 | LIMIT 1
39 | """
40 | )
41 | fun getNextByIntakeId(intakeId: Long): Alarm?
42 |
43 | @Query("DELETE FROM alarms WHERE intakeId = :intakeId")
44 | suspend fun deleteByIntakeId(intakeId: Long)
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/BaseDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Delete
4 | import androidx.room.Insert
5 | import androidx.room.Update
6 | import androidx.room.Upsert
7 |
8 | interface BaseDAO {
9 | @Insert
10 | suspend fun insert(item: T): Long
11 |
12 | @Insert
13 | suspend fun insert(items: Iterable)
14 |
15 | @Update
16 | suspend fun update(item: T)
17 |
18 | @Upsert
19 | suspend fun upsert(item: T)
20 |
21 | @Delete
22 | suspend fun delete(item: T)
23 |
24 | @Delete
25 | suspend fun delete(items: Iterable)
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/IntakeDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.Query
6 | import androidx.room.Transaction
7 | import kotlinx.coroutines.flow.Flow
8 | import ru.application.homemedkit.data.dto.Alarm
9 | import ru.application.homemedkit.data.dto.Intake
10 | import ru.application.homemedkit.data.dto.IntakeTime
11 | import ru.application.homemedkit.data.model.IntakeFull
12 | import ru.application.homemedkit.data.model.IntakeList
13 |
14 | @Dao
15 | interface IntakeDAO : BaseDAO {
16 | // ============================== Queries ==============================
17 | @Transaction
18 | @Query(
19 | """
20 | SELECT intakeId, medicineId, interval, finalDate, productName, nameAlias
21 | FROM intakes
22 | JOIN medicines ON medicines.id = intakes.medicineId
23 | WHERE (:searchQuery = '' OR LOWER(productName) LIKE '%' || LOWER(:searchQuery) || '%'
24 | OR LOWER(nameAlias) LIKE '%' || LOWER(:searchQuery) || '%')
25 | """
26 | )
27 | fun getFlow(searchQuery: String): Flow>
28 |
29 | @Query("SELECT * FROM intakes WHERE intakeId = :intakeId")
30 | fun getById(intakeId: Long): IntakeFull?
31 |
32 | @Query("SELECT * FROM alarms WHERE intakeId = :intakeId")
33 | fun getAlarms(intakeId: Long): List
34 |
35 | // ============================== Insert ==============================
36 | @Insert
37 | suspend fun addIntakeTime(intakeTime: IntakeTime): Long
38 |
39 | // ============================== Delete ==============================
40 | @Query("DELETE FROM intake_time WHERE intakeId = :intakeId")
41 | suspend fun deleteIntakeTime(intakeId: Long)
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/IntakeDayDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import ru.application.homemedkit.data.dto.IntakeDay
6 |
7 | @Dao
8 | interface IntakeDayDAO : BaseDAO {
9 | @Query("DELETE FROM intake_days WHERE intakeId = :intakeId")
10 | fun deleteByIntakeId(intakeId: Long)
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/KitDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.Query
6 | import androidx.room.Upsert
7 | import kotlinx.coroutines.flow.Flow
8 | import ru.application.homemedkit.data.dto.Kit
9 | import ru.application.homemedkit.data.dto.MedicineKit
10 |
11 | @Dao
12 | interface KitDAO : BaseDAO {
13 | // ============================== Queries ==============================
14 | @Query("SELECT * FROM kits ORDER BY position ASC, kitId ASC")
15 | fun getFlow(): Flow>
16 |
17 | @Query("SELECT * FROM kits WHERE kitId IN (:kitIds)")
18 | suspend fun getKitList(kitIds: List): List
19 |
20 | @Query("DELETE FROM medicines_kits WHERE medicineId = :medicineId")
21 | suspend fun deleteAll(medicineId: Long)
22 |
23 | // ============================== Insert ==============================
24 | @Insert
25 | suspend fun pinKit(kits: Iterable)
26 |
27 | @Upsert
28 | suspend fun updatePositions(kits: Iterable)
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/MedicineDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.Query
6 | import androidx.room.Transaction
7 | import kotlinx.coroutines.flow.Flow
8 | import ru.application.homemedkit.data.dto.Image
9 | import ru.application.homemedkit.data.dto.Medicine
10 | import ru.application.homemedkit.data.model.MedicineFull
11 | import ru.application.homemedkit.data.model.MedicineMain
12 | import ru.application.homemedkit.helpers.enums.Sorting
13 |
14 | @Dao
15 | interface MedicineDAO : BaseDAO {
16 | // ============================== Queries ==============================
17 | @Query("SELECT * FROM medicines")
18 | fun getAll(): List
19 |
20 | @Transaction
21 | @Query(
22 | """
23 | SELECT id, productName, nameAlias, prodAmount, doseType, expDate, prodFormNormName, structure, phKinetics
24 | FROM medicines
25 | WHERE (:search = '' OR LOWER(productName) LIKE '%' || LOWER(:search) || '%'
26 | OR LOWER(nameAlias) LIKE '%' || LOWER(:search) || '%'
27 | OR LOWER(structure) LIKE '%' || LOWER(:search) || '%'
28 | OR LOWER(phKinetics) LIKE '%' || LOWER(:search) || '%')
29 | AND (:kitsEnabled = 0 OR EXISTS (
30 | SELECT 1
31 | FROM medicines_kits
32 | WHERE medicines_kits.medicineId = medicines.id
33 | AND medicines_kits.kitId IN (:kitIds)
34 | ))
35 | ORDER BY
36 | CASE WHEN :sorting = 'IN_NAME' THEN (CASE WHEN nameAlias = '' THEN productName ELSE nameAlias END) COLLATE NOCASE ELSE NULL END ASC,
37 | CASE WHEN :sorting = 'RE_NAME' THEN (CASE WHEN nameAlias = '' THEN productName ELSE nameAlias END) COLLATE NOCASE ELSE NULL END DESC,
38 | CASE WHEN :sorting = 'IN_DATE' THEN expDate ELSE NULL END ASC,
39 | CASE WHEN :sorting = 'RE_DATE' THEN expDate ELSE NULL END DESC,
40 | id ASC -- Дополнительная сортировка для разрешения конфликтов (id уникальный)
41 | """
42 | )
43 | fun getListFlow(search: String, sorting: Sorting, kitIds: List, kitsEnabled: Boolean): Flow>
44 |
45 | @Query("SELECT id FROM medicines where cis = :cis")
46 | fun getIdByCis(cis: String): Long
47 |
48 | @Query("SELECT * FROM medicines WHERE id = :id ")
49 | fun getById(id: Long): MedicineFull?
50 |
51 | @Query("SELECT cis from medicines")
52 | fun getAllCis(): List
53 |
54 | @Query("SELECT image FROM images")
55 | fun getAllImages(): List
56 |
57 | @Query("SELECT image FROM images WHERE medicineId = :medicineId")
58 | fun getMedicineImages(medicineId: Long): List
59 |
60 | @Query("UPDATE medicines SET prodAmount = prodAmount - :amount WHERE id = :id")
61 | fun intakeMedicine(id: Long, amount: Double)
62 |
63 | @Query("UPDATE medicines SET prodAmount = prodAmount + :amount WHERE id = :id")
64 | fun untakeMedicine(id: Long, amount: Double)
65 |
66 | // ============================== Insert ==============================
67 | @Insert
68 | suspend fun addImage(image: Image)
69 |
70 | @Insert
71 | suspend fun addImage(image: Iterable)
72 |
73 | // ============================== Update ==============================
74 | @Transaction
75 | suspend fun updateImages(images: Iterable) {
76 | deleteImages(images.first().medicineId)
77 | addImage(images)
78 | }
79 |
80 | // ============================== Delete ==============================
81 | @Query("DELETE FROM images WHERE medicineId = :medicineId")
82 | suspend fun deleteImages(medicineId: Long)
83 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dao/TakenDAO.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import androidx.room.Transaction
6 | import kotlinx.coroutines.flow.Flow
7 | import ru.application.homemedkit.data.dto.IntakeTaken
8 | import ru.application.homemedkit.data.model.IntakeTakenFull
9 |
10 | @Dao
11 | interface TakenDAO : BaseDAO {
12 | // ============================== Queries ==============================
13 | @Query("SELECT * FROM intakes_taken")
14 | fun getAll(): List
15 |
16 | @Query(
17 | """
18 | SELECT * FROM intakes_taken
19 | WHERE (:search = '' OR LOWER(productName) LIKE '%' || LOWER(:search) || '%')
20 | """
21 | )
22 | fun getFlow(search: String): Flow>
23 |
24 | @Transaction
25 | @Query("SELECT * FROM intakes_taken WHERE takenId = :takenId")
26 | fun getById(takenId: Long): IntakeTakenFull?
27 |
28 | @Query("UPDATE intakes_taken SET taken = :taken, inFact = :inFact WHERE takenId = :id")
29 | fun setTaken(id: Long, taken: Boolean, inFact: Long)
30 |
31 | @Query("UPDATE intakes_taken SET notified = 1 WHERE takenId = :id")
32 | fun setNotified(id: Long)
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/Alarm.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.ForeignKey.Companion.CASCADE
6 | import androidx.room.PrimaryKey
7 |
8 | @Entity(
9 | tableName = "alarms",
10 | foreignKeys = [
11 | ForeignKey(
12 | entity = Intake::class,
13 | parentColumns = ["intakeId"],
14 | childColumns = ["intakeId"],
15 | onUpdate = CASCADE,
16 | onDelete = CASCADE
17 | )
18 | ]
19 | )
20 | data class Alarm(
21 | @PrimaryKey(autoGenerate = true)
22 | val alarmId: Long = 0L,
23 | val intakeId: Long = 0L,
24 | val trigger: Long = 0L,
25 | val amount: Double = 0.0,
26 | val preAlarm: Boolean = false
27 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/Image.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.PrimaryKey
6 | import ru.application.homemedkit.helpers.BLANK
7 |
8 | @Entity(
9 | tableName = "images",
10 | foreignKeys = [
11 | ForeignKey(
12 | entity = Medicine::class,
13 | parentColumns = ["id"],
14 | childColumns = ["medicineId"],
15 | onUpdate = ForeignKey.CASCADE,
16 | onDelete = ForeignKey.CASCADE
17 | )
18 | ]
19 | )
20 | data class Image(
21 | @PrimaryKey(autoGenerate = true)
22 | val id: Long = 0L,
23 | val medicineId: Long = 0L,
24 | val image: String = BLANK
25 | )
26 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/Intake.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.ForeignKey.Companion.CASCADE
6 | import androidx.room.PrimaryKey
7 | import ru.application.homemedkit.helpers.BLANK
8 | import ru.application.homemedkit.helpers.enums.SchemaType
9 |
10 | @Entity(
11 | tableName = "intakes",
12 | foreignKeys = [
13 | ForeignKey(
14 | entity = Medicine::class,
15 | parentColumns = ["id"],
16 | childColumns = ["medicineId"],
17 | onUpdate = CASCADE,
18 | onDelete = CASCADE
19 | )
20 | ]
21 | )
22 | data class Intake(
23 | @PrimaryKey(autoGenerate = true)
24 | val intakeId: Long = 0L,
25 | val medicineId: Long = 0L,
26 | val interval: Int = 0,
27 | val foodType: Int = -1,
28 | val period: Int = 0,
29 | val startDate: String = BLANK,
30 | val finalDate: String = BLANK,
31 | val schemaType: SchemaType = SchemaType.BY_DAYS,
32 | val sameAmount: Boolean = true,
33 | val fullScreen: Boolean = false,
34 | val noSound: Boolean = false,
35 | val preAlarm: Boolean = false,
36 | val cancellable: Boolean = true
37 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/IntakeDay.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.ForeignKey.Companion.CASCADE
6 | import java.time.DayOfWeek
7 |
8 | @Entity(
9 | tableName = "intake_days",
10 | primaryKeys = ["intakeId", "day"],
11 | foreignKeys = [
12 | ForeignKey(
13 | entity = Intake::class,
14 | parentColumns = ["intakeId"],
15 | childColumns = ["intakeId"],
16 | onUpdate = CASCADE,
17 | onDelete = CASCADE
18 | )
19 | ]
20 | )
21 | data class IntakeDay(
22 | val intakeId: Long,
23 | val day: DayOfWeek
24 | )
25 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/IntakeTaken.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import ru.application.homemedkit.helpers.BLANK
6 | import ru.application.homemedkit.helpers.enums.DoseType
7 |
8 | @Entity(tableName = "intakes_taken")
9 | data class IntakeTaken(
10 | @PrimaryKey(autoGenerate = true)
11 | val takenId: Long = 0L,
12 | val medicineId: Long = 0L,
13 | val intakeId: Long = 0L,
14 | val alarmId: Long = 0L,
15 | val productName: String = BLANK,
16 | val formName: String = BLANK,
17 | val amount: Double = 0.0,
18 | val doseType: DoseType = DoseType.UNKNOWN,
19 | val image: String = BLANK,
20 | val trigger: Long = 0L,
21 | val inFact: Long = 0L,
22 | val taken: Boolean = false,
23 | val notified: Boolean = false
24 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/IntakeTime.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.PrimaryKey
6 | import ru.application.homemedkit.helpers.BLANK
7 |
8 | @Entity(
9 | tableName = "intake_time",
10 | foreignKeys = [
11 | ForeignKey(
12 | entity = Intake::class,
13 | parentColumns = ["intakeId"],
14 | childColumns = ["intakeId"],
15 | onUpdate = ForeignKey.CASCADE,
16 | onDelete = ForeignKey.CASCADE
17 | )
18 | ]
19 | )
20 | data class IntakeTime(
21 | @PrimaryKey(autoGenerate = true)
22 | val id: Long = 0L,
23 | val intakeId: Long = 0L,
24 | val time: String = BLANK,
25 | val amount: Double = 0.0
26 | )
27 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/Kit.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import ru.application.homemedkit.helpers.BLANK
6 |
7 | @Entity(tableName = "kits")
8 | data class Kit(
9 | @PrimaryKey(autoGenerate = true)
10 | val kitId: Long = 0L,
11 | val title: String = BLANK,
12 | val position: Long = 0L
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/Medicine.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Embedded
5 | import androidx.room.Entity
6 | import androidx.room.PrimaryKey
7 | import ru.application.homemedkit.helpers.BLANK
8 | import ru.application.homemedkit.helpers.enums.DoseType
9 |
10 | @Entity(tableName = "medicines")
11 | data class Medicine(
12 | @PrimaryKey(autoGenerate = true)
13 | val id: Long = 0L,
14 | val cis: String = BLANK,
15 | val productName: String = BLANK,
16 | val nameAlias: String = BLANK,
17 | val expDate: Long = -1L,
18 | @ColumnInfo(defaultValue = "-1")
19 | val packageOpenedDate: Long = -1L,
20 | val prodFormNormName: String = BLANK,
21 | val structure: String = BLANK,
22 | val prodDNormName: String = BLANK,
23 | val prodAmount: Double = -1.0,
24 | val doseType: DoseType = DoseType.UNKNOWN,
25 | val phKinetics: String = BLANK,
26 | val recommendations: String = BLANK,
27 | val storageConditions: String = BLANK,
28 | val comment: String = BLANK,
29 | @Embedded
30 | val technical: Technical = Technical()
31 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/MedicineKit.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 |
6 | @Entity(
7 | tableName = "medicines_kits",
8 | primaryKeys = ["medicineId", "kitId"],
9 | foreignKeys = [
10 | ForeignKey(
11 | entity = Medicine::class,
12 | parentColumns = ["id"],
13 | childColumns = ["medicineId"],
14 | onUpdate = ForeignKey.CASCADE,
15 | onDelete = ForeignKey.CASCADE
16 | ),
17 | ForeignKey(
18 | entity = Kit::class,
19 | parentColumns = ["kitId"],
20 | childColumns = ["kitId"],
21 | onUpdate = ForeignKey.CASCADE,
22 | onDelete = ForeignKey.CASCADE
23 | )
24 | ]
25 | )
26 | data class MedicineKit(
27 | val medicineId: Long,
28 | val kitId: Long
29 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/dto/Technical.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.dto
2 |
3 |
4 | data class Technical(
5 | val scanned: Boolean = false,
6 | val verified: Boolean = false
7 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/Intake.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.helpers.ResourceText
4 |
5 | data class Intake(
6 | val intakeId: Long,
7 | val title: String,
8 | val interval: ResourceText,
9 | val days: ResourceText,
10 | val time: String,
11 | val image: String,
12 | val active: Boolean
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeAmountTime.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package ru.application.homemedkit.data.model
4 |
5 | import androidx.compose.material3.ExperimentalMaterial3Api
6 | import androidx.compose.material3.TimePickerState
7 | import ru.application.homemedkit.helpers.BLANK
8 |
9 | data class IntakeAmountTime(
10 | val amount: String = BLANK,
11 | val time: String = BLANK,
12 | val picker: TimePickerState = TimePickerState(12, 0, true)
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeFull.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import androidx.room.Relation
4 | import ru.application.homemedkit.data.dto.Image
5 | import ru.application.homemedkit.data.dto.IntakeDay
6 | import ru.application.homemedkit.data.dto.IntakeTime
7 | import ru.application.homemedkit.data.dto.Medicine
8 | import ru.application.homemedkit.helpers.BLANK
9 | import ru.application.homemedkit.helpers.enums.SchemaType
10 | import java.time.DayOfWeek
11 |
12 | data class IntakeFull(
13 | val intakeId: Long = 0L,
14 | val medicineId: Long = 0L,
15 | val interval: Int = 0,
16 | val foodType: Int = -1,
17 | val period: Int = 0,
18 | val startDate: String = BLANK,
19 | val finalDate: String = BLANK,
20 | val schemaType: SchemaType = SchemaType.BY_DAYS,
21 | val sameAmount: Boolean = true,
22 | val fullScreen: Boolean = false,
23 | val noSound: Boolean = false,
24 | val preAlarm: Boolean = false,
25 | val cancellable: Boolean = true,
26 |
27 | @Relation(
28 | entity = Medicine::class,
29 | parentColumn = "medicineId",
30 | entityColumn = "id",
31 | projection = ["productName", "nameAlias", "prodFormNormName", "expDate", "prodAmount", "doseType"]
32 | )
33 | val medicine: MedicineIntake,
34 |
35 | @Relation(
36 | entity = IntakeDay::class,
37 | parentColumn = "intakeId",
38 | entityColumn = "intakeId",
39 | projection = ["day"]
40 | )
41 | val pickedDays: List,
42 |
43 | @Relation(
44 | entity = IntakeTime::class,
45 | parentColumn = "intakeId",
46 | entityColumn = "intakeId"
47 | )
48 | val pickedTime: List,
49 |
50 | @Relation(
51 | entity = Image::class,
52 | parentColumn = "medicineId",
53 | entityColumn = "medicineId",
54 | projection = ["image"]
55 | )
56 | val images: List
57 | )
58 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeList.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import androidx.room.Relation
4 | import ru.application.homemedkit.data.dto.Image
5 | import ru.application.homemedkit.data.dto.IntakeDay
6 | import ru.application.homemedkit.data.dto.IntakeTime
7 | import java.time.DayOfWeek
8 |
9 | data class IntakeList(
10 | val intakeId: Long,
11 | val medicineId: Long,
12 | val interval: Int,
13 | val productName: String,
14 | val nameAlias: String,
15 | val finalDate: String,
16 |
17 | @Relation(
18 | entity = Image::class,
19 | parentColumn = "medicineId",
20 | entityColumn = "medicineId",
21 | projection = ["image"]
22 | )
23 | val image: List,
24 |
25 | @Relation(
26 | entity = IntakeDay::class,
27 | parentColumn = "intakeId",
28 | entityColumn = "intakeId",
29 | projection = ["day"]
30 | )
31 | val days: List,
32 |
33 | @Relation(
34 | entity = IntakeTime::class,
35 | parentColumn = "intakeId",
36 | entityColumn = "intakeId",
37 | projection = ["time"]
38 | )
39 | val time: List
40 | )
41 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeListScheme.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | interface IntakeListScheme {
4 | val epochDay: Long
5 | val date: String
6 | val intakes: List
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeModel.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.helpers.ResourceText
4 |
5 | interface IntakeModel {
6 | val id: Long
7 | val alarmId: Long
8 | val title: String
9 | val doseAmount: ResourceText.StringResource
10 | val image: String
11 | val time: String
12 | val taken: Boolean
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakePast.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | data class IntakePast(
4 | override val epochDay: Long,
5 | override val date: String,
6 | override val intakes: List
7 | ) : IntakeListScheme
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeSchedule.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | data class IntakeSchedule(
4 | override val epochDay: Long,
5 | override val date: String,
6 | override val intakes: List
7 | ) : IntakeListScheme
8 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/IntakeTakenFull.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import androidx.room.Relation
4 | import ru.application.homemedkit.data.dto.Medicine
5 | import ru.application.homemedkit.helpers.enums.DoseType
6 |
7 | data class IntakeTakenFull(
8 | val takenId: Long,
9 | val medicineId: Long,
10 | val intakeId: Long,
11 | val alarmId: Long,
12 | val productName: String,
13 | val formName: String,
14 | val amount: Double,
15 | val doseType: DoseType,
16 | val image: String,
17 | val trigger: Long,
18 | val inFact: Long,
19 | val taken: Boolean,
20 | val notified: Boolean,
21 |
22 | @Relation(
23 | parentColumn = "medicineId",
24 | entityColumn = "id"
25 | )
26 | val medicine: Medicine?
27 | )
28 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/MedicineFull.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import androidx.room.Junction
4 | import androidx.room.Relation
5 | import ru.application.homemedkit.data.dto.Image
6 | import ru.application.homemedkit.data.dto.Kit
7 | import ru.application.homemedkit.data.dto.MedicineKit
8 | import ru.application.homemedkit.helpers.enums.DoseType
9 |
10 | data class MedicineFull(
11 | val id: Long,
12 | val cis: String,
13 | val productName: String,
14 | val nameAlias: String,
15 | val expDate: Long,
16 | val packageOpenedDate: Long,
17 | val prodFormNormName: String,
18 | val structure: String,
19 | val prodDNormName: String,
20 | val prodAmount: Double,
21 | val doseType: DoseType,
22 | val phKinetics: String,
23 | val recommendations: String,
24 | val storageConditions: String,
25 | val comment: String,
26 | val scanned: Boolean,
27 | val verified: Boolean,
28 |
29 | @Relation(
30 | parentColumn = "id",
31 | entityColumn = "medicineId",
32 | projection = ["image"],
33 | entity = Image::class
34 | )
35 | val images: List,
36 |
37 | @Relation(
38 | entity = Kit::class,
39 | parentColumn = "id",
40 | entityColumn = "kitId",
41 | associateBy = Junction(
42 | value = MedicineKit::class,
43 | parentColumn = "medicineId",
44 | entityColumn = "kitId"
45 | )
46 | )
47 | val kits: List
48 | )
49 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/MedicineGrouped.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.data.dto.Kit
4 |
5 | data class MedicineGrouped(
6 | val kit: Kit,
7 | val medicines: List
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/MedicineIntake.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.helpers.BLANK
4 | import ru.application.homemedkit.helpers.enums.DoseType
5 |
6 | data class MedicineIntake(
7 | val productName: String = BLANK,
8 | val nameAlias: String = BLANK,
9 | val prodFormNormName: String = BLANK,
10 | val expDate: Long = -1L,
11 | val prodAmount: Double = 0.0,
12 | val doseType: DoseType = DoseType.MILLIGRAMS
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/MedicineList.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | data class MedicineList(
4 | val id: Long,
5 | val title: String,
6 | val prodAmount: String,
7 | val doseType: Int,
8 | val expDateS: String,
9 | val expDateL: Long,
10 | val formName: String,
11 | val image: String
12 | )
13 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/MedicineMain.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import androidx.room.Relation
4 | import ru.application.homemedkit.data.dto.Image
5 | import ru.application.homemedkit.data.dto.MedicineKit
6 | import ru.application.homemedkit.helpers.enums.DoseType
7 |
8 | data class MedicineMain(
9 | val id: Long,
10 | val productName: String,
11 | val nameAlias: String,
12 | val prodAmount: Double,
13 | val doseType: DoseType,
14 | val expDate: Long,
15 | val prodFormNormName: String,
16 | val structure: String,
17 | val phKinetics: String,
18 |
19 | @Relation(
20 | parentColumn = "id",
21 | entityColumn = "medicineId",
22 | projection = ["image"],
23 | entity = Image::class
24 | )
25 | val image: List,
26 |
27 | @Relation(
28 | parentColumn = "id",
29 | entityColumn = "medicineId",
30 | entity = MedicineKit::class,
31 | projection = ["kitId"]
32 | )
33 | val kitIds: List
34 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/Schedule.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.helpers.enums.DoseType
4 |
5 | data class Schedule(
6 | val alarmId: Long,
7 | val productName: String,
8 | val nameAlias: String,
9 | val prodFormNormName: String,
10 | val doseType: DoseType,
11 | val amount: Double,
12 | val image: String,
13 | val trigger: Long
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/ScheduleModel.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.helpers.ResourceText
4 |
5 | data class ScheduleModel(
6 | override val id: Long,
7 | override val alarmId: Long,
8 | override val title: String,
9 | override val doseAmount: ResourceText.StringResource,
10 | override val image: String,
11 | override val time: String,
12 | override val taken: Boolean = true
13 | ) : IntakeModel
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/data/model/TakenModel.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.data.model
2 |
3 | import ru.application.homemedkit.helpers.ResourceText
4 |
5 | data class TakenModel(
6 | override val id: Long,
7 | override val alarmId: Long,
8 | override val title: String,
9 | override val doseAmount: ResourceText.StringResource,
10 | override val image: String,
11 | override val time: String,
12 | override val taken: Boolean
13 | ) : IntakeModel
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/dialogs/DatePicker.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.dialogs
2 |
3 | import androidx.compose.material3.DatePickerDialog
4 | import androidx.compose.material3.ExperimentalMaterial3Api
5 | import androidx.compose.material3.Text
6 | import androidx.compose.material3.TextButton
7 | import androidx.compose.material3.rememberDatePickerState
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.res.stringResource
10 | import ru.application.homemedkit.R.string.text_cancel
11 | import ru.application.homemedkit.R.string.text_save
12 |
13 | @OptIn(ExperimentalMaterial3Api::class)
14 | @Composable
15 | fun DatePicker(onSelect: (Long) -> Unit, onDismiss: () -> Unit) {
16 | val state = rememberDatePickerState()
17 |
18 | DatePickerDialog(
19 | onDismissRequest = onDismiss,
20 | dismissButton = { TextButton(onDismiss) { Text(stringResource(text_cancel)) } },
21 | confirmButton = {
22 | TextButton(
23 | enabled = state.selectedDateMillis != null,
24 | onClick = { state.selectedDateMillis?.let { onSelect(it) } }
25 | ) { Text(stringResource(text_save)) }
26 | }
27 | ) {
28 | androidx.compose.material3.DatePicker(
29 | state = state,
30 | showModeToggle = false
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/dialogs/DateRangePicker.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.dialogs
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.DatePickerDefaults
8 | import androidx.compose.material3.DatePickerDialog
9 | import androidx.compose.material3.DateRangePickerDefaults
10 | import androidx.compose.material3.ExperimentalMaterial3Api
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.SelectableDates
13 | import androidx.compose.material3.Text
14 | import androidx.compose.material3.TextButton
15 | import androidx.compose.material3.rememberDateRangePickerState
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment.Companion.CenterHorizontally
18 | import androidx.compose.ui.Alignment.Companion.CenterVertically
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.text.intl.Locale
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.unit.sp
24 | import ru.application.homemedkit.R.string.text_cancel
25 | import ru.application.homemedkit.R.string.text_finish_date
26 | import ru.application.homemedkit.R.string.text_save
27 | import ru.application.homemedkit.R.string.text_start_date
28 | import ru.application.homemedkit.helpers.FORMAT_DD_MM_YYYY
29 | import ru.application.homemedkit.helpers.ZONE
30 | import ru.application.homemedkit.helpers.getDateTime
31 | import java.time.LocalDate
32 | import java.time.LocalDateTime
33 | import java.time.LocalTime
34 |
35 | @OptIn(ExperimentalMaterial3Api::class)
36 | @Composable
37 | fun DateRangePicker(
38 | startDate: String,
39 | finalDate: String,
40 | onRangeSelected: (Pair) -> Unit,
41 | onDismiss: () -> Unit
42 | ) {
43 | val initialStart = startDate.let {
44 | if (it.isNotEmpty()) LocalDateTime.of(
45 | LocalDate.parse(it, FORMAT_DD_MM_YYYY), LocalTime.of(12, 0, 0)
46 | ).toInstant(ZONE).toEpochMilli() else null
47 | }
48 |
49 | val initialFinal = finalDate.let {
50 | if (it.isNotEmpty()) LocalDateTime.of(
51 | LocalDate.parse(it, FORMAT_DD_MM_YYYY), LocalTime.of(12, 0, 0)
52 | ).toInstant(ZONE).toEpochMilli() else null
53 | }
54 |
55 | val state = rememberDateRangePickerState(
56 | initialSelectedStartDateMillis = initialStart,
57 | initialSelectedEndDateMillis = initialFinal,
58 | yearRange = if (initialStart != null) DatePickerDefaults.YearRange
59 | else IntRange(LocalDate.now().year, LocalDate.now().year + 10),
60 | selectableDates = if (initialStart != null) DatePickerDefaults.AllDates
61 | else object : SelectableDates {
62 | override fun isSelectableDate(utcTimeMillis: Long) =
63 | getDateTime(utcTimeMillis).toLocalDate() >= LocalDate.now()
64 |
65 | override fun isSelectableYear(year: Int) = LocalDate.now().year <= year
66 | }
67 | )
68 |
69 | DatePickerDialog(
70 | onDismissRequest = onDismiss,
71 | dismissButton = { TextButton(onDismiss) { Text(stringResource(text_cancel)) } },
72 | confirmButton = {
73 | TextButton(
74 | enabled = state.selectedStartDateMillis != null && state.selectedEndDateMillis != null,
75 | onClick = {
76 | onRangeSelected(state.selectedStartDateMillis to state.selectedEndDateMillis)
77 | onDismiss()
78 | }
79 | ) { Text(stringResource(text_save)) }
80 | }
81 | ) {
82 | androidx.compose.material3.DateRangePicker(
83 | state = state,
84 | showModeToggle = false,
85 | title = {
86 | DateRangePickerDefaults.DateRangePickerTitle(
87 | displayMode = state.displayMode,
88 | modifier = Modifier
89 | .fillMaxWidth()
90 | .padding(20.dp)
91 | )
92 | },
93 | headline = {
94 | Row(
95 | modifier = Modifier
96 | .fillMaxWidth()
97 | .padding(8.dp),
98 | horizontalArrangement = Arrangement.spacedBy(4.dp, CenterHorizontally),
99 | verticalAlignment = CenterVertically
100 | ) {
101 | val formattedStart = DatePickerDefaults.dateFormatter()
102 | .formatDate(state.selectedStartDateMillis, Locale.current.platformLocale)
103 |
104 | val formattedFinal = DatePickerDefaults.dateFormatter()
105 | .formatDate(state.selectedEndDateMillis, Locale.current.platformLocale)
106 |
107 | Text(
108 | text = formattedStart ?: stringResource(text_start_date),
109 | style = MaterialTheme.typography.titleLarge.copy(fontSize = 20.sp)
110 | )
111 |
112 | Text("-")
113 |
114 | Text(
115 | text = formattedFinal ?: stringResource(text_finish_date),
116 | style = MaterialTheme.typography.titleLarge.copy(fontSize = 20.sp)
117 | )
118 | }
119 | }
120 | )
121 | }
122 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/dialogs/TimePickerDialog.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.dialogs
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.IntrinsicSize
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.layout.width
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.Surface
14 | import androidx.compose.material3.Text
15 | import androidx.compose.material3.TextButton
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.window.Dialog
22 | import androidx.compose.ui.window.DialogProperties
23 | import ru.application.homemedkit.R
24 |
25 | @Composable
26 | fun TimePickerDialog(onCancel: () -> Unit, onConfirm: () -> Unit, content: @Composable () -> Unit) =
27 | Dialog(onCancel, DialogProperties(usePlatformDefaultWidth = false)) {
28 | Surface(
29 | shape = MaterialTheme.shapes.extraLarge,
30 | tonalElevation = 6.dp,
31 | modifier = Modifier
32 | .width(IntrinsicSize.Min)
33 | .height(IntrinsicSize.Min)
34 | .background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.extraLarge)
35 | ) {
36 | Column(Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
37 | Text(
38 | modifier = Modifier
39 | .fillMaxWidth()
40 | .padding(bottom = 20.dp),
41 | text = stringResource(R.string.text_select_time),
42 | style = MaterialTheme.typography.labelMedium
43 | )
44 | content()
45 | Row(Modifier.height(40.dp).fillMaxWidth()) {
46 | Spacer(Modifier.weight(1f))
47 | TextButton(onCancel) { Text(stringResource(R.string.text_cancel)) }
48 | TextButton(onConfirm) { Text(stringResource(R.string.text_save)) }
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/AppUtils.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import android.icu.math.BigDecimal
4 | import android.icu.text.DecimalFormat
5 | import androidx.compose.ui.text.intl.Locale
6 | import ru.application.homemedkit.data.dto.Image
7 | import ru.application.homemedkit.helpers.enums.DrugType
8 | import ru.application.homemedkit.network.Network
9 | import java.io.File
10 | import java.time.Instant
11 | import java.time.LocalDate
12 | import java.time.LocalDateTime
13 | import java.time.LocalTime
14 | import java.time.YearMonth
15 | import java.time.ZoneId
16 | import java.time.ZoneOffset
17 | import java.time.format.DateTimeFormatter
18 | import java.time.format.FormatStyle
19 |
20 | val ZONE: ZoneOffset
21 | get() = ZoneId.systemDefault().rules.getOffset(Instant.now())
22 |
23 | val FORMAT_LONG: DateTimeFormatter
24 | get() = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(Locale.current.platformLocale)
25 |
26 | val FORMAT_D_MMMM_E: DateTimeFormatter
27 | get() = DateTimeFormatter.ofPattern("d MMMM, E", Locale.current.platformLocale)
28 |
29 | val FORMAT_DD_MM_YYYY: DateTimeFormatter
30 | get() = DateTimeFormatter.ofPattern("dd.MM.yyyy")
31 |
32 | val FORMAT_H_MM: DateTimeFormatter
33 | get() = DateTimeFormatter.ofPattern("H:mm")
34 |
35 | private val FORMAT_MM_YYYY: DateTimeFormatter
36 | get() = DateTimeFormatter.ofPattern("MM/yyyy")
37 |
38 | fun getDateTime(milli: Long) = Instant.ofEpochMilli(milli).atZone(ZONE)
39 |
40 | fun inCard(milli: Long) = if (milli == -1L) BLANK else getDateTime(milli).format(FORMAT_MM_YYYY)
41 |
42 | fun toExpDate(milli: Long) = if (milli > 0) getDateTime(milli).format(FORMAT_LONG) else BLANK
43 |
44 | fun toTimestamp(month: Int, year: Int) = LocalDateTime.of(
45 | year,
46 | month,
47 | YearMonth.of(year, month).lengthOfMonth(),
48 | LocalTime.MAX.hour,
49 | LocalTime.MAX.minute
50 | ).toInstant(ZONE).toEpochMilli()
51 |
52 | fun expirationCheckTime(): Long {
53 | var unix = LocalDateTime.of(LocalDate.now(), LocalTime.of(12, 0))
54 |
55 | if (unix.toInstant(ZONE).toEpochMilli() < System.currentTimeMillis()) unix = unix.plusDays(1)
56 |
57 | return unix.toInstant(ZONE).toEpochMilli()
58 | }
59 |
60 | fun formName(name: String) = name.substringBefore(" ")
61 |
62 | fun decimalFormat(text: Any?): String {
63 | val amount = try {
64 | text.toString().toDouble()
65 | } catch (_: NumberFormatException) {
66 | 0.0
67 | }
68 |
69 | return DecimalFormat.getInstance().apply {
70 | maximumFractionDigits = 4
71 | roundingMode = BigDecimal.ROUND_HALF_EVEN
72 | }.format(amount)
73 | }
74 |
75 | suspend fun getMedicineImages(
76 | medicineId: Long,
77 | form: String,
78 | directory: File,
79 | urls: List?
80 | ): List {
81 | val imageList = mutableListOf()
82 |
83 | if (Preferences.imageFetch) {
84 | val images = Network.getImage(directory, urls)
85 | imageList.addAll(images)
86 | }
87 |
88 | val images = if (imageList.isEmpty()) listOf(
89 | Image(
90 | medicineId = medicineId,
91 | image = DrugType.setIcon(form)
92 | )
93 | ) else imageList.map { image ->
94 | Image(
95 | medicineId = medicineId,
96 | image = image
97 | )
98 | }
99 |
100 | return images
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/Constants.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | const val ALARM_ID = "alarmId"
4 | const val BLANK = ""
5 | const val CHANNEL_ID_EXP = "channel_expiration"
6 | const val CHANNEL_ID_INTAKES = "channel_intakes"
7 | const val CHANNEL_ID_PRE = "channel_prealarm"
8 | const val CIS = "cis"
9 | const val DATABASE_NAME = "medicines"
10 | const val ID = "id"
11 | const val KEY_APP_SYSTEM = "app_system"
12 | const val KEY_APP_THEME = "app_theme"
13 | const val KEY_APP_VIEW = "app_view"
14 | const val KEY_BASIC_SETTINGS = "app_basic_settings"
15 | const val KEY_CHECK_EXP_DATE = "check_exp_date"
16 | const val KEY_CLEAR_CACHE = "clear_app_cache"
17 | const val KEY_CONFIRM_EXIT = "confirm_exit"
18 | const val KEY_DOWNLOAD = "download_images"
19 | const val KEY_DYNAMIC_COLOR = "dynamic_color"
20 | const val KEY_EXP_IMP = "export_import"
21 | const val KEY_FIRST_LAUNCH_INTAKE = "first_launch_intake"
22 | const val KEY_FIXING = "fixing_alarms"
23 | const val KEY_KITS = "kits_group"
24 | const val KEY_LANGUAGE = "language"
25 | const val KEY_ORDER = "sorting_order"
26 | const val KEY_PERMISSIONS = "give_permissions"
27 | const val TAKEN_ID = "takenId"
28 | const val TYPE = "vector_type"
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/DataMatrixAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import android.graphics.ImageFormat.YUV_420_888
4 | import android.graphics.ImageFormat.YUV_422_888
5 | import android.graphics.ImageFormat.YUV_444_888
6 | import androidx.camera.core.ImageAnalysis
7 | import androidx.camera.core.ImageProxy
8 | import com.google.zxing.BarcodeFormat.DATA_MATRIX
9 | import com.google.zxing.BinaryBitmap
10 | import com.google.zxing.DecodeHintType.POSSIBLE_FORMATS
11 | import com.google.zxing.DecodeHintType.TRY_HARDER
12 | import com.google.zxing.MultiFormatReader
13 | import com.google.zxing.PlanarYUVLuminanceSource
14 | import com.google.zxing.common.HybridBinarizer
15 | import java.nio.ByteBuffer
16 | import kotlin.math.roundToInt
17 |
18 | class DataMatrixAnalyzer(private val onResult: (String) -> Unit) : ImageAnalysis.Analyzer {
19 | override fun analyze(image: ImageProxy) {
20 | if (image.format in listOf(YUV_420_888, YUV_422_888, YUV_444_888)) {
21 | val length = if (image.width > image.height) image.height * 0.5f else image.width * 0.7f
22 |
23 | val source = PlanarYUVLuminanceSource(
24 | image.planes.first().buffer.toByteArray(),
25 | image.width,
26 | image.height,
27 | ((image.width - length) / 2).roundToInt(),
28 | ((image.height - length) / 2).roundToInt(),
29 | length.roundToInt(),
30 | length.roundToInt(),
31 | false
32 | )
33 |
34 | try {
35 | val result = MultiFormatReader().apply {
36 | setHints(mapOf(POSSIBLE_FORMATS to setOf(DATA_MATRIX), TRY_HARDER to true))
37 | }.decodeWithState(BinaryBitmap(HybridBinarizer(source)))
38 |
39 | onResult(result.text)
40 | } catch (_: Throwable) {
41 | } finally {
42 | image.close()
43 | }
44 | }
45 | }
46 |
47 | private fun ByteBuffer.toByteArray() = ByteArray(rewind().remaining()).also(::get)
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/FileManager.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import android.content.Context
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.withContext
6 |
7 | class FileManager(private val context: Context) {
8 | suspend fun saveImage(bytes: ByteArray, fileName: String) = withContext(Dispatchers.IO) {
9 | context.openFileOutput(fileName, Context.MODE_PRIVATE).use { it.write(bytes) }
10 | }
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/ImageCompressor.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap.CompressFormat.JPEG
5 | import android.graphics.Bitmap.CompressFormat.PNG
6 | import android.graphics.Bitmap.CompressFormat.WEBP
7 | import android.graphics.Bitmap.CompressFormat.WEBP_LOSSLESS
8 | import android.graphics.BitmapFactory
9 | import android.net.Uri
10 | import android.os.Build
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.ensureActive
13 | import kotlinx.coroutines.isActive
14 | import kotlinx.coroutines.withContext
15 | import java.io.ByteArrayOutputStream
16 | import kotlin.math.roundToInt
17 |
18 | class ImageCompressor(private val context: Context) {
19 | suspend fun compressImage(uri: Uri, size: Long) = withContext(Dispatchers.IO) {
20 | val mimeType = context.contentResolver.getType(uri)
21 | val inputBytes = context.contentResolver.openInputStream(uri)
22 | ?.use { inputStream -> inputStream.readBytes() } ?: return@withContext null
23 |
24 | ensureActive()
25 |
26 | withContext(Dispatchers.Default) {
27 | val bitmap = BitmapFactory.decodeByteArray(inputBytes, 0, inputBytes.size)
28 |
29 | ensureActive()
30 |
31 | val format = when (mimeType) {
32 | "image/png" -> PNG
33 | "image/jpeg" -> JPEG
34 | "image/webp" -> if (Build.VERSION.SDK_INT >= 30) WEBP_LOSSLESS else WEBP
35 | else -> JPEG
36 | }
37 |
38 | var outputBytes: ByteArray
39 | var quality = 90
40 |
41 | do {
42 | ByteArrayOutputStream().use { outputStream ->
43 | bitmap.compress(format, quality, outputStream)
44 | outputBytes = outputStream.toByteArray()
45 | quality -= (quality * 0.1).roundToInt()
46 | }
47 | } while (isActive && outputBytes.size > size && quality > 10 && format != PNG)
48 |
49 | outputBytes
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/PhotoCapture.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.Matrix
6 | import androidx.camera.core.ImageCapture
7 | import androidx.camera.core.ImageCaptureException
8 | import androidx.camera.core.ImageProxy
9 | import androidx.core.net.toUri
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.runBlocking
12 | import java.io.File
13 |
14 | class PhotoCapture(val context: Context, val onSave: (String) -> Unit) : ImageCapture.OnImageCapturedCallback() {
15 | override fun onCaptureSuccess(image: ImageProxy) {
16 | super.onCaptureSuccess(image)
17 |
18 | val matrix = Matrix().apply {
19 | postRotate(image.imageInfo.rotationDegrees.toFloat())
20 | }
21 |
22 | val bitmap = Bitmap.createBitmap(
23 | image.toBitmap(),
24 | 0,
25 | 0,
26 | image.width,
27 | image.height,
28 | matrix,
29 | true
30 | )
31 |
32 | val name = "${System.currentTimeMillis()}_product.jpg"
33 | val file = File(context.filesDir, name)
34 |
35 | runBlocking(Dispatchers.Default) {
36 | context.contentResolver.openOutputStream(file.toUri())?.use {
37 | bitmap.compress(Bitmap.CompressFormat.JPEG, 10, it)
38 | }
39 | }
40 |
41 | onSave(name)
42 | }
43 |
44 | override fun onError(exception: ImageCaptureException) {
45 | exception.printStackTrace()
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/Preferences.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Context.MODE_PRIVATE
6 | import android.content.SharedPreferences
7 | import android.os.Build
8 | import android.os.LocaleList
9 | import androidx.core.content.edit
10 | import androidx.lifecycle.ViewModel
11 | import androidx.lifecycle.viewModelScope
12 | import kotlinx.coroutines.flow.SharingStarted
13 | import kotlinx.coroutines.flow.StateFlow
14 | import kotlinx.coroutines.flow.stateIn
15 | import ru.application.homemedkit.helpers.enums.Sorting
16 | import ru.application.homemedkit.helpers.enums.Theme
17 | import ru.application.homemedkit.helpers.extensions.getColorsFlow
18 | import ru.application.homemedkit.helpers.extensions.getEnum
19 | import ru.application.homemedkit.helpers.extensions.getSelectedLanguage
20 | import ru.application.homemedkit.helpers.extensions.getThemeFlow
21 | import ru.application.homemedkit.helpers.extensions.putEnum
22 | import ru.application.homemedkit.receivers.AlarmSetter
23 | import java.util.Locale
24 |
25 | object Preferences : ViewModel() {
26 |
27 | private lateinit var preferences: SharedPreferences
28 | lateinit var theme: StateFlow
29 | lateinit var dynamicColors: StateFlow
30 |
31 | fun getInstance(context: Context) {
32 | preferences = context.getSharedPreferences("${context.packageName}_preferences", MODE_PRIVATE)
33 | theme = preferences.getThemeFlow()
34 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Theme.SYSTEM)
35 | dynamicColors = preferences.getColorsFlow()
36 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
37 | }
38 |
39 | var sortingOrder: Sorting
40 | get() = preferences.getEnum(KEY_ORDER, Sorting.IN_NAME)
41 | set(value) = preferences.edit { putEnum(KEY_ORDER, value) }
42 |
43 | val imageFetch: Boolean
44 | get() = preferences.getBoolean(KEY_DOWNLOAD, true)
45 |
46 | val checkExpiration: Boolean
47 | get() = preferences.getBoolean(KEY_CHECK_EXP_DATE, false)
48 |
49 | val confirmExit: Boolean
50 | get() = preferences.getBoolean(KEY_CONFIRM_EXIT, true)
51 |
52 | var isFirstLaunch: Boolean
53 | get() = preferences.getBoolean(KEY_FIRST_LAUNCH_INTAKE, true)
54 | set(_) = preferences.edit { putBoolean(KEY_FIRST_LAUNCH_INTAKE, false) }
55 |
56 | fun getLanguage(context: Context?) = if (context == null) Locale.ENGLISH.language
57 | else preferences.getString(KEY_LANGUAGE, context.getSelectedLanguage()) ?: Locale.ENGLISH.language
58 |
59 | fun setCheckExpDate(context: Context, check: Boolean) {
60 | AlarmSetter(context).checkExpiration(check)
61 | preferences.edit { putBoolean(KEY_CHECK_EXP_DATE, check) }
62 | }
63 |
64 | fun setLocale(context: Context, locale: String) {
65 | preferences.edit { putString(KEY_LANGUAGE, locale) }
66 | changeLanguage(context)
67 | (context as Activity).recreate()
68 | }
69 |
70 | fun setTheme(theme: Theme) = preferences.edit {
71 | putEnum(KEY_APP_THEME, theme)
72 | }
73 |
74 | fun changeLanguage(context: Context?) =
75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) context
76 | else {
77 | val newLocale = Locale.forLanguageTag(getLanguage(context))
78 | Locale.setDefault(newLocale)
79 |
80 | val resources = context?.resources
81 | val configuration = resources?.configuration
82 | configuration?.setLocales(LocaleList(newLocale))
83 |
84 | configuration?.let { context.createConfigurationContext(it) } ?: context
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/ResourceText.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers
2 |
3 | import androidx.annotation.PluralsRes
4 | import androidx.annotation.StringRes
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.res.pluralStringResource
7 | import androidx.compose.ui.res.stringResource
8 |
9 | sealed interface ResourceText {
10 | data class StaticString(val value: String) : ResourceText
11 |
12 | class StringResource(
13 | @StringRes val resourceId: Int,
14 | vararg val args: Any
15 | ) : ResourceText
16 |
17 | class PluralStringResource(
18 | @PluralsRes val resourceId: Int,
19 | val count: Int,
20 | vararg val args: Any
21 | ) : ResourceText
22 |
23 | @Composable
24 | fun asString() = when (this) {
25 | is StaticString -> value
26 |
27 | is StringResource -> stringResource(
28 | resourceId,
29 | *args.map {
30 | if (it is StringResource) stringResource(it.resourceId, it.args)
31 | else it
32 | }.toTypedArray()
33 | )
34 |
35 | is PluralStringResource -> pluralStringResource(resourceId, count, *args)
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/DoseType.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 |
7 | enum class DoseType(@StringRes val title: Int) {
8 | UNKNOWN(R.string.blank),
9 | UNITS(R.string.dose_ed),
10 | PIECES(R.string.dose_pcs),
11 | SACHETS(R.string.dose_sach),
12 | GRAMS(R.string.dose_g),
13 | MILLIGRAMS(R.string.dose_mg),
14 | LITERS(R.string.dose_l),
15 | MILLILITERS(R.string.dose_ml),
16 | RATIO(R.string.dose_ratio)
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/FoodType.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class FoodType(val value: Int, @StringRes val title: Int) {
7 | BEFORE(0, R.string.intake_text_food_before),
8 | DURING(1, R.string.intake_text_food_during),
9 | AFTER(2, R.string.intake_text_food_after)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/IntakeExtra.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 |
7 | enum class IntakeExtra(
8 | @StringRes val title: Int,
9 | @StringRes val description: Int
10 | ) {
11 | CANCELLABLE(
12 | title = R.string.intake_extra_cancellable,
13 | description = R.string.intake_extra_desc_cancellable
14 | ),
15 | FULLSCREEN(
16 | title = R.string.intake_extra_fullscreen,
17 | description = R.string.intake_extra_desc_fullscreen
18 | ),
19 | NO_SOUND(
20 | title = R.string.intake_extra_no_sound,
21 | description = R.string.intake_extra_desc_no_sound
22 | ),
23 | PREALARM(
24 | title = R.string.intake_extra_prealarm,
25 | description = R.string.intake_extra_desc_prealarm
26 | )
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/IntakeTab.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class IntakeTab(@StringRes val title: Int) {
7 | LIST(R.string.tab_list),
8 | CURRENT(R.string.tab_current),
9 | PAST(R.string.tab_past)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/Interval.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class Interval(val days: Int, @StringRes val title: Int) {
7 | DAILY(1, R.string.intake_interval_daily),
8 | WEEKLY(7, R.string.intake_interval_weekly),
9 | CUSTOM(10, R.string.intake_interval_other);
10 |
11 | companion object {
12 | fun getValue(days: Int) = entries.find { it.days == days } ?: CUSTOM
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/MedicineTab.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class MedicineTab(@StringRes val title: Int) {
7 | LIST(R.string.tab_list),
8 | GROUPS(R.string.tab_groups)
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/Menu.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import ru.application.homemedkit.R
6 | import ru.application.homemedkit.ui.navigation.Screen
7 |
8 | enum class Menu(
9 | val route: Screen,
10 | @StringRes val title: Int,
11 | @DrawableRes val icon: Int
12 | ) {
13 | MEDICINES(
14 | route = Screen.Medicines,
15 | title = R.string.bottom_bar_medicines,
16 | icon = R.drawable.vector_medicine
17 | ),
18 | INTAKES(
19 | route = Screen.Intakes,
20 | title = R.string.bottom_bar_intakes,
21 | icon = R.drawable.vector_time
22 | ),
23 | SETTINGS(
24 | route = Screen.Settings,
25 | title = R.string.bottom_bar_settings,
26 | icon = R.drawable.vector_settings
27 | )
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/Period.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class Period(val days: Int, @StringRes val title: Int) {
7 | PICK(-1, R.string.intake_period_pick),
8 | OTHER(21, R.string.intake_period_other),
9 | INDEFINITE(1825, R.string.intake_period_indef);
10 |
11 | companion object {
12 | fun getValue(days: Int) = entries.find { it.days == days } ?: OTHER
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/SchemaType.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class SchemaType(
7 | @StringRes val title: Int,
8 | val interval: Interval
9 | ) {
10 | INDEFINITELY(
11 | title = R.string.intake_schema_type_indefinitely,
12 | interval = Interval.DAILY
13 | ),
14 | BY_DAYS(
15 | title = R.string.intake_schema_type_day_picker,
16 | interval = Interval.WEEKLY
17 | ),
18 | PERSONAL(
19 | title = R.string.intake_schema_type_personal,
20 | interval = Interval.CUSTOM
21 | )
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/Sorting.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class Sorting(@StringRes val title: Int) {
7 | IN_NAME(R.string.sorting_a_z),
8 | RE_NAME(R.string.sorting_z_a),
9 | IN_DATE(R.string.sorting_from_oldest),
10 | RE_DATE(R.string.sorting_from_newest)
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/enums/Theme.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.enums
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.R
5 |
6 | enum class Theme(@StringRes val title: Int) {
7 | SYSTEM(R.string.theme_system),
8 | LIGHT(R.string.theme_light),
9 | DARK(R.string.theme_dark)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/extensions/Context.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.extensions
2 |
3 | import android.app.AlarmManager
4 | import android.content.Context
5 | import android.content.Context.*
6 | import android.content.Intent
7 | import android.content.res.XmlResourceParser
8 | import android.media.AudioAttributes
9 | import android.media.RingtoneManager
10 | import android.os.Build
11 | import android.os.Bundle
12 | import android.os.PowerManager
13 | import android.widget.Toast
14 | import androidx.annotation.StringRes
15 | import androidx.core.app.LocaleManagerCompat
16 | import androidx.core.app.NotificationChannelCompat
17 | import androidx.core.app.NotificationManagerCompat
18 | import org.xmlpull.v1.XmlPullParser
19 | import ru.application.homemedkit.R
20 | import java.util.Locale
21 |
22 | fun Context.isIgnoringBatteryOptimizations() =
23 | (getSystemService(POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations(packageName)
24 |
25 | fun Context.canScheduleExactAlarms() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) true
26 | else (getSystemService(ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()
27 |
28 | fun Context.canUseFullScreenIntent() = NotificationManagerCompat.from(this).canUseFullScreenIntent()
29 |
30 | fun Context.getLanguageList() = mutableListOf().apply {
31 | resources.getXml(R.xml._generated_res_locale_config).use { xml ->
32 | while (xml.eventType != XmlResourceParser.END_DOCUMENT) {
33 | if (xml.eventType == XmlPullParser.START_TAG && xml.name == "locale") {
34 | add(xml.getAttributeValue(0))
35 | }
36 | xml.next()
37 | }
38 | }
39 | }.sortedBy { Locale.forLanguageTag(it).getDisplayRegionName() }
40 |
41 | fun Context.restartApplication(extras: Bundle.() -> Unit = {}) {
42 | packageManager.getLaunchIntentForPackage(packageName)?.component?.let {
43 | startActivity(Intent.makeMainActivity(it).putExtras(Bundle().apply(extras)))
44 | }
45 |
46 | Runtime.getRuntime().exit(0)
47 | }
48 |
49 | fun Context.getSelectedLanguage() = LocaleManagerCompat.getSystemLocales(this)
50 | .getFirstMatch(getLanguageList().toTypedArray())?.language ?: Locale.ENGLISH.language
51 |
52 | fun Context.createNotificationChannel(channelId: String, @StringRes channelName: Int) =
53 | NotificationManagerCompat.from(this).createNotificationChannel(
54 | NotificationChannelCompat.Builder(channelId, NotificationManagerCompat.IMPORTANCE_MAX)
55 | .setName(getString(channelName))
56 | .setDescription(getString(R.string.channel_name))
57 | .setSound(
58 | RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_NOTIFICATION),
59 | AudioAttributes.Builder()
60 | .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
61 | .setUsage(AudioAttributes.USAGE_NOTIFICATION)
62 | .build()
63 | )
64 | .build()
65 | )
66 |
67 | fun Context.showToast(@StringRes message: Int) = Toast.makeText(this, message, Toast.LENGTH_LONG).show()
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/extensions/Locale.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.extensions
2 |
3 | import java.util.Locale
4 |
5 | fun Locale.getDisplayRegionName() = getDisplayName(this).replaceFirstChar(Char::uppercase)
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/extensions/Medicine.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.extensions
2 |
3 | import androidx.compose.runtime.toMutableStateList
4 | import androidx.core.text.HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH
5 | import androidx.core.text.HtmlCompat.fromHtml
6 | import ru.application.homemedkit.data.dto.Medicine
7 | import ru.application.homemedkit.data.dto.Technical
8 | import ru.application.homemedkit.data.model.MedicineFull
9 | import ru.application.homemedkit.data.model.MedicineIntake
10 | import ru.application.homemedkit.data.model.MedicineList
11 | import ru.application.homemedkit.data.model.MedicineMain
12 | import ru.application.homemedkit.helpers.BLANK
13 | import ru.application.homemedkit.helpers.decimalFormat
14 | import ru.application.homemedkit.helpers.enums.DrugType
15 | import ru.application.homemedkit.helpers.formName
16 | import ru.application.homemedkit.helpers.inCard
17 | import ru.application.homemedkit.helpers.toExpDate
18 | import ru.application.homemedkit.models.states.MedicineState
19 | import ru.application.homemedkit.models.states.TechnicalState
20 | import ru.application.homemedkit.network.models.bio.BioData
21 | import ru.application.homemedkit.network.models.medicine.DrugsData
22 |
23 | fun MedicineFull.toState() = MedicineState(
24 | adding = false,
25 | editing = false,
26 | default = true,
27 | id = id,
28 | kits = kits.toMutableStateList(),
29 | cis = cis,
30 | productName = productName,
31 | nameAlias = nameAlias,
32 | expDate = expDate,
33 | expDateString = toExpDate(expDate),
34 | dateOpened = packageOpenedDate,
35 | dateOpenedString = toExpDate(packageOpenedDate),
36 | prodFormNormName = prodFormNormName,
37 | structure = structure,
38 | prodDNormName = prodDNormName,
39 | prodAmount = prodAmount.toString(),
40 | doseType = doseType,
41 | phKinetics = fromHtml(phKinetics, FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH).toString(),
42 | recommendations = fromHtml(recommendations, FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH).toString(),
43 | storageConditions = fromHtml(storageConditions, FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH).toString(),
44 | comment = comment,
45 | images = images.toMutableStateList(),
46 | technical = TechnicalState(
47 | scanned = scanned,
48 | verified = verified
49 | )
50 | )
51 |
52 | fun MedicineFull.toMedicineIntake() = MedicineIntake(
53 | productName = productName,
54 | nameAlias = nameAlias,
55 | prodFormNormName = prodFormNormName,
56 | expDate = expDate,
57 | prodAmount = prodAmount,
58 | doseType = doseType
59 | )
60 |
61 | fun MedicineMain.toMedicineList() = MedicineList(
62 | id = id,
63 | title = nameAlias.ifEmpty(::productName),
64 | prodAmount = decimalFormat(prodAmount),
65 | doseType = doseType.title,
66 | expDateS = inCard(expDate),
67 | expDateL = expDate,
68 | formName = formName(prodFormNormName),
69 | image = image.firstOrNull() ?: BLANK
70 | )
71 |
72 | fun MedicineState.toMedicine() = Medicine(
73 | id = id,
74 | cis = cis,
75 | productName = productName,
76 | nameAlias = nameAlias,
77 | expDate = expDate,
78 | packageOpenedDate = dateOpened,
79 | prodFormNormName = prodFormNormName,
80 | structure = structure,
81 | prodDNormName = prodDNormName,
82 | prodAmount = prodAmount.ifEmpty { "0.0" }.toDouble(),
83 | doseType = doseType,
84 | phKinetics = phKinetics,
85 | recommendations = recommendations,
86 | storageConditions = storageConditions,
87 | comment = comment,
88 | technical = Technical(
89 | scanned = cis.isNotBlank(),
90 | verified = technical.verified
91 | )
92 | )
93 |
94 | fun DrugsData.toMedicine() = Medicine(
95 | productName = prodDescLabel,
96 | expDate = expireDate,
97 | prodFormNormName = foiv.prodFormNormName,
98 | prodDNormName = foiv.prodDNormName.orEmpty(),
99 | doseType = DrugType.getDoseType(foiv.prodFormNormName),
100 | phKinetics = vidalData?.phKinetics.orEmpty(),
101 | technical = Technical(scanned = true, verified = true),
102 | prodAmount = foiv.prodPack1Size?.let { it.toDouble() * (foiv.prodPack12?.toDoubleOrNull() ?: 1.0) } ?: 0.0
103 | )
104 |
105 | fun BioData.toMedicine() = Medicine(
106 | productName = productName,
107 | expDate = expireDate,
108 | prodDNormName = productProperty.unitVolumeWeight.orEmpty(),
109 | prodAmount = productProperty.quantityInPack ?: 0.0,
110 | phKinetics = productProperty.applicationArea.orEmpty(),
111 | recommendations = productProperty.recommendForUse.orEmpty(),
112 | storageConditions = productProperty.storageConditions.orEmpty(),
113 | structure = productProperty.structure.orEmpty(),
114 | prodFormNormName = productProperty.releaseForm.orEmpty().substringBefore(" ").uppercase(),
115 | doseType = DrugType.getDoseType(productProperty.releaseForm.orEmpty()),
116 | technical = Technical(
117 | scanned = true,
118 | verified = true
119 | )
120 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/extensions/Navigation.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.extensions
2 |
3 | import androidx.navigation.NavBackStackEntry
4 | import androidx.navigation.NavDestination.Companion.hasRoute
5 | import androidx.navigation.NavDestination.Companion.hierarchy
6 | import androidx.navigation.NavGraph.Companion.findStartDestination
7 | import androidx.navigation.NavHostController
8 | import ru.application.homemedkit.ui.navigation.Screen
9 | import kotlin.reflect.KClass
10 |
11 | fun NavBackStackEntry?.isCurrentRoute(route: KClass) =
12 | this?.destination?.hierarchy?.any { it.hasRoute(route) } == true
13 |
14 | fun NavHostController.toBottomBarItem(route: Screen) = navigate(route) {
15 | launchSingleTop = true
16 | restoreState = true
17 |
18 | popUpTo(this@toBottomBarItem.graph.findStartDestination().id) {
19 | saveState = true
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/extensions/Notification.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.extensions
2 |
3 | import android.Manifest
4 | import android.app.Notification
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.pm.PackageManager
8 | import androidx.core.app.ActivityCompat
9 | import androidx.core.app.NotificationManagerCompat
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.cancel
13 | import kotlinx.coroutines.coroutineScope
14 | import kotlinx.coroutines.launch
15 | import kotlin.coroutines.CoroutineContext
16 |
17 | fun NotificationManagerCompat.safeNotify(context: Context, code: Int, notification: Notification) {
18 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
19 | notify(code, notification)
20 | }
21 | }
22 |
23 | fun BroadcastReceiver.goAsync(
24 | coroutineContext: CoroutineContext = Dispatchers.Default,
25 | block: suspend CoroutineScope.() -> Unit
26 | ) {
27 | val parentScope = CoroutineScope(coroutineContext)
28 | val pendingResult = goAsync()
29 |
30 | parentScope.launch {
31 | try {
32 | try {
33 | coroutineScope { this.block() }
34 | } catch (e: Throwable) {
35 | e.printStackTrace()
36 | } finally {
37 | parentScope.cancel()
38 | }
39 | } finally {
40 | try {
41 | pendingResult.finish()
42 | } catch (e: IllegalStateException) {
43 | e.printStackTrace()
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/extensions/SharedPreferences.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.extensions
2 |
3 | import android.content.SharedPreferences
4 | import kotlinx.coroutines.channels.awaitClose
5 | import kotlinx.coroutines.flow.callbackFlow
6 | import ru.application.homemedkit.helpers.KEY_APP_THEME
7 | import ru.application.homemedkit.helpers.KEY_DYNAMIC_COLOR
8 | import ru.application.homemedkit.helpers.enums.Theme
9 |
10 | fun > SharedPreferences.Editor.putEnum(key: String, value: E) {
11 | putString(key, value.name)
12 | }
13 |
14 | fun > SharedPreferences.getEnum(key: String, enum: Class): E? {
15 | val stringValue = getString(key, null) ?: return null
16 | return enum.enumConstants?.find {
17 | it.name == stringValue
18 | }
19 | }
20 |
21 | fun > SharedPreferences.getEnum(key: String, defaultValue: E): E {
22 | return getEnum(key, defaultValue.javaClass) ?: defaultValue
23 | }
24 |
25 | fun SharedPreferences.getThemeFlow(changedKey: String = KEY_APP_THEME) = callbackFlow {
26 | val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
27 | if (changedKey == key) trySend(getEnum(key, Theme.SYSTEM))
28 | }
29 |
30 | registerOnSharedPreferenceChangeListener(listener)
31 |
32 | if (contains(changedKey)) send(getEnum(changedKey, Theme.SYSTEM))
33 |
34 | awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
35 | }
36 |
37 | fun SharedPreferences.getColorsFlow(changedKey: String = KEY_DYNAMIC_COLOR) = callbackFlow {
38 | val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
39 | if (changedKey == key) trySend(getBoolean(key, false))
40 | }
41 |
42 | registerOnSharedPreferenceChangeListener(listener)
43 |
44 | if (contains(changedKey)) send(getBoolean(changedKey, false))
45 |
46 | awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/permissions/Permission.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.permissions
2 |
3 | import android.Manifest.permission.CAMERA
4 | import android.Manifest.permission.POST_NOTIFICATIONS
5 | import android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
6 | import android.Manifest.permission.SCHEDULE_EXACT_ALARM
7 | import android.Manifest.permission.USE_FULL_SCREEN_INTENT
8 | import android.app.Activity
9 | import android.content.Context
10 | import android.content.Intent
11 | import android.content.pm.PackageManager
12 | import android.net.Uri
13 | import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
14 | import android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS
15 | import android.provider.Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
16 | import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
17 | import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
18 | import android.provider.Settings.EXTRA_APP_PACKAGE
19 | import androidx.activity.compose.rememberLauncherForActivityResult
20 | import androidx.activity.result.ActivityResultLauncher
21 | import androidx.activity.result.contract.ActivityResultContracts
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.Stable
24 | import androidx.compose.runtime.getValue
25 | import androidx.compose.runtime.mutableStateOf
26 | import androidx.compose.runtime.remember
27 | import androidx.compose.runtime.setValue
28 | import androidx.compose.ui.platform.LocalContext
29 | import androidx.core.app.ActivityCompat
30 | import androidx.core.content.ContextCompat
31 | import androidx.lifecycle.compose.LifecycleResumeEffect
32 | import ru.application.homemedkit.helpers.extensions.canScheduleExactAlarms
33 | import ru.application.homemedkit.helpers.extensions.canUseFullScreenIntent
34 | import ru.application.homemedkit.helpers.extensions.isIgnoringBatteryOptimizations
35 |
36 | @Stable
37 | class Permission(private val context: Context, private val permission: String) : PermissionState {
38 | override var showRationale by mutableStateOf(false)
39 | override var isGranted by mutableStateOf(hasPermission())
40 |
41 | override fun launchRequest() {
42 | if (permission !in listOf(POST_NOTIFICATIONS, CAMERA)) openSettings()
43 | else if (showRationale) openSettings()
44 | else launcher?.launch(permission)
45 | }
46 |
47 | override fun refresh() {
48 | isGranted = hasPermission()
49 | }
50 |
51 | override fun openSettings() {
52 | val action = when (permission) {
53 | SCHEDULE_EXACT_ALARM -> ACTION_REQUEST_SCHEDULE_EXACT_ALARM
54 | POST_NOTIFICATIONS -> ACTION_APP_NOTIFICATION_SETTINGS
55 | USE_FULL_SCREEN_INTENT -> ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
56 | REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -> ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
57 | else -> ACTION_APPLICATION_DETAILS_SETTINGS
58 | }
59 |
60 | context.startActivity(
61 | Intent(action).apply {
62 | flags = Intent.FLAG_ACTIVITY_NEW_TASK
63 | if (action == ACTION_APP_NOTIFICATION_SETTINGS) putExtra(EXTRA_APP_PACKAGE, context.packageName)
64 | else data = Uri.fromParts("package", context.packageName, null)
65 | }
66 | )
67 | }
68 |
69 | internal var launcher: ActivityResultLauncher? = null
70 |
71 | private fun hasPermission(): Boolean {
72 | val granted = when (permission) {
73 | SCHEDULE_EXACT_ALARM -> context.canScheduleExactAlarms()
74 | USE_FULL_SCREEN_INTENT -> context.canUseFullScreenIntent()
75 | REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -> context.isIgnoringBatteryOptimizations()
76 | else -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
77 | }
78 |
79 | showRationale = !granted && ActivityCompat.shouldShowRequestPermissionRationale(context as Activity, permission)
80 |
81 | return granted
82 | }
83 | }
84 |
85 | @Composable
86 | fun rememberPermissionState(permission: String): PermissionState {
87 | val context = LocalContext.current
88 | val permissionState = remember(permission) { Permission(context, permission) }
89 | val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
90 | permissionState.refresh()
91 | }
92 |
93 | LifecycleResumeEffect(permission, launcher) {
94 | if (!permissionState.isGranted) permissionState.refresh()
95 | if (permissionState.launcher == null) permissionState.launcher = launcher
96 |
97 | onPauseOrDispose { permissionState.launcher = null }
98 | }
99 |
100 | return permissionState
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/helpers/permissions/PermissionState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.helpers.permissions
2 |
3 | interface PermissionState {
4 | var isGranted: Boolean
5 | var showRationale: Boolean
6 |
7 | fun launchRequest()
8 | fun refresh()
9 | fun openSettings()
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/events/IntakeEvent.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.events
2 |
3 | import androidx.annotation.StringRes
4 | import ru.application.homemedkit.helpers.enums.IntakeExtra
5 | import ru.application.homemedkit.helpers.enums.SchemaType
6 | import java.time.DayOfWeek
7 |
8 | sealed interface IntakeEvent {
9 | data class SetSchemaType(val type: SchemaType) : IntakeEvent
10 | data class SetAmount(val amount: String, val index: Int = 0) : IntakeEvent
11 | data class SetInterval(val interval: Any?) : IntakeEvent
12 | data class SetPeriod(val period: Any?) : IntakeEvent
13 | data class SetFoodType(val type: Int) : IntakeEvent
14 | data class SetPickedDay(val day: DayOfWeek) : IntakeEvent
15 | data class SetSameAmount(val flag: Boolean) : IntakeEvent
16 | data class SetIntakeExtra(val extra: IntakeExtra) : IntakeEvent
17 | data object SetPickedTime : IntakeEvent
18 |
19 | data object ShowSchemaTypePicker : IntakeEvent
20 | data object ShowDateRangePicker : IntakeEvent
21 | data object ShowPeriodTypePicker : IntakeEvent
22 | data object ShowIntervalTypePicker : IntakeEvent
23 | data class ShowTimePicker(val index: Int = 0) : IntakeEvent
24 | data class ShowDialogDescription(@StringRes val description: Int? = null) : IntakeEvent
25 | data object ShowDialogDelete : IntakeEvent
26 | data class ShowDialogDataLoss(val flag: Boolean) : IntakeEvent
27 |
28 | data object IncTime : IntakeEvent
29 | data object DecTime : IntakeEvent
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/events/MedicineEvent.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.events
2 |
3 | import androidx.compose.runtime.snapshots.SnapshotStateList
4 | import ru.application.homemedkit.data.dto.Kit
5 | import ru.application.homemedkit.helpers.enums.DoseType
6 |
7 | sealed interface MedicineEvent {
8 | data class SetCis(val cis: String) : MedicineEvent
9 | data class SetProductName(val productName: String) : MedicineEvent
10 | data class SetNameAlias(val alias: String) : MedicineEvent
11 | data class SetExpDate(val month: Int, val year: Int) : MedicineEvent
12 | data class SetPackageDate(val timestamp: Long) : MedicineEvent
13 | data class SetFormName(val formName: String) : MedicineEvent
14 | data class SetDoseName(val doseName: String) : MedicineEvent
15 | data class SetDoseType(val type: DoseType) : MedicineEvent
16 | data class SetAmount(val amount: String) : MedicineEvent
17 | data class SetPhKinetics(val phKinetics: String) : MedicineEvent
18 | data class SetComment(val comment: String) : MedicineEvent
19 |
20 | data class PickKit(val kit: Kit) : MedicineEvent
21 | data object ClearKit : MedicineEvent
22 |
23 | data class SetIcon(val icon: String) : MedicineEvent
24 | data class SetImage(val images: SnapshotStateList) : MedicineEvent
25 | data class ShowDialogFullImage(val index: Int = 0) : MedicineEvent
26 | data class SetFullImage(val index: Int) : MedicineEvent
27 |
28 | data object ShowKitDialog : MedicineEvent
29 | data object ShowDatePicker : MedicineEvent
30 | data object ShowPackageDatePicker : MedicineEvent
31 | data object ShowDialogPictureChoose : MedicineEvent
32 | data object ShowIconPicker : MedicineEvent
33 | data object ShowDialogDelete : MedicineEvent
34 | data object ShowDoseMenu : MedicineEvent
35 | data object ShowTakePhoto : MedicineEvent
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/events/Response.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.events
2 |
3 | sealed interface Response {
4 | data object Duplicate : Response
5 | data object Loading : Response
6 | data object IncorrectCode : Response
7 | data class NetworkError(val code: String? = null) : Response
8 | data object UnknownError : Response
9 | data class Success(val id: Long, val duplicate: Boolean = false) : Response
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/events/TakenEvent.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.events
2 |
3 | import android.content.Context
4 |
5 | sealed interface TakenEvent {
6 | data class SaveTaken(val context: Context) : TakenEvent
7 |
8 | data class SetSelection(val index: Int) : TakenEvent
9 | data object SetFactTime : TakenEvent
10 |
11 | data class ShowTimePicker(val flag: Boolean) : TakenEvent
12 | data object HideDialog : TakenEvent
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/CameraState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.states
2 |
3 | import android.content.Context
4 | import android.util.Size
5 | import androidx.camera.core.ImageAnalysis.Analyzer
6 | import androidx.camera.core.TorchState
7 | import androidx.camera.core.resolutionselector.AspectRatioStrategy
8 | import androidx.camera.core.resolutionselector.ResolutionSelector
9 | import androidx.camera.core.resolutionselector.ResolutionStrategy
10 | import androidx.camera.view.LifecycleCameraController
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.platform.LocalContext
14 | import kotlinx.coroutines.Dispatchers
15 | import kotlinx.coroutines.asExecutor
16 | import ru.application.homemedkit.helpers.DataMatrixAnalyzer
17 | import ru.application.homemedkit.helpers.PhotoCapture
18 |
19 | class CameraState(val context: Context) {
20 | val controller = LifecycleCameraController(context)
21 |
22 | private val defaultExecutor = Dispatchers.Default.asExecutor()
23 |
24 | private val resolution = ResolutionStrategy(
25 | Size(1920, 1080),
26 | ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
27 | )
28 |
29 | private val imageResolution = ResolutionSelector.Builder()
30 | .setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY)
31 | .setResolutionStrategy(resolution)
32 | .build()
33 |
34 | private val hasFlashUnit = controller.cameraInfo?.hasFlashUnit() != false
35 |
36 | fun setUseCases(useCases: Int) = with(controller) { setEnabledUseCases(useCases) }
37 |
38 | fun setAnalyzer(analyzer: Analyzer) = with(controller) {
39 | setImageAnalysisAnalyzer(defaultExecutor, analyzer)
40 | }
41 |
42 | fun setResolution() = with(controller) {
43 | imageAnalysisResolutionSelector = imageResolution
44 | imageCaptureResolutionSelector = imageResolution
45 | }
46 |
47 | fun toggleTorch() = with(controller) {
48 | if (hasFlashUnit) cameraControl?.enableTorch(cameraInfo?.torchState?.value != TorchState.ON)
49 | }
50 |
51 | fun takePicture(onResult: (String) -> Unit) = with(controller) {
52 | takePicture(
53 | defaultExecutor,
54 | PhotoCapture(context, onResult)
55 | )
56 | }
57 | }
58 |
59 | @Composable
60 | fun rememberCameraState(useCases: Int, analyzer: Analyzer? = null): CameraState {
61 | val context = LocalContext.current
62 |
63 | return remember {
64 | CameraState(context).apply {
65 | setResolution()
66 | setUseCases(useCases)
67 | analyzer?.let(::setAnalyzer)
68 | }
69 | }
70 | }
71 |
72 | @Composable
73 | fun rememberImageAnalyzer(onResult: (String) -> Unit) = remember { DataMatrixAnalyzer(onResult) }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/IntakeState.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package ru.application.homemedkit.models.states
4 |
5 | import androidx.annotation.StringRes
6 | import androidx.compose.material3.ExperimentalMaterial3Api
7 | import androidx.compose.runtime.mutableStateListOf
8 | import androidx.compose.runtime.snapshots.SnapshotStateList
9 | import androidx.compose.runtime.toMutableStateList
10 | import ru.application.homemedkit.R
11 | import ru.application.homemedkit.data.model.IntakeAmountTime
12 | import ru.application.homemedkit.data.model.MedicineIntake
13 | import ru.application.homemedkit.helpers.BLANK
14 | import ru.application.homemedkit.helpers.enums.IntakeExtra
15 | import ru.application.homemedkit.helpers.enums.Interval
16 | import ru.application.homemedkit.helpers.enums.Period
17 | import ru.application.homemedkit.helpers.Preferences
18 | import ru.application.homemedkit.helpers.enums.SchemaType
19 | import java.time.DayOfWeek
20 |
21 | data class IntakeState(
22 | val adding: Boolean = true,
23 | val editing: Boolean = false,
24 | val default: Boolean = false,
25 | val intakeId: Long = 0L,
26 | val medicineId: Long = 0L,
27 | val medicine: MedicineIntake = MedicineIntake(),
28 | val image: String = BLANK,
29 | val schemaType: SchemaType = SchemaType.BY_DAYS,
30 | val amountStock: String = BLANK,
31 | @StringRes val amountError: Int? = null,
32 | val sameAmount: Boolean = true,
33 | @StringRes val doseType: Int = R.string.blank,
34 | val interval: String = Interval.DAILY.days.toString(),
35 | val intervalType: Interval = Interval.DAILY,
36 | @StringRes val intervalError: Int? = null,
37 | val period: String = BLANK,
38 | val periodType: Period = Period.PICK,
39 | @StringRes val periodError: Int? = null,
40 | val foodType: Int = -1,
41 | val pickedDays: SnapshotStateList = DayOfWeek.entries.toMutableStateList(),
42 | val pickedTime: SnapshotStateList = mutableStateListOf(IntakeAmountTime()),
43 | val timePickerIndex: Int = 0,
44 | @StringRes val timesError: Int? = null,
45 | val startDate: String = BLANK,
46 | @StringRes val startDateError: Int? = null,
47 | val finalDate: String = BLANK,
48 | @StringRes val finalDateError: Int? = null,
49 | @StringRes val extraDesc: Int? = null,
50 | val selectedExtras: SnapshotStateList = mutableStateListOf(IntakeExtra.CANCELLABLE),
51 | val showIntervalTypePicker: Boolean = false,
52 | val showDateRangePicker: Boolean = false,
53 | val showSchemaTypePicker: Boolean = false,
54 | val showPeriodTypePicker: Boolean = false,
55 | val showTimePicker: Boolean = false,
56 | val fullScreen: Boolean = false,
57 | val noSound: Boolean = false,
58 | val preAlarm: Boolean = false,
59 | val cancellable: Boolean = true,
60 | val showDialogDescription: Boolean = false,
61 | val showDialogDelete: Boolean = false,
62 | val showDialogDataLoss: Boolean = false,
63 | val isFirstLaunch: Boolean = Preferences.isFirstLaunch
64 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/IntakesState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.states
2 |
3 | import ru.application.homemedkit.helpers.BLANK
4 | import ru.application.homemedkit.helpers.enums.IntakeTab
5 |
6 | data class IntakesState(
7 | val search: String = BLANK,
8 | val tab: IntakeTab = IntakeTab.LIST,
9 | val showDialog: Boolean = false,
10 | val showDialogDate: Boolean = false,
11 | val showDialogDelete: Boolean = false
12 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/MedicineState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.states
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material3.SnackbarHostState
5 | import androidx.compose.runtime.mutableStateListOf
6 | import androidx.compose.runtime.snapshots.SnapshotStateList
7 | import ru.application.homemedkit.data.dto.Kit
8 | import ru.application.homemedkit.helpers.BLANK
9 | import ru.application.homemedkit.helpers.enums.DoseType
10 | import ru.application.homemedkit.helpers.enums.DrugType
11 | import kotlin.random.Random
12 |
13 | data class MedicineState(
14 | val adding: Boolean = true,
15 | val editing: Boolean = false,
16 | val default: Boolean = false,
17 | val id: Long = 0L,
18 | val kits: SnapshotStateList = mutableStateListOf(),
19 | val cis: String = BLANK,
20 | val productName: String = BLANK,
21 | @StringRes val productNameError: Int? = null,
22 | val nameAlias: String = BLANK,
23 | val expDate: Long = -1L,
24 | val expDateString: String = BLANK,
25 | val dateOpened: Long = -1L,
26 | val dateOpenedString: String = BLANK,
27 | val prodFormNormName: String = BLANK,
28 | val structure: String = BLANK,
29 | val prodDNormName: String = BLANK,
30 | val prodAmount: String = BLANK,
31 | val doseType: DoseType = DoseType.UNKNOWN,
32 | val phKinetics: String = BLANK,
33 | val recommendations: String = BLANK,
34 | val storageConditions: String = BLANK,
35 | val comment: String = BLANK,
36 | val fullImage: Int = 0,
37 | val images: SnapshotStateList = mutableStateListOf(DrugType.entries[Random.nextInt(0, DrugType.entries.size)].value),
38 | val technical: TechnicalState = TechnicalState(),
39 | val snackbarHostState: SnackbarHostState = SnackbarHostState(),
40 | val showDialogKits: Boolean = false,
41 | val showDialogDate: Boolean = false,
42 | val showDialogPackageDate: Boolean = false,
43 | val showDialogIcons: Boolean = false,
44 | val showDialogPictureChoose: Boolean = false,
45 | val showDialogFullImage: Boolean = false,
46 | val showDialogDelete: Boolean = false,
47 | val showMenuDose: Boolean = false,
48 | val showTakePhoto: Boolean = false
49 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/MedicinesState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.states
2 |
3 | import androidx.compose.runtime.mutableStateListOf
4 | import androidx.compose.runtime.snapshots.SnapshotStateList
5 | import ru.application.homemedkit.data.dto.Kit
6 | import ru.application.homemedkit.helpers.BLANK
7 | import ru.application.homemedkit.helpers.Preferences
8 | import ru.application.homemedkit.helpers.enums.MedicineTab
9 | import ru.application.homemedkit.helpers.enums.Sorting
10 |
11 | data class MedicinesState(
12 | val search: String = BLANK,
13 | val sorting: Sorting = Preferences.sortingOrder,
14 | val kits: SnapshotStateList = mutableStateListOf(),
15 | val tab: MedicineTab = MedicineTab.LIST,
16 | val showSort: Boolean = false,
17 | val showFilter: Boolean = false,
18 | val showAdding: Boolean = false,
19 | val showExit: Boolean = false
20 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/ScannerState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.states
2 |
3 | import androidx.compose.material3.SnackbarHostState
4 |
5 | data class ScannerState(
6 | val doImageAnalysis: Boolean = true,
7 | val snackbarHostState: SnackbarHostState = SnackbarHostState()
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/TakenState.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package ru.application.homemedkit.models.states
4 |
5 | import androidx.compose.material3.ExperimentalMaterial3Api
6 | import androidx.compose.material3.TimePickerState
7 | import ru.application.homemedkit.R
8 | import ru.application.homemedkit.data.dto.Medicine
9 | import ru.application.homemedkit.helpers.BLANK
10 | import ru.application.homemedkit.helpers.ResourceText
11 |
12 | data class TakenState(
13 | val takenId: Long = 0L,
14 | val alarmId: Long = 0L,
15 | val medicine: Medicine? = null,
16 | val productName: String = BLANK,
17 | val amount: Double = 0.0,
18 | val date: String = BLANK,
19 | val scheduled: String = BLANK,
20 | val actual: ResourceText = ResourceText.StringResource(R.string.intake_text_not_taken),
21 | val inFact: Long = 0L,
22 | val pickerState: TimePickerState = TimePickerState(12, 0, true),
23 | val selection: Int = 0,
24 | val taken: Boolean = false,
25 | val notified: Boolean = false,
26 | val showPicker: Boolean = false
27 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/states/TechnicalState.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.states
2 |
3 | data class TechnicalState(
4 | val scanned: Boolean = false,
5 | val verified: Boolean = false
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/validation/Validation.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.validation
2 |
3 | import ru.application.homemedkit.R.string.text_fill_field
4 | import ru.application.homemedkit.data.model.IntakeAmountTime
5 |
6 | object Validation {
7 | fun checkAmount(list: List) = when {
8 | list.all { it.amount.isNotEmpty() } -> ValidationResult(successful = true)
9 | else -> ValidationResult(
10 | successful = false,
11 | errorMessage = text_fill_field
12 | )
13 | }
14 |
15 | fun checkTime(list: List) = when {
16 | list.all { it.time.isNotEmpty() } -> ValidationResult(successful = true)
17 | else -> ValidationResult(
18 | successful = false,
19 | errorMessage = text_fill_field
20 | )
21 | }
22 |
23 | fun textNotEmpty(text: String) = if (text.isNotEmpty()) ValidationResult(successful = true)
24 | else ValidationResult(
25 | successful = false,
26 | errorMessage = text_fill_field
27 | )
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/validation/ValidationResult.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.validation
2 |
3 | import androidx.annotation.StringRes
4 |
5 | data class ValidationResult(
6 | val successful: Boolean,
7 | @StringRes val errorMessage: Int? = null
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/viewModels/MedicinesViewModel.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.viewModels
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.runtime.mutableStateListOf
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.ExperimentalCoroutinesApi
9 | import kotlinx.coroutines.flow.MutableStateFlow
10 | import kotlinx.coroutines.flow.SharingStarted
11 | import kotlinx.coroutines.flow.asStateFlow
12 | import kotlinx.coroutines.flow.flatMapLatest
13 | import kotlinx.coroutines.flow.flowOn
14 | import kotlinx.coroutines.flow.map
15 | import kotlinx.coroutines.flow.stateIn
16 | import kotlinx.coroutines.flow.update
17 | import ru.application.homemedkit.HomeMeds.Companion.database
18 | import ru.application.homemedkit.data.dto.Kit
19 | import ru.application.homemedkit.data.model.MedicineGrouped
20 | import ru.application.homemedkit.data.model.MedicineMain
21 | import ru.application.homemedkit.helpers.BLANK
22 | import ru.application.homemedkit.helpers.enums.MedicineTab
23 | import ru.application.homemedkit.helpers.enums.Sorting
24 | import ru.application.homemedkit.helpers.extensions.toMedicineList
25 | import ru.application.homemedkit.models.states.MedicinesState
26 |
27 | class MedicinesViewModel : ViewModel() {
28 | private val _state = MutableStateFlow(MedicinesState())
29 | val state = _state.asStateFlow()
30 |
31 | private val listStates = MedicineTab.entries.associateWith { LazyListState() }
32 | val listState: LazyListState
33 | get() = listStates.getValue(_state.value.tab)
34 |
35 | @OptIn(ExperimentalCoroutinesApi::class)
36 | private val _medicines = _state.flatMapLatest { query ->
37 | database.medicineDAO().getListFlow(
38 | search = query.search,
39 | sorting = query.sorting,
40 | kitIds = query.kits.map(Kit::kitId),
41 | kitsEnabled = query.kits.isNotEmpty()
42 | )
43 | }
44 |
45 | val medicines = _medicines
46 | .map { list -> list.map(MedicineMain::toMedicineList) }
47 | .flowOn(Dispatchers.IO)
48 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
49 |
50 | val grouped = _medicines.map { list ->
51 | val kitIds = list.flatMap(MedicineMain::kitIds).distinct()
52 | val kitsMap = database.kitDAO().getKitList(kitIds).associateBy(Kit::kitId)
53 |
54 | list.flatMap { medicine ->
55 | medicine.kitIds.mapNotNull { kitId ->
56 | kitsMap[kitId]?.let { kit -> Pair(kit, medicine) }
57 | }
58 | }
59 | .groupBy(Pair::first, Pair::second)
60 | .map { (kit, medicines) ->
61 | MedicineGrouped(
62 | kit = kit,
63 | medicines = medicines.map(MedicineMain::toMedicineList)
64 | )
65 | }
66 | .sortedBy { it.kit.position }
67 | }
68 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
69 |
70 | val kits = database.kitDAO().getFlow()
71 | .flowOn(Dispatchers.IO)
72 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
73 |
74 | fun pickTab(tab: MedicineTab) = _state.update { it.copy(tab = tab) }
75 |
76 | fun showAdding() = _state.update { it.copy(showAdding = !it.showAdding) }
77 | fun showExit(flag: Boolean = false) = _state.update { it.copy(showExit = flag) }
78 |
79 | fun setSearch(text: String) = _state.update { it.copy(search = text) }
80 | fun clearSearch() = _state.update { it.copy(search = BLANK) }
81 |
82 | fun showSort() = _state.update { it.copy(showSort = !it.showSort) }
83 | fun setSorting(sorting: Sorting) = _state.update { it.copy(sorting = sorting) }
84 |
85 | fun showFilter() = _state.update { it.copy(showFilter = !it.showFilter) }
86 | fun clearFilter() = _state.update { it.copy(showFilter = false, kits = mutableStateListOf()) }
87 | fun pickFilter(kit: Kit) = _state.update {
88 | it.copy(kits = it.kits.apply { if (kit in this) remove(kit) else add(kit) })
89 | }
90 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/models/viewModels/ScannerViewModel.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.models.viewModels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.channels.Channel
7 | import kotlinx.coroutines.delay
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.asStateFlow
10 | import kotlinx.coroutines.flow.receiveAsFlow
11 | import kotlinx.coroutines.flow.update
12 | import kotlinx.coroutines.launch
13 | import kotlinx.coroutines.withContext
14 | import ru.application.homemedkit.HomeMeds.Companion.database
15 | import ru.application.homemedkit.data.dto.Image
16 | import ru.application.homemedkit.helpers.enums.DrugType
17 | import ru.application.homemedkit.helpers.extensions.toMedicine
18 | import ru.application.homemedkit.helpers.getMedicineImages
19 | import ru.application.homemedkit.models.events.Response
20 | import ru.application.homemedkit.models.states.ScannerState
21 | import ru.application.homemedkit.network.Network
22 | import java.io.File
23 |
24 | class ScannerViewModel : ViewModel() {
25 | private val dao = database.medicineDAO()
26 |
27 | private val _state = MutableStateFlow(ScannerState())
28 | val state = _state.asStateFlow()
29 |
30 | private val _response = Channel()
31 | val response = _response.receiveAsFlow()
32 |
33 | fun fetch(dir: File, code: String) {
34 | viewModelScope.launch {
35 | if (!_state.value.doImageAnalysis)
36 | return@launch
37 |
38 | setLoading()
39 |
40 | dao.getAllCis().filterNot(String::isBlank).find { it in code }?.let {
41 | val duplicateId = dao.getIdByCis(it)
42 | _response.send(Response.Success(duplicateId, true))
43 |
44 | return@launch
45 | }
46 |
47 | withContext(Dispatchers.IO) {
48 | try {
49 | Network.getMedicine(code).run {
50 | if (!codeFounded) {
51 | showGeneralError()
52 |
53 | return@withContext
54 | }
55 |
56 | drugsData?.let {
57 | val medicine = it.toMedicine().copy(cis = this.code)
58 | val id = dao.insert(medicine)
59 | val images = getMedicineImages(
60 | medicineId = id,
61 | form = it.foiv.prodFormNormName,
62 | directory = dir,
63 | urls = it.vidalData?.images
64 | )
65 |
66 | dao.updateImages(images)
67 |
68 | _response.send(Response.Success(id))
69 |
70 | return@withContext
71 | }
72 |
73 | bioData?.let {
74 | val medicine = it.toMedicine().copy(cis = this.code)
75 | val id = dao.insert(medicine)
76 | val image = Image(
77 | medicineId = id,
78 | image = DrugType.setIcon(it.productProperty.releaseForm.orEmpty())
79 | )
80 |
81 | dao.addImage(image)
82 |
83 | _response.send(Response.Success(id))
84 |
85 | return@withContext
86 | }
87 |
88 | showIncorrectCodeError()
89 | }
90 | } catch (_: Throwable) {
91 | showNetworkError(code)
92 | }
93 | }
94 | }
95 | }
96 |
97 | fun setInitial() {
98 | viewModelScope.launch {
99 | _state.update {
100 | it.copy(
101 | doImageAnalysis = true
102 | )
103 | }
104 |
105 | _response.send(null)
106 | }
107 | }
108 |
109 | private suspend fun setLoading() {
110 | _state.update {
111 | it.copy(
112 | doImageAnalysis = false
113 | )
114 | }
115 |
116 | _response.send(Response.Loading)
117 | }
118 |
119 | private suspend fun showIncorrectCodeError() {
120 | _response.send(Response.IncorrectCode)
121 |
122 | delay(2500L)
123 |
124 | _state.update {
125 | it.copy(
126 | doImageAnalysis = true
127 | )
128 | }
129 | }
130 |
131 | private suspend fun showGeneralError() {
132 | _response.send(Response.UnknownError)
133 |
134 | delay(2500L)
135 |
136 | _state.update {
137 | it.copy(
138 | doImageAnalysis = true
139 | )
140 | }
141 | }
142 |
143 | private suspend fun showNetworkError(code: String) {
144 | _response.send(Response.NetworkError(code))
145 | }
146 |
147 | fun showSnackbar(message: String) {
148 | viewModelScope.launch {
149 | _state.value.snackbarHostState.showSnackbar(message)
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/Network.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.call.body
5 | import io.ktor.client.engine.android.Android
6 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
7 | import io.ktor.client.plugins.defaultRequest
8 | import io.ktor.client.request.get
9 | import io.ktor.client.request.parameter
10 | import io.ktor.client.statement.bodyAsChannel
11 | import io.ktor.serialization.kotlinx.json.json
12 | import io.ktor.util.cio.use
13 | import io.ktor.util.cio.writeChannel
14 | import io.ktor.utils.io.copyAndClose
15 | import kotlinx.coroutines.Dispatchers
16 | import kotlinx.coroutines.withContext
17 | import kotlinx.serialization.json.Json
18 | import ru.application.homemedkit.helpers.CIS
19 | import ru.application.homemedkit.network.models.MainModel
20 | import java.io.File
21 |
22 | object Network {
23 | private val ktor = HttpClient(Android) {
24 | defaultRequest { url("https://mobile.api.crpt.ru/") }
25 | install(ContentNegotiation) {
26 | json(
27 | Json {
28 | explicitNulls = false
29 | ignoreUnknownKeys = true
30 | }
31 | )
32 | }
33 | }
34 |
35 | suspend fun getMedicine(cis: String) =
36 | ktor.get("mobile/check") { parameter(CIS, cis) }.body()
37 |
38 | suspend fun getImage(dir: File, urls: List?): List {
39 | if (urls.isNullOrEmpty())
40 | return emptyList()
41 |
42 | val images = mutableListOf()
43 |
44 | withContext(Dispatchers.IO) {
45 | urls.forEach { url ->
46 | try {
47 | val name = url.substringAfterLast("/").substringBefore(".")
48 | val file = File(dir, name)
49 | val response = ktor.get(url)
50 |
51 | file.writeChannel().use {
52 | response.bodyAsChannel().copyAndClose(this)
53 | }
54 |
55 | images.add(name)
56 | } catch (_: Throwable) {
57 | null
58 | }
59 | }
60 | }
61 |
62 | return images
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/models/MainModel.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network.models
2 |
3 | import kotlinx.serialization.Serializable
4 | import ru.application.homemedkit.network.models.bio.BioData
5 | import ru.application.homemedkit.network.models.medicine.DrugsData
6 |
7 | @Serializable
8 | data class MainModel(
9 | val codeFounded: Boolean,
10 | val checkResult: Boolean,
11 | val category: String?,
12 | val code: String,
13 | val drugsData: DrugsData?,
14 | val bioData: BioData?
15 | )
16 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/models/bio/BioData.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network.models.bio
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class BioData(
7 | val productName: String,
8 | val expireDate: Long,
9 | val productProperty: ProductProperty
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/models/bio/ProductProperty.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network.models.bio
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ProductProperty(
7 | val structure: String?,
8 | val unitVolumeWeight: String?,
9 | val applicationArea: String?,
10 | val recommendForUse: String?,
11 | val storageConditions: String?,
12 | val releaseForm: String?,
13 | val quantityInPack: Double?
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/models/medicine/DrugsData.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network.models.medicine
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class DrugsData(
7 | val prodDescLabel: String,
8 | val foiv: Foiv,
9 | val expireDate: Long,
10 | val vidalData: VidalData?
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/models/medicine/Foiv.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network.models.medicine
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Foiv(
7 | val prodFormNormName: String,
8 | val prodDNormName: String?,
9 | val prodPack12: String?,
10 | val prodPack1Size: String?
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/network/models/medicine/VidalData.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.network.models.medicine
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class VidalData(
7 | val phKinetics: String?,
8 | val images: List?
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/receivers/ActionReceiver.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.receivers
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import androidx.core.app.NotificationManagerCompat
7 | import ru.application.homemedkit.data.MedicineDatabase
8 | import ru.application.homemedkit.helpers.BLANK
9 | import ru.application.homemedkit.helpers.ID
10 | import ru.application.homemedkit.helpers.TAKEN_ID
11 | import ru.application.homemedkit.helpers.TYPE
12 |
13 | class ActionReceiver : BroadcastReceiver() {
14 | override fun onReceive(context: Context, intent: Intent) {
15 | val database = MedicineDatabase.getInstance(context)
16 |
17 | val medicineId = intent.getLongExtra(ID, 0L)
18 | val takenId = intent.getLongExtra(TAKEN_ID, 0L)
19 | val amount = intent.getDoubleExtra(BLANK, 0.0)
20 |
21 | NotificationManagerCompat.from(context).cancel(takenId.toInt())
22 | database.takenDAO().setNotified(takenId)
23 | if (intent.action == TYPE) {
24 | database.takenDAO().setTaken(takenId, true, System.currentTimeMillis())
25 | database.medicineDAO().intakeMedicine(medicineId, amount)
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/receivers/AlarmSetter.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.receivers
2 |
3 | import android.app.AlarmManager
4 | import android.app.AlarmManager.RTC_WAKEUP
5 | import android.app.PendingIntent.FLAG_CANCEL_CURRENT
6 | import android.app.PendingIntent.FLAG_IMMUTABLE
7 | import android.app.PendingIntent.FLAG_UPDATE_CURRENT
8 | import android.app.PendingIntent.getBroadcast
9 | import android.content.Context
10 | import android.content.Intent
11 | import ru.application.homemedkit.data.MedicineDatabase
12 | import ru.application.homemedkit.data.dto.Alarm
13 | import ru.application.homemedkit.helpers.ALARM_ID
14 | import ru.application.homemedkit.helpers.expirationCheckTime
15 | import ru.application.homemedkit.helpers.extensions.canScheduleExactAlarms
16 |
17 | class AlarmSetter(private val context: Context) {
18 | private val manager = context.getSystemService(AlarmManager::class.java)
19 | private val database = MedicineDatabase.getInstance(context)
20 |
21 | private val alarmIntent = Intent(context, AlarmReceiver::class.java).addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
22 | private val preAlarmIntent = Intent(context, PreAlarmReceiver::class.java).addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
23 |
24 | fun setAlarm(takenId: Long, trigger: Long) {
25 | val pending = getBroadcast(
26 | context,
27 | takenId.toInt(),
28 | alarmIntent.putExtra(ALARM_ID, takenId),
29 | FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
30 | )
31 |
32 | with(manager) {
33 | if (context.canScheduleExactAlarms()) {
34 | setExactAndAllowWhileIdle(RTC_WAKEUP, trigger, pending)
35 | } else {
36 | setAndAllowWhileIdle(RTC_WAKEUP, trigger, pending)
37 | }
38 | }
39 | }
40 |
41 | fun setPreAlarm(intakeId: Long) {
42 | val alarm = database.alarmDAO().getNextByIntakeId(intakeId) ?: return
43 | val preTrigger = alarm.trigger - 1800000L
44 |
45 | val pending = getBroadcast(
46 | context,
47 | alarm.alarmId.toInt(),
48 | preAlarmIntent.putExtra(ALARM_ID, alarm.alarmId),
49 | FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
50 | )
51 |
52 | with(manager) {
53 | if (context.canScheduleExactAlarms()) {
54 | setExactAndAllowWhileIdle(RTC_WAKEUP, preTrigger, pending)
55 | } else {
56 | setAndAllowWhileIdle(RTC_WAKEUP, preTrigger, pending)
57 | }
58 | }
59 | }
60 |
61 | fun removeAlarm(intakeId: Long) {
62 | val nextAlarm = database.alarmDAO().getNextByIntakeId(intakeId) ?: return
63 |
64 | val pendingA = getBroadcast(
65 | context,
66 | nextAlarm.alarmId.toInt(),
67 | preAlarmIntent.putExtra(ALARM_ID, nextAlarm.alarmId),
68 | FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
69 | )
70 |
71 | val pendingB = getBroadcast(
72 | context,
73 | nextAlarm.alarmId.toInt(),
74 | alarmIntent.putExtra(ALARM_ID, nextAlarm.alarmId),
75 | FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
76 | )
77 |
78 | with(manager) {
79 | cancel(pendingA)
80 | cancel(pendingB)
81 | }
82 | }
83 |
84 | fun resetAll() = database.alarmDAO().getAll()
85 | .filter { it.trigger < System.currentTimeMillis() }
86 | .sortedBy(Alarm::trigger)
87 | .mapNotNull(Alarm::intakeId)
88 | .forEach(::setPreAlarm)
89 |
90 | fun cancelAll() = database.alarmDAO().getAll().forEach {
91 | with(manager) {
92 | cancel(
93 | getBroadcast(
94 | context,
95 | it.alarmId.toInt(),
96 | preAlarmIntent.putExtra(ALARM_ID, it.alarmId),
97 | FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE
98 | )
99 | )
100 |
101 | cancel(
102 | getBroadcast(
103 | context,
104 | it.alarmId.toInt(),
105 | alarmIntent.putExtra(ALARM_ID, it.alarmId),
106 | FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE
107 | )
108 | )
109 | }
110 | }
111 |
112 | fun checkExpiration(check: Boolean) {
113 | val broadcast = getBroadcast(
114 | context,
115 | 81000,
116 | Intent(context, ExpirationReceiver::class.java).addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
117 | FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
118 | )
119 |
120 | with(manager) {
121 | if (check) {
122 | if (context.canScheduleExactAlarms()) {
123 | setExactAndAllowWhileIdle(RTC_WAKEUP, expirationCheckTime(), broadcast)
124 | } else {
125 | setAndAllowWhileIdle(RTC_WAKEUP, expirationCheckTime(), broadcast)
126 | }
127 | } else {
128 | cancel(broadcast)
129 | }
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/receivers/BootReceiver.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.receivers
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.Intent.ACTION_BOOT_COMPLETED
7 | import ru.application.homemedkit.data.MedicineDatabase
8 |
9 | class BootReceiver : BroadcastReceiver() {
10 | override fun onReceive(context: Context, intent: Intent) {
11 | if (intent.action == ACTION_BOOT_COMPLETED) {
12 | val takenDAO = MedicineDatabase.getInstance(context).takenDAO()
13 |
14 | AlarmSetter(context).resetAll()
15 | takenDAO.getAll().forEach { takenDAO.setNotified(it.takenId) }
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/receivers/ExpirationReceiver.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.receivers
2 |
3 | import android.app.AlarmManager.INTERVAL_DAY
4 | import android.app.PendingIntent.FLAG_IMMUTABLE
5 | import android.app.PendingIntent.FLAG_UPDATE_CURRENT
6 | import android.content.BroadcastReceiver
7 | import android.content.Context
8 | import android.content.Intent
9 | import androidx.core.app.NotificationCompat.Builder
10 | import androidx.core.app.NotificationCompat.CATEGORY_REMINDER
11 | import androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC
12 | import androidx.core.app.NotificationManagerCompat
13 | import androidx.core.app.TaskStackBuilder
14 | import androidx.core.net.toUri
15 | import ru.application.homemedkit.MainActivity
16 | import ru.application.homemedkit.R
17 | import ru.application.homemedkit.R.string.text_attention
18 | import ru.application.homemedkit.R.string.text_expire_soon
19 | import ru.application.homemedkit.data.MedicineDatabase
20 | import ru.application.homemedkit.helpers.CHANNEL_ID_EXP
21 | import ru.application.homemedkit.helpers.extensions.safeNotify
22 |
23 | class ExpirationReceiver : BroadcastReceiver() {
24 | override fun onReceive(context: Context, intent: Intent) {
25 | val medicines = MedicineDatabase.getInstance(context).medicineDAO().getAll()
26 |
27 | medicines.forEach {
28 | if (it.expDate < System.currentTimeMillis() + 30 * INTERVAL_DAY && it.prodAmount > 0) {
29 | NotificationManagerCompat.from(context).safeNotify(
30 | context,
31 | it.id.toInt(),
32 | Builder(context, CHANNEL_ID_EXP)
33 | .setAutoCancel(true)
34 | .setCategory(CATEGORY_REMINDER)
35 | .setContentIntent(TaskStackBuilder.create(context).run {
36 | addNextIntentWithParentStack(
37 | Intent(context, MainActivity::class.java).apply {
38 | action = Intent.ACTION_VIEW
39 | data = "app://medicines/${it.id}".toUri()
40 | }
41 | )
42 | getPendingIntent(it.id.toInt(), FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
43 | })
44 | .setContentText(
45 | context.getString(text_expire_soon, it.nameAlias.ifEmpty(it::productName))
46 | )
47 | .setContentTitle(context.getString(text_attention))
48 | .setSmallIcon(R.drawable.ic_launcher_notification)
49 | .setVisibility(VISIBILITY_PUBLIC)
50 | .build()
51 | )
52 | AlarmSetter(context).checkExpiration(true)
53 | }
54 | }
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/receivers/LocaleReceiver.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.receivers
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.Intent.ACTION_LOCALE_CHANGED
7 | import ru.application.homemedkit.R.string.channel_exp_desc
8 | import ru.application.homemedkit.R.string.channel_intakes_desc
9 | import ru.application.homemedkit.R.string.channel_pre_desc
10 | import ru.application.homemedkit.helpers.CHANNEL_ID_EXP
11 | import ru.application.homemedkit.helpers.CHANNEL_ID_INTAKES
12 | import ru.application.homemedkit.helpers.CHANNEL_ID_PRE
13 | import ru.application.homemedkit.helpers.extensions.createNotificationChannel
14 |
15 | class LocaleReceiver : BroadcastReceiver() {
16 | override fun onReceive(context: Context, intent: Intent) {
17 | if (intent.action == ACTION_LOCALE_CHANGED) mapOf(
18 | CHANNEL_ID_INTAKES to channel_intakes_desc,
19 | CHANNEL_ID_PRE to channel_pre_desc,
20 | CHANNEL_ID_EXP to channel_exp_desc
21 | ).forEach { (id, name) -> context.createNotificationChannel(id, name) }
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/receivers/PreAlarmReceiver.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.receivers
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import androidx.core.app.NotificationCompat.BigTextStyle
7 | import androidx.core.app.NotificationCompat.Builder
8 | import androidx.core.app.NotificationCompat.CATEGORY_REMINDER
9 | import androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC
10 | import androidx.core.app.NotificationManagerCompat
11 | import kotlinx.coroutines.Dispatchers
12 | import ru.application.homemedkit.R
13 | import ru.application.homemedkit.R.string.text_intake_prealarm_text
14 | import ru.application.homemedkit.R.string.text_intake_prealarm_title
15 | import ru.application.homemedkit.data.MedicineDatabase.Companion.getInstance
16 | import ru.application.homemedkit.data.dto.IntakeTaken
17 | import ru.application.homemedkit.helpers.ALARM_ID
18 | import ru.application.homemedkit.helpers.BLANK
19 | import ru.application.homemedkit.helpers.CHANNEL_ID_PRE
20 | import ru.application.homemedkit.helpers.FORMAT_H_MM
21 | import ru.application.homemedkit.helpers.decimalFormat
22 | import ru.application.homemedkit.helpers.extensions.goAsync
23 | import ru.application.homemedkit.helpers.extensions.safeNotify
24 | import ru.application.homemedkit.helpers.getDateTime
25 |
26 | class PreAlarmReceiver : BroadcastReceiver() {
27 | override fun onReceive(context: Context, intent: Intent) {
28 | val database = getInstance(context)
29 |
30 | val alarmId = intent.getLongExtra(ALARM_ID, 0L)
31 |
32 | val alarm = database.alarmDAO().getById(alarmId) ?: return
33 | val intake = database.intakeDAO().getById(alarm.intakeId) ?: return
34 | val medicine = database.medicineDAO().getById(intake.medicineId) ?: return
35 | val image = database.medicineDAO().getMedicineImages(medicine.id).firstOrNull() ?: BLANK
36 |
37 | goAsync(Dispatchers.IO) {
38 | database.alarmDAO().delete(alarm)
39 |
40 | val takenId = database.takenDAO().insert(
41 | IntakeTaken(
42 | medicineId = medicine.id,
43 | intakeId = alarm.intakeId,
44 | alarmId = alarmId,
45 | productName = medicine.productName,
46 | formName = medicine.prodFormNormName,
47 | amount = alarm.amount,
48 | doseType = medicine.doseType,
49 | image = image,
50 | trigger = alarm.trigger
51 | )
52 | )
53 |
54 | AlarmSetter(context).setAlarm(takenId, alarm.trigger)
55 | }
56 |
57 | if (alarm.preAlarm) with(NotificationManagerCompat.from(context)) {
58 | safeNotify(
59 | context,
60 | alarmId.toInt(),
61 | Builder(context, CHANNEL_ID_PRE)
62 | .setCategory(CATEGORY_REMINDER)
63 | .setContentTitle(context.getString(text_intake_prealarm_title))
64 | .setSilent(true)
65 | .setSmallIcon(R.drawable.ic_launcher_notification)
66 | .setStyle(
67 | BigTextStyle().bigText(
68 | context.getString(
69 | text_intake_prealarm_text,
70 | medicine.nameAlias.ifEmpty(medicine::productName),
71 | decimalFormat(alarm.amount),
72 | context.getString(medicine.doseType.title),
73 | getDateTime(alarm.trigger).format(FORMAT_H_MM)
74 | )
75 | )
76 | )
77 | .setTimeoutAfter(1800000L)
78 | .setVisibility(VISIBILITY_PUBLIC)
79 | .build()
80 | )
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/ui/elements/Box.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.ui.elements
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.res.stringResource
10 | import androidx.compose.ui.text.style.TextAlign
11 |
12 | @Composable
13 | fun BoxWithEmptyListText(@StringRes text: Int, modifier: Modifier = Modifier) =
14 | Box(modifier, Alignment.Center) {
15 | Text(
16 | text = stringResource(text),
17 | textAlign = TextAlign.Center
18 | )
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/ui/navigation/Screen.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.ui.navigation
2 |
3 | import kotlinx.serialization.Serializable
4 | import ru.application.homemedkit.helpers.BLANK
5 |
6 | sealed interface Screen {
7 | @Serializable
8 | object Medicines : Screen
9 |
10 | @Serializable
11 | object Intakes : Screen
12 |
13 | @Serializable
14 | object Settings : Screen
15 |
16 | @Serializable
17 | object Scanner : Screen
18 |
19 | @Serializable
20 | data class Medicine(
21 | val id: Long = 0L,
22 | val cis: String = BLANK,
23 | val duplicate: Boolean = false
24 | ) : Screen
25 |
26 | @Serializable
27 | data class Intake(
28 | val intakeId: Long = 0L,
29 | val medicineId: Long = 0L
30 | ) : Screen
31 |
32 | // ===== Settings items ===== //
33 | @Serializable
34 | object KitsManager : Screen
35 |
36 | @Serializable
37 | object PermissionsScreen : Screen
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/ui/screens/CameraPreview.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.ui.screens
2 |
3 | import androidx.camera.view.PreviewView
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.platform.LocalContext
7 | import androidx.compose.ui.viewinterop.AndroidView
8 | import androidx.lifecycle.compose.LocalLifecycleOwner
9 | import ru.application.homemedkit.models.states.CameraState
10 |
11 | @Composable
12 | fun CameraPreview(cameraState: CameraState, modifier: Modifier = Modifier) {
13 | val context = LocalContext.current
14 | val lifecycleOwner = LocalLifecycleOwner.current
15 |
16 | AndroidView(
17 | modifier = modifier,
18 | factory = {
19 | PreviewView(context).apply {
20 | controller = cameraState.controller.apply {
21 | bindToLifecycle(lifecycleOwner)
22 | }
23 | }
24 | }
25 | )
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/application/homemedkit/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package ru.application.homemedkit.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val primaryLight = Color(0xFF37693D)
6 | val onPrimaryLight = Color(0xFFFFFFFF)
7 | val primaryContainerLight = Color(0xFFB8F0B8)
8 | val onPrimaryContainerLight = Color(0xFF002107)
9 | val secondaryLight = Color(0xFF516350)
10 | val onSecondaryLight = Color(0xFFFFFFFF)
11 | val secondaryContainerLight = Color(0xFFD4E8D0)
12 | val onSecondaryContainerLight = Color(0xFF0F1F10)
13 | val tertiaryLight = Color(0xFF39656C)
14 | val onTertiaryLight = Color(0xFFFFFFFF)
15 | val tertiaryContainerLight = Color(0xFFBCEAF2)
16 | val onTertiaryContainerLight = Color(0xFF001F24)
17 | val errorLight = Color(0xFFBA1A1A)
18 | val onErrorLight = Color(0xFFFFFFFF)
19 | val errorContainerLight = Color(0xFFFFDAD6)
20 | val onErrorContainerLight = Color(0xFF410002)
21 | val backgroundLight = Color(0xFFF7FBF2)
22 | val onBackgroundLight = Color(0xFF181D18)
23 | val surfaceLight = Color(0xFFF7FBF2)
24 | val onSurfaceLight = Color(0xFF181D18)
25 | val surfaceVariantLight = Color(0xFFDEE5D9)
26 | val onSurfaceVariantLight = Color(0xFF424940)
27 | val outlineLight = Color(0xFF727970)
28 | val outlineVariantLight = Color(0xFFC2C9BE)
29 | val scrimLight = Color(0xFF000000)
30 | val inverseSurfaceLight = Color(0xFF2D322C)
31 | val inverseOnSurfaceLight = Color(0xFFEEF2E9)
32 | val inversePrimaryLight = Color(0xFF9DD49E)
33 | val surfaceDimLight = Color(0xFFD7DBD3)
34 | val surfaceBrightLight = Color(0xFFF7FBF2)
35 | val surfaceContainerLowestLight = Color(0xFFFFFFFF)
36 | val surfaceContainerLowLight = Color(0xFFF1F5EC)
37 | val surfaceContainerLight = Color(0xFFEBEFE6)
38 | val surfaceContainerHighLight = Color(0xFFE5E9E1)
39 | val surfaceContainerHighestLight = Color(0xFFE0E4DB)
40 |
41 | val primaryDark = Color(0xFF9DD49E)
42 | val onPrimaryDark = Color(0xFF023912)
43 | val primaryContainerDark = Color(0xFF1F5027)
44 | val onPrimaryContainerDark = Color(0xFFB8F0B8)
45 | val secondaryDark = Color(0xFFB8CCB5)
46 | val onSecondaryDark = Color(0xFF243424)
47 | val secondaryContainerDark = Color(0xFF3A4B3A)
48 | val onSecondaryContainerDark = Color(0xFFD4E8D0)
49 | val tertiaryDark = Color(0xFFA1CED6)
50 | val onTertiaryDark = Color(0xFF00363D)
51 | val tertiaryContainerDark = Color(0xFF1F4D54)
52 | val onTertiaryContainerDark = Color(0xFFBCEAF2)
53 | val errorDark = Color(0xFFFFB4AB)
54 | val onErrorDark = Color(0xFF690005)
55 | val errorContainerDark = Color(0xFF93000A)
56 | val onErrorContainerDark = Color(0xFFFFDAD6)
57 | val backgroundDark = Color(0xFF101510)
58 | val onBackgroundDark = Color(0xFFE0E4DB)
59 | val surfaceDark = Color(0xFF101510)
60 | val onSurfaceDark = Color(0xFFE0E4DB)
61 | val surfaceVariantDark = Color(0xFF424940)
62 | val onSurfaceVariantDark = Color(0xFFC2C9BE)
63 | val outlineDark = Color(0xFF8C9389)
64 | val outlineVariantDark = Color(0xFF424940)
65 | val scrimDark = Color(0xFF000000)
66 | val inverseSurfaceDark = Color(0xFFE0E4DB)
67 | val inverseOnSurfaceDark = Color(0xFF2D322C)
68 | val inversePrimaryDark = Color(0xFF37693D)
69 | val surfaceDimDark = Color(0xFF101510)
70 | val surfaceBrightDark = Color(0xFF363A35)
71 | val surfaceContainerLowestDark = Color(0xFF0B0F0B)
72 | val surfaceContainerLowDark = Color(0xFF181D18)
73 | val surfaceContainerDark = Color(0xFF1C211C)
74 | val surfaceContainerHighDark = Color(0xFF272B26)
75 | val surfaceContainerHighestDark = Color(0xFF313630)
76 |
77 | val seed = Color(0xFF1E6C30)
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
17 |
20 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
21 |
26 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_notification.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
17 |
22 |
27 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_add_photo.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_barcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_filter.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_flash.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_medicine.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_period.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_remove.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_scanner.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_sort.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_time.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/resources.properties:
--------------------------------------------------------------------------------
1 | unqualifiedResLocale=en
--------------------------------------------------------------------------------
/app/src/main/res/values-in/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Mengecek tanggal kadaluarsa dari obat-obatan
4 | Tanggal kadaluarsa
5 | Asupan obat-obatan
6 | Suara notifikasi
7 | Pengaturan
8 | Sistem
9 | Terang
10 | Pengaturan Basic
11 | Tanggal Kadaluarsa (dari paling terbaru)
12 | Obat-obatan
13 | Mengunduh gambar obat obatan
14 | Gambar dinamis
15 | Gangguan dengan notifikasi?
16 | Menurut abjad (z sampai A)
17 | Konsumsi
18 | Tampilan Applikasi
19 | Konfirmasi keluar dari applikasi
20 | Sistem
21 | Tema Applikasi
22 | Bersihkan Cache Applikasi
23 | Menurut abjad (A sampai z)
24 | Tanggal Kadaluarsa (dari paling terlama)
25 | Pengobatan
26 | Grup Obat-obatan
27 | Penyortiran obat-obatan
28 | Perizinan
29 | Bahasa applikasi
30 | Impor/Ekspor
31 | Gelap
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Alfabetisch (Z tot A)
4 | Alfabetisch (A tot Z)
5 | Licht
6 | Donker
7 | Vervaldatum
8 | Medicijninname
9 | Let op!
10 | De vervaldatum van %1$s staat op het punt te verlopen
11 | Doe inname
12 | Medicijn: %1$s\nHoeveelheid: %2$s %3$s\nTijd: %4$s
13 | Tijd
14 | Commentaar
15 | Scanned, ongeoorloofd
16 | SOLITIE
17 | Inname
18 | Dynamische kleur
19 | Geneesmiddelgroepen
20 | GELED
21 | Systeem
22 | Laat het formulier vrij
23 | Het is tijd om %1$s in te nemen, maar de hoeveelheid is minder dan voorgeschreven!
24 | Medicijn: %1$s\nHoeveelheid: %2$s %3$s
25 | Niet genoeg hoeveelheid over
26 | Groep
27 | Product naam
28 | Systeem
29 | GRANULE
30 | App-thema
31 | Toegevoegd
32 | Het controleren van de vervaldatum van medicijnen
33 | Medicijnen sortering
34 | Scanned, geverifieerd
35 | Beschrijving
36 | App-taal
37 | Download medicijnen afbeeldingen
38 | Vervaldatum (van oudst)
39 | Indicatie voor gebruik
40 | Medicijnen
41 | Instellingen
42 | App-weergave
43 | Dosis
44 | Vertaling:
45 | Aankomende inname
46 | Vervaldatum (van nieuwst)
47 | Aankomende inname
48 | Compositie
49 | Vergoeding voor gebruik
50 | Opslag voorwaarden
51 | BANDAG
52 | sach
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 藥物
5 | 攝取量
6 | 設定
7 |
8 | 應用程式檢視
9 | 藥物群組
10 | 藥物排序
11 | 下載藥物圖片
12 | 檢查藥物的到期日
13 | 系統
14 | 應用程式語言
15 | 應用程式主題
16 | 動態色彩
17 | 匯入/匯出
18 |
19 | 按字母順序(A 至 z)
20 | 按字母順序(z 至 A)
21 | 到期日(從最舊開始)
22 | 到期日(從最新開始)
23 |
24 | 藥物提醒
25 | 聲音通知
26 | 注意!
27 | %1$s 的到期日即將到期
28 |
29 | 0.5 mg + 84 mcg
30 | 時間 #%1$d
31 |
32 | 到期日
33 | 使用說明
34 | 評論
35 | 描述
36 | 劑量
37 | 產品名稱
38 |
39 | 清單
40 | 目前
41 | 采取
42 | 數量
43 | 用餐後
44 | 用餐前
45 | 用餐期間
46 | 間隔
47 | 時間
48 |
49 | 连接错误!
50 | 授予權限
51 | 新群組
52 | 要掃描標籤,應用程式必須獲得使用相機的權限
53 | 選擇時間
54 |
55 | 是
56 | 點擊以檢視
57 | 成功!
58 | 開始日期
59 | 儲存
60 | 已開啟
61 | 已關閉
62 | 否
63 | 找不到藥物。增加它們或變更過濾器。
64 | 匯入
65 | 完成日期
66 | 匯出
67 | "每 "
68 | 錯誤!
69 | 搜尋
70 | 空白
71 | 編輯
72 | 刪除
73 | 天
74 | 每天 12:00
75 | 清除
76 | 取消
77 | 數量
78 | 增加
79 |
80 |
81 | - 每天 %d 次
82 |
83 | Medication: %1$s Amount: %2$s %3$s Time: %4$s
84 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android) apply false
4 | alias(libs.plugins.compose) apply false
5 | alias(libs.plugins.kotlin) apply false
6 | alias(libs.plugins.kotlin.serialization) apply false
7 | alias(libs.plugins.ksp) apply false
8 | alias(libs.plugins.room) apply false
9 | }
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/54.txt:
--------------------------------------------------------------------------------
1 | — Neue Listenansicht: Medikamente nach Gruppe sortiert.
2 | — Repariert: Fehler, der dazu führte, dass es doppelte Gruppennamen in der Medikamentenkarte gab.
3 | — Repariert: Fehler, der dazu führte, dass Medikamente manchmal in der Liste nicht sortiert wurden.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/full_description.txt:
--------------------------------------------------------------------------------
1 | Medkit
2 |
3 | Eine Anwendung, mit der Sie einfach und schnell Informationen über eingenommene Medikamente registrieren und speichern können.
4 |
5 | Eigenschaften
6 |
7 | * Automatisches Hinzufügen von Medikamenten durch Scannen des Etiketts.
8 | * Die Möglichkeit, Medikamente manuell hinzuzufügen.
9 | * Aktualisieren der Informationen über das Medikament, falls es ohne Internetverbindung gescannt wurde.
10 | * Bearbeiten von Informationen zu manuell hinzugefügten Medikamenten.
11 | * Hinzufügen von Medikamentenbildern.
12 | * Sortieren von Medikamenten nach Verfallsdatum.
13 | * Gruppierung von Medikamenten nach Kategorien.
14 | * Benachrichtigungen für die Einnahme von Medikamenten.
15 | * Anzeige der Medikamente, die als Nächste eingenommen werden müssen.
16 | * Benachrichtigung zum Verfallsdatum von Medikamenten.
17 | * Exportieren und Importieren der Datenbank auf andere Geräte.
18 |
19 | Mit Medkit können Sie das Verfallsdatum von Medikamenten verfolgen, einen Zeitplan für deren Einnahme erstellen und Benachrichtigungen darüber erhalten.
20 | Medkit ist ein zuverlässiger und bequemer Assistent, der Ihnen helfen wird, Ihre Gesundheit zu überwachen.
21 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/short_description.txt:
--------------------------------------------------------------------------------
1 | Medkit — Benachrichtigungen zu Medikamenten-Vorräten und -Einnahmen.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/title.txt:
--------------------------------------------------------------------------------
1 | Medkit
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/54.txt:
--------------------------------------------------------------------------------
1 | — Added a new view the list of medications by group.
2 | — Fixed a bug that caused the names of groups to be duplicated in the medicine card.
3 | — Fixed a bug that sometimes caused medications to not be sorted in the list.
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Home Medkit
2 |
3 | An application that allows you to easily and quickly register and store information about medications taken.
4 |
5 | Features
6 |
7 | * Automatic addition of medications by scanning the label.
8 | * The ability to add medications manually.
9 | * Update information about the medication if it was scanned without an internet connection.
10 | * Edit information about medications added manually.
11 | * Adding medication images.
12 | * Sorting of medications by expiration date.
13 | * Grouping of medications by categories.
14 | * Notifications for when to take medications.
15 | * Display of the nearest medications to be taken.
16 | * Reminder of the expiration date of medications.
17 | * Export and import the database to other devices.
18 |
19 | With it, you can track the expiration dates of medications, create a schedule for their intake and receive notifications about it.
20 | It is a reliable and convenient assistant that will help you monitor your health.
21 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/01.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/02.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/03.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/04.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/05.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/06.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/07.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/08.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/08.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/09.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/09.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/en-US/images/phoneScreenshots/10.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Home Medkit — medicine storage and intake reminders.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Home Medkit
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/id/full_description.txt:
--------------------------------------------------------------------------------
1 | Home Medkit
2 |
3 | Aplikasi yang memungkinkan Anda untuk dengan mudah dan cepat mendaftarkan dan menyimpan informasi tentang obat yang diminum.
4 |
5 | Fitur
6 |
7 | * Penambahan obat secara otomatis dengan memindai label.
8 | * Kemampuan untuk menambahkan obat secara manual.
9 | * Memperbarui informasi tentang obat jika dipindai tanpa koneksi internet.
10 | * Mengedit informasi tentang obat yang ditambahkan secara manual.
11 | * Menambahkan gambar obat.
12 | * Menyortir obat berdasarkan tanggal kedaluwarsa.
13 | * Pengelompokan obat berdasarkan kategori.
14 | * Pemberitahuan kapan harus minum obat.
15 | * Tampilan obat terdekat yang harus diminum.
16 | * Pengingat tanggal kadaluarsa obat.
17 | * Ekspor dan impor basis data ke perangkat lain.
18 |
19 | Dengannya, Anda dapat melacak tanggal kedaluwarsa obat, membuat jadwal untuk asupannya, dan menerima pemberitahuan tentangnya.
20 | Ini adalah asisten yang andal dan nyaman yang akan membantu Anda memantau kesehatan Anda.
21 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/id/short_description.txt:
--------------------------------------------------------------------------------
1 | Home Medkit - penyimpanan obat dan pengingat asupan.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/id/title.txt:
--------------------------------------------------------------------------------
1 | Home medkit
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/pt-BR/full_description.txt:
--------------------------------------------------------------------------------
1 | Kit Médico Residencial
2 |
3 | Um aplicativo que permite que você registre e armazene informações sobre os medicamentos tomados facilmente.
4 |
5 | Recursos
6 |
7 | * Adição automática de medicamentos ao escanear a etiqueta.
8 | * Adicionar medicamentos manualmente.
9 | * Atualizar informações do medicamento se foi escaneado sem uma conexão à internet.
10 | * Editar informações sobre medicamentos adicionados manualmente.
11 | * Adicionar fotos de medicamentos.
12 | * Ordenar medicamentos pela data de validade.
13 | * Agrupar medicamentos por categorias.
14 | * Notificações de quanto tomar um medicamento.
15 | * Visão de quais medicamentos estão mais pertos de serem consumidos.
16 | * Lembrete da data de validade dos medicamentos.
17 | * Exportar e importar o banco de dados para outros dispositivos.
18 |
19 | Com ele, você pode rastrear as datas de validade dos medicamentos, criar um agendamento pro seu consumo, e receber notificações disso.
20 | É um assistente confiável e conveniente que ajuda a monitorar sua saúde.
21 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/pt-BR/short_description.txt:
--------------------------------------------------------------------------------
1 | Kit Médico — armazenamento de medicamentos e lembretes de ingestão.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/pt-BR/title.txt:
--------------------------------------------------------------------------------
1 | Kit Médico Residencial
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/changelogs/54.txt:
--------------------------------------------------------------------------------
1 | — Добавлен новый вид просмотра списка лекарств по группам.
2 | — Исправлена ошибка, из-за которой в карточке лекарства дублировались названия групп.
3 | — Исправлена ошибка, из-за которой иногда не сортировались лекарства в списке.
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/full_description.txt:
--------------------------------------------------------------------------------
1 | Домашняя аптечка
2 |
3 | Приложение позволяет легко и быстро регистрировать и хранить информацию о принимаемых лекарствах.
4 |
5 | Особенности
6 |
7 | * Автоматическое добавление лекарства при помощи сканирования маркировки.
8 | * Возможность добавления лекарства вручную.
9 | * Обновление информации о лекарстве, если оно было отсканировано без подключения к интернету.
10 | * Редактирование информации о лекарствах, добавленных вручную.
11 | * Добавление изображения лекарства.
12 | * Сортировка лекарств по срокам годности.
13 | * Группировка лекарств по категориям.
14 | * Звуковое уведомление о необходимости приема лекарства.
15 | * Отображение ближайшего расписания приёма лекарств.
16 | * Напоминание об истечении срока годности лекарств.
17 | * Экспорт и импорт базы данных на другие устройства.
18 |
19 | С помощью приложения вы сможете отслеживать сроки годности лекарств, создавать график их приёма и получать уведомления о нём. Это надёжный и удобный помощник, который поможет вам следить за вашим здоровьем.
20 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/01.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/02.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/03.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/04.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/05.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/06.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/07.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/08.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/08.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/09.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/09.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/images/phoneScreenshots/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/fastlane/metadata/android/ru/images/phoneScreenshots/10.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/short_description.txt:
--------------------------------------------------------------------------------
1 | Домашняя аптечка — приложение для учета и соблюдения приёма лекарств.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/title.txt:
--------------------------------------------------------------------------------
1 | Домашняя аптечка
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | camera2 = "1.4.2"
3 | coilCompose = "3.1.0"
4 | composeLifecycle = "2.8.7"
5 | composeNavigation = "2.8.9"
6 | googleKsp = "2.1.20-1.0.32"
7 | gradle = "8.9.2"
8 | kotlin = "2.1.20"
9 | kotlinxSerializationJson = "1.8.1"
10 | ktor = "3.1.2"
11 | material3 = "1.3.2"
12 | materialPreferences = "1.1.1"
13 | roomCompiler = "2.7.1"
14 | zxing = "3.5.3"
15 |
16 | [libraries]
17 | androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camera2" }
18 | androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camera2" }
19 | androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camera2" }
20 |
21 | androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "composeLifecycle" }
22 | androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "composeLifecycle" }
23 | androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
24 | androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation" }
25 |
26 | androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomCompiler" }
27 | androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomCompiler" }
28 | androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "roomCompiler" }
29 |
30 | coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coilCompose" }
31 |
32 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
33 |
34 | ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
35 | ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
36 | ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
37 | ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
38 |
39 | material-preferences = { group = "me.zhanghai.compose.preference", name = "library", version.ref = "materialPreferences" }
40 |
41 | zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
42 |
43 | [bundles]
44 | camera = [
45 | "androidx-camera-camera2",
46 | "androidx-camera-lifecycle",
47 | "androidx-camera-view"
48 | ]
49 | ktor = [
50 | "ktor-client-android",
51 | "ktor-client-content-negotiation",
52 | "ktor-client-core",
53 | "ktor-serialization-kotlinx-json"
54 | ]
55 |
56 | [plugins]
57 | android = { id = "com.android.application", version.ref = "gradle" }
58 | compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
59 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
60 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
61 | ksp = { id = "com.google.devtools.ksp", version.ref = "googleKsp" }
62 | room = { id = "androidx.room", version.ref = "roomCompiler" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pewaru-333/HomeMedkit-App/4a148f56943f315052ae4dc050b74e90bb1ad98d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Oct 05 10:09:24 MSK 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "HomeMeds"
17 | include(":app")
18 |
--------------------------------------------------------------------------------