Skip to content

Commit

Permalink
Merge branch 'refs/heads/main' into analog_clock
Browse files Browse the repository at this point in the history
# Conflicts:
#	app/src/main/res/values/strings.xml
  • Loading branch information
SuhasDissa committed Apr 25, 2024
2 parents 4885362 + acda51c commit 89f7017
Show file tree
Hide file tree
Showing 46 changed files with 1,027 additions and 629 deletions.
9 changes: 6 additions & 3 deletions app/src/main/java/com/bnyro/clock/App.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package com.bnyro.clock

import android.app.Application
import com.bnyro.clock.data.database.DatabaseHolder
import com.bnyro.clock.data.database.AppDatabase
import com.bnyro.clock.util.NotificationHelper
import com.bnyro.clock.util.Preferences

class App : Application() {
lateinit var container: AppContainer
private val database by lazy { AppDatabase.getDatabase(this) }
override fun onCreate() {
super.onCreate()

DatabaseHolder.init(this)
Preferences.init(this)
NotificationHelper.createNotificationChannels(this)
NotificationHelper().createNotificationChannels(this)

container = AppContainer(database)
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/bnyro/clock/AppContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.bnyro.clock

import com.bnyro.clock.data.database.AppDatabase
import com.bnyro.clock.domain.repository.AlarmRepository
import com.bnyro.clock.domain.repository.TimezoneRepository

class AppContainer(database: AppDatabase) {
val alarmRepository: AlarmRepository by lazy {
AlarmRepository(database.alarmsDao())
}
val timezoneRepository: TimezoneRepository by lazy {
TimezoneRepository(database.timeZonesDao())
}
}
70 changes: 70 additions & 0 deletions app/src/main/java/com/bnyro/clock/data/database/AppDatabase.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package com.bnyro.clock.data.database

import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.DeleteColumn
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.bnyro.clock.data.database.dao.AlarmsDao
import com.bnyro.clock.data.database.dao.Converters
import com.bnyro.clock.data.database.dao.TimeZonesDao
import com.bnyro.clock.domain.model.Alarm
import com.bnyro.clock.domain.model.TimeZone
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

@Database(
entities = [TimeZone::class, Alarm::class],
Expand All @@ -33,4 +40,67 @@ abstract class AppDatabase : RoomDatabase() {

abstract fun timeZonesDao(): TimeZonesDao
abstract fun alarmsDao(): AlarmsDao

companion object {
@Volatile
private var INSTANCE: AppDatabase? = null

private const val dbName = "com.bnyro.clock"
lateinit var instance: AppDatabase

private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE alarms ADD COLUMN label TEXT DEFAULT NULL"
)
db.execSQL(
"ALTER TABLE alarms ADD COLUMN soundUri TEXT DEFAULT NULL"
)
}
}

private val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE alarms ADD COLUMN soundName TEXT DEFAULT NULL"
)
}
}

private val MIGRATION_7_8 = object : Migration(7, 8) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE timeZones RENAME TO temp_table")
db.execSQL("CREATE TABLE IF NOT EXISTS `timeZones` (`zoneId` TEXT NOT NULL, `zoneName` TEXT NOT NULL, `countryName` TEXT NOT NULL, `offset` INTEGER NOT NULL, `key` TEXT NOT NULL, PRIMARY KEY(`key`))")
db.execSQL("INSERT INTO timeZones (key, zoneId, offset, zoneName, countryName) SELECT name, name, offset, displayName, countryName FROM temp_table")
db.execSQL("DROP TABLE temp_table")

postMigrate7to8()
}
}

private fun postMigrate7to8() {
CoroutineScope(Dispatchers.IO).launch {
val zones = instance.timeZonesDao().getAll().map {
it.copy(key = arrayOf(it.zoneId, it.zoneName, it.countryName).joinToString(","))
}
instance.timeZonesDao().clear()
instance.timeZonesDao().insertAll(*zones.toTypedArray())
}
}

fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room
.databaseBuilder(context, AppDatabase::class.java, dbName)
.addMigrations(
MIGRATION_1_2,
MIGRATION_3_4,
MIGRATION_7_8
)
.build()
INSTANCE = instance
instance
}
}
}
}
61 changes: 0 additions & 61 deletions app/src/main/java/com/bnyro/clock/data/database/DatabaseHolder.kt

This file was deleted.

73 changes: 73 additions & 0 deletions app/src/main/java/com/bnyro/clock/domain/model/Permission.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.bnyro.clock.domain.model

import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.net.toUri
import com.bnyro.clock.BuildConfig
import com.bnyro.clock.R

sealed class Permission(
@StringRes
val titleRes: Int,
@StringRes
val descriptionRes: Int,
@DrawableRes
val iconRes: Int
) {
abstract fun hasPermission(context: Context): Boolean
abstract fun requestPermission(activity: Activity)

object NotificationPermission :
Permission(
titleRes = R.string.notification_permission_title,
descriptionRes = R.string.notification_permission_description,
iconRes = R.drawable.ic_alarm
) {
override fun hasPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return true
return ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
}

override fun requestPermission(activity: Activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
1
)
}
}

object AlarmPermission : Permission(
titleRes = R.string.alarm_permission_title,
descriptionRes = R.string.alarm_permission_description,
iconRes = R.drawable.ic_alarm
) {

override fun hasPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
return alarmManager.canScheduleExactAlarms()
}

override fun requestPermission(activity: Activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = "package:${BuildConfig.APPLICATION_ID}".toUri()
}
activity.startActivity(intent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.bnyro.clock.domain.repository

import com.bnyro.clock.data.database.dao.AlarmsDao
import com.bnyro.clock.domain.model.Alarm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext

class AlarmRepository(private val alarmsDao: AlarmsDao) {
suspend fun addAlarm(alarm: Alarm) = withContext(Dispatchers.IO) { alarmsDao.insert(alarm) }

suspend fun getAlarms(): List<Alarm> = withContext(Dispatchers.IO) { alarmsDao.getAll() }
fun getAlarmsStream(): Flow<List<Alarm>> = alarmsDao.getAllStream()

suspend fun getAlarmById(id: Long): Alarm =
withContext(Dispatchers.IO) { alarmsDao.findById(id) }

suspend fun deleteAlarm(alarm: Alarm) = withContext(Dispatchers.IO) { alarmsDao.delete(alarm) }

suspend fun updateAlarm(alarm: Alarm) = withContext(Dispatchers.IO) { alarmsDao.update(alarm) }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.bnyro.clock.domain.repository

import android.content.Context
import com.bnyro.clock.data.database.dao.TimeZonesDao
import com.bnyro.clock.domain.model.CountryTimezone
import com.bnyro.clock.domain.model.TimeZone
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.util.Calendar

class TimezoneRepository(private val timeZonesDao: TimeZonesDao) {
suspend fun getTimezones(): List<TimeZone> =
withContext(Dispatchers.IO) { timeZonesDao.getAll() }

fun getTimezonesStream(): Flow<List<TimeZone>> = timeZonesDao.getAllStream()
suspend fun replaceAll(vararg timeZone: TimeZone) = withContext(Dispatchers.IO) {
timeZonesDao.clear()
timeZonesDao.insertAll(*timeZone)
}

suspend fun delete(timeZone: TimeZone) =
withContext(Dispatchers.IO) { timeZonesDao.delete(timeZone) }

fun getTimezonesForCountries(context: Context): List<TimeZone> {
val countryTimezones = getCountryTimezones(context)
return getTimezonesForCountries(countryTimezones)
}

private fun getTimezonesForCountries(zoneIds: List<CountryTimezone>): List<TimeZone> {
return zoneIds.map {
val zone = java.util.TimeZone.getTimeZone(it.zoneId)
val zoneKey = arrayOf(it.zoneId, it.zoneName, it.countryName).joinToString(",")
val offset = zone.getOffset(Calendar.getInstance().timeInMillis)
TimeZone(zoneKey, it.zoneId, offset, it.zoneName, it.countryName)
}.sortedBy { it.zoneName }
}

private fun getCountryTimezones(context: Context): List<CountryTimezone> {
val tzData =
context.resources.assets.open("tz_data.json").bufferedReader()
.use { it.readText() }

val json = Json { ignoreUnknownKeys = true }
return json.decodeFromString(tzData)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.bnyro.clock.domain.usecase

import android.content.Context
import com.bnyro.clock.domain.model.Alarm
import com.bnyro.clock.domain.repository.AlarmRepository
import com.bnyro.clock.util.AlarmHelper

class CreateUpdateDeleteAlarmUseCase(
private val context: Context,
private val alarmRepository: AlarmRepository
) {
suspend fun createAlarm(alarm: Alarm) {
alarm.enabled = true
AlarmHelper.enqueue(context, alarm)
alarmRepository.addAlarm(alarm)
}

suspend fun updateAlarm(alarm: Alarm) {
AlarmHelper.enqueue(context, alarm)
alarmRepository.updateAlarm(alarm)
}

suspend fun deleteAlarm(alarm: Alarm) {
AlarmHelper.cancel(context, alarm)
alarmRepository.deleteAlarm(alarm)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import com.bnyro.clock.presentation.screens.timer.model.TimerModel

@Composable
fun HomeNavContainer(
onNavigate: (NavRoutes) -> Unit,
onNavigate: (route: String) -> Unit,
initialTab: HomeRoutes,
clockModel: ClockModel,
timerModel: TimerModel,
Expand Down
Loading

0 comments on commit 89f7017

Please sign in to comment.