├── .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 | [Скачайте в RuStore](https://www.rustore.ru/catalog/app/ru.application.homemedkit) 14 | [Get it on IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/ru.application.homemedkit) 15 | [Get it on F-Droid](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 | --------------------------------------------------------------------------------