diff --git a/CHANGELOG.md b/CHANGELOG.md index d9ef7458cf0..72dfadd6724 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # CHANGELOG +## Next Release + +### Internal +- Bump AGP to v8.12.0 +- Bump Lint to v31.12.0 +- Bump Kotlin to v2.2.0 +- Bump KSP to v2.2.0-2.0.2 +- Bump dagger to v2.57 +- Bump okhttp to v5.1.0 +- Bump retrofit to v3.0.0 +- Bump sqlcipher to v4.10.0 +- Bump AndroidX AppCompat to v1.7.1 +- Bump AndroidX Fragment KTX to v1.8.8 +- Bump asm to v9.8 +- Bump firebase remote config to v23.0.0 +- Bump Firebase Analytics KTX to v22.5.0 +- Bump Jackson Core to v2.19.2 +- Bump Lottie Compose to v6.6.7 +- Add `isUsingSmokelessTobacco` in `MedicalHistory` table + +### Changes + +- Update min sdk version to 26 (Android 8.0) +- Update statin translations for `om-ET` and `sid-ET` +- Update tobacco use dialog to single option select in Ethiopia + ## 2025.08.13 ### Internal @@ -27,7 +53,6 @@ - Rename `Smoker` to `Smokes` - Update tobacco translations for `bn-BD`, `ta-LK` and `si-LK` - Remove `LongTeleconsultMessageBuilder_Old.kt` -- Update statin translations for `om-ET` and `sid-ET` ## 2025.05.20 @@ -51,6 +76,7 @@ - Bump Sentry to v8.11.0 - Bump Dagger to v2.56.2 - Bump Coroutines to v1.10.2 +- Bump AGP to v8.9.2 - Skip Sentry config during app startup in the debug mode - Show spanish language option in non-production builds only - Increase lab based risk threshold to 20% diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b6727ac24c2..df70ae00e8d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -184,19 +184,6 @@ android { beforeVariants { variant -> variant.enable = variant.name !in filteredVariants } - - onVariants(selector().all()) { variant -> - afterEvaluate { - // This is a workaround for https://issuetracker.google.com/301245705 which depends on internal - // implementations of the android gradle plugin and the ksp gradle plugin which might change in the future - // in an unpredictable way. - val variantName = variant.name.replaceFirstChar { it.titlecase() } - project.tasks.getByName("ksp" + variantName + "Kotlin") { - val dataBindingTask = project.tasks.getByName("dataBindingGenBaseClasses$variantName") as DataBindingGenBaseClassesTask - (this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder) - } - } - } } lint { @@ -216,7 +203,10 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() - freeCompilerArgs = freeCompilerArgs + listOf("-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") + freeCompilerArgs = freeCompilerArgs + listOf( + "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-Xannotation-default-target=param-property" + ) } sourceSets { diff --git a/app/lint.xml b/app/lint.xml index b9bdc285f58..2119c0d92f9 100644 --- a/app/lint.xml +++ b/app/lint.xml @@ -55,6 +55,7 @@ + diff --git a/app/schemas/org.simple.clinic.AppDatabase/121.json b/app/schemas/org.simple.clinic.AppDatabase/121.json new file mode 100644 index 00000000000..637ea4a4173 --- /dev/null +++ b/app/schemas/org.simple.clinic.AppDatabase/121.json @@ -0,0 +1,2211 @@ +{ + "formatVersion": 1, + "database": { + "version": 121, + "identityHash": "5d488050f3e10d8e49723f7c5e4a1085", + "entities": [ + { + "tableName": "Patient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `addressUuid` TEXT NOT NULL, `fullName` TEXT NOT NULL, `gender` TEXT NOT NULL, `status` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, `recordedAt` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `reminderConsent` TEXT NOT NULL, `deletedReason` TEXT, `registeredFacilityId` TEXT, `assignedFacilityId` TEXT, `retainUntil` TEXT, `eligibleForReassignment` TEXT NOT NULL, `age_value` INTEGER, `age_updatedAt` TEXT, `dateOfBirth` TEXT, PRIMARY KEY(`uuid`), FOREIGN KEY(`addressUuid`) REFERENCES `PatientAddress`(`uuid`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addressUuid", + "columnName": "addressUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "recordedAt", + "columnName": "recordedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reminderConsent", + "columnName": "reminderConsent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedReason", + "columnName": "deletedReason", + "affinity": "TEXT" + }, + { + "fieldPath": "registeredFacilityId", + "columnName": "registeredFacilityId", + "affinity": "TEXT" + }, + { + "fieldPath": "assignedFacilityId", + "columnName": "assignedFacilityId", + "affinity": "TEXT" + }, + { + "fieldPath": "retainUntil", + "columnName": "retainUntil", + "affinity": "TEXT" + }, + { + "fieldPath": "eligibleForReassignment", + "columnName": "eligibleForReassignment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ageDetails.ageValue", + "columnName": "age_value", + "affinity": "INTEGER" + }, + { + "fieldPath": "ageDetails.ageUpdatedAt", + "columnName": "age_updatedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "ageDetails.dateOfBirth", + "columnName": "dateOfBirth", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_Patient_addressUuid", + "unique": false, + "columnNames": [ + "addressUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Patient_addressUuid` ON `${TABLE_NAME}` (`addressUuid`)" + }, + { + "name": "index_Patient_assignedFacilityId", + "unique": false, + "columnNames": [ + "assignedFacilityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Patient_assignedFacilityId` ON `${TABLE_NAME}` (`assignedFacilityId`)" + } + ], + "foreignKeys": [ + { + "table": "PatientAddress", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "addressUuid" + ], + "referencedColumns": [ + "uuid" + ] + } + ] + }, + { + "tableName": "PatientAddress", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `streetAddress` TEXT, `colonyOrVillage` TEXT, `zone` TEXT, `district` TEXT NOT NULL, `state` TEXT NOT NULL, `country` TEXT, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "streetAddress", + "columnName": "streetAddress", + "affinity": "TEXT" + }, + { + "fieldPath": "colonyOrVillage", + "columnName": "colonyOrVillage", + "affinity": "TEXT" + }, + { + "fieldPath": "zone", + "columnName": "zone", + "affinity": "TEXT" + }, + { + "fieldPath": "district", + "columnName": "district", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "country", + "columnName": "country", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + } + }, + { + "tableName": "PatientPhoneNumber", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `number` TEXT NOT NULL, `phoneType` TEXT NOT NULL, `active` INTEGER NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`), FOREIGN KEY(`patientUuid`) REFERENCES `Patient`(`uuid`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneType", + "columnName": "phoneType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "active", + "columnName": "active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_PatientPhoneNumber_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PatientPhoneNumber_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + } + ], + "foreignKeys": [ + { + "table": "Patient", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "patientUuid" + ], + "referencedColumns": [ + "uuid" + ] + } + ] + }, + { + "tableName": "BloodPressureMeasurement", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `userUuid` TEXT NOT NULL, `facilityUuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, `recordedAt` TEXT NOT NULL, `systolic` INTEGER NOT NULL, `diastolic` INTEGER NOT NULL, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userUuid", + "columnName": "userUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityUuid", + "columnName": "facilityUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "recordedAt", + "columnName": "recordedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reading.systolic", + "columnName": "systolic", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reading.diastolic", + "columnName": "diastolic", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_BloodPressureMeasurement_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BloodPressureMeasurement_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + }, + { + "name": "index_BloodPressureMeasurement_facilityUuid", + "unique": false, + "columnNames": [ + "facilityUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BloodPressureMeasurement_facilityUuid` ON `${TABLE_NAME}` (`facilityUuid`)" + } + ] + }, + { + "tableName": "PrescribedDrug", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `dosage` TEXT, `rxNormCode` TEXT, `isDeleted` INTEGER NOT NULL, `isProtocolDrug` INTEGER NOT NULL, `patientUuid` TEXT NOT NULL, `facilityUuid` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `frequency` TEXT, `durationInDays` INTEGER, `teleconsultationId` TEXT, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dosage", + "columnName": "dosage", + "affinity": "TEXT" + }, + { + "fieldPath": "rxNormCode", + "columnName": "rxNormCode", + "affinity": "TEXT" + }, + { + "fieldPath": "isDeleted", + "columnName": "isDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isProtocolDrug", + "columnName": "isProtocolDrug", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityUuid", + "columnName": "facilityUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frequency", + "columnName": "frequency", + "affinity": "TEXT" + }, + { + "fieldPath": "durationInDays", + "columnName": "durationInDays", + "affinity": "INTEGER" + }, + { + "fieldPath": "teleconsultationId", + "columnName": "teleconsultationId", + "affinity": "TEXT" + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_PrescribedDrug_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PrescribedDrug_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + }, + { + "name": "index_PrescribedDrug_facilityUuid", + "unique": false, + "columnNames": [ + "facilityUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PrescribedDrug_facilityUuid` ON `${TABLE_NAME}` (`facilityUuid`)" + } + ] + }, + { + "tableName": "Facility", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `facilityType` TEXT, `streetAddress` TEXT, `villageOrColony` TEXT, `district` TEXT NOT NULL, `state` TEXT NOT NULL, `country` TEXT NOT NULL, `pinCode` TEXT, `protocolUuid` TEXT, `groupUuid` TEXT, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `deletedAt` TEXT, `syncGroup` TEXT NOT NULL, `location_latitude` REAL, `location_longitude` REAL, `config_diabetesManagementEnabled` INTEGER NOT NULL, `config_teleconsultationEnabled` INTEGER, `config_monthlyScreeningReportsEnabled` INTEGER, `config_monthlySuppliesReportsEnabled` INTEGER NOT NULL, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityType", + "columnName": "facilityType", + "affinity": "TEXT" + }, + { + "fieldPath": "streetAddress", + "columnName": "streetAddress", + "affinity": "TEXT" + }, + { + "fieldPath": "villageOrColony", + "columnName": "villageOrColony", + "affinity": "TEXT" + }, + { + "fieldPath": "district", + "columnName": "district", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "country", + "columnName": "country", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinCode", + "columnName": "pinCode", + "affinity": "TEXT" + }, + { + "fieldPath": "protocolUuid", + "columnName": "protocolUuid", + "affinity": "TEXT" + }, + { + "fieldPath": "groupUuid", + "columnName": "groupUuid", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "syncGroup", + "columnName": "syncGroup", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "location.latitude", + "columnName": "location_latitude", + "affinity": "REAL" + }, + { + "fieldPath": "location.longitude", + "columnName": "location_longitude", + "affinity": "REAL" + }, + { + "fieldPath": "config.diabetesManagementEnabled", + "columnName": "config_diabetesManagementEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "config.teleconsultationEnabled", + "columnName": "config_teleconsultationEnabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "config.monthlyScreeningReportsEnabled", + "columnName": "config_monthlyScreeningReportsEnabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "config.monthlySuppliesReportsEnabled", + "columnName": "config_monthlySuppliesReportsEnabled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + } + }, + { + "tableName": "LoggedInUser", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `fullName` TEXT NOT NULL, `phoneNumber` TEXT NOT NULL, `pinDigest` TEXT NOT NULL, `status` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `loggedInStatus` TEXT NOT NULL, `registrationFacilityUuid` TEXT NOT NULL, `currentFacilityUuid` TEXT NOT NULL, `teleconsultPhoneNumber` TEXT, `capability_canTeleconsult` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phoneNumber", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinDigest", + "columnName": "pinDigest", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loggedInStatus", + "columnName": "loggedInStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registrationFacilityUuid", + "columnName": "registrationFacilityUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentFacilityUuid", + "columnName": "currentFacilityUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teleconsultPhoneNumber", + "columnName": "teleconsultPhoneNumber", + "affinity": "TEXT" + }, + { + "fieldPath": "capabilities.canTeleconsult", + "columnName": "capability_canTeleconsult", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + } + }, + { + "tableName": "Appointment", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `facilityUuid` TEXT NOT NULL, `scheduledDate` TEXT NOT NULL, `status` TEXT NOT NULL, `cancelReason` TEXT, `remindOn` TEXT, `agreedToVisit` INTEGER, `appointmentType` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, `creationFacilityUuid` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityUuid", + "columnName": "facilityUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scheduledDate", + "columnName": "scheduledDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cancelReason", + "columnName": "cancelReason", + "affinity": "TEXT" + }, + { + "fieldPath": "remindOn", + "columnName": "remindOn", + "affinity": "TEXT" + }, + { + "fieldPath": "agreedToVisit", + "columnName": "agreedToVisit", + "affinity": "INTEGER" + }, + { + "fieldPath": "appointmentType", + "columnName": "appointmentType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "creationFacilityUuid", + "columnName": "creationFacilityUuid", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_Appointment_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Appointment_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + }, + { + "name": "index_Appointment_creationFacilityUuid", + "unique": false, + "columnNames": [ + "creationFacilityUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Appointment_creationFacilityUuid` ON `${TABLE_NAME}` (`creationFacilityUuid`)" + }, + { + "name": "index_Appointment_facilityUuid", + "unique": false, + "columnNames": [ + "facilityUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Appointment_facilityUuid` ON `${TABLE_NAME}` (`facilityUuid`)" + } + ] + }, + { + "tableName": "MedicalHistory", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `diagnosedWithHypertension` TEXT NOT NULL, `isOnHypertensionTreatment` TEXT NOT NULL, `isOnDiabetesTreatment` TEXT NOT NULL, `hasHadHeartAttack` TEXT NOT NULL, `hasHadStroke` TEXT NOT NULL, `hasHadKidneyDisease` TEXT NOT NULL, `hasDiabetes` TEXT NOT NULL, `isSmoking` TEXT NOT NULL, `isUsingSmokelessTobacco` TEXT NOT NULL, `cholesterol_value` REAL, `syncStatus` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "diagnosedWithHypertension", + "columnName": "diagnosedWithHypertension", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isOnHypertensionTreatment", + "columnName": "isOnHypertensionTreatment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isOnDiabetesTreatment", + "columnName": "isOnDiabetesTreatment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasHadHeartAttack", + "columnName": "hasHadHeartAttack", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasHadStroke", + "columnName": "hasHadStroke", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasHadKidneyDisease", + "columnName": "hasHadKidneyDisease", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "diagnosedWithDiabetes", + "columnName": "hasDiabetes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isSmoking", + "columnName": "isSmoking", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isUsingSmokelessTobacco", + "columnName": "isUsingSmokelessTobacco", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cholesterol", + "columnName": "cholesterol_value", + "affinity": "REAL" + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_MedicalHistory_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_MedicalHistory_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + } + ] + }, + { + "tableName": "OngoingLoginEntry", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `phoneNumber` TEXT, `pin` TEXT, `fullName` TEXT, `pinDigest` TEXT, `registrationFacilityUuid` TEXT, `status` TEXT, `createdAt` TEXT, `updatedAt` TEXT, `teleconsultPhoneNumber` TEXT, `capability_canTeleconsult` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phoneNumber", + "affinity": "TEXT" + }, + { + "fieldPath": "pin", + "columnName": "pin", + "affinity": "TEXT" + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT" + }, + { + "fieldPath": "pinDigest", + "columnName": "pinDigest", + "affinity": "TEXT" + }, + { + "fieldPath": "registrationFacilityUuid", + "columnName": "registrationFacilityUuid", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT" + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultPhoneNumber", + "columnName": "teleconsultPhoneNumber", + "affinity": "TEXT" + }, + { + "fieldPath": "capabilities.canTeleconsult", + "columnName": "capability_canTeleconsult", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + } + }, + { + "tableName": "Protocol", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `followUpDays` INTEGER NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "followUpDays", + "columnName": "followUpDays", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + } + }, + { + "tableName": "ProtocolDrug", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `protocolUuid` TEXT NOT NULL, `name` TEXT NOT NULL, `rxNormCode` TEXT, `dosage` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, `order` INTEGER NOT NULL, PRIMARY KEY(`uuid`), FOREIGN KEY(`protocolUuid`) REFERENCES `Protocol`(`uuid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "protocolUuid", + "columnName": "protocolUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rxNormCode", + "columnName": "rxNormCode", + "affinity": "TEXT" + }, + { + "fieldPath": "dosage", + "columnName": "dosage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_ProtocolDrug_protocolUuid", + "unique": false, + "columnNames": [ + "protocolUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ProtocolDrug_protocolUuid` ON `${TABLE_NAME}` (`protocolUuid`)" + } + ], + "foreignKeys": [ + { + "table": "Protocol", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "protocolUuid" + ], + "referencedColumns": [ + "uuid" + ] + } + ] + }, + { + "tableName": "BusinessId", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `metaVersion` TEXT NOT NULL, `meta` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, `searchHelp` TEXT NOT NULL, `identifier` TEXT NOT NULL, `identifierType` TEXT NOT NULL, PRIMARY KEY(`uuid`), FOREIGN KEY(`patientUuid`) REFERENCES `Patient`(`uuid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "metaDataVersion", + "columnName": "metaVersion", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "metaData", + "columnName": "meta", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "searchHelp", + "columnName": "searchHelp", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identifier.value", + "columnName": "identifier", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identifier.type", + "columnName": "identifierType", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_BusinessId_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BusinessId_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + }, + { + "name": "index_BusinessId_identifier", + "unique": false, + "columnNames": [ + "identifier" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BusinessId_identifier` ON `${TABLE_NAME}` (`identifier`)" + } + ], + "foreignKeys": [ + { + "table": "Patient", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "patientUuid" + ], + "referencedColumns": [ + "uuid" + ] + } + ] + }, + { + "tableName": "MissingPhoneReminder", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`patientUuid` TEXT NOT NULL, `remindedAt` TEXT NOT NULL, PRIMARY KEY(`patientUuid`))", + "fields": [ + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remindedAt", + "columnName": "remindedAt", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "patientUuid" + ] + } + }, + { + "tableName": "BloodSugarMeasurements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `recordedAt` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `userUuid` TEXT NOT NULL, `facilityUuid` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `reading_value` TEXT NOT NULL, `reading_type` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recordedAt", + "columnName": "recordedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userUuid", + "columnName": "userUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityUuid", + "columnName": "facilityUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reading.value", + "columnName": "reading_value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reading.type", + "columnName": "reading_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_BloodSugarMeasurements_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BloodSugarMeasurements_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + }, + { + "name": "index_BloodSugarMeasurements_facilityUuid", + "unique": false, + "columnNames": [ + "facilityUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BloodSugarMeasurements_facilityUuid` ON `${TABLE_NAME}` (`facilityUuid`)" + } + ] + }, + { + "tableName": "TextRecords", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `text` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "TeleconsultationFacilityInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`teleconsultationFacilityId` TEXT NOT NULL, `facilityId` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, `syncStatus` TEXT NOT NULL, PRIMARY KEY(`teleconsultationFacilityId`))", + "fields": [ + { + "fieldPath": "teleconsultationFacilityId", + "columnName": "teleconsultationFacilityId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityId", + "columnName": "facilityId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "teleconsultationFacilityId" + ] + } + }, + { + "tableName": "MedicalOfficer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`medicalOfficerId` TEXT NOT NULL, `fullName` TEXT NOT NULL, `phoneNumber` TEXT NOT NULL, PRIMARY KEY(`medicalOfficerId`))", + "fields": [ + { + "fieldPath": "medicalOfficerId", + "columnName": "medicalOfficerId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phoneNumber", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "medicalOfficerId" + ] + } + }, + { + "tableName": "TeleconsultationFacilityMedicalOfficersCrossRef", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`teleconsultationFacilityId` TEXT NOT NULL, `medicalOfficerId` TEXT NOT NULL, PRIMARY KEY(`teleconsultationFacilityId`, `medicalOfficerId`))", + "fields": [ + { + "fieldPath": "teleconsultationFacilityId", + "columnName": "teleconsultationFacilityId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "medicalOfficerId", + "columnName": "medicalOfficerId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "teleconsultationFacilityId", + "medicalOfficerId" + ] + } + }, + { + "tableName": "TeleconsultRecord", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `patientId` TEXT NOT NULL, `medicalOfficerId` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `request_requesterId` TEXT, `request_facilityId` TEXT, `request_requestedAt` TEXT, `request_requesterCompletionStatus` TEXT, `record_recordedAt` TEXT, `record_teleconsultationType` TEXT, `record_patientTookMedicines` TEXT, `record_patientConsented` TEXT, `record_medicalOfficerNumber` TEXT, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientId", + "columnName": "patientId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "medicalOfficerId", + "columnName": "medicalOfficerId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teleconsultRequestInfo.requesterId", + "columnName": "request_requesterId", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRequestInfo.facilityId", + "columnName": "request_facilityId", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRequestInfo.requestedAt", + "columnName": "request_requestedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRequestInfo.requesterCompletionStatus", + "columnName": "request_requesterCompletionStatus", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRecordInfo.recordedAt", + "columnName": "record_recordedAt", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRecordInfo.teleconsultationType", + "columnName": "record_teleconsultationType", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRecordInfo.patientTookMedicines", + "columnName": "record_patientTookMedicines", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRecordInfo.patientConsented", + "columnName": "record_patientConsented", + "affinity": "TEXT" + }, + { + "fieldPath": "teleconsultRecordInfo.medicalOfficerNumber", + "columnName": "record_medicalOfficerNumber", + "affinity": "TEXT" + }, + { + "fieldPath": "timestamp.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Drug", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `category` TEXT, `frequency` TEXT, `composition` TEXT, `dosage` TEXT, `rxNormCode` TEXT, `protocol` TEXT NOT NULL, `common` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT" + }, + { + "fieldPath": "frequency", + "columnName": "frequency", + "affinity": "TEXT" + }, + { + "fieldPath": "composition", + "columnName": "composition", + "affinity": "TEXT" + }, + { + "fieldPath": "dosage", + "columnName": "dosage", + "affinity": "TEXT" + }, + { + "fieldPath": "rxNormCode", + "columnName": "rxNormCode", + "affinity": "TEXT" + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "common", + "columnName": "common", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "CallResult", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userId` TEXT NOT NULL, `patientId` TEXT, `facilityId` TEXT, `appointmentId` TEXT NOT NULL, `removeReason` TEXT, `outcome` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientId", + "columnName": "patientId", + "affinity": "TEXT" + }, + { + "fieldPath": "facilityId", + "columnName": "facilityId", + "affinity": "TEXT" + }, + { + "fieldPath": "appointmentId", + "columnName": "appointmentId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "removeReason", + "columnName": "removeReason", + "affinity": "TEXT" + }, + { + "fieldPath": "outcome", + "columnName": "outcome", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_CallResult_appointmentId", + "unique": false, + "columnNames": [ + "appointmentId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CallResult_appointmentId` ON `${TABLE_NAME}` (`appointmentId`)" + } + ] + }, + { + "tableName": "PatientFts", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`uuid` TEXT NOT NULL, `fullName` TEXT NOT NULL, content=`Patient`)", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "fullName", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "simple", + "tokenizerArgs": [], + "contentTable": "Patient", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [ + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientFts_BEFORE_UPDATE BEFORE UPDATE ON `Patient` BEGIN DELETE FROM `PatientFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientFts_BEFORE_DELETE BEFORE DELETE ON `Patient` BEGIN DELETE FROM `PatientFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientFts_AFTER_UPDATE AFTER UPDATE ON `Patient` BEGIN INSERT INTO `PatientFts`(`docid`, `uuid`, `fullName`) VALUES (NEW.`rowid`, NEW.`uuid`, NEW.`fullName`); END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientFts_AFTER_INSERT AFTER INSERT ON `Patient` BEGIN INSERT INTO `PatientFts`(`docid`, `uuid`, `fullName`) VALUES (NEW.`rowid`, NEW.`uuid`, NEW.`fullName`); END" + ] + }, + { + "tableName": "PatientPhoneNumberFts", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`patientUuid` TEXT NOT NULL, `number` TEXT NOT NULL, content=`PatientPhoneNumber`)", + "fields": [ + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "simple", + "tokenizerArgs": [], + "contentTable": "PatientPhoneNumber", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [ + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientPhoneNumberFts_BEFORE_UPDATE BEFORE UPDATE ON `PatientPhoneNumber` BEGIN DELETE FROM `PatientPhoneNumberFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientPhoneNumberFts_BEFORE_DELETE BEFORE DELETE ON `PatientPhoneNumber` BEGIN DELETE FROM `PatientPhoneNumberFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientPhoneNumberFts_AFTER_UPDATE AFTER UPDATE ON `PatientPhoneNumber` BEGIN INSERT INTO `PatientPhoneNumberFts`(`docid`, `patientUuid`, `number`) VALUES (NEW.`rowid`, NEW.`patientUuid`, NEW.`number`); END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientPhoneNumberFts_AFTER_INSERT AFTER INSERT ON `PatientPhoneNumber` BEGIN INSERT INTO `PatientPhoneNumberFts`(`docid`, `patientUuid`, `number`) VALUES (NEW.`rowid`, NEW.`patientUuid`, NEW.`number`); END" + ] + }, + { + "tableName": "BusinessIdFts", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`patientUuid` TEXT NOT NULL, `searchHelp` TEXT NOT NULL, content=`BusinessId`)", + "fields": [ + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "searchHelp", + "columnName": "searchHelp", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "simple", + "tokenizerArgs": [], + "contentTable": "BusinessId", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [ + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_BusinessIdFts_BEFORE_UPDATE BEFORE UPDATE ON `BusinessId` BEGIN DELETE FROM `BusinessIdFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_BusinessIdFts_BEFORE_DELETE BEFORE DELETE ON `BusinessId` BEGIN DELETE FROM `BusinessIdFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_BusinessIdFts_AFTER_UPDATE AFTER UPDATE ON `BusinessId` BEGIN INSERT INTO `BusinessIdFts`(`docid`, `patientUuid`, `searchHelp`) VALUES (NEW.`rowid`, NEW.`patientUuid`, NEW.`searchHelp`); END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_BusinessIdFts_AFTER_INSERT AFTER INSERT ON `BusinessId` BEGIN INSERT INTO `BusinessIdFts`(`docid`, `patientUuid`, `searchHelp`) VALUES (NEW.`rowid`, NEW.`patientUuid`, NEW.`searchHelp`); END" + ] + }, + { + "tableName": "PatientAddressFts", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`uuid` TEXT NOT NULL, `colonyOrVillage` TEXT, content=`PatientAddress`)", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "colonyOrVillage", + "columnName": "colonyOrVillage", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "simple", + "tokenizerArgs": [], + "contentTable": "PatientAddress", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [ + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientAddressFts_BEFORE_UPDATE BEFORE UPDATE ON `PatientAddress` BEGIN DELETE FROM `PatientAddressFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientAddressFts_BEFORE_DELETE BEFORE DELETE ON `PatientAddress` BEGIN DELETE FROM `PatientAddressFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientAddressFts_AFTER_UPDATE AFTER UPDATE ON `PatientAddress` BEGIN INSERT INTO `PatientAddressFts`(`docid`, `uuid`, `colonyOrVillage`) VALUES (NEW.`rowid`, NEW.`uuid`, NEW.`colonyOrVillage`); END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_PatientAddressFts_AFTER_INSERT AFTER INSERT ON `PatientAddress` BEGIN INSERT INTO `PatientAddressFts`(`docid`, `uuid`, `colonyOrVillage`) VALUES (NEW.`rowid`, NEW.`uuid`, NEW.`colonyOrVillage`); END" + ] + }, + { + "tableName": "Questionnaire", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `questionnaire_type` TEXT NOT NULL, `layout` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`questionnaire_type`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "questionnaire_type", + "columnName": "questionnaire_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "layout", + "columnName": "layout", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "questionnaire_type" + ] + } + }, + { + "tableName": "QuestionnaireResponse", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `questionnaireId` TEXT NOT NULL, `questionnaireType` TEXT NOT NULL, `facilityId` TEXT NOT NULL, `lastUpdatedByUserId` TEXT, `content` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "questionnaireId", + "columnName": "questionnaireId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "questionnaireType", + "columnName": "questionnaireType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "facilityId", + "columnName": "facilityId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdatedByUserId", + "columnName": "lastUpdatedByUserId", + "affinity": "TEXT" + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + } + }, + { + "tableName": "PatientAttribute", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `userUuid` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `height` REAL NOT NULL, `weight` REAL NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userUuid", + "columnName": "userUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bmiReading.height", + "columnName": "height", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "bmiReading.weight", + "columnName": "weight", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_PatientAttribute_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PatientAttribute_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + } + ] + }, + { + "tableName": "CVDRisk", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `patientUuid` TEXT NOT NULL, `syncStatus` TEXT NOT NULL, `min` INTEGER NOT NULL, `max` INTEGER NOT NULL, `createdAt` TEXT NOT NULL, `updatedAt` TEXT NOT NULL, `deletedAt` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "patientUuid", + "columnName": "patientUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "riskScore.min", + "columnName": "min", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "riskScore.max", + "columnName": "max", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamps.createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.updatedAt", + "columnName": "updatedAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamps.deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_CVDRisk_patientUuid", + "unique": false, + "columnNames": [ + "patientUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CVDRisk_patientUuid` ON `${TABLE_NAME}` (`patientUuid`)" + } + ] + } + ], + "views": [ + { + "viewName": "PatientSearchResult", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT P.uuid, P.fullName, P.gender, P.dateOfBirth, P.age_value, P.age_updatedAt,\n P.assignedFacilityId, P.status, P.eligibleForReassignment,\n PA.uuid addr_uuid, PA.streetAddress addr_streetAddress, PA.colonyOrVillage addr_colonyOrVillage, PA.zone addr_zone, PA.district addr_district,\n PA.state addr_state, PA.country addr_country,\n PA.createdAt addr_createdAt, PA.updatedAt addr_updatedAt, PA.deletedAt addr_deletedAt,\n PP.number phoneNumber,\n B.identifier id_identifier, B.identifierType id_identifierType, B.searchHelp identifierSearchHelp, AF.name assignedFacilityName\n FROM Patient P\n INNER JOIN PatientAddress PA ON PA.uuid = P.addressUuid\n LEFT JOIN PatientPhoneNumber PP ON PP.patientUuid = P.uuid\n LEFT JOIN Facility AF ON AF.uuid = P.assignedFacilityId\n LEFT JOIN BusinessId B ON B.patientUuid = P.uuid\n WHERE P.deletedAt IS NULL" + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5d488050f3e10d8e49723f7c5e4a1085')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/simple/clinic/medicalhistory/MedicalHistoryRepositoryAndroidTest.kt b/app/src/androidTest/java/org/simple/clinic/medicalhistory/MedicalHistoryRepositoryAndroidTest.kt index 766e09c6897..b988e67a86d 100644 --- a/app/src/androidTest/java/org/simple/clinic/medicalhistory/MedicalHistoryRepositoryAndroidTest.kt +++ b/app/src/androidTest/java/org/simple/clinic/medicalhistory/MedicalHistoryRepositoryAndroidTest.kt @@ -54,6 +54,7 @@ class MedicalHistoryRepositoryAndroidTest { hasHadKidneyDisease = Yes, hasDiabetes = No, isSmoking = No, + isUsingSmokelessTobacco = No, ) repository.save( diff --git a/app/src/androidTest/java/org/simple/clinic/storage/migrations/Migration121AndroidTest.kt b/app/src/androidTest/java/org/simple/clinic/storage/migrations/Migration121AndroidTest.kt new file mode 100644 index 00000000000..dcc4918f18f --- /dev/null +++ b/app/src/androidTest/java/org/simple/clinic/storage/migrations/Migration121AndroidTest.kt @@ -0,0 +1,47 @@ +package org.simple.clinic.storage.migrations + +import org.junit.Test +import org.simple.clinic.assertColumns + +class Migration121AndroidTest : BaseDatabaseMigrationTest(120, 121) { + + @Test + fun isSmoking_should_be_added_to_medical_history_table() { + before.assertColumns("MedicalHistory", setOf( + "uuid", + "patientUuid", + "diagnosedWithHypertension", + "isOnHypertensionTreatment", + "isOnDiabetesTreatment", + "hasHadHeartAttack", + "hasHadStroke", + "hasHadKidneyDisease", + "hasDiabetes", + "isSmoking", + "cholesterol_value", + "syncStatus", + "createdAt", + "updatedAt", + "deletedAt" + )) + + after.assertColumns("MedicalHistory", setOf( + "uuid", + "patientUuid", + "diagnosedWithHypertension", + "isOnHypertensionTreatment", + "isOnDiabetesTreatment", + "hasHadHeartAttack", + "hasHadStroke", + "hasHadKidneyDisease", + "hasDiabetes", + "isSmoking", + "isUsingSmokelessTobacco", + "cholesterol_value", + "syncStatus", + "createdAt", + "updatedAt", + "deletedAt" + )) + } +} diff --git a/app/src/debug/java/org/simple/clinic/DebugNotification.kt b/app/src/debug/java/org/simple/clinic/DebugNotification.kt index f67d273ebf7..62c455a6d5a 100644 --- a/app/src/debug/java/org/simple/clinic/DebugNotification.kt +++ b/app/src/debug/java/org/simple/clinic/DebugNotification.kt @@ -6,7 +6,6 @@ import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Build import androidx.core.app.NotificationCompat import org.simple.clinic.sync.DataSync import javax.inject.Inject @@ -19,15 +18,10 @@ object DebugNotification { fun show(context: Context, appSignature: String) { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel(NotificationChannel(NOTIF_CHANNEL_ID, "Debug", NotificationManager.IMPORTANCE_MIN)) - } + notificationManager.createNotificationChannel(NotificationChannel(NOTIF_CHANNEL_ID, "Debug", NotificationManager.IMPORTANCE_MIN)) - val syncPendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } + val syncPendingIntentFlags = + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT val syncPendingIntent = PendingIntent.getBroadcast( context, 0, diff --git a/app/src/debug/res/drawable-v26/ic_launcher_foreground.xml b/app/src/debug/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/debug/res/drawable-v26/ic_launcher_foreground.xml rename to app/src/debug/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi/ic_launcher.xml similarity index 79% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app/src/debug/res/mipmap-anydpi/ic_launcher.xml index a1bc384da78..370b2a4527c 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/debug/res/mipmap-anydpi/ic_launcher.xml @@ -2,4 +2,5 @@ + diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index a9c770e85c6..00000000000 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index 1176e66cc7a..00000000000 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 87457839baf..00000000000 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index cd3300b7c00..00000000000 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index f6e225e8961..00000000000 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d89719ef468..ee9fa727b43 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,8 +58,7 @@ android:supportsRtl="true" android:theme="@style/Theme.Simple" tools:ignore="AllowBackup,DataExtractionRules,GoogleAppIndexingWarning,LockedOrientationActivity" - tools:replace="android:allowBackup" - tools:targetApi="n"> + tools:replace="android:allowBackup"> - + diff --git a/app/src/main/java/org/simple/clinic/AppDatabase.kt b/app/src/main/java/org/simple/clinic/AppDatabase.kt index d62542bc7bc..ff54641be7a 100644 --- a/app/src/main/java/org/simple/clinic/AppDatabase.kt +++ b/app/src/main/java/org/simple/clinic/AppDatabase.kt @@ -110,7 +110,7 @@ import org.simple.clinic.patient.Answer as PatientAnswer views = [ PatientSearchResult::class ], - version = 120, + version = 121, exportSchema = true ) @TypeConverters( diff --git a/app/src/main/java/org/simple/clinic/analytics/NetworkCapabilitiesProvider.kt b/app/src/main/java/org/simple/clinic/analytics/NetworkCapabilitiesProvider.kt index 90d18892b39..106e17b2895 100644 --- a/app/src/main/java/org/simple/clinic/analytics/NetworkCapabilitiesProvider.kt +++ b/app/src/main/java/org/simple/clinic/analytics/NetworkCapabilitiesProvider.kt @@ -7,7 +7,6 @@ import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.os.Build import org.simple.clinic.analytics.NetworkConnectivityStatus.ACTIVE import org.simple.clinic.analytics.NetworkConnectivityStatus.INACTIVE import javax.inject.Inject @@ -16,11 +15,8 @@ class NetworkCapabilitiesProvider @Inject constructor(private val application: A fun activeNetworkCapabilities(): NetworkCapabilities? { val connectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val currentActiveNetwork = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - connectivityManager.activeNetwork - } else { - findCurrentActiveNetworkV21(connectivityManager) - } + val currentActiveNetwork = + connectivityManager.activeNetwork return currentActiveNetwork?.let { network -> connectivityManager.getNetworkCapabilities(network) @@ -36,11 +32,8 @@ class NetworkCapabilitiesProvider @Inject constructor(private val application: A fun networkConnectivityStatus(): NetworkConnectivityStatus { val networkCapabilities = activeNetworkCapabilities() ?: return INACTIVE val isConnectedToNetwork = networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET) - val isConnectedToInternet = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - isConnectedToNetwork && networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) - } else { - isConnectedToNetwork // Network validation check is not available before API 23 - } + val isConnectedToInternet = + isConnectedToNetwork && networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) return if (isConnectedToInternet) { ACTIVE diff --git a/app/src/main/java/org/simple/clinic/appupdate/AppUpdateNotificationWorker.kt b/app/src/main/java/org/simple/clinic/appupdate/AppUpdateNotificationWorker.kt index 6d2c1905358..a00e4f265e1 100644 --- a/app/src/main/java/org/simple/clinic/appupdate/AppUpdateNotificationWorker.kt +++ b/app/src/main/java/org/simple/clinic/appupdate/AppUpdateNotificationWorker.kt @@ -6,9 +6,8 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.net.Uri -import android.os.Build import androidx.core.app.NotificationCompat +import androidx.core.net.toUri import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder import androidx.work.RxWorker @@ -62,7 +61,7 @@ class AppUpdateNotificationWorker( .build() } - private fun notificationScheduledTime(scheduledDateTime: LocalDateTime, currentDateTime: LocalDateTime): LocalDateTime { + private fun notificationScheduledTime(scheduledDateTime: LocalDateTime, currentDateTime: LocalDateTime): LocalDateTime { return if (currentDateTime.isAfter(scheduledDateTime)) { scheduledDateTime.plusDays(1) } else { @@ -104,7 +103,8 @@ class AppUpdateNotificationWorker( when (result) { is ShowAppUpdate -> showAppUpdateNotificationBasedOnThePriority(result) is AppUpdateState.AppUpdateStateError -> resetPreferences() - DontShowAppUpdate -> { /* no-op */ } + DontShowAppUpdate -> { /* no-op */ + } }.exhaustive() Result.success() @@ -139,13 +139,11 @@ class AppUpdateNotificationWorker( } private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel(NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH - )) - } + notificationManager.createNotificationChannel(NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + )) } private fun showLightAppUpdateNotification() { @@ -179,13 +177,10 @@ class AppUpdateNotificationWorker( } private fun openSimpleInPlayStorePendingIntent(): PendingIntent { - val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } + val flag = + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(PLAY_STORE_URL_FOR_SIMPLE)) + val intent = Intent(Intent.ACTION_VIEW, PLAY_STORE_URL_FOR_SIMPLE.toUri()) return PendingIntent.getActivity(context, 0, intent, flag) } diff --git a/app/src/main/java/org/simple/clinic/appupdate/criticalupdatedialog/CriticalAppUpdateDialog.kt b/app/src/main/java/org/simple/clinic/appupdate/criticalupdatedialog/CriticalAppUpdateDialog.kt index 3790d31f83b..c68f15f351c 100644 --- a/app/src/main/java/org/simple/clinic/appupdate/criticalupdatedialog/CriticalAppUpdateDialog.kt +++ b/app/src/main/java/org/simple/clinic/appupdate/criticalupdatedialog/CriticalAppUpdateDialog.kt @@ -3,13 +3,13 @@ package org.simple.clinic.appupdate.criticalupdatedialog import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import androidx.core.net.toUri import com.jakewharton.rxbinding3.view.clicks import com.spotify.mobius.functions.Consumer import io.reactivex.Observable @@ -148,7 +148,7 @@ class CriticalAppUpdateDialog : BaseDialog< override fun openContactUrl(url: String) { val packageManager = requireContext().packageManager - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + val intent = Intent(Intent.ACTION_VIEW, url.toUri()) if (intent.resolveActivity(packageManager) != null) { requireContext().startActivity(intent) @@ -159,7 +159,7 @@ class CriticalAppUpdateDialog : BaseDialog< override fun openSimpleInGooglePlay() { val packageManager = requireContext().packageManager - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(PLAY_STORE_URL_FOR_SIMPLE)) + val intent = Intent(Intent.ACTION_VIEW, PLAY_STORE_URL_FOR_SIMPLE.toUri()) if (intent.resolveActivity(packageManager) != null) { requireContext().startActivity(intent) diff --git a/app/src/main/java/org/simple/clinic/appupdate/dialog/AppUpdateDialog.kt b/app/src/main/java/org/simple/clinic/appupdate/dialog/AppUpdateDialog.kt index 6e06da4027d..f157528cd67 100644 --- a/app/src/main/java/org/simple/clinic/appupdate/dialog/AppUpdateDialog.kt +++ b/app/src/main/java/org/simple/clinic/appupdate/dialog/AppUpdateDialog.kt @@ -3,12 +3,11 @@ package org.simple.clinic.appupdate.dialog import android.app.Dialog import android.content.Intent import android.content.Intent.ACTION_VIEW -import android.net.Uri import android.os.Bundle +import androidx.core.net.toUri import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.simple.clinic.BuildConfig import org.simple.clinic.PLAY_STORE_URL_FOR_SIMPLE import org.simple.clinic.R @@ -40,7 +39,7 @@ class AppUpdateDialog : DialogFragment() { private fun launchPlayStoreForUpdate() { val intent = Intent(ACTION_VIEW).apply { - data = Uri.parse(PLAY_STORE_URL_FOR_SIMPLE) + data = PLAY_STORE_URL_FOR_SIMPLE.toUri() } startActivity(intent) } diff --git a/app/src/main/java/org/simple/clinic/drugstockreminders/DrugStockWorker.kt b/app/src/main/java/org/simple/clinic/drugstockreminders/DrugStockWorker.kt index f0fc1138d23..01fc85bdb58 100644 --- a/app/src/main/java/org/simple/clinic/drugstockreminders/DrugStockWorker.kt +++ b/app/src/main/java/org/simple/clinic/drugstockreminders/DrugStockWorker.kt @@ -6,7 +6,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.os.Build import androidx.core.app.NotificationCompat import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder @@ -146,23 +145,17 @@ class DrugStockWorker( private fun openAppToHomeScreen(): PendingIntent? { val intent = Intent(context, SetupActivity::class.java) - val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } + val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT return PendingIntent.getActivity(context, 0, intent, flag) } private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel( - NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH - )) - } + notificationManager.createNotificationChannel( + NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + )) } } diff --git a/app/src/main/java/org/simple/clinic/drugstockreminders/enterdrugstock/EnterDrugStockWebViewClient.kt b/app/src/main/java/org/simple/clinic/drugstockreminders/enterdrugstock/EnterDrugStockWebViewClient.kt index 0d3563bab85..f600a3ce05c 100644 --- a/app/src/main/java/org/simple/clinic/drugstockreminders/enterdrugstock/EnterDrugStockWebViewClient.kt +++ b/app/src/main/java/org/simple/clinic/drugstockreminders/enterdrugstock/EnterDrugStockWebViewClient.kt @@ -1,11 +1,9 @@ package org.simple.clinic.drugstockreminders.enterdrugstock -import android.os.Build import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import androidx.annotation.RequiresApi class EnterDrugStockWebViewClient( private val backClicked: () -> Unit @@ -20,7 +18,6 @@ class EnterDrugStockWebViewClient( return false } - @RequiresApi(Build.VERSION_CODES.M) override fun onReceivedError( view: WebView?, request: WebResourceRequest?, diff --git a/app/src/main/java/org/simple/clinic/facilitypicker/FacilityPickerView.kt b/app/src/main/java/org/simple/clinic/facilitypicker/FacilityPickerView.kt index 524ca849d64..59036782c39 100644 --- a/app/src/main/java/org/simple/clinic/facilitypicker/FacilityPickerView.kt +++ b/app/src/main/java/org/simple/clinic/facilitypicker/FacilityPickerView.kt @@ -6,7 +6,7 @@ import android.os.Parcelable import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout -import android.widget.RelativeLayout +import androidx.core.content.withStyledAttributes import androidx.recyclerview.widget.LinearLayoutManager import com.jakewharton.rxbinding3.recyclerview.scrollEvents import com.jakewharton.rxbinding3.recyclerview.scrollStateChanges @@ -65,7 +65,7 @@ class FacilityPickerView( ) ) - private val pickFrom: PickFrom + private lateinit var pickFrom: PickFrom private var binding: ViewFacilitypickerBinding? = null @@ -88,9 +88,9 @@ class FacilityPickerView( val layoutInflater = LayoutInflater.from(context) binding = ViewFacilitypickerBinding.inflate(layoutInflater, this) - val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.FacilityPickerView) - pickFrom = PickFrom.forAttribute(typedArray) - typedArray.recycle() + context.withStyledAttributes(attributeSet, R.styleable.FacilityPickerView) { + pickFrom = PickFrom.forAttribute(this) + } context.injector().inject(this) @@ -151,11 +151,11 @@ class FacilityPickerView( } override fun showProgressIndicator() { - progressIndicator.visibility = RelativeLayout.VISIBLE + progressIndicator.visibility = VISIBLE } override fun hideProgressIndicator() { - progressIndicator.visibility = RelativeLayout.GONE + progressIndicator.visibility = GONE } override fun updateFacilities(facilityItems: List) { diff --git a/app/src/main/java/org/simple/clinic/feature/Feature.kt b/app/src/main/java/org/simple/clinic/feature/Feature.kt index 7284d7634d5..f33ee931e20 100644 --- a/app/src/main/java/org/simple/clinic/feature/Feature.kt +++ b/app/src/main/java/org/simple/clinic/feature/Feature.kt @@ -1,7 +1,5 @@ package org.simple.clinic.feature -import android.os.Build - enum class Feature( val enabledByDefault: Boolean, val remoteConfigKey: String = "" @@ -19,7 +17,7 @@ enum class Feature( * There's a corresponding activity [org.simple.clinic.WebviewTestActivity], which you * can run to verify the fix. **/ - ChangeLanguage(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M), + ChangeLanguage(true), HttpRequestBodyCompression(false, "http_request_body_compression_enabled"), CallResultSyncEnabled(true), NotifyAppUpdateAvailableV2(false, "appupdate_enabled_v2"), diff --git a/app/src/main/java/org/simple/clinic/home/HomeScreen.kt b/app/src/main/java/org/simple/clinic/home/HomeScreen.kt index 1e3af2d6d01..266d04d47a9 100644 --- a/app/src/main/java/org/simple/clinic/home/HomeScreen.kt +++ b/app/src/main/java/org/simple/clinic/home/HomeScreen.kt @@ -51,8 +51,8 @@ import org.simple.clinic.settings.SettingsScreen import org.simple.clinic.summary.OpenIntention import org.simple.clinic.summary.PatientSummaryScreenKey import org.simple.clinic.util.UtcClock -import org.simple.clinic.util.exhaustive import org.simple.clinic.util.applyStatusBarPadding +import org.simple.clinic.util.exhaustive import org.simple.clinic.widgets.findCurrentFragment import org.simple.clinic.widgets.hideKeyboard import java.time.Instant @@ -207,6 +207,7 @@ class HomeScreen : router.push(SettingsScreen.Key()) true } + else -> false } } diff --git a/app/src/main/java/org/simple/clinic/home/HomeScreenEvent.kt b/app/src/main/java/org/simple/clinic/home/HomeScreenEvent.kt index e6599332722..70b5fb66b44 100644 --- a/app/src/main/java/org/simple/clinic/home/HomeScreenEvent.kt +++ b/app/src/main/java/org/simple/clinic/home/HomeScreenEvent.kt @@ -1,13 +1,10 @@ package org.simple.clinic.home -import android.Manifest -import android.os.Build -import androidx.annotation.RequiresApi -import java.util.Optional import org.simple.clinic.activity.permissions.RequiresPermission import org.simple.clinic.facility.Facility import org.simple.clinic.platform.util.RuntimePermissionResult import org.simple.clinic.widgets.UiEvent +import java.util.Optional sealed class HomeScreenEvent : UiEvent diff --git a/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt b/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt index fad3cba5950..8b08140c870 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/OverdueScreen.kt @@ -1,7 +1,6 @@ package org.simple.clinic.home.overdue import android.content.Context -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -150,11 +149,7 @@ class OverdueScreen : BaseScreen< override fun createUpdate(): Update { val date = LocalDate.now(userClock) - val canGeneratePdf = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - return OverdueUpdate( - date = date, - canGeneratePdf = canGeneratePdf - ) + return OverdueUpdate(date = date, canGeneratePdf = true) } override fun createInit() = OverdueInit() diff --git a/app/src/main/java/org/simple/clinic/home/overdue/search/OverdueSearchScreen.kt b/app/src/main/java/org/simple/clinic/home/overdue/search/OverdueSearchScreen.kt index d76acc44de3..d97245c8435 100644 --- a/app/src/main/java/org/simple/clinic/home/overdue/search/OverdueSearchScreen.kt +++ b/app/src/main/java/org/simple/clinic/home/overdue/search/OverdueSearchScreen.kt @@ -1,7 +1,6 @@ package org.simple.clinic.home.overdue.search import android.content.Context -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -166,9 +165,7 @@ class OverdueSearchScreen : BaseScreen< override fun defaultModel() = OverdueSearchModel.create() override fun createUpdate(): OverdueSearchUpdate { - val canGeneratePdf = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - - return OverdueSearchUpdate(LocalDate.now(userClock), canGeneratePdf) + return OverdueSearchUpdate(LocalDate.now(userClock), true) } override fun createInit() = OverdueSearchInit() diff --git a/app/src/main/java/org/simple/clinic/home/patients/PatientsTabScreen.kt b/app/src/main/java/org/simple/clinic/home/patients/PatientsTabScreen.kt index 837cd788181..c87ec7d49b9 100644 --- a/app/src/main/java/org/simple/clinic/home/patients/PatientsTabScreen.kt +++ b/app/src/main/java/org/simple/clinic/home/patients/PatientsTabScreen.kt @@ -3,7 +3,6 @@ package org.simple.clinic.home.patients import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -11,6 +10,7 @@ import android.view.ViewGroup import android.view.animation.AnimationUtils import androidx.annotation.IdRes import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri import androidx.interpolator.view.animation.FastOutSlowInInterpolator import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.jakewharton.rxbinding3.view.clicks @@ -336,7 +336,7 @@ class PatientsTabScreen : BaseScreen< override fun openSimpleOnPlaystore() { val packageManager = requireContext().packageManager - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(PLAY_STORE_URL_FOR_SIMPLE)) + val intent = Intent(Intent.ACTION_VIEW, PLAY_STORE_URL_FOR_SIMPLE.toUri()) if (intent.resolveActivity(packageManager) != null) { requireContext().startActivity(intent) diff --git a/app/src/main/java/org/simple/clinic/home/report/ReportsWebViewClient.kt b/app/src/main/java/org/simple/clinic/home/report/ReportsWebViewClient.kt index ccd67f58b66..29849ed7e0d 100644 --- a/app/src/main/java/org/simple/clinic/home/report/ReportsWebViewClient.kt +++ b/app/src/main/java/org/simple/clinic/home/report/ReportsWebViewClient.kt @@ -1,11 +1,9 @@ package org.simple.clinic.home.report -import android.os.Build import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import androidx.annotation.RequiresApi class ReportsWebViewClient( private val backClicked: () -> Unit @@ -36,7 +34,6 @@ class ReportsWebViewClient( super.onPageFinished(view, url) } - @RequiresApi(Build.VERSION_CODES.M) override fun onReceivedError( view: WebView?, request: WebResourceRequest?, diff --git a/app/src/main/java/org/simple/clinic/introvideoscreen/IntroVideoScreen.kt b/app/src/main/java/org/simple/clinic/introvideoscreen/IntroVideoScreen.kt index f35c405ddb2..f5c20517462 100644 --- a/app/src/main/java/org/simple/clinic/introvideoscreen/IntroVideoScreen.kt +++ b/app/src/main/java/org/simple/clinic/introvideoscreen/IntroVideoScreen.kt @@ -3,11 +3,11 @@ package org.simple.clinic.introvideoscreen import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.net.toUri import com.jakewharton.rxbinding3.view.clicks import com.spotify.mobius.functions.Consumer import io.reactivex.Observable @@ -96,7 +96,7 @@ class IntroVideoScreen : BaseScreen< private fun openYoutubeLinkForSimpleVideo() { val packageManager = requireContext().packageManager - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(simpleVideo.url)) + val intent = Intent(Intent.ACTION_VIEW, simpleVideo.url.toUri()) if (intent.resolveActivity(packageManager) != null) { requireContext().startActivity(intent) diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistory.kt b/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistory.kt index d0e9ce626c7..e4be1c248d9 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistory.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistory.kt @@ -22,6 +22,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco import org.simple.clinic.patient.PatientUuid import org.simple.clinic.patient.SyncStatus import java.time.Instant @@ -57,6 +58,8 @@ data class MedicalHistory( val isSmoking: Answer, + val isUsingSmokelessTobacco: Answer, + @ColumnInfo(name = "cholesterol_value") val cholesterol: Float?, @@ -89,6 +92,7 @@ data class MedicalHistory( is IsOnHypertensionTreatment -> copy(isOnHypertensionTreatment = answer) IsOnDiabetesTreatment -> copy(isOnDiabetesTreatment = answer) IsSmoking -> copy(isSmoking = answer) + IsUsingSmokelessTobacco -> copy(isUsingSmokelessTobacco = answer) } } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryQuestion.kt b/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryQuestion.kt index 47d85628b12..e319c7c6e9e 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryQuestion.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryQuestion.kt @@ -22,5 +22,6 @@ sealed class MedicalHistoryQuestion( ) object IsOnDiabetesTreatment : MedicalHistoryQuestion(R.string.medicalhistory_question_is_on_diabetes_treatment) - data object IsSmoking : MedicalHistoryQuestion(R.string.medicalhistory_question_is_current_smoker) + data object IsSmoking : MedicalHistoryQuestion(R.string.medicalhistory_question_is_smoking) + data object IsUsingSmokelessTobacco : MedicalHistoryQuestion(R.string.medicalhistory_question_is_using_smokeless_tobacco) } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryRepository.kt b/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryRepository.kt index 85da9f5bf8d..64b3095ce5c 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryRepository.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/MedicalHistoryRepository.kt @@ -6,7 +6,6 @@ import org.simple.clinic.medicalhistory.Answer.Unanswered import org.simple.clinic.medicalhistory.sync.MedicalHistoryPayload import org.simple.clinic.patient.PatientUuid import org.simple.clinic.patient.SyncStatus -import org.simple.clinic.patient.SyncStatus.PENDING import org.simple.clinic.sync.SynceableRepository import org.simple.clinic.util.UtcClock import java.time.Instant @@ -34,6 +33,7 @@ class MedicalHistoryRepository @Inject constructor( hasHadKidneyDisease = Unanswered, diagnosedWithDiabetes = Unanswered, isSmoking = Unanswered, + isUsingSmokelessTobacco = Unanswered, cholesterol = null, syncStatus = SyncStatus.DONE, createdAt = Instant.now(utcClock), @@ -75,6 +75,7 @@ class MedicalHistoryRepository @Inject constructor( hasHadKidneyDisease = Unanswered, diagnosedWithDiabetes = Unanswered, isSmoking = Unanswered, + isUsingSmokelessTobacco = Unanswered, cholesterol = null, syncStatus = SyncStatus.DONE, createdAt = Instant.now(utcClock), @@ -100,6 +101,7 @@ class MedicalHistoryRepository @Inject constructor( hasHadKidneyDisease = historyEntry.hasHadKidneyDisease, diagnosedWithDiabetes = historyEntry.hasDiabetes, isSmoking = historyEntry.isSmoking, + isUsingSmokelessTobacco = historyEntry.isUsingSmokelessTobacco, cholesterol = null, syncStatus = SyncStatus.PENDING, createdAt = Instant.now(utcClock), @@ -166,6 +168,7 @@ class MedicalHistoryRepository @Inject constructor( hasHadKidneyDisease = hasHadKidneyDisease, diagnosedWithDiabetes = hasDiabetes, isSmoking = isSmoking, + isUsingSmokelessTobacco = isUsingSmokelessTobacco, cholesterol = cholesterol, syncStatus = syncStatus, createdAt = createdAt, diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/OngoingMedicalHistoryEntry.kt b/app/src/main/java/org/simple/clinic/medicalhistory/OngoingMedicalHistoryEntry.kt index e355d63c600..1aa858154cf 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/OngoingMedicalHistoryEntry.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/OngoingMedicalHistoryEntry.kt @@ -11,6 +11,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco @Parcelize data class OngoingMedicalHistoryEntry( @@ -22,6 +23,7 @@ data class OngoingMedicalHistoryEntry( val isOnDiabetesTreatment: Answer = Unanswered, val hasDiabetes: Answer = Unanswered, val isSmoking: Answer = Unanswered, + val isUsingSmokelessTobacco: Answer = Unanswered, ) : Parcelable { fun answerChanged(question: MedicalHistoryQuestion, answer: Answer): OngoingMedicalHistoryEntry { @@ -34,6 +36,7 @@ data class OngoingMedicalHistoryEntry( is IsOnHypertensionTreatment -> copy(isOnHypertensionTreatment = answer) IsOnDiabetesTreatment -> copy(isOnDiabetesTreatment = answer) IsSmoking -> copy(isSmoking = answer) + IsUsingSmokelessTobacco -> copy(isUsingSmokelessTobacco = answer) } } } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt index 8ad94862a7f..4787d13f24c 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryModel.kt @@ -21,6 +21,7 @@ data class NewMedicalHistoryModel( val nextButtonState: ButtonState?, val hasShownChangeDiagnosisError: Boolean, val showIsSmokingQuestion: Boolean, + val showSmokelessTobaccoQuestion: Boolean, ) : Parcelable { val hasLoadedPatientEntry: Boolean @@ -72,14 +73,19 @@ data class NewMedicalHistoryModel( get() = facilityDiabetesManagementEnabled && !hasShownChangeDiagnosisError && hasNoHypertension && hasNoDiabetes companion object { - fun default(country: Country, showIsSmokingQuestion: Boolean): NewMedicalHistoryModel = NewMedicalHistoryModel( + fun default( + country: Country, + showIsSmokingQuestion: Boolean, + showSmokelessTobaccoQuestion: Boolean + ): NewMedicalHistoryModel = NewMedicalHistoryModel( country = country, ongoingPatientEntry = null, ongoingMedicalHistoryEntry = OngoingMedicalHistoryEntry(), currentFacility = null, nextButtonState = null, hasShownChangeDiagnosisError = false, - showIsSmokingQuestion = showIsSmokingQuestion + showIsSmokingQuestion = showIsSmokingQuestion, + showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion ) } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt index 2420022f2ff..8aa544b7986 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreen.kt @@ -8,6 +8,15 @@ import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.dimensionResource import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.jakewharton.rxbinding3.view.clicks import com.spotify.mobius.functions.Consumer @@ -19,6 +28,7 @@ import kotlinx.parcelize.Parcelize import org.simple.clinic.R import org.simple.clinic.ReportAnalyticsEvents import org.simple.clinic.appconfig.Country +import org.simple.clinic.common.ui.theme.SimpleTheme import org.simple.clinic.databinding.ScreenNewMedicalHistoryBinding import org.simple.clinic.di.injector import org.simple.clinic.feature.Feature @@ -32,10 +42,10 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAKidneyDise import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment -import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking import org.simple.clinic.medicalhistory.SelectDiagnosisErrorDialog import org.simple.clinic.medicalhistory.SelectOngoingDiabetesTreatmentErrorDialog import org.simple.clinic.medicalhistory.SelectOngoingHypertensionTreatmentErrorDialog +import org.simple.clinic.medicalhistory.ui.TobaccoQuestion import org.simple.clinic.navigation.v2.Router import org.simple.clinic.navigation.v2.ScreenKey import org.simple.clinic.navigation.v2.fragments.BaseScreen @@ -100,27 +110,27 @@ class NewMedicalHistoryScreen : BaseScreen< private val diabetesQuestionView get() = binding.diabetesQuestionView - private val scrollView - get() = binding.scrollView - private val hypertensionDiagnosis get() = binding.hypertensionDiagnosis private val diabetesDiagnosis get() = binding.diabetesDiagnosis - private val currentSmokerQuestionContainer - get() = binding.currentSmokerQuestionContainer + private var showSmokerQuestion by mutableStateOf(false) + private var isSmoking by mutableStateOf(Answer.Unanswered) + private var showSmokelessTobaccoQuestion by mutableStateOf(false) + private var isUsingSmokelessTobacco by mutableStateOf(Answer.Unanswered) - private val currentSmokerQuestionView - get() = binding.currentSmokerQuestionView + private val composeView + get() = binding.composeView private val hotEvents: Subject = PublishSubject.create() override fun defaultModel() = NewMedicalHistoryModel.default( country = country, showIsSmokingQuestion = features.isEnabled(Feature.NonLabBasedStatinNudge) || - features.isEnabled(Feature.LabBasedStatinNudge) + features.isEnabled(Feature.LabBasedStatinNudge), + showSmokelessTobaccoQuestion = country.isoCountryCode != Country.ETHIOPIA ) override fun bindView( @@ -164,6 +174,31 @@ class NewMedicalHistoryScreen : BaseScreen< toolbar.setNavigationOnClickListener { router.pop() } + + composeView.apply { + setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed + ) + setContent { + SimpleTheme { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(R.dimen.spacing_8)), + ) { + if (showSmokerQuestion) { + TobaccoQuestion( + isSmokingAnswer = isSmoking, + isUsingSmokelessTobaccoAnswer = isUsingSmokelessTobacco, + showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion, + ) { question, answer -> + hotEvents.onNext(NewMedicalHistoryAnswerToggled(question, answer)) + } + } + } + } + } + } } private fun saveClicks() = nextButton @@ -184,13 +219,20 @@ class NewMedicalHistoryScreen : BaseScreen< HasHadAStroke -> strokeQuestionView HasHadAKidneyDisease -> kidneyDiseaseQuestionView DiagnosedWithDiabetes -> diabetesQuestionView - IsSmoking -> currentSmokerQuestionView else -> null } view?.render(question, answer) { questionForView, newAnswer -> hotEvents.onNext(NewMedicalHistoryAnswerToggled(questionForView, newAnswer)) } + + if (question == MedicalHistoryQuestion.IsSmoking) { + isSmoking = answer + } + + if (question == MedicalHistoryQuestion.IsUsingSmokelessTobacco) { + isUsingSmokelessTobacco = answer + } } override fun showDiabetesDiagnosisView() { @@ -268,12 +310,19 @@ class NewMedicalHistoryScreen : BaseScreen< } override fun showCurrentSmokerQuestion() { - currentSmokerQuestionContainer.visibility = VISIBLE - currentSmokerQuestionView.hideDivider() + showSmokerQuestion = true } override fun hideCurrentSmokerQuestion() { - currentSmokerQuestionContainer.visibility = GONE + showSmokerQuestion = false + } + + override fun showSmokelessTobaccoQuestion() { + showSmokelessTobaccoQuestion = true + } + + override fun hideSmokelessTobaccoQuestion() { + showSmokelessTobaccoQuestion = false } override fun showOngoingHypertensionTreatmentErrorDialog() { diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUi.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUi.kt index 671b0b441f1..792c36b7bec 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUi.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUi.kt @@ -19,4 +19,6 @@ interface NewMedicalHistoryUi { fun hideDiabetesTreatmentQuestion() fun showCurrentSmokerQuestion() fun hideCurrentSmokerQuestion() + fun showSmokelessTobaccoQuestion() + fun hideSmokelessTobaccoQuestion() } diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRenderer.kt b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRenderer.kt index 6950be3518f..c8f322ec8c6 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRenderer.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRenderer.kt @@ -6,6 +6,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAHeartAttac import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAKidneyDisease import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco import org.simple.clinic.mobius.ViewRenderer class NewMedicalHistoryUiRenderer( @@ -26,6 +27,7 @@ class NewMedicalHistoryUiRenderer( } renderSmokingQuestion(model) + renderSmokelessTobaccoQuestion(model) renderNextButton(model) } @@ -82,6 +84,15 @@ class NewMedicalHistoryUiRenderer( } } + private fun renderSmokelessTobaccoQuestion(model: NewMedicalHistoryModel) { + if (model.showSmokelessTobaccoQuestion) { + ui.showSmokelessTobaccoQuestion() + ui.renderAnswerForQuestion(IsUsingSmokelessTobacco, model.ongoingMedicalHistoryEntry.isUsingSmokelessTobacco) + } else { + ui.hideSmokelessTobaccoQuestion() + } + } + private fun renderNextButton(model: NewMedicalHistoryModel) { if (model.registeringPatient) { ui.showNextButtonProgress() diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistoryPayload.kt b/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistoryPayload.kt index 45f70f140e8..65d58c935f5 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistoryPayload.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistoryPayload.kt @@ -46,6 +46,9 @@ data class MedicalHistoryPayload( @Json(name = "smoking") val isSmoking: Answer, + @Json(name = "smokeless_tobacco") + val isUsingSmokelessTobacco: Answer, + @Json(name = "cholesterol") val cholesterol: Float?, diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistorySync.kt b/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistorySync.kt index 32d68d35d40..a217dc60f9e 100644 --- a/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistorySync.kt +++ b/app/src/main/java/org/simple/clinic/medicalhistory/sync/MedicalHistorySync.kt @@ -50,6 +50,7 @@ class MedicalHistorySync @Inject constructor( hasDiabetes = diagnosedWithDiabetes, hasHypertension = diagnosedWithHypertension, isSmoking = isSmoking, + isUsingSmokelessTobacco = isUsingSmokelessTobacco, cholesterol = cholesterol, createdAt = createdAt, updatedAt = updatedAt, diff --git a/app/src/main/java/org/simple/clinic/medicalhistory/ui/TobaccoQuestion.kt b/app/src/main/java/org/simple/clinic/medicalhistory/ui/TobaccoQuestion.kt new file mode 100644 index 00000000000..57676f5724e --- /dev/null +++ b/app/src/main/java/org/simple/clinic/medicalhistory/ui/TobaccoQuestion.kt @@ -0,0 +1,128 @@ +package org.simple.clinic.medicalhistory.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import org.simple.clinic.R +import org.simple.clinic.common.ui.theme.SimpleTheme +import org.simple.clinic.medicalhistory.Answer +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion + +@Composable +fun TobaccoQuestion( + isSmokingAnswer: Answer?, + isUsingSmokelessTobaccoAnswer: Answer?, + showSmokelessTobaccoQuestion: Boolean, + onAnswerChange: (MedicalHistoryQuestion, Answer) -> Unit, +) { + if (showSmokelessTobaccoQuestion) { + TobaccoUseContainer( + isSmokingAnswer = isSmokingAnswer, + isUsingSmokelessTobaccoAnswer = isUsingSmokelessTobaccoAnswer, + onAnswerChange = onAnswerChange + ) + } else { + SmokerContainer( + smokerAnswer = isSmokingAnswer, + onAnswerChange = onAnswerChange + ) + } +} + +@Composable +fun SmokerContainer( + smokerAnswer: Answer?, + modifier: Modifier = Modifier, + onAnswerChange: (MedicalHistoryQuestion, Answer) -> Unit, +) { + Card(modifier = modifier) { + MedicalHistoryQuestionItem( + modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.spacing_16)) + .padding(vertical = dimensionResource(R.dimen.spacing_8)), + question = MedicalHistoryQuestion.IsSmoking, + selectedAnswer = smokerAnswer, + showDivider = false, + onSelectionChange = { + onAnswerChange(MedicalHistoryQuestion.IsSmoking, it) + } + ) + } +} + +@Composable +fun TobaccoUseContainer( + isSmokingAnswer: Answer?, + isUsingSmokelessTobaccoAnswer: Answer?, + modifier: Modifier = Modifier, + onAnswerChange: (MedicalHistoryQuestion, Answer) -> Unit, +) { + Card(modifier = modifier) { + Column( + modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.spacing_16)) + .padding( + top = dimensionResource(R.dimen.spacing_16), + bottom = dimensionResource(R.dimen.spacing_4) + ) + ) { + Text( + text = stringResource(R.string.medicalhistorysummaryview_tobacco_use), + style = SimpleTheme.typography.subtitle1Medium, + color = MaterialTheme.colors.onSurface, + ) + + Spacer(Modifier.requiredHeight(dimensionResource(R.dimen.spacing_4))) + + MedicalHistoryQuestionItem( + question = MedicalHistoryQuestion.IsSmoking, + selectedAnswer = isSmokingAnswer, + showDivider = true, + ) { + onAnswerChange(MedicalHistoryQuestion.IsSmoking, it) + } + + MedicalHistoryQuestionItem( + question = MedicalHistoryQuestion.IsUsingSmokelessTobacco, + selectedAnswer = isUsingSmokelessTobaccoAnswer, + showDivider = false, + ) { + onAnswerChange(MedicalHistoryQuestion.IsUsingSmokelessTobacco, it) + } + } + } +} + +@Preview +@Composable +fun TobaccoQuestionPreview() { + TobaccoQuestion( + isSmokingAnswer = Answer.Yes, + isUsingSmokelessTobaccoAnswer = Answer.Unanswered, + showSmokelessTobaccoQuestion = false + ) { _, _ -> + //no-op + } +} + +@Preview +@Composable +fun TobaccoWithSmokelessQuestionPreview() { + TobaccoQuestion( + isSmokingAnswer = Answer.Yes, + isUsingSmokelessTobaccoAnswer = Answer.Yes, + showSmokelessTobaccoQuestion = true + ) { _, _ -> + //no-op + } +} + diff --git a/app/src/main/java/org/simple/clinic/monthlyreports/form/compose/util/ColorUtil.kt b/app/src/main/java/org/simple/clinic/monthlyreports/form/compose/util/ColorUtil.kt index 733ae1384fb..b6e0ae7a4b4 100644 --- a/app/src/main/java/org/simple/clinic/monthlyreports/form/compose/util/ColorUtil.kt +++ b/app/src/main/java/org/simple/clinic/monthlyreports/form/compose/util/ColorUtil.kt @@ -3,12 +3,13 @@ package org.simple.clinic.monthlyreports.form.compose.util import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource +import androidx.core.graphics.toColorInt import org.simple.clinic.R @Composable fun getColor(colorString: String): Color { return try { - Color(android.graphics.Color.parseColor(colorString)) + Color(colorString.toColorInt()) } catch (_: Throwable) { colorResource(id = R.color.simple_dark_grey) } diff --git a/app/src/main/java/org/simple/clinic/newentry/PatientEntryScreen.kt b/app/src/main/java/org/simple/clinic/newentry/PatientEntryScreen.kt index 23f111ced54..378ca861271 100644 --- a/app/src/main/java/org/simple/clinic/newentry/PatientEntryScreen.kt +++ b/app/src/main/java/org/simple/clinic/newentry/PatientEntryScreen.kt @@ -13,6 +13,7 @@ import android.widget.EditText import android.widget.RadioButton import androidx.core.text.buildSpannedString import androidx.core.text.inSpans +import androidx.core.view.isVisible import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.transition.ChangeBounds import androidx.transition.Fade @@ -243,7 +244,7 @@ class PatientEntryScreen : BaseScreen< // FIXME This is temporally coupled to `scrollToFirstFieldWithError()`. private val allTextInputFields: List by unsafeLazy { - val ageOrDateOfBirthEditText = if (ageEditTextInputLayout.visibility == View.VISIBLE) { + val ageOrDateOfBirthEditText = if (ageEditTextInputLayout.isVisible) { ageEditText } else { dateOfBirthEditText @@ -697,7 +698,7 @@ class PatientEntryScreen : BaseScreen< val firstFieldWithError = views .filter { when { - isGenderErrorView(it) -> it.visibility == View.VISIBLE + isGenderErrorView(it) -> it.isVisible it is TextInputLayout -> it.error.isNullOrBlank().not() else -> throw AssertionError() } diff --git a/app/src/main/java/org/simple/clinic/overdue/download/OverdueDownloadWorker.kt b/app/src/main/java/org/simple/clinic/overdue/download/OverdueDownloadWorker.kt index f27b8ae55f0..807488ba078 100644 --- a/app/src/main/java/org/simple/clinic/overdue/download/OverdueDownloadWorker.kt +++ b/app/src/main/java/org/simple/clinic/overdue/download/OverdueDownloadWorker.kt @@ -10,6 +10,7 @@ import android.content.pm.ServiceInfo import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat +import androidx.core.net.toUri import androidx.work.ForegroundInfo import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder @@ -122,13 +123,11 @@ class OverdueDownloadWorker( } private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel(NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH - )) - } + notificationManager.createNotificationChannel(NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + )) } private fun downloadInProgressNotification(): ForegroundInfo { @@ -166,11 +165,8 @@ class OverdueDownloadWorker( } private fun downloadSucceededNotification(uri: Uri, fileFormat: OverdueListFileFormat): Notification { - val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } + val flag = + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT var intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(uri, fileFormat.mimeType) @@ -185,7 +181,7 @@ class OverdueDownloadWorker( if (fileFormat == CSV) intent.setPackage(GOOGLE_SHEETS_PACKAGE_NAME) if (intent.resolveActivity(context.packageManager) == null) { - intent = Intent(Intent.ACTION_VIEW, Uri.parse(playStoreUrl)) + intent = Intent(Intent.ACTION_VIEW, playStoreUrl.toUri()) } val pendingIntent = PendingIntent.getActivity(context, 0, intent, flag) diff --git a/app/src/main/java/org/simple/clinic/patient/download/PatientLinetListDownloadWorker.kt b/app/src/main/java/org/simple/clinic/patient/download/PatientLinetListDownloadWorker.kt index 751ac1de655..d4bf051b716 100644 --- a/app/src/main/java/org/simple/clinic/patient/download/PatientLinetListDownloadWorker.kt +++ b/app/src/main/java/org/simple/clinic/patient/download/PatientLinetListDownloadWorker.kt @@ -10,6 +10,7 @@ import android.content.pm.ServiceInfo import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat +import androidx.core.net.toUri import androidx.work.ForegroundInfo import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder @@ -125,13 +126,11 @@ class PatientLinetListDownloadWorker( } private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel(NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH - )) - } + notificationManager.createNotificationChannel(NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + )) } private fun downloadInProgressNotification(): ForegroundInfo { @@ -169,11 +168,8 @@ class PatientLinetListDownloadWorker( } private fun downloadSucceededNotification(uri: Uri, fileFormat: PatientLineListFileFormat): Notification { - val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } + val flag = + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT var intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(uri, fileFormat.mimeType) @@ -188,7 +184,7 @@ class PatientLinetListDownloadWorker( if (fileFormat == CSV) intent.setPackage(GOOGLE_SHEETS_PACKAGE_NAME) if (intent.resolveActivity(context.packageManager) == null) { - intent = Intent(Intent.ACTION_VIEW, Uri.parse(playStoreUrl)) + intent = Intent(Intent.ACTION_VIEW, playStoreUrl.toUri()) } val pendingIntent = PendingIntent.getActivity(context, 0, intent, flag) diff --git a/app/src/main/java/org/simple/clinic/patient/onlinelookup/api/LookupPatientOnline.kt b/app/src/main/java/org/simple/clinic/patient/onlinelookup/api/LookupPatientOnline.kt index c7e8556da86..4c9a48f4674 100644 --- a/app/src/main/java/org/simple/clinic/patient/onlinelookup/api/LookupPatientOnline.kt +++ b/app/src/main/java/org/simple/clinic/patient/onlinelookup/api/LookupPatientOnline.kt @@ -138,6 +138,7 @@ class LookupPatientOnline @Inject constructor( hasHadKidneyDisease = response.medicalHistory.hasHadKidneyDisease, diagnosedWithDiabetes = response.medicalHistory.hasDiabetes, isSmoking = response.medicalHistory.isSmoking, + isUsingSmokelessTobacco = response.medicalHistory.isUsingSmokelessTobacco, cholesterol = response.medicalHistory.cholesterol, syncStatus = SyncStatus.DONE, createdAt = response.medicalHistory.createdAt, diff --git a/app/src/main/java/org/simple/clinic/purge/PurgeWorker.kt b/app/src/main/java/org/simple/clinic/purge/PurgeWorker.kt index 4f347c64afe..51666ec0e9e 100644 --- a/app/src/main/java/org/simple/clinic/purge/PurgeWorker.kt +++ b/app/src/main/java/org/simple/clinic/purge/PurgeWorker.kt @@ -5,7 +5,6 @@ import android.app.NotificationManager import android.content.Context import android.content.pm.ServiceInfo import android.os.Build -import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.work.ForegroundInfo import androidx.work.OneTimeWorkRequest @@ -78,9 +77,7 @@ class PurgeWorker( } private fun createForegroundInfo(): ForegroundInfo { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createChannel() - } + createChannel() val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) .setContentTitle(applicationContext.getString(R.string.purge_notification_title)) @@ -97,7 +94,6 @@ class PurgeWorker( } } - @RequiresApi(Build.VERSION_CODES.O) private fun createChannel() { notificationManager.createNotificationChannel(NotificationChannel( NOTIFICATION_CHANNEL_ID, diff --git a/app/src/main/java/org/simple/clinic/scanid/qrcodeanalyzer/ZxingQrCodeAnalyzer.kt b/app/src/main/java/org/simple/clinic/scanid/qrcodeanalyzer/ZxingQrCodeAnalyzer.kt index 42a59e3f916..ecd8de9a020 100644 --- a/app/src/main/java/org/simple/clinic/scanid/qrcodeanalyzer/ZxingQrCodeAnalyzer.kt +++ b/app/src/main/java/org/simple/clinic/scanid/qrcodeanalyzer/ZxingQrCodeAnalyzer.kt @@ -3,7 +3,6 @@ package org.simple.clinic.scanid.qrcodeanalyzer import android.graphics.ImageFormat.YUV_420_888 import android.graphics.ImageFormat.YUV_422_888 import android.graphics.ImageFormat.YUV_444_888 -import android.os.Build import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.google.zxing.BarcodeFormat @@ -35,9 +34,7 @@ class ZxingQrCodeAnalyzer( private val yuvFormats = mutableListOf(YUV_420_888) init { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - yuvFormats.addAll(listOf(YUV_422_888, YUV_444_888)) - } + yuvFormats.addAll(listOf(YUV_422_888, YUV_444_888)) } override fun analyze(image: ImageProxy) { diff --git a/app/src/main/java/org/simple/clinic/security/pin/PinEntryCardView.kt b/app/src/main/java/org/simple/clinic/security/pin/PinEntryCardView.kt index 7056721a88b..781b3bb2383 100644 --- a/app/src/main/java/org/simple/clinic/security/pin/PinEntryCardView.kt +++ b/app/src/main/java/org/simple/clinic/security/pin/PinEntryCardView.kt @@ -6,8 +6,8 @@ import android.os.CountDownTimer import android.os.Parcelable import android.util.AttributeSet import android.view.LayoutInflater -import android.view.View import android.view.inputmethod.EditorInfo +import androidx.core.content.withStyledAttributes import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.transition.AutoTransition import androidx.transition.TransitionManager @@ -55,9 +55,9 @@ class PinEntryCardView( @Inject lateinit var verificationMethods: Map - val downstreamUiEvents: PublishSubject = PublishSubject.create() + val downstreamUiEvents: PublishSubject = PublishSubject.create() - private val method: Method + private lateinit var method: Method private var binding: PinEntryCardBinding? = null @@ -89,15 +89,14 @@ class PinEntryCardView( setPinEntryMode(PinEntryUi.Mode.PinEntry) setForgotButtonVisible(true) - with(context.obtainStyledAttributes(attrs, R.styleable.PinEntryCardView)) { + context.withStyledAttributes(attrs, R.styleable.PinEntryCardView) { val methodIndex = getInt(R.styleable.PinEntryCardView_verificationMethod, -1) if (methodIndex < 0) { throw RuntimeException("No verification method defined!") } - method = Method.values()[methodIndex] + method = Method.entries.toTypedArray()[methodIndex] - recycle() } } @@ -236,12 +235,12 @@ class PinEntryCardView( fun showError(error: String) { errorTextView.text = error - errorTextView.visibility = View.VISIBLE + errorTextView.visibility = VISIBLE clearPin() } override fun hideError() { - errorTextView.visibility = View.GONE + errorTextView.visibility = GONE } override fun showIncorrectPinErrorForFirstAttempt() { @@ -281,11 +280,11 @@ class PinEntryCardView( /** Defaults to visible. */ fun setForgotButtonVisible(visible: Boolean) { if (visible) { - forgotPinButton.visibility = View.VISIBLE + forgotPinButton.visibility = VISIBLE contentContainer.setPaddingBottom(R.dimen.pinentry_content_bottom_spacing_with_forgot_pin) } else { - forgotPinButton.visibility = View.GONE + forgotPinButton.visibility = GONE contentContainer.setPaddingBottom(R.dimen.pinentry_content_bottom_spacing_without_forgot_pin) } } diff --git a/app/src/main/java/org/simple/clinic/settings/SettingsScreen.kt b/app/src/main/java/org/simple/clinic/settings/SettingsScreen.kt index 2ef3048189a..7a4c065db70 100644 --- a/app/src/main/java/org/simple/clinic/settings/SettingsScreen.kt +++ b/app/src/main/java/org/simple/clinic/settings/SettingsScreen.kt @@ -2,7 +2,6 @@ package org.simple.clinic.settings import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -11,6 +10,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.core.net.toUri import androidx.fragment.app.Fragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.parcelize.Parcelize @@ -122,7 +122,7 @@ class SettingsScreen : Fragment(), UiActions, HandlesBack { private fun launchPlayStoreForUpdate() { val intent = Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse(PLAY_STORE_URL_FOR_SIMPLE) + data = PLAY_STORE_URL_FOR_SIMPLE.toUri() } requireContext().startActivity(intent) } diff --git a/app/src/main/java/org/simple/clinic/storage/migrations/Migration_121.kt b/app/src/main/java/org/simple/clinic/storage/migrations/Migration_121.kt new file mode 100644 index 00000000000..cddf98d76f3 --- /dev/null +++ b/app/src/main/java/org/simple/clinic/storage/migrations/Migration_121.kt @@ -0,0 +1,19 @@ +package org.simple.clinic.storage.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import org.simple.clinic.storage.inTransaction +import javax.inject.Inject + +@Suppress("ClassName") +class Migration_121 @Inject constructor() : Migration(120, 121) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.inTransaction { + execSQL(""" + ALTER TABLE "MedicalHistory" + ADD COLUMN "isUsingSmokelessTobacco" TEXT NOT NULL DEFAULT "unknown" + """.trimIndent()) + } + } +} diff --git a/app/src/main/java/org/simple/clinic/storage/migrations/RoomMigrationsModule.kt b/app/src/main/java/org/simple/clinic/storage/migrations/RoomMigrationsModule.kt index 83e6a2e4526..ffe427de045 100644 --- a/app/src/main/java/org/simple/clinic/storage/migrations/RoomMigrationsModule.kt +++ b/app/src/main/java/org/simple/clinic/storage/migrations/RoomMigrationsModule.kt @@ -126,6 +126,7 @@ class RoomMigrationsModule { migration118: Migration_118, migration119: Migration_119, migration120: Migration_120, + migration121: Migration_121, ): List { return listOf( migration_3_4, @@ -245,6 +246,7 @@ class RoomMigrationsModule { migration118, migration119, migration120, + migration121, ) } } diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt index bd8ce524d3f..cde8485dfa7 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffect.kt @@ -77,7 +77,11 @@ data class UpdateCVDRisk( data class LoadStatinInfo(val patientUuid: UUID) : PatientSummaryEffect() -data class UpdateSmokingStatus(val patientId: UUID, val isSmoker: MedicalHistoryAnswer) : PatientSummaryEffect() +data class UpdateTobaccoUse( + val patientId: UUID, + val isSmoker: MedicalHistoryAnswer, + val isUsingSmokelessTobacco: MedicalHistoryAnswer +) : PatientSummaryEffect() sealed class PatientSummaryViewEffect : PatientSummaryEffect() diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt index 27e36405322..9b2018806de 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEffectHandler.kt @@ -111,7 +111,7 @@ class PatientSummaryEffectHandler @AssistedInject constructor( .addTransformer(UpdateCVDRisk::class.java, updateCVDRisk()) .addTransformer(SaveCVDRisk::class.java, saveCVDRisk()) .addTransformer(LoadStatinInfo::class.java, loadStatinInfo()) - .addConsumer(UpdateSmokingStatus::class.java, { updateSmokingStatus(it.patientId, it.isSmoker) }, schedulersProvider.io()) + .addConsumer(UpdateTobaccoUse::class.java, { updateTobaccoUse(it.patientId, it.isSmoker, it.isUsingSmokelessTobacco) }, schedulersProvider.io()) .build() } @@ -302,7 +302,11 @@ class PatientSummaryEffectHandler @AssistedInject constructor( } } - private fun updateSmokingStatus(patientUuid: UUID, isSmoker: MedicalHistoryAnswer) { + private fun updateTobaccoUse( + patientUuid: UUID, + isSmoker: MedicalHistoryAnswer, + isUsingSmokelessTobacco: MedicalHistoryAnswer + ) { val medicalHistory = medicalHistoryRepository.historyForPatientOrDefaultImmediate( patientUuid = patientUuid, defaultHistoryUuid = uuidGenerator.v4() @@ -310,6 +314,9 @@ class PatientSummaryEffectHandler @AssistedInject constructor( val updatedMedicalHistory = medicalHistory.answered( question = MedicalHistoryQuestion.IsSmoking, answer = isSmoker + ).answered( + question = MedicalHistoryQuestion.IsUsingSmokelessTobacco, + answer = isUsingSmokelessTobacco ) medicalHistoryRepository.save(updatedMedicalHistory, Instant.now(clock)) diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt index 6262cbe359e..cbc3630c5f4 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryEvent.kt @@ -172,8 +172,9 @@ data class StatinInfoLoaded( data object AddTobaccoUseClicked : PatientSummaryEvent() -data class SmokingStatusAnswered( - val isSmoker: Answer +data class TobaccoUseAnswered( + val isSmoker: Answer, + val isUsingSmokelessTobacco: Answer = Answer.Unanswered ) : PatientSummaryEvent() data object BMIReadingAdded : PatientSummaryEvent() diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt index ec50dc44c4b..f7b02e79c1f 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryScreen.kt @@ -692,18 +692,19 @@ class PatientSummaryScreen : } override fun showTobaccoStatusDialog() { - val options = if (country.isoCountryCode == Country.ETHIOPIA) { - arrayOf( - getString(R.string.tobacco_status_dialog_option_smokes), - getString(R.string.tobacco_status_dialog_option_no) - ) + if (country.isoCountryCode == Country.ETHIOPIA) { + showTobaccoUseDialogWithoutSmokelessTobacco() } else { - arrayOf( - getString(R.string.tobacco_status_dialog_option_smokes), - getString(R.string.tobacco_status_dialog_option_smokeless), - getString(R.string.tobacco_status_dialog_option_no) - ) + showTobaccoUseDialogWithSmokelessTobacco() } + } + + fun showTobaccoUseDialogWithSmokelessTobacco() { + val options = arrayOf( + getString(R.string.tobacco_status_dialog_option_smokes), + getString(R.string.tobacco_status_dialog_option_smokeless), + getString(R.string.tobacco_status_dialog_option_no) + ) val checkedItems = BooleanArray(options.size) @@ -713,10 +714,36 @@ class PatientSummaryScreen : checkedItems[index] = isChecked } .setPositiveButton(R.string.tobacco_status_dialog_title_positive_button) { _, _ -> - if (checkedItems[0]) { - hotEvents.onNext(SmokingStatusAnswered(Answer.Yes)) - } else { - hotEvents.onNext(SmokingStatusAnswered(Answer.No)) + val answeredNo = checkedItems[2] + val isSmoker = if (checkedItems[0] && !answeredNo) Answer.Yes else Answer.No + val isUsingSmokeless = if (checkedItems[1] && !answeredNo) Answer.Yes else Answer.No + + hotEvents.onNext(TobaccoUseAnswered( + isSmoker = isSmoker, + isUsingSmokelessTobacco = isUsingSmokeless + )) + } + .setNegativeButton(R.string.tobacco_status_dialog_title_negative_button, null) + .show() + } + + fun showTobaccoUseDialogWithoutSmokelessTobacco() { + val options = arrayOf( + getString(R.string.tobacco_status_dialog_option_smokes), + getString(R.string.tobacco_status_dialog_option_no)) + + var selectedOption = 1 + + MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Simple_MaterialAlertDialog_CheckedItem) + .setTitle(R.string.tobacco_status_dialog_title) + .setSingleChoiceItems(options, selectedOption) { _, indexSelected -> + selectedOption = indexSelected + } + .setPositiveButton(R.string.tobacco_status_dialog_title_positive_button) { _, _ -> + when (selectedOption) { + 0 -> hotEvents.onNext(TobaccoUseAnswered(Answer.Yes)) + 1 -> hotEvents.onNext(TobaccoUseAnswered(Answer.No)) + else -> {} } } .setNegativeButton(R.string.tobacco_status_dialog_title_negative_button, null) diff --git a/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt b/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt index 66cf53901ad..60bbe8295b9 100644 --- a/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt +++ b/app/src/main/java/org/simple/clinic/summary/PatientSummaryUpdate.kt @@ -106,7 +106,7 @@ class PatientSummaryUpdate( is CVDRiskUpdated -> dispatch(LoadStatinInfo(model.patientUuid)) is StatinInfoLoaded -> statinInfoLoaded(event, model) is AddTobaccoUseClicked -> dispatch(ShowTobaccoStatusDialog) - is SmokingStatusAnswered -> dispatch(UpdateSmokingStatus(model.patientUuid, event.isSmoker)) + is TobaccoUseAnswered -> dispatch(UpdateTobaccoUse(model.patientUuid, event.isSmoker, event.isUsingSmokelessTobacco)) is BMIReadingAdded -> dispatch(CalculateNonLabBasedCVDRisk(model.patientSummaryProfile!!.patient)) is AddBMIClicked -> dispatch(OpenBMIEntrySheet(model.patientUuid)) is AddCholesterolClicked -> dispatch(OpenCholesterolEntrySheet(model.patientUuid)) diff --git a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryModel.kt b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryModel.kt index a3c375adc97..6fe53b58fab 100644 --- a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryModel.kt +++ b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryModel.kt @@ -15,12 +15,18 @@ data class MedicalHistorySummaryModel( val medicalHistory: MedicalHistory? = null, val currentFacility: Facility? = null, val showIsSmokingQuestion: Boolean, + val showSmokelessTobaccoQuestion: Boolean, ) : Parcelable, PatientSummaryChildModel { companion object { - fun create(patientUuid: UUID, showIsSmokingQuestion: Boolean): MedicalHistorySummaryModel = MedicalHistorySummaryModel( + fun create( + patientUuid: UUID, + showIsSmokingQuestion: Boolean, + showSmokelessTobaccoQuestion: Boolean, + ): MedicalHistorySummaryModel = MedicalHistorySummaryModel( patientUuid = patientUuid, - showIsSmokingQuestion = showIsSmokingQuestion + showIsSmokingQuestion = showIsSmokingQuestion, + showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion ) } diff --git a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUi.kt b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUi.kt index a43c10f450d..87d2e03f3cc 100644 --- a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUi.kt +++ b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUi.kt @@ -8,4 +8,7 @@ interface MedicalHistorySummaryUi { fun hideDiagnosisView() fun showCurrentSmokerQuestion() fun hideCurrentSmokerQuestion() + + fun showSmokelessTobaccoQuestion() + fun hideSmokelessTobaccoQuestion() } diff --git a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUiRenderer.kt b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUiRenderer.kt index 5128d2c4ad8..0e377d26e63 100644 --- a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUiRenderer.kt +++ b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUiRenderer.kt @@ -14,6 +14,8 @@ class MedicalHistorySummaryUiRenderer( private val smokerQuestionVisibilityChangedCallback = ValueChangedCallback() + private val smokelessTobaccoQuestionVisibilityChangedCallback = ValueChangedCallback() + override fun render(model: MedicalHistorySummaryModel) { if (model.hasLoadedMedicalHistory) { medicalHistoryChangedCallback.pass(model.medicalHistory!!, ui::populateMedicalHistory) @@ -30,6 +32,14 @@ class MedicalHistorySummaryUiRenderer( ui.hideCurrentSmokerQuestion() } } + + smokelessTobaccoQuestionVisibilityChangedCallback.pass(model.showSmokelessTobaccoQuestion) { show -> + if (show) { + ui.showSmokelessTobaccoQuestion() + } else { + ui.hideSmokelessTobaccoQuestion() + } + } } private fun toggleDiabetesManagementUi(diabetesManagementEnabled: Boolean) { diff --git a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryView.kt b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryView.kt index 9046f7f0eee..9a4af3c8a29 100644 --- a/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryView.kt +++ b/app/src/main/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryView.kt @@ -15,6 +15,7 @@ import io.reactivex.Observable import io.reactivex.rxkotlin.ofType import io.reactivex.subjects.PublishSubject import org.simple.clinic.ReportAnalyticsEvents +import org.simple.clinic.appconfig.Country import org.simple.clinic.common.ui.theme.SimpleTheme import org.simple.clinic.di.injector import org.simple.clinic.feature.Feature @@ -44,6 +45,7 @@ class MedicalHistorySummaryView( private var medicalHistory by mutableStateOf(null) private var diabetesManagementEnabled by mutableStateOf(false) private var showSmokerQuestion by mutableStateOf(false) + private var showSmokelessTobaccoQuestion by mutableStateOf(false) @Inject lateinit var activity: AppCompatActivity @@ -57,6 +59,9 @@ class MedicalHistorySummaryView( @Inject lateinit var features: Features + @Inject + lateinit var country: Country + private var modelUpdateCallback: PatientSummaryModelUpdateCallback? = null private val screenKey by unsafeLazy { screenKeyProvider.keyFor(this) } @@ -78,7 +83,8 @@ class MedicalHistorySummaryView( defaultModel = MedicalHistorySummaryModel.create( patientUuid = screenKey.patientUuid, showIsSmokingQuestion = features.isEnabled(Feature.NonLabBasedStatinNudge) || - features.isEnabled(Feature.LabBasedStatinNudge) + features.isEnabled(Feature.LabBasedStatinNudge), + showSmokelessTobaccoQuestion = country.isoCountryCode != Country.ETHIOPIA ), update = MedicalHistorySummaryUpdate(), init = MedicalHistorySummaryInit(), @@ -112,9 +118,11 @@ class MedicalHistorySummaryView( heartAttackAnswer = medicalHistory?.hasHadHeartAttack, strokeAnswer = medicalHistory?.hasHadStroke, kidneyAnswer = medicalHistory?.hasHadKidneyDisease, - smokerAnswer = medicalHistory?.isSmoking, + isSmokingAnswer = medicalHistory?.isSmoking, + isUsingSmokelessTobaccoAnswer = medicalHistory?.isUsingSmokelessTobacco, diabetesManagementEnabled = diabetesManagementEnabled, showSmokerQuestion = showSmokerQuestion, + showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion ) { question, answer -> answerToggled(question, answer) } @@ -164,6 +172,14 @@ class MedicalHistorySummaryView( showSmokerQuestion = false } + override fun showSmokelessTobaccoQuestion() { + showSmokelessTobaccoQuestion = true + } + + override fun hideSmokelessTobaccoQuestion() { + showSmokelessTobaccoQuestion = false + } + override fun registerSummaryModelUpdateCallback(callback: PatientSummaryModelUpdateCallback?) { modelUpdateCallback = callback } diff --git a/app/src/main/java/org/simple/clinic/summary/medicalhistory/ui/MedicalHistorySummary.kt b/app/src/main/java/org/simple/clinic/summary/medicalhistory/ui/MedicalHistorySummary.kt index bce102cc6ae..cb21c83b511 100644 --- a/app/src/main/java/org/simple/clinic/summary/medicalhistory/ui/MedicalHistorySummary.kt +++ b/app/src/main/java/org/simple/clinic/summary/medicalhistory/ui/MedicalHistorySummary.kt @@ -19,6 +19,7 @@ import org.simple.clinic.common.ui.theme.SimpleTheme import org.simple.clinic.medicalhistory.Answer import org.simple.clinic.medicalhistory.MedicalHistoryQuestion import org.simple.clinic.medicalhistory.ui.MedicalHistoryQuestionItem +import org.simple.clinic.medicalhistory.ui.TobaccoQuestion @Composable fun MedicalHistorySummary( @@ -27,9 +28,11 @@ fun MedicalHistorySummary( heartAttackAnswer: Answer?, strokeAnswer: Answer?, kidneyAnswer: Answer?, - smokerAnswer: Answer?, + isSmokingAnswer: Answer?, + isUsingSmokelessTobaccoAnswer: Answer?, diabetesManagementEnabled: Boolean, showSmokerQuestion: Boolean, + showSmokelessTobaccoQuestion: Boolean, modifier: Modifier = Modifier, onAnswerChange: (MedicalHistoryQuestion, Answer) -> Unit, ) { @@ -58,35 +61,16 @@ fun MedicalHistorySummary( ) if (showSmokerQuestion) { - SmokerContainer( - smokerAnswer = smokerAnswer, + TobaccoQuestion( + isSmokingAnswer = isSmokingAnswer, + isUsingSmokelessTobaccoAnswer = isUsingSmokelessTobaccoAnswer, + showSmokelessTobaccoQuestion = showSmokelessTobaccoQuestion, onAnswerChange = onAnswerChange ) } } } -@Composable -fun SmokerContainer( - smokerAnswer: Answer?, - modifier: Modifier = Modifier, - onAnswerChange: (MedicalHistoryQuestion, Answer) -> Unit, -) { - Card(modifier = modifier) { - MedicalHistoryQuestionItem( - modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.spacing_16)) - .padding(vertical = dimensionResource(R.dimen.spacing_8)), - question = MedicalHistoryQuestion.IsSmoking, - selectedAnswer = smokerAnswer, - showDivider = false, - onSelectionChange = { - onAnswerChange(MedicalHistoryQuestion.IsSmoking, it) - } - ) - } -} - @Composable fun HistoryContainer( heartAttackAnswer: Answer?, @@ -204,9 +188,11 @@ private fun MedicalHistorySummaryPreview() { heartAttackAnswer = Answer.Yes, strokeAnswer = Answer.No, kidneyAnswer = null, - smokerAnswer = null, + isSmokingAnswer = null, + isUsingSmokelessTobaccoAnswer = null, diabetesManagementEnabled = true, showSmokerQuestion = false, + showSmokelessTobaccoQuestion = false, onAnswerChange = { _, _ -> // no-op } @@ -224,9 +210,11 @@ private fun MedicalHistorySummaryNoDiabetesManagementPreview() { heartAttackAnswer = Answer.Yes, strokeAnswer = Answer.No, kidneyAnswer = null, - smokerAnswer = null, + isSmokingAnswer = null, + isUsingSmokelessTobaccoAnswer = null, diabetesManagementEnabled = false, showSmokerQuestion = false, + showSmokelessTobaccoQuestion = false, onAnswerChange = { _, _ -> // no-op } @@ -244,9 +232,33 @@ private fun MedicalHistorySummarySmokerPreview() { heartAttackAnswer = Answer.Yes, strokeAnswer = Answer.No, kidneyAnswer = null, - smokerAnswer = Answer.Yes, + isSmokingAnswer = Answer.Yes, + isUsingSmokelessTobaccoAnswer = Answer.Yes, + diabetesManagementEnabled = true, + showSmokerQuestion = true, + showSmokelessTobaccoQuestion = false, + onAnswerChange = { _, _ -> + // no-op + } + ) + } +} + +@Preview +@Composable +private fun MedicalHistorySummaryTobaccoUsePreview() { + SimpleTheme { + MedicalHistorySummary( + hypertensionAnswer = Answer.Yes, + diabetesAnswer = null, + heartAttackAnswer = Answer.Yes, + strokeAnswer = Answer.No, + kidneyAnswer = null, + isSmokingAnswer = Answer.Yes, + isUsingSmokelessTobaccoAnswer = Answer.Yes, diabetesManagementEnabled = true, showSmokerQuestion = true, + showSmokelessTobaccoQuestion = true, onAnswerChange = { _, _ -> // no-op } diff --git a/app/src/main/java/org/simple/clinic/teleconsultlog/shareprescription/TeleconsultSharePrescriptionScreen.kt b/app/src/main/java/org/simple/clinic/teleconsultlog/shareprescription/TeleconsultSharePrescriptionScreen.kt index 70b366a3e0e..02adccc9ad2 100644 --- a/app/src/main/java/org/simple/clinic/teleconsultlog/shareprescription/TeleconsultSharePrescriptionScreen.kt +++ b/app/src/main/java/org/simple/clinic/teleconsultlog/shareprescription/TeleconsultSharePrescriptionScreen.kt @@ -12,6 +12,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.core.graphics.createBitmap import com.jakewharton.rxbinding3.appcompat.navigationClicks import com.jakewharton.rxbinding3.view.clicks import com.spotify.mobius.functions.Consumer @@ -21,6 +22,8 @@ import io.reactivex.rxkotlin.ofType import io.reactivex.subjects.PublishSubject import org.simple.clinic.R import org.simple.clinic.ReportAnalyticsEvents +import org.simple.clinic.activity.permissions.RequestPermissions +import org.simple.clinic.activity.permissions.RuntimePermissions import org.simple.clinic.databinding.ListItemTeleconsultSharePrescriptionMedicineBinding import org.simple.clinic.databinding.ScreenTeleconsultSharePrescriptionBinding import org.simple.clinic.di.injector @@ -28,13 +31,11 @@ import org.simple.clinic.drugs.PrescribedDrug import org.simple.clinic.home.HomeScreenKey import org.simple.clinic.mobius.ViewRenderer import org.simple.clinic.navigation.v2.Router +import org.simple.clinic.navigation.v2.ScreenResultBus import org.simple.clinic.navigation.v2.fragments.BaseScreen import org.simple.clinic.patient.PatientProfile import org.simple.clinic.patient.displayLetterRes -import org.simple.clinic.navigation.v2.ScreenResultBus import org.simple.clinic.teleconsultlog.prescription.medicines.TeleconsultMedicinesConfig -import org.simple.clinic.activity.permissions.RequestPermissions -import org.simple.clinic.activity.permissions.RuntimePermissions import org.simple.clinic.util.UserClock import org.simple.clinic.util.applyInsetsBottomPadding import org.simple.clinic.util.applyStatusBarPadding @@ -306,7 +307,7 @@ class TeleconsultSharePrescriptionScreen : val bitmapWidth = (sourceWidth * scaleFactor).toInt() val bitmapHeight = (sourceHeight * scaleFactor).toInt() - val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888) + val bitmap = createBitmap(bitmapWidth, bitmapHeight) val canvas = Canvas(bitmap).apply { drawColor(Color.WHITE) } diff --git a/app/src/main/java/org/simple/clinic/text/style/TextAppearanceWithLetterSpacingSpan.kt b/app/src/main/java/org/simple/clinic/text/style/TextAppearanceWithLetterSpacingSpan.kt index 446c999c738..a06dd87d445 100644 --- a/app/src/main/java/org/simple/clinic/text/style/TextAppearanceWithLetterSpacingSpan.kt +++ b/app/src/main/java/org/simple/clinic/text/style/TextAppearanceWithLetterSpacingSpan.kt @@ -6,6 +6,7 @@ import android.text.Spanned import android.text.TextPaint import android.text.style.TextAppearanceSpan import androidx.annotation.StyleRes +import androidx.core.content.withStyledAttributes /** * The platform [TextAppearanceSpan] does not support setting letter spacing for the spanned text. @@ -24,12 +25,12 @@ class TextAppearanceWithLetterSpacingSpan( @StyleRes textAppearanceResId: Int ) : TextAppearanceSpan(context, textAppearanceResId) { - private val letterSpacing: Float + private var letterSpacing: Float = 0.0f init { - val typedArray = context.obtainStyledAttributes(textAppearanceResId, intArrayOf(android.R.attr.letterSpacing)) - letterSpacing = typedArray.getFloat(0, 0F) - typedArray.recycle() + context.withStyledAttributes(textAppearanceResId, intArrayOf(android.R.attr.letterSpacing)) { + letterSpacing = getFloat(0, 0F) + } } override fun updateMeasureState(ds: TextPaint) { diff --git a/app/src/main/java/org/simple/clinic/user/UserSession.kt b/app/src/main/java/org/simple/clinic/user/UserSession.kt index e36517e0d29..a47fe3f6071 100644 --- a/app/src/main/java/org/simple/clinic/user/UserSession.kt +++ b/app/src/main/java/org/simple/clinic/user/UserSession.kt @@ -3,6 +3,7 @@ package org.simple.clinic.user import android.content.SharedPreferences import android.os.Parcelable import androidx.annotation.WorkerThread +import androidx.core.content.edit import com.f2prateek.rx.preferences2.Preference import io.reactivex.Completable import io.reactivex.Observable @@ -15,7 +16,6 @@ import org.simple.clinic.main.TypedPreference import org.simple.clinic.main.TypedPreference.Type.OnboardingComplete import org.simple.clinic.platform.analytics.Analytics import org.simple.clinic.plumbing.infrastructure.Infrastructure -import org.simple.clinic.plumbing.infrastructure.UpdateInfrastructureUserDetails import org.simple.clinic.security.PasswordHasher import org.simple.clinic.storage.SharedPreferencesMode import org.simple.clinic.storage.SharedPreferencesMode.Mode.Default @@ -126,14 +126,14 @@ class UserSession @Inject constructor( // Retain the saved country when clearing the shared preferences val savedCountryData = sharedPreferences.getString(selectedCountryPreference.key(), "") - sharedPreferences.edit().clear().apply() + sharedPreferences.edit { clear() } // When we clear all shared preferences, we also end up clearing the flag that states whether // the user has completed the onboarding flow or not. This means that if the user opens the // again after getting logged out and before logging in, they will be shown the Onboarding // screen instead of the Registration phone screen. This is a workaround that sets the flag // again after clearing the shared preferences to fix this. onboardingComplete.set(true) - sharedPreferences.edit().putString(selectedCountryPreference.key(), savedCountryData).apply() + sharedPreferences.edit { putString(selectedCountryPreference.key(), savedCountryData) } } } diff --git a/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt b/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt index 8ef294b3b8e..c7d74564ac9 100644 --- a/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt +++ b/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt @@ -5,7 +5,6 @@ import android.app.Dialog import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.os.Build import android.view.KeyEvent import androidx.annotation.AttrRes import androidx.annotation.ColorInt @@ -63,8 +62,7 @@ fun Configuration.withLocale( private fun Configuration.isLocaleAlreadyOverriden(): Boolean { return when { - Build.VERSION.SDK_INT >= 24 && !this.locales.isEmpty -> true - Build.VERSION.SDK_INT < 24 && this.locale != null -> true + !this.locales.isEmpty -> true else -> false } } diff --git a/app/src/main/java/org/simple/clinic/util/AppSignature.kt b/app/src/main/java/org/simple/clinic/util/AppSignature.kt index f59c8160a46..9b468e2a75c 100644 --- a/app/src/main/java/org/simple/clinic/util/AppSignature.kt +++ b/app/src/main/java/org/simple/clinic/util/AppSignature.kt @@ -1,15 +1,14 @@ package org.simple.clinic.util import android.annotation.SuppressLint -import android.annotation.TargetApi import android.content.Context import android.content.pm.PackageManager import android.content.pm.Signature import android.os.Build import android.util.Base64 +import androidx.annotation.RequiresApi import java.nio.charset.StandardCharsets import java.security.MessageDigest -import java.util.Arrays private const val HASH_TYPE = "SHA-256" private const val NUM_HASHED_BYTES = 9 @@ -45,7 +44,7 @@ class AppSignature(private val context: Context) { } } - @TargetApi(Build.VERSION_CODES.P) + @RequiresApi(Build.VERSION_CODES.P) private fun signaturesV28(packageManager: PackageManager, packageName: String): List { val signingInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo ?: return emptyList() @@ -67,7 +66,7 @@ class AppSignature(private val context: Context) { var hashSignature = messageDigest.digest() // Truncated into NUM_HASHED_BYTES - hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES) + hashSignature = hashSignature.copyOfRange(0, NUM_HASHED_BYTES) val base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP) diff --git a/app/src/main/java/org/simple/clinic/util/Strings.kt b/app/src/main/java/org/simple/clinic/util/Strings.kt index e9fba9c7917..40dee8ab80b 100644 --- a/app/src/main/java/org/simple/clinic/util/Strings.kt +++ b/app/src/main/java/org/simple/clinic/util/Strings.kt @@ -1,7 +1,6 @@ package org.simple.clinic.util import android.graphics.Typeface -import android.os.Build import android.text.Html import android.text.Spanned import android.text.style.StyleSpan @@ -42,9 +41,5 @@ private fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString } private fun getSpannedText(text: String): Spanned { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT) - } else { - Html.fromHtml(text) - } + return Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT) } diff --git a/app/src/main/java/org/simple/clinic/util/Vibrator.kt b/app/src/main/java/org/simple/clinic/util/Vibrator.kt index 165e20f1719..9f32c72d89a 100644 --- a/app/src/main/java/org/simple/clinic/util/Vibrator.kt +++ b/app/src/main/java/org/simple/clinic/util/Vibrator.kt @@ -1,6 +1,5 @@ package org.simple.clinic.util -import android.os.Build import android.os.VibrationEffect import android.os.Vibrator import javax.inject.Inject @@ -9,11 +8,7 @@ class Vibrator @Inject constructor(private val vibrator: Vibrator) { @Suppress("DEPRECATION") fun vibrate(millis: Long) { - if (Build.VERSION.SDK_INT >= 26) { - vibrator.vibrate(VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE)) - } else { - vibrator.vibrate(millis) - } + vibrator.vibrate(VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE)) } } diff --git a/app/src/main/java/org/simple/clinic/util/messagesender/SmsMessageSender.kt b/app/src/main/java/org/simple/clinic/util/messagesender/SmsMessageSender.kt index 59604dab442..5aa8baeca55 100644 --- a/app/src/main/java/org/simple/clinic/util/messagesender/SmsMessageSender.kt +++ b/app/src/main/java/org/simple/clinic/util/messagesender/SmsMessageSender.kt @@ -1,8 +1,8 @@ package org.simple.clinic.util.messagesender import android.content.Intent -import android.net.Uri import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri import javax.inject.Inject class SmsMessageSender @Inject constructor( @@ -10,7 +10,7 @@ class SmsMessageSender @Inject constructor( ) : MessageSender { override fun send(phoneNumber: String, message: String) { - val uri = Uri.parse("smsto:$phoneNumber") + val uri = "smsto:$phoneNumber".toUri() val intent = Intent(Intent.ACTION_SENDTO, uri).apply { putExtra("sms_body", message) } diff --git a/app/src/main/java/org/simple/clinic/util/messagesender/WhatsAppMessageSender.kt b/app/src/main/java/org/simple/clinic/util/messagesender/WhatsAppMessageSender.kt index e3259ca0a59..f7fbcf0f70c 100644 --- a/app/src/main/java/org/simple/clinic/util/messagesender/WhatsAppMessageSender.kt +++ b/app/src/main/java/org/simple/clinic/util/messagesender/WhatsAppMessageSender.kt @@ -1,8 +1,8 @@ package org.simple.clinic.util.messagesender import android.content.Intent -import android.net.Uri import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri import java.net.URLEncoder import javax.inject.Inject @@ -15,7 +15,7 @@ class WhatsAppMessageSender @Inject constructor( val encodedMessage = URLEncoder.encode(message, "UTF-8") val intent = Intent().apply { action = Intent.ACTION_VIEW - data = Uri.parse("https://wa.me/$whatsAppPhoneNumber?text=$encodedMessage") + data = "https://wa.me/$whatsAppPhoneNumber?text=$encodedMessage".toUri() } activity.startActivity(intent) } diff --git a/app/src/main/java/org/simple/clinic/widgets/ChipInputAutoCompleteTextView.kt b/app/src/main/java/org/simple/clinic/widgets/ChipInputAutoCompleteTextView.kt index 823c592ded9..e58e9bef62a 100644 --- a/app/src/main/java/org/simple/clinic/widgets/ChipInputAutoCompleteTextView.kt +++ b/app/src/main/java/org/simple/clinic/widgets/ChipInputAutoCompleteTextView.kt @@ -3,7 +3,6 @@ package org.simple.clinic.widgets import android.content.Context import android.content.res.ColorStateList import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.os.Parcelable import android.util.AttributeSet @@ -17,6 +16,7 @@ import android.widget.ArrayAdapter import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.res.use +import androidx.core.graphics.drawable.toDrawable import androidx.core.os.bundleOf import androidx.core.view.children import com.google.android.material.chip.Chip @@ -233,7 +233,7 @@ class ChipInputAutoCompleteTextView( private fun dropDownConfig() { autoCompleteTextView.onItemClickListener = this autoCompleteTextView.dropDownWidth = MATCH_PARENT - autoCompleteTextView.setDropDownBackgroundDrawable(ColorDrawable(Color.WHITE)) + autoCompleteTextView.setDropDownBackgroundDrawable(Color.WHITE.toDrawable()) } override fun onSaveInstanceState(): Parcelable { diff --git a/app/src/main/java/org/simple/clinic/widgets/DividerItemDecorator.kt b/app/src/main/java/org/simple/clinic/widgets/DividerItemDecorator.kt index d244b59d992..e3bd48500ff 100644 --- a/app/src/main/java/org/simple/clinic/widgets/DividerItemDecorator.kt +++ b/app/src/main/java/org/simple/clinic/widgets/DividerItemDecorator.kt @@ -6,6 +6,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.view.View import androidx.core.content.res.use +import androidx.core.graphics.withSave import androidx.recyclerview.widget.RecyclerView import timber.log.Timber import kotlin.math.roundToInt @@ -39,20 +40,20 @@ class DividerItemDecorator( } private fun drawDividers(canvas: Canvas, parent: RecyclerView) { - canvas.save() - val left = marginStart - val right = parent.width - marginEnd + canvas.withSave { + val left = marginStart + val right = parent.width - marginEnd - val childCount = parent.childCount - for (i in 0 until childCount - 1) { - val child = parent.getChildAt(i) - parent.getDecoratedBoundsWithMargins(child, mBounds) - val bottom: Int = mBounds.bottom + child.translationY.roundToInt() - val top: Int = bottom - divider!!.intrinsicHeight - divider!!.setBounds(left, top, right, bottom) - divider!!.draw(canvas) + val childCount = parent.childCount + for (i in 0 until childCount - 1) { + val child = parent.getChildAt(i) + parent.getDecoratedBoundsWithMargins(child, mBounds) + val bottom: Int = mBounds.bottom + child.translationY.roundToInt() + val top: Int = bottom - divider!!.intrinsicHeight + divider!!.setBounds(left, top, right, bottom) + divider!!.draw(this) + } } - canvas.restore() } override fun getItemOffsets( diff --git a/app/src/main/java/org/simple/clinic/widgets/PrimarySolidButtonWithFrame.kt b/app/src/main/java/org/simple/clinic/widgets/PrimarySolidButtonWithFrame.kt index 65c10d38784..ed38b1a5f64 100644 --- a/app/src/main/java/org/simple/clinic/widgets/PrimarySolidButtonWithFrame.kt +++ b/app/src/main/java/org/simple/clinic/widgets/PrimarySolidButtonWithFrame.kt @@ -5,6 +5,7 @@ import android.util.AttributeSet import android.widget.Button import android.widget.FrameLayout import androidx.annotation.ColorInt +import androidx.core.content.withStyledAttributes import org.simple.clinic.R class PrimarySolidButtonWithFrame( @@ -27,14 +28,14 @@ class PrimarySolidButtonWithFrame( private var buttonBackgroundDisabledResId: Int = 0 init { - val attrs = context.obtainStyledAttributes(attributeSet, R.styleable.PrimarySolidButtonWithFrame) + context.withStyledAttributes(attributeSet, R.styleable.PrimarySolidButtonWithFrame) { - frameBackgroundEnabledResId = attrs.getColor(R.styleable.PrimarySolidButtonWithFrame_frameBackgroundEnabled, frameBackgroundEnabledResId) - frameBackgroundDisabledResId = attrs.getColor(R.styleable.PrimarySolidButtonWithFrame_frameBackgroundDisabled, frameBackgroundDisabledResId) - buttonBackgroundEnabledResId = attrs.getColor(R.styleable.PrimarySolidButtonWithFrame_buttonBackgroundEnabled, buttonBackgroundEnabledResId) - buttonBackgroundDisabledResId = attrs.getColor(R.styleable.PrimarySolidButtonWithFrame_buttonBackgroundDisabled, buttonBackgroundDisabledResId) + frameBackgroundEnabledResId = getColor(R.styleable.PrimarySolidButtonWithFrame_frameBackgroundEnabled, frameBackgroundEnabledResId) + frameBackgroundDisabledResId = getColor(R.styleable.PrimarySolidButtonWithFrame_frameBackgroundDisabled, frameBackgroundDisabledResId) + buttonBackgroundEnabledResId = getColor(R.styleable.PrimarySolidButtonWithFrame_buttonBackgroundEnabled, buttonBackgroundEnabledResId) + buttonBackgroundDisabledResId = getColor(R.styleable.PrimarySolidButtonWithFrame_buttonBackgroundDisabled, buttonBackgroundDisabledResId) - attrs.recycle() + } verifyColorsSet() } diff --git a/app/src/main/java/org/simple/clinic/widgets/ProgressMaterialButton.kt b/app/src/main/java/org/simple/clinic/widgets/ProgressMaterialButton.kt index 91f90f6c0d8..02a43accbe7 100644 --- a/app/src/main/java/org/simple/clinic/widgets/ProgressMaterialButton.kt +++ b/app/src/main/java/org/simple/clinic/widgets/ProgressMaterialButton.kt @@ -3,6 +3,7 @@ package org.simple.clinic.widgets import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.core.content.withStyledAttributes import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.google.android.material.button.MaterialButton import org.simple.clinic.R @@ -16,7 +17,7 @@ class ProgressMaterialButton( InProgress, Enabled, Disabled } - private var buttonState: ButtonState + private lateinit var buttonState: ButtonState private var buttonText: String? = null private var buttonIcon: Drawable? = null private var buttonIconGravity: Int @@ -28,12 +29,12 @@ class ProgressMaterialButton( buttonIcon = icon buttonIconGravity = iconGravity - val typeArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressMaterialButton) - buttonState = ButtonState.values()[typeArray.getInt(R.styleable.ProgressMaterialButton_buttonState, 0)] + context.withStyledAttributes(attrs, R.styleable.ProgressMaterialButton) { + buttonState = ButtonState.entries.toTypedArray()[getInt(R.styleable.ProgressMaterialButton_buttonState, 0)] - setButtonState(buttonState) + setButtonState(buttonState) - typeArray.recycle() + } } fun setButtonState(buttonState: ButtonState) { @@ -47,6 +48,7 @@ class ProgressMaterialButton( progressDrawable.start() } + ButtonState.Enabled -> { isEnabled = true isClickable = true @@ -56,6 +58,7 @@ class ProgressMaterialButton( progressDrawable.stop() } + ButtonState.Disabled -> { isEnabled = false icon = buttonIcon diff --git a/app/src/main/java/org/simple/clinic/widgets/ToolbarWithTintedNavIcon.kt b/app/src/main/java/org/simple/clinic/widgets/ToolbarWithTintedNavIcon.kt index 79f8de9926d..5f1b26b682f 100644 --- a/app/src/main/java/org/simple/clinic/widgets/ToolbarWithTintedNavIcon.kt +++ b/app/src/main/java/org/simple/clinic/widgets/ToolbarWithTintedNavIcon.kt @@ -1,26 +1,28 @@ package org.simple.clinic.widgets +import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.graphics.drawable.Drawable import android.util.AttributeSet import androidx.annotation.ColorInt import androidx.appcompat.widget.Toolbar +import androidx.core.content.withStyledAttributes import androidx.core.graphics.drawable.DrawableCompat import org.simple.clinic.R -class ToolbarWithTintedNavIcon(context: Context, attrs: AttributeSet) : Toolbar(context, attrs) { +@SuppressLint("CustomViewStyleable") class ToolbarWithTintedNavIcon(context: Context, attrs: AttributeSet) : Toolbar(context, attrs) { @ColorInt - private val navigationIconTint: Int + private var navigationIconTint: Int = 0 init { // Bright red so that nobody forgets to set the value. val defaultTint = Color.RED - val attributes = context.obtainStyledAttributes(attrs, R.styleable.ToolbarWithTintedNavIcon) - navigationIconTint = attributes.getColor(R.styleable.ToolbarWithTintedNavIcon_navigationIconTint, defaultTint) - attributes.recycle() + context.withStyledAttributes(attrs, R.styleable.ToolbarWithTintedNavIcon) { + navigationIconTint = getColor(R.styleable.ToolbarWithTintedNavIcon_navigationIconTint, defaultTint) + } val icon = navigationIcon if (icon != null) { diff --git a/app/src/main/java/org/simple/clinic/widgets/ViewFlipperWithLayoutEditorPreview.kt b/app/src/main/java/org/simple/clinic/widgets/ViewFlipperWithLayoutEditorPreview.kt index 4998dafbd70..a87e1db4a58 100644 --- a/app/src/main/java/org/simple/clinic/widgets/ViewFlipperWithLayoutEditorPreview.kt +++ b/app/src/main/java/org/simple/clinic/widgets/ViewFlipperWithLayoutEditorPreview.kt @@ -3,6 +3,7 @@ package org.simple.clinic.widgets import android.content.Context import android.util.AttributeSet import android.widget.ViewFlipper +import androidx.core.content.withStyledAttributes import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -21,9 +22,9 @@ class ViewFlipperWithLayoutEditorPreview( init { if (isInEditMode) { - val attributes = context.obtainStyledAttributes(attrs, R.styleable.ViewFlipperWithLayoutEditorPreview) - childToDisplayPostInflate = attributes.getInt(R.styleable.ViewFlipperWithLayoutEditorPreview_debug_displayedChild, 0) - attributes.recycle() + context.withStyledAttributes(attrs, R.styleable.ViewFlipperWithLayoutEditorPreview) { + childToDisplayPostInflate = getInt(R.styleable.ViewFlipperWithLayoutEditorPreview_debug_displayedChild, 0) + } } displayedChildChangesSubject.onNext(displayedChild) } diff --git a/app/src/main/java/org/simple/clinic/widgets/Views.kt b/app/src/main/java/org/simple/clinic/widgets/Views.kt index 4c78fb7033b..53bf6ce14ac 100644 --- a/app/src/main/java/org/simple/clinic/widgets/Views.kt +++ b/app/src/main/java/org/simple/clinic/widgets/Views.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.res.Resources import android.graphics.Rect import android.graphics.drawable.Drawable -import android.os.Build import android.text.TextWatcher import android.util.DisplayMetrics import android.view.MenuItem @@ -27,6 +26,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.doOnDetach +import androidx.core.view.isGone import androidx.core.widget.NestedScrollView import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.DynamicAnimation.ALPHA @@ -52,7 +52,6 @@ import io.reactivex.Observable import org.simple.clinic.R import timber.log.Timber import java.time.Duration -import androidx.core.view.isGone fun EditText.showKeyboard() { val openKeyboard = Runnable { @@ -284,12 +283,7 @@ fun dpToPx(dp: Float): Float { fun dpToPx(dp: Int) = dpToPx(dp.toFloat()) fun TextView.setTextAppearanceCompat(@StyleRes resourceId: Int) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setTextAppearance(resourceId) - } else { - @Suppress("DEPRECATION") - setTextAppearance(context, resourceId) - } + setTextAppearance(resourceId) } fun ScrollView.scrollToChild(view: View, onScrollComplete: () -> Unit = {}) { @@ -306,7 +300,7 @@ fun ScrollView.scrollToChild(view: View, onScrollComplete: () -> Unit = {}) { } fun NestedScrollView.scrollToChild(view: View, onScrollComplete: () -> Unit = {}) { - val runnable= Runnable { onScrollComplete() } + val runnable = Runnable { onScrollComplete() } post { val distanceToScrollFromTop = view.topRelativeTo(this) diff --git a/app/src/main/res/color-v23/appointment_date_stepper.xml b/app/src/main/res/color-v23/appointment_date_stepper.xml deleted file mode 100644 index 29911380149..00000000000 --- a/app/src/main/res/color-v23/appointment_date_stepper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color-v23/changelanguage_radio_button.xml b/app/src/main/res/color-v23/changelanguage_radio_button.xml deleted file mode 100644 index 69a4ea08b11..00000000000 --- a/app/src/main/res/color-v23/changelanguage_radio_button.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color-v23/color_on_toolbar_primary_72.xml b/app/src/main/res/color-v23/color_on_toolbar_primary_72.xml deleted file mode 100644 index 773892d9806..00000000000 --- a/app/src/main/res/color-v23/color_on_toolbar_primary_72.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/color-v23/deletereason_radio_button.xml b/app/src/main/res/color-v23/deletereason_radio_button.xml deleted file mode 100644 index aaae5529005..00000000000 --- a/app/src/main/res/color-v23/deletereason_radio_button.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color-v23/editpatient_radio_button.xml b/app/src/main/res/color-v23/editpatient_radio_button.xml deleted file mode 100644 index aaae5529005..00000000000 --- a/app/src/main/res/color-v23/editpatient_radio_button.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color-v23/intro_video_subtitle.xml b/app/src/main/res/color-v23/intro_video_subtitle.xml deleted file mode 100644 index edf4740f774..00000000000 --- a/app/src/main/res/color-v23/intro_video_subtitle.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/color-v23/patiententry_radio_button.xml b/app/src/main/res/color-v23/patiententry_radio_button.xml deleted file mode 100644 index aaae5529005..00000000000 --- a/app/src/main/res/color-v23/patiententry_radio_button.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color-v23/removeappointment_radio_button.xml b/app/src/main/res/color-v23/removeappointment_radio_button.xml deleted file mode 100644 index aaae5529005..00000000000 --- a/app/src/main/res/color-v23/removeappointment_radio_button.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color-v23/snackbar_subtitle.xml b/app/src/main/res/color-v23/snackbar_subtitle.xml deleted file mode 100644 index b3274e9b166..00000000000 --- a/app/src/main/res/color-v23/snackbar_subtitle.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/color/appointment_date_stepper.xml b/app/src/main/res/color/appointment_date_stepper.xml index d0bd304fe02..29911380149 100644 --- a/app/src/main/res/color/appointment_date_stepper.xml +++ b/app/src/main/res/color/appointment_date_stepper.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/color/changelanguage_radio_button.xml b/app/src/main/res/color/changelanguage_radio_button.xml index 2dc971cd754..69a4ea08b11 100644 --- a/app/src/main/res/color/changelanguage_radio_button.xml +++ b/app/src/main/res/color/changelanguage_radio_button.xml @@ -1,5 +1,5 @@ - + - + diff --git a/app/src/main/res/color/color_on_toolbar_primary_72.xml b/app/src/main/res/color/color_on_toolbar_primary_72.xml index 4a2bded758d..773892d9806 100644 --- a/app/src/main/res/color/color_on_toolbar_primary_72.xml +++ b/app/src/main/res/color/color_on_toolbar_primary_72.xml @@ -1,4 +1,4 @@ - + - + diff --git a/app/src/main/res/color/deletereason_radio_button.xml b/app/src/main/res/color/deletereason_radio_button.xml index a7163870e14..aaae5529005 100644 --- a/app/src/main/res/color/deletereason_radio_button.xml +++ b/app/src/main/res/color/deletereason_radio_button.xml @@ -1,5 +1,5 @@ - + - + diff --git a/app/src/main/res/color/editpatient_radio_button.xml b/app/src/main/res/color/editpatient_radio_button.xml index a7163870e14..aaae5529005 100644 --- a/app/src/main/res/color/editpatient_radio_button.xml +++ b/app/src/main/res/color/editpatient_radio_button.xml @@ -1,5 +1,5 @@ - + - + diff --git a/app/src/main/res/color/intro_video_subtitle.xml b/app/src/main/res/color/intro_video_subtitle.xml index c0802300e62..edf4740f774 100644 --- a/app/src/main/res/color/intro_video_subtitle.xml +++ b/app/src/main/res/color/intro_video_subtitle.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/color/patiententry_radio_button.xml b/app/src/main/res/color/patiententry_radio_button.xml index a7163870e14..aaae5529005 100644 --- a/app/src/main/res/color/patiententry_radio_button.xml +++ b/app/src/main/res/color/patiententry_radio_button.xml @@ -1,5 +1,5 @@ - + - + diff --git a/app/src/main/res/color/removeappointment_radio_button.xml b/app/src/main/res/color/removeappointment_radio_button.xml index a7163870e14..aaae5529005 100644 --- a/app/src/main/res/color/removeappointment_radio_button.xml +++ b/app/src/main/res/color/removeappointment_radio_button.xml @@ -1,5 +1,5 @@ - + - + diff --git a/app/src/main/res/color/snackbar_subtitle.xml b/app/src/main/res/color/snackbar_subtitle.xml index 844f0d14579..b3274e9b166 100644 --- a/app/src/main/res/color/snackbar_subtitle.xml +++ b/app/src/main/res/color/snackbar_subtitle.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/drawable-v26/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/main/res/drawable-v26/ic_launcher_foreground.xml rename to app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/main/res/layout/patient_edit_alternate_id_view.xml b/app/src/main/res/layout/patient_edit_alternate_id_view.xml index 26508e49c1e..993ba6c0b76 100644 --- a/app/src/main/res/layout/patient_edit_alternate_id_view.xml +++ b/app/src/main/res/layout/patient_edit_alternate_id_view.xml @@ -13,5 +13,4 @@ android:textColor="?attr/colorOnSurface" app:drawableStartCompat="@drawable/patient_id_card" app:drawableTint="@color/color_on_surface_67" - tools:targetApi="m" tools:text="12345678" /> diff --git a/app/src/main/res/layout/patient_edit_bp_passport_view.xml b/app/src/main/res/layout/patient_edit_bp_passport_view.xml index 671ae03b4cd..60dd9b0c8be 100644 --- a/app/src/main/res/layout/patient_edit_bp_passport_view.xml +++ b/app/src/main/res/layout/patient_edit_bp_passport_view.xml @@ -13,5 +13,4 @@ android:textColor="?attr/colorOnSurface" app:drawableStartCompat="@drawable/patient_id_card" app:drawableTint="@color/color_on_surface_67" - tools:targetApi="m" tools:text="12345678" /> diff --git a/app/src/main/res/layout/patient_entry_alternate_id_view.xml b/app/src/main/res/layout/patient_entry_alternate_id_view.xml index fa1f241d16d..849955e792a 100644 --- a/app/src/main/res/layout/patient_entry_alternate_id_view.xml +++ b/app/src/main/res/layout/patient_entry_alternate_id_view.xml @@ -13,5 +13,4 @@ android:textColor="?attr/colorOnSurface" app:drawableStartCompat="@drawable/patient_id_card" app:drawableTint="@color/color_on_surface_67" - tools:targetApi="m" tools:text="1234123412341234" /> diff --git a/app/src/main/res/layout/screen_new_medical_history.xml b/app/src/main/res/layout/screen_new_medical_history.xml index 8da937e03a6..61ecef7a30b 100644 --- a/app/src/main/res/layout/screen_new_medical_history.xml +++ b/app/src/main/res/layout/screen_new_medical_history.xml @@ -99,24 +99,10 @@ - - - - - + android:layout_height="match_parent" /> diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 00000000000..370b2a4527c --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4099cb6b081..00000000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bb46aa7bab6..00000000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e04873bf664..00000000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3ac4c94c771..00000000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 119bee1c1a1..00000000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/values-am-rET/strings.xml b/app/src/main/res/values-am-rET/strings.xml index 0cd49103209..1e5bd79c0c3 100644 --- a/app/src/main/res/values-am-rET/strings.xml +++ b/app/src/main/res/values-am-rET/strings.xml @@ -488,6 +488,7 @@ ታሪክ ምርመራ (አስፈላጊ) + ትምባሆ አጠቃቀም ምንም መድሃኒቶች አልታከሉም diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 2bb11b9864b..5a8e02231c5 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -338,7 +338,6 @@ ইতিহাস গত মাসে রোগী কি কোনও সময় বিপি-র ওষুধ খেয়েছিলেন? (আবশ্যিক) গত মাসে রোগী কি কোনও সময় ডায়াবেটিসের ওষুধ খেয়েছিলেন? (আবশ্যিক) - তিনি কি ধূমপান করেন ? কোনটিই নয় @@ -490,6 +489,7 @@ ইতিহাস রোগ নির্ণয় (প্রয়োজন) + তামাক সেবন কোনো ওষুধ যোগ করা হয়নি diff --git a/app/src/main/res/values-om-rET/strings.xml b/app/src/main/res/values-om-rET/strings.xml index c10128a83a4..02e8fdd452b 100644 --- a/app/src/main/res/values-om-rET/strings.xml +++ b/app/src/main/res/values-om-rET/strings.xml @@ -489,6 +489,7 @@ Seenaa Qorannoo (Barbaadameera) + Fayyadama tamboo Qorichi kamiyyuu hin dabalamne diff --git a/app/src/main/res/values-si-rLK/strings.xml b/app/src/main/res/values-si-rLK/strings.xml index ffab4299f85..4d15283f5c9 100644 --- a/app/src/main/res/values-si-rLK/strings.xml +++ b/app/src/main/res/values-si-rLK/strings.xml @@ -338,7 +338,6 @@ ඉතිහාසය රෝගියා පසුගිය මාසය තුල කිසියම් දිනකදී රුධිර පීඩනය පාලනය කිරීම සඳහා ඖෂධයක් ලබා ගත්තේද? (අවශ්‍යයි) රෝගියා පසුගිය මාසය තුල කිසියම් දිනකදී දියවැඩියාව පාලනය කිරීම සඳහා ඖෂධයක් ලබා ගත්තේද? (අවශ්‍යයි) - දුම් බොනවාද? කිසිවක් නැත @@ -490,6 +489,7 @@ ඉතිහාසය රෝග නිශ්චය (අවශ්‍යයි) + දුම්කොළ ආශ්‍රිත නිෂ්පාදන භාවිතය කිසිදු ඖෂධයක් එකතු කර නැත diff --git a/app/src/main/res/values-ta-rLK/strings.xml b/app/src/main/res/values-ta-rLK/strings.xml index d1191678c47..1e881fe5026 100644 --- a/app/src/main/res/values-ta-rLK/strings.xml +++ b/app/src/main/res/values-ta-rLK/strings.xml @@ -338,7 +338,6 @@ வரலாறு கடந்த மாதத்தில் நோயாளர் எப்போதாவது பிபி குளிகைகளை எடுத்துக்கொண்டாரா? (தேவையானவை) கடந்த மாதத்தில் நோயாளர் எப்போதாவது நீரழிவுநோய் மருந்துகளை எடுத்துக்கொண்டாரா? (தேவையானவை) - புகைபிடிக்கிறதா? எதுவுமில்லை @@ -490,6 +489,7 @@ வரலாறு நோய் கண்டறிதல் (தேவை) + புகையிலை பாவனை குளிகைகள் எதுவும் சேர்க்கப்படவில்லை diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e2e94cec46b..fd5f7872696 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -397,7 +397,8 @@ Did patient take BP medicines any time in the last month? (Required) Is patient starting Simple treatment protocol for Hypertension? (Required) Did patient take diabetes medicines any time in the last month? (Required) - Smokes? + Smokes + Smokeless tobacco None @@ -573,6 +574,7 @@ History Diagnosis (Required) + Tobacco use No medicines added diff --git a/app/src/sandbox/res/drawable-v26/ic_launcher_foreground.xml b/app/src/sandbox/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/sandbox/res/drawable-v26/ic_launcher_foreground.xml rename to app/src/sandbox/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/sandbox/res/mipmap-anydpi/ic_launcher.xml b/app/src/sandbox/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 00000000000..370b2a4527c --- /dev/null +++ b/app/src/sandbox/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/sandbox/res/mipmap-hdpi/ic_launcher.png b/app/src/sandbox/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c850717b4fd..00000000000 Binary files a/app/src/sandbox/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/sandbox/res/mipmap-mdpi/ic_launcher.png b/app/src/sandbox/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4a0d5ac6c1d..00000000000 Binary files a/app/src/sandbox/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/sandbox/res/mipmap-xhdpi/ic_launcher.png b/app/src/sandbox/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ed6230e1677..00000000000 Binary files a/app/src/sandbox/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/sandbox/res/mipmap-xxhdpi/ic_launcher.png b/app/src/sandbox/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 392ecb5dc95..00000000000 Binary files a/app/src/sandbox/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/sandbox/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/sandbox/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index edfa17eb0f1..00000000000 Binary files a/app/src/sandbox/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/security/res/drawable-v26/ic_launcher_foreground.xml b/app/src/security/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/security/res/drawable-v26/ic_launcher_foreground.xml rename to app/src/security/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/security/res/mipmap-anydpi/ic_launcher.xml b/app/src/security/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 00000000000..370b2a4527c --- /dev/null +++ b/app/src/security/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/security/res/mipmap-hdpi/ic_launcher.png b/app/src/security/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c850717b4fd..00000000000 Binary files a/app/src/security/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/security/res/mipmap-mdpi/ic_launcher.png b/app/src/security/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4a0d5ac6c1d..00000000000 Binary files a/app/src/security/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/security/res/mipmap-xhdpi/ic_launcher.png b/app/src/security/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ed6230e1677..00000000000 Binary files a/app/src/security/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/security/res/mipmap-xxhdpi/ic_launcher.png b/app/src/security/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 392ecb5dc95..00000000000 Binary files a/app/src/security/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/security/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/security/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index edfa17eb0f1..00000000000 Binary files a/app/src/security/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/staging/res/drawable-v26/ic_launcher_foreground.xml b/app/src/staging/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/staging/res/drawable-v26/ic_launcher_foreground.xml rename to app/src/staging/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/staging/res/mipmap-anydpi/ic_launcher.xml b/app/src/staging/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 00000000000..370b2a4527c --- /dev/null +++ b/app/src/staging/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/staging/res/mipmap-hdpi/ic_launcher.png b/app/src/staging/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5634fe17066..00000000000 Binary files a/app/src/staging/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/staging/res/mipmap-mdpi/ic_launcher.png b/app/src/staging/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 12a9f092357..00000000000 Binary files a/app/src/staging/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/staging/res/mipmap-xhdpi/ic_launcher.png b/app/src/staging/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e8064296942..00000000000 Binary files a/app/src/staging/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png b/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 70f2aa76597..00000000000 Binary files a/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2db13770c0a..00000000000 Binary files a/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt index 9f1904426b1..3491c94f103 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryInitTest.kt @@ -11,7 +11,11 @@ import org.simple.clinic.appconfig.Country class NewMedicalHistoryInitTest { private val country = TestData.country(isoCountryCode = Country.INDIA) - private val defaultModel = NewMedicalHistoryModel.default(country, false) + private val defaultModel = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false + ) private val initSpec = InitSpec(NewMedicalHistoryInit()) diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt index 100fe5065e7..18aab4b1ccd 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryScreenLogicTest.kt @@ -13,6 +13,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.simple.clinic.TestData import org.simple.clinic.appconfig.Country import org.simple.clinic.medicalhistory.Answer.No import org.simple.clinic.medicalhistory.Answer.Unanswered @@ -25,6 +26,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco import org.simple.clinic.medicalhistory.MedicalHistoryRepository import org.simple.clinic.medicalhistory.OngoingMedicalHistoryEntry import org.simple.clinic.patient.Gender @@ -32,13 +34,12 @@ import org.simple.clinic.patient.OngoingNewPatientEntry import org.simple.clinic.patient.OngoingNewPatientEntry.PersonalDetails import org.simple.clinic.patient.PatientProfile import org.simple.clinic.patient.PatientRepository +import org.simple.clinic.util.RxErrorsRule import org.simple.clinic.util.scheduler.TrampolineSchedulersProvider +import org.simple.clinic.uuid.FakeUuidGenerator import org.simple.clinic.uuid.UuidGenerator import org.simple.clinic.widgets.UiEvent import org.simple.mobius.migration.MobiusTestFixture -import org.simple.clinic.TestData -import org.simple.clinic.util.RxErrorsRule -import org.simple.clinic.uuid.FakeUuidGenerator import java.time.format.DateTimeFormatter import java.util.Locale import java.util.UUID @@ -122,6 +123,7 @@ class NewMedicalHistoryScreenLogicTest { uiEvents.onNext(NewMedicalHistoryAnswerToggled(DiagnosedWithDiabetes, Yes)) uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsOnDiabetesTreatment, Yes)) uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsSmoking, Yes)) + uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsUsingSmokelessTobacco, Yes)) uiEvents.onNext(SaveMedicalHistoryClicked()) // then @@ -148,6 +150,7 @@ class NewMedicalHistoryScreenLogicTest { hasDiabetes = Yes, isOnDiabetesTreatment = Yes, isSmoking = Yes, + isUsingSmokelessTobacco = Yes, ) ) verify(uiActions).openPatientSummaryScreen(savedPatient.patientUuid) @@ -213,6 +216,7 @@ class NewMedicalHistoryScreenLogicTest { hasDiabetes = Unanswered, isOnHypertensionTreatment = Yes, isSmoking = Unanswered, + isUsingSmokelessTobacco = Unanswered, )) verify(uiActions).openPatientSummaryScreen(savedPatient.patientUuid) } @@ -253,6 +257,7 @@ class NewMedicalHistoryScreenLogicTest { uiEvents.onNext(NewMedicalHistoryAnswerToggled(HasHadAKidneyDisease, Yes)) uiEvents.onNext(NewMedicalHistoryAnswerToggled(DiagnosedWithDiabetes, Yes)) uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsSmoking, Yes)) + uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsUsingSmokelessTobacco, Yes)) // Updated answers uiEvents.onNext(NewMedicalHistoryAnswerToggled(DiagnosedWithHypertension, Yes)) @@ -262,6 +267,7 @@ class NewMedicalHistoryScreenLogicTest { uiEvents.onNext(NewMedicalHistoryAnswerToggled(DiagnosedWithDiabetes, No)) uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsOnHypertensionTreatment(Country.INDIA), Yes)) uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsSmoking, No)) + uiEvents.onNext(NewMedicalHistoryAnswerToggled(IsUsingSmokelessTobacco, No)) uiEvents.onNext(SaveMedicalHistoryClicked()) @@ -289,6 +295,7 @@ class NewMedicalHistoryScreenLogicTest { hasDiabetes = No, isOnHypertensionTreatment = Yes, isSmoking = No, + isUsingSmokelessTobacco = No, ) ) verify(uiActions).openPatientSummaryScreen(savedPatient.patientUuid) @@ -320,7 +327,11 @@ class NewMedicalHistoryScreenLogicTest { testFixture = MobiusTestFixture( events = uiEvents.ofType(), - defaultModel = NewMedicalHistoryModel.default(country, true), + defaultModel = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = true, + showSmokelessTobaccoQuestion = true + ), init = NewMedicalHistoryInit(), update = NewMedicalHistoryUpdate(), effectHandler = effectHandler, diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRendererTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRendererTest.kt index afc05720e1f..84fb19a6398 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRendererTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUiRendererTest.kt @@ -4,6 +4,7 @@ import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions +import org.simple.clinic.TestData import org.simple.clinic.appconfig.Country import org.simple.clinic.facility.FacilityConfig import org.simple.clinic.medicalhistory.Answer.No @@ -15,7 +16,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAHeartAttac import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAKidneyDisease import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking -import org.simple.clinic.TestData +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco import java.util.UUID class NewMedicalHistoryUiRendererTest { @@ -43,7 +44,11 @@ class NewMedicalHistoryUiRendererTest { ) private val country = TestData.country(isoCountryCode = Country.INDIA) - private val defaultModel = NewMedicalHistoryModel.default(country, false) + private val defaultModel = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false + ) private val ui = mock() private val uiRenderer = NewMedicalHistoryUiRenderer(ui) @@ -71,6 +76,7 @@ class NewMedicalHistoryUiRendererTest { verify(ui).showDiabetesHistorySection() verify(ui).renderAnswerForQuestion(DiagnosedWithDiabetes, Unanswered) verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -164,7 +170,11 @@ class NewMedicalHistoryUiRendererTest { fun `when patient has hypertension and country is not from india, then don't show hypertension treatment question`() { // given val bangladesh = TestData.country(isoCountryCode = Country.BANGLADESH) - val model = NewMedicalHistoryModel.default(country = bangladesh, false) + val model = NewMedicalHistoryModel.default( + country = bangladesh, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false + ) .currentFacilityLoaded(facilityWithDiabetesManagementEnabled) .answerChanged(DiagnosedWithHypertension, Yes) @@ -230,7 +240,11 @@ class NewMedicalHistoryUiRendererTest { fun `when diabetes management is enabled and patient has diabetes and is not from india, then don't show diabetes treatment question`() { // given val bangladesh = TestData.country(isoCountryCode = Country.BANGLADESH) - val model = NewMedicalHistoryModel.default(country = bangladesh, false) + val model = NewMedicalHistoryModel.default( + country = bangladesh, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false + ) .currentFacilityLoaded(facilityWithDiabetesManagementEnabled) .answerChanged(DiagnosedWithDiabetes, Yes) @@ -274,12 +288,17 @@ class NewMedicalHistoryUiRendererTest { @Test fun `when show smoker question is enabled, then show current smoker question`() { // given - val model = NewMedicalHistoryModel.default(country, true) + val model = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = true, + showSmokelessTobaccoQuestion = false + ) .answerChanged(DiagnosedWithHypertension, Unanswered) .answerChanged(HasHadAHeartAttack, Yes) .answerChanged(HasHadAStroke, No) .answerChanged(HasHadAKidneyDisease, Unanswered) .answerChanged(IsSmoking, No) + .answerChanged(IsUsingSmokelessTobacco, No) // when uiRenderer.render(model) @@ -295,6 +314,7 @@ class NewMedicalHistoryUiRendererTest { verify(ui).showDiabetesHistorySection() verify(ui).renderAnswerForQuestion(DiagnosedWithDiabetes, Unanswered) verify(ui).showCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verify(ui).renderAnswerForQuestion(IsSmoking, No) verifyNoMoreInteractions(ui) } @@ -302,7 +322,11 @@ class NewMedicalHistoryUiRendererTest { @Test fun `when show smoker question is disabled, then hide current smoker question`() { // given - val model = NewMedicalHistoryModel.default(country, false) + val model = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false, + ) .answerChanged(DiagnosedWithHypertension, Unanswered) .answerChanged(HasHadAHeartAttack, Yes) .answerChanged(HasHadAStroke, No) @@ -322,14 +346,50 @@ class NewMedicalHistoryUiRendererTest { verify(ui).showDiabetesHistorySection() verify(ui).renderAnswerForQuestion(DiagnosedWithDiabetes, Unanswered) verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } + @Test + fun `when show smokeless tobacco question is enabled, then show smokeless tobacco question`() { + // given + val model = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = true, + showSmokelessTobaccoQuestion = true + ) + .answerChanged(DiagnosedWithHypertension, Unanswered) + .answerChanged(HasHadAHeartAttack, Yes) + .answerChanged(HasHadAStroke, No) + .answerChanged(HasHadAKidneyDisease, Unanswered) + .answerChanged(IsSmoking, No) + .answerChanged(IsUsingSmokelessTobacco, No) + + // when + uiRenderer.render(model) + + // then + verify(ui).renderDiagnosisAnswer(DiagnosedWithHypertension, Unanswered) + verify(ui).hideHypertensionTreatmentQuestion() + verify(ui).renderAnswerForQuestion(HasHadAHeartAttack, Yes) + verify(ui).renderAnswerForQuestion(HasHadAStroke, No) + verify(ui).renderAnswerForQuestion(HasHadAKidneyDisease, Unanswered) + verify(ui).hideNextButtonProgress() + verify(ui).hideDiabetesDiagnosisView() + verify(ui).showDiabetesHistorySection() + verify(ui).renderAnswerForQuestion(DiagnosedWithDiabetes, Unanswered) + verify(ui).showCurrentSmokerQuestion() + verify(ui).showSmokelessTobaccoQuestion() + verify(ui).renderAnswerForQuestion(IsSmoking, No) + verify(ui).renderAnswerForQuestion(IsUsingSmokelessTobacco, No) + verifyNoMoreInteractions(ui) + } private fun verifyImplicitRenders() { verify(ui).renderAnswerForQuestion(HasHadAHeartAttack, Unanswered) verify(ui).renderAnswerForQuestion(HasHadAStroke, Unanswered) verify(ui).renderAnswerForQuestion(HasHadAKidneyDisease, Unanswered) verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() } } diff --git a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt index 595b8a17dee..275434c0f98 100644 --- a/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/medicalhistory/newentry/NewMedicalHistoryUpdateTest.kt @@ -9,6 +9,7 @@ import com.spotify.mobius.test.UpdateSpec.assertThatNext import junitparams.JUnitParamsRunner import org.junit.Test import org.junit.runner.RunWith +import org.simple.clinic.TestData import org.simple.clinic.appconfig.Country import org.simple.clinic.facility.FacilityConfig import org.simple.clinic.medicalhistory.Answer.No @@ -19,14 +20,17 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithHype import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment import org.simple.clinic.patient.OngoingNewPatientEntry -import org.simple.clinic.TestData import java.util.UUID @RunWith(JUnitParamsRunner::class) class NewMedicalHistoryUpdateTest { private val country = TestData.country(isoCountryCode = Country.INDIA) - private val defaultModel = NewMedicalHistoryModel.default(country, false) + private val defaultModel = NewMedicalHistoryModel.default( + country = country, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false + ) private val facilityWithDiabetesManagementEnabled = TestData.facility( uuid = UUID.fromString("3c7bc1c8-1bb6-4c3a-b6d0-52700bdaac5c"), facilityConfig = FacilityConfig( @@ -224,7 +228,11 @@ class NewMedicalHistoryUpdateTest { @Test fun `when save is clicked and patient is diagnosed with hypertension and ongoing hypertension treatment question is not answered and selected country is not india, then register patient`() { val bangladesh = TestData.country(isoCountryCode = Country.BANGLADESH) - val model = NewMedicalHistoryModel.default(country = bangladesh, false) + val model = NewMedicalHistoryModel.default( + country = bangladesh, + showIsSmokingQuestion = false, + showSmokelessTobaccoQuestion = false + ) .ongoingPatientEntryLoaded(patientEntry) .currentFacilityLoaded(facilityWithDiabetesManagementEnabled) .answerChanged(DiagnosedWithHypertension, Yes) diff --git a/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt b/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt index 558bcc6d24f..9dc967eec31 100644 --- a/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/summary/PatientSummaryUpdateTest.kt @@ -2535,11 +2535,12 @@ class PatientSummaryUpdateTest { fun `when smoking is answered, then update the smoking status`() { updateSpec .given(defaultModel) - .whenEvent(SmokingStatusAnswered( - isSmoker = No + .whenEvent(TobaccoUseAnswered( + isSmoker = No, + isUsingSmokelessTobacco = Yes )) .then(assertThatNext( - hasEffects(UpdateSmokingStatus(patientId = patientUuid, isSmoker = No)), + hasEffects(UpdateTobaccoUse(patientId = patientUuid, isSmoker = No, isUsingSmokelessTobacco = Yes)), hasNoModel() )) } diff --git a/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryLogicTest.kt b/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryLogicTest.kt index 5211551aedb..ccb402f62ce 100644 --- a/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryLogicTest.kt +++ b/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryLogicTest.kt @@ -30,6 +30,7 @@ import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.HasHadAStroke import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnDiabetesTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsOnHypertensionTreatment import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsSmoking +import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.IsUsingSmokelessTobacco import org.simple.clinic.medicalhistory.MedicalHistoryRepository import org.simple.clinic.util.TestUtcClock import org.simple.clinic.util.randomMedicalHistoryAnswer @@ -99,6 +100,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).hideDiagnosisView() verify(ui).populateMedicalHistory(medicalHistory) verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -143,6 +145,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(medicalHistory) verify(ui).showDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -158,6 +161,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(medicalHistory) verify(ui).hideDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -177,6 +181,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(updatedMedicalHistory) verify(ui).showDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -196,6 +201,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(updatedMedicalHistory) verify(ui).showDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -215,6 +221,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(updatedMedicalHistory) verify(ui).showDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -234,6 +241,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(updatedMedicalHistory) verify(ui).showDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -253,6 +261,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(updatedMedicalHistory) verify(ui).showDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -272,6 +281,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(updatedMedicalHistory) verify(ui).showDiagnosisView() verify(ui).showCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -287,6 +297,7 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(medicalHistory) verify(ui).hideDiagnosisView() verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -302,6 +313,39 @@ class MedicalHistorySummaryLogicTest { verify(ui).populateMedicalHistory(medicalHistory) verify(ui).hideDiagnosisView() verify(ui).showCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() + verifyNoMoreInteractions(ui) + } + + @Test + fun `when show smokeless tobacco question is disabled, then hide the show smokeless tobacco question view`() { + // given + whenever(medicalHistoryRepository.historyForPatientOrDefault(medicalHistoryUuid, patientUuid)) doReturn Observable.just(medicalHistory) + + // when + setupController(showSmokelessTobaccoQuestion = false) + + // then + verify(ui).populateMedicalHistory(medicalHistory) + verify(ui).hideDiagnosisView() + verify(ui).hideCurrentSmokerQuestion() + verify(ui).hideSmokelessTobaccoQuestion() + verifyNoMoreInteractions(ui) + } + + @Test + fun `when show smokeless tobacco question is enabled, then show the show smokeless tobacco question view`() { + // given + whenever(medicalHistoryRepository.historyForPatientOrDefault(medicalHistoryUuid, patientUuid)) doReturn Observable.just(medicalHistory) + + // when + setupController(showCurrentSmokerQuestion = true, showSmokelessTobaccoQuestion = true) + + // then + verify(ui).populateMedicalHistory(medicalHistory) + verify(ui).hideDiagnosisView() + verify(ui).showCurrentSmokerQuestion() + verify(ui).showSmokelessTobaccoQuestion() verifyNoMoreInteractions(ui) } @@ -325,6 +369,7 @@ class MedicalHistorySummaryLogicTest { private fun setupController( facility: Facility = facilityWithDiabetesManagementDisabled, showCurrentSmokerQuestion: Boolean = false, + showSmokelessTobaccoQuestion: Boolean = false, ) { val effectHandler = MedicalHistorySummaryEffectHandler( schedulers = TestSchedulersProvider.trampoline(), @@ -337,7 +382,7 @@ class MedicalHistorySummaryLogicTest { val uiRenderer = MedicalHistorySummaryUiRenderer(ui) testFixture = MobiusTestFixture( events = events.ofType(), - defaultModel = MedicalHistorySummaryModel.create(patientUuid, showCurrentSmokerQuestion), + defaultModel = MedicalHistorySummaryModel.create(patientUuid, showCurrentSmokerQuestion, showSmokelessTobaccoQuestion), init = MedicalHistorySummaryInit(), update = MedicalHistorySummaryUpdate(), effectHandler = effectHandler.build(), diff --git a/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUpdateTest.kt b/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUpdateTest.kt index 70928282f19..7cd21d09b05 100644 --- a/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUpdateTest.kt +++ b/app/src/test/java/org/simple/clinic/summary/medicalhistory/MedicalHistorySummaryUpdateTest.kt @@ -5,9 +5,9 @@ import com.spotify.mobius.test.NextMatchers.hasModel import com.spotify.mobius.test.UpdateSpec import com.spotify.mobius.test.UpdateSpec.assertThatNext import org.junit.Test +import org.simple.clinic.TestData import org.simple.clinic.medicalhistory.Answer import org.simple.clinic.medicalhistory.MedicalHistoryQuestion.DiagnosedWithHypertension -import org.simple.clinic.TestData import java.util.UUID class MedicalHistorySummaryUpdateTest { @@ -28,7 +28,7 @@ class MedicalHistorySummaryUpdateTest { isSmoking = Answer.No ) val medicalHistoryLoadedModel = MedicalHistorySummaryModel - .create(patientUuid, true) + .create(patientUuid, showIsSmokingQuestion = true, showSmokelessTobaccoQuestion = true) .medicalHistoryLoaded(medicalHistory) val updatedMedicalHistory = medicalHistory diff --git a/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt b/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt index 1a6629978b8..df868f8e539 100644 --- a/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt +++ b/app/src/testFixtures/kotlin/org/simple/clinic/TestData.kt @@ -760,6 +760,7 @@ object TestData { isOnDiabetesTreatment: Answer = randomMedicalHistoryAnswer(), hasDiabetes: Answer = randomMedicalHistoryAnswer(), isSmoking: Answer = randomMedicalHistoryAnswer(), + isUsingSmokelessTobacco: Answer = randomMedicalHistoryAnswer(), cholesterol: Float? = 400f, syncStatus: SyncStatus = randomOfEnum(SyncStatus::class), createdAt: Instant = Instant.now(), @@ -777,6 +778,7 @@ object TestData { hasHadKidneyDisease = hasHadKidneyDisease, diagnosedWithDiabetes = hasDiabetes, isSmoking = isSmoking, + isUsingSmokelessTobacco = isUsingSmokelessTobacco, cholesterol = cholesterol, syncStatus = syncStatus, createdAt = createdAt, @@ -797,6 +799,7 @@ object TestData { isOnDiabetesTreatment: Answer = randomMedicalHistoryAnswer(), hasDiabetes: Answer = randomMedicalHistoryAnswer(), isSmoking: Answer = randomMedicalHistoryAnswer(), + isUsingSmokelessTobacco: Answer = randomMedicalHistoryAnswer(), cholesterol: Float? = 400f, createdAt: Instant = Instant.now(), updatedAt: Instant = Instant.now(), @@ -814,6 +817,7 @@ object TestData { hasDiabetes = hasDiabetes, hasHypertension = diagnosedWithHypertension, isSmoking = isSmoking, + isUsingSmokelessTobacco = isUsingSmokelessTobacco, cholesterol = cholesterol, createdAt = createdAt, updatedAt = updatedAt, diff --git a/build.gradle.kts b/build.gradle.kts index b13ef7c9ad4..e177aa648f7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { buildscript { extra.apply { set("compileSdkVersion", 35) - set("minSdkVersion", 21) + set("minSdkVersion", 26) set("targetSdkVersion", 35) } diff --git a/common-ui/src/main/res/color-v23/answer_chip_background.xml b/common-ui/src/main/res/color-v23/answer_chip_background.xml deleted file mode 100644 index 387fdcb9b37..00000000000 --- a/common-ui/src/main/res/color-v23/answer_chip_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/common-ui/src/main/res/color-v23/answer_chip_text.xml b/common-ui/src/main/res/color-v23/answer_chip_text.xml deleted file mode 100644 index 710cc8549d1..00000000000 --- a/common-ui/src/main/res/color-v23/answer_chip_text.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/common-ui/src/main/res/color-v23/color_on_surface_11.xml b/common-ui/src/main/res/color-v23/color_on_surface_11.xml deleted file mode 100644 index 12b5fe4c1ce..00000000000 --- a/common-ui/src/main/res/color-v23/color_on_surface_11.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/common-ui/src/main/res/color-v23/color_on_surface_34.xml b/common-ui/src/main/res/color-v23/color_on_surface_34.xml deleted file mode 100644 index edf4740f774..00000000000 --- a/common-ui/src/main/res/color-v23/color_on_surface_34.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/common-ui/src/main/res/color-v23/color_on_surface_67.xml b/common-ui/src/main/res/color-v23/color_on_surface_67.xml deleted file mode 100644 index 7616f5c03e9..00000000000 --- a/common-ui/src/main/res/color-v23/color_on_surface_67.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/common-ui/src/main/res/color-v23/outline_btn_stroke_color_selector.xml b/common-ui/src/main/res/color-v23/outline_btn_stroke_color_selector.xml deleted file mode 100644 index 36684e24083..00000000000 --- a/common-ui/src/main/res/color-v23/outline_btn_stroke_color_selector.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/common-ui/src/main/res/color-v23/pin_edittext_underline.xml b/common-ui/src/main/res/color-v23/pin_edittext_underline.xml deleted file mode 100644 index 41865e5c876..00000000000 --- a/common-ui/src/main/res/color-v23/pin_edittext_underline.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/common-ui/src/main/res/color/answer_chip_background.xml b/common-ui/src/main/res/color/answer_chip_background.xml index 96dd6266aa1..387fdcb9b37 100644 --- a/common-ui/src/main/res/color/answer_chip_background.xml +++ b/common-ui/src/main/res/color/answer_chip_background.xml @@ -1,5 +1,5 @@ - - + + diff --git a/common-ui/src/main/res/color/answer_chip_text.xml b/common-ui/src/main/res/color/answer_chip_text.xml index a6e18bbed70..710cc8549d1 100644 --- a/common-ui/src/main/res/color/answer_chip_text.xml +++ b/common-ui/src/main/res/color/answer_chip_text.xml @@ -1,5 +1,5 @@ - - + + diff --git a/common-ui/src/main/res/color/color_on_surface_11.xml b/common-ui/src/main/res/color/color_on_surface_11.xml index e544123f7cc..12b5fe4c1ce 100644 --- a/common-ui/src/main/res/color/color_on_surface_11.xml +++ b/common-ui/src/main/res/color/color_on_surface_11.xml @@ -1,4 +1,4 @@ - + - + diff --git a/common-ui/src/main/res/color/color_on_surface_34.xml b/common-ui/src/main/res/color/color_on_surface_34.xml index 2add02a1457..edf4740f774 100644 --- a/common-ui/src/main/res/color/color_on_surface_34.xml +++ b/common-ui/src/main/res/color/color_on_surface_34.xml @@ -1,4 +1,4 @@ - + - + diff --git a/common-ui/src/main/res/color/color_on_surface_67.xml b/common-ui/src/main/res/color/color_on_surface_67.xml index b167015344f..7616f5c03e9 100644 --- a/common-ui/src/main/res/color/color_on_surface_67.xml +++ b/common-ui/src/main/res/color/color_on_surface_67.xml @@ -1,4 +1,4 @@ - + - + diff --git a/common-ui/src/main/res/color/outline_btn_stroke_color_selector.xml b/common-ui/src/main/res/color/outline_btn_stroke_color_selector.xml index de37af3aa64..36684e24083 100644 --- a/common-ui/src/main/res/color/outline_btn_stroke_color_selector.xml +++ b/common-ui/src/main/res/color/outline_btn_stroke_color_selector.xml @@ -1,5 +1,5 @@ - + - - + + diff --git a/common-ui/src/main/res/color/pin_edittext_underline.xml b/common-ui/src/main/res/color/pin_edittext_underline.xml index 120ba8605d1..41865e5c876 100644 --- a/common-ui/src/main/res/color/pin_edittext_underline.xml +++ b/common-ui/src/main/res/color/pin_edittext_underline.xml @@ -1,5 +1,5 @@ - + - - + + diff --git a/gradle.properties b/gradle.properties index 4b65988b86c..f868c5eb73f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,3 +47,7 @@ disableScreenshot=false allowRootedDevice=true maestroTests=false org.gradle.unsafe.configuration-cache=true + +# This is a workaround for https://github.com/square/moshi/issues/1874#issuecomment-2827552256 +# which causes a KSP2 crash when using @JsonQualifier +ksp.useKSP2=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 837a852be4f..996410f87a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.8.2" +agp = "8.12.0" androidx-cameraView = "1.4.2" androidx-camera = "1.4.2" @@ -12,21 +12,23 @@ androidx-lifecycle = "2.8.7" androidx-activity = "1.10.1" chucker = "4.2.0" -dagger = "2.56.2" +dagger = "2.57" -kotlin = "2.1.20" +kotlin = "2.2.0" + +ksp = "2.2.0-2.0.2" ktlint = "0.36.0" -lint = "31.9.2" +lint = "31.12.0" mobius = "2.1.1" moshi = "1.15.2" -okhttp = "4.12.0" +okhttp = "5.1.0" -retrofit = "2.11.0" +retrofit = "3.0.0" room-metadataGenerator = "2.1.0" @@ -40,9 +42,7 @@ androidx-compose-bom = "2025.07.00" composeThemeAdapter = "0.36.0" -ksp = "2.1.20-1.0.31" - -sqlCipher = "4.9.0" +sqlCipher = "4.10.0" [libraries] android-desugaring = "com.android.tools:desugar_jdk_libs:2.1.5" @@ -51,13 +51,13 @@ androidx-annotation-annotation = "androidx.annotation:annotation:1.9.1" # When bumping this dependency verify whether `org.simple.clinic.feature.Feature.ChangeLanguage` # can be enabled again. -androidx-appcompat = "androidx.appcompat:appcompat:1.7.0" +androidx-appcompat = "androidx.appcompat:appcompat:1.7.1" androidx-archCoreTesting = "androidx.arch.core:core-testing:2.2.0" androidx-cardview = "androidx.cardview:cardview:1.0.0" androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.1" androidx-core-ktx = "androidx.core:core-ktx:1.16.0" -androidx-fragment = "androidx.fragment:fragment-ktx:1.8.6" +androidx-fragment = "androidx.fragment:fragment-ktx:1.8.8" androidx-preference = "androidx.preference:preference:1.2.1" androidx-recyclerview = "androidx.recyclerview:recyclerview:1.4.0" androidx-viewpager2 = "androidx.viewpager2:viewpager2:1.1.0" @@ -94,7 +94,7 @@ androidx-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmod androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle" } -asm = "org.ow2.asm:asm:9.7.1" +asm = "org.ow2.asm:asm:9.8" chucker = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" } chucker-no-op = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" } @@ -107,8 +107,8 @@ edittext-pinentry = "com.alimuzaffar.lib:pinentryedittext:2.0.6" faker = "com.github.blocoio:faker:2.0.4" -firebase-config = "com.google.firebase:firebase-config:22.1.2" -firebase-analytics = "com.google.firebase:firebase-analytics-ktx:22.4.0" +firebase-config = "com.google.firebase:firebase-config:23.0.0" +firebase-analytics = "com.google.firebase:firebase-analytics-ktx:22.5.0" gson = "com.google.code.gson:gson:2.13.1" @@ -116,7 +116,7 @@ itemanimators = "com.mikepenz:itemanimators:1.1.0" itext7 = "com.itextpdf:itext7-core:7.2.5" -jackson-core = "com.fasterxml.jackson.core:jackson-core:2.18.2" +jackson-core = "com.fasterxml.jackson.core:jackson-core:2.19.2" jbcrypt = "org.mindrot:jbcrypt:0.4" @@ -138,7 +138,7 @@ lint-tests = { module = "com.android.tools.lint:lint-tests", version.ref = "lint #noinspection GradleDependency logback-classic = "ch.qos.logback:logback-classic:1.2.11" -lottie = "com.airbnb.android:lottie-compose:6.6.2" +lottie = "com.airbnb.android:lottie-compose:6.6.7" material = "com.google.android.material:material:1.12.0"