Skip to content

Commit

Permalink
Merge pull request #1 from coxtor/silentAlarm
Browse files Browse the repository at this point in the history
Silent alarm
  • Loading branch information
coxtor authored Oct 29, 2019
2 parents 6c9e1a3 + 96de166 commit 1375574
Show file tree
Hide file tree
Showing 93 changed files with 1,483 additions and 271 deletions.
875 changes: 674 additions & 201 deletions LICENSE

Large diffs are not rendered by default.

58 changes: 39 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
# Simple Clock
# Simple Clock with Broadcast events
This is a fork of the SimpleMobileTools Simple Clock application. I have added broadcast events to alarms for home automation purposes, the broadcasts are called silent alarms.

A silent alarm is a non audible alarm, represented as a broadcast to the system which can be processed by other applications such as Tasker, Easer, Broadcast to mqtt or other tools for scenarios such as home automation.

Following broadcasts are emitted:
- com.simplemobiletools.ALARM_SET : when a new Alarm is created.
- com.simplemobiletools.ALARM_GOING_TO_RING : when a silent alarm is triggered - only if the main alarm is enabled.
- com.simplemobiletools.ALARM_IS_ACTIVE when an audible alarm is active.
- com.simplemobiletools.ALARM_SNOOZED when an audible alarm has been snoozed.
- com.simplemobiletools.ALARM_DISABLED when an audible alarm has been disabled.

All broadcasts contain the same payload with the attributes:
- label
- hours
- minutes
- days
- id




## Example usage scenarios
Forward broadcasts and the respective payload to an mqtt broker and have your smart home such as home assistant react to your alarms dynamically with automations such as:

- Trigger a wakeup light before the audible alarm rings
- Trigger a wakeup routine once the audible alarm rings
- Have automated actions if you use the snooze button to often :-)
- ...

## Disclaimer

- I am not responsible, if the app messes up and you are late for anything ;)


# Original Simple Clock readme
<img alt="Logo" src="app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" width="80" />

The app has multiple functions related to timing.
Expand All @@ -21,23 +56,8 @@ This app is just one piece of a bigger series of apps. You can find the rest of
<a href='https://f-droid.org/packages/com.simplemobiletools.clock'><img src='https://simplemobiletools.com/assets/images/f-droid.png' alt='Get it on F-Droid' height='45' /></a>

<div style="display:flex;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app.png" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.png" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.png" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_1.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.jpg" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.jpg" width="30%">
</div>

License
-------
Copyright 2018-present SimpleMobileTools

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
22 changes: 12 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
//def keystorePropertiesFile = rootProject.file("keystore.properties")
//def keystoreProperties = new Properties()
//keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
compileSdkVersion 28
Expand All @@ -22,10 +22,10 @@ android {

signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
// keyAlias keystoreProperties['keyAlias']
// keyPassword keystoreProperties['keyPassword']
// // storeFile file(keystoreProperties['storeFile'])
// storePassword keystoreProperties['storePassword']
}
}

Expand All @@ -34,9 +34,9 @@ android {
applicationIdSuffix ".debug"
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
// minifyEnabled true
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// signingConfig signingConfigs.release
}
}

Expand All @@ -52,7 +52,9 @@ android {

dependencies {
implementation 'com.simplemobiletools:commons:5.18.7'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'com.shawnlin:number-picker:2.4.6'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.media.MediaPlayer
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.animation.AnimationUtils
Expand Down Expand Up @@ -118,12 +119,32 @@ class ReminderActivity : SimpleActivity() {
if (!didVibrate) {
reminder_draggable.performHapticFeedback()
didVibrate = true
Intent().also { intent ->
intent.setAction("com.simplemobiletools.ALARM_DISABLED")
intent.putExtra("hours", alarm?.timeInMinutes!! / 60)
intent.putExtra("minutes", alarm?.timeInMinutes!! % 60)
intent.putExtra("days", alarm?.days)
intent.putExtra("id", alarm?.id)
intent.putExtra("label", alarm?.label)
sendBroadcast(intent)
Log.i("SENT_BROADCAST", "ALARM_DISABLED")
}
finishActivity()
}
} else if (reminder_draggable.x <= minDragX + 50f) {
if (!didVibrate) {
reminder_draggable.performHapticFeedback()
didVibrate = true
Intent().also { intent ->
intent.setAction("com.simplemobiletools.ALARM_SNOOZED")
intent.putExtra("hours", alarm?.timeInMinutes!! / 60)
intent.putExtra("minutes", alarm?.timeInMinutes!! % 60)
intent.putExtra("days", alarm?.days)
intent.putExtra("id", alarm?.id)
intent.putExtra("label", alarm?.label)
sendBroadcast(intent)
Log.i("SENT_BROADCAST", "ALARM_SNOOZED")
}
snoozeAlarm()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class AlarmsAdapter(activity: SimpleActivity, var alarms: ArrayList<Alarm>, val
alarm_label.setTextColor(textColor)
alarm_label.beVisibleIf(alarm.label.isNotEmpty())

var childrenNo = context.dbHelper.getChildAlarms(alarm.id).size
alarm_children.setTextColor(textColor)
alarm_children.text = ""
if(childrenNo > 0)
alarm_children.text = resources.getString(R.string.has_children, childrenNo)

alarm_switch.isChecked = alarm.isEnabled
alarm_switch.setColors(textColor, adjustedPrimaryColor, backgroundColor)
alarm_switch.setOnCheckedChangeListener { buttonView, isChecked ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.simplemobiletools.clock.adapters

import android.app.TimePickerDialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.extensions.config
import com.simplemobiletools.clock.extensions.getFormattedTime
import com.simplemobiletools.clock.models.Alarm
import com.simplemobiletools.commons.extensions.getDialogTheme
import kotlinx.android.synthetic.main.item_child_alarm.view.*
import com.simplemobiletools.commons.extensions.toast
import kotlin.math.abs

class ChildAlarmsAdapter (var items: List<Alarm>, var shownItems: List<Int>, val parentAlarm: Alarm, val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

var alarmsToDelete = ArrayList<Alarm>()

override fun getItemCount(): Int {
return shownItems.size
}

fun updateItems(newItems: List<Alarm>, newShownItems: List<Int>) {
items = newItems
shownItems = newShownItems
notifyDataSetChanged()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ViewHolder (LayoutInflater.from(context).inflate(R.layout.item_child_alarm, parent, false))
}


override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var childAlarm : Alarm = items.get(shownItems.get(position))
var time = context.getFormattedTime(childAlarm.timeInMinutes *60, false, true)
holder.itemView.tv_child_alarm_info.setTextColor(context!!.config.textColor)
holder.itemView.tv_child_alarm_info.setText(time)
holder.itemView.switch_toggle_silent_alarm.isChecked = childAlarm.isEnabled

val timeSetListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
childAlarm.timeInMinutes = hourOfDay * 60 + minute
var time = context.getFormattedTime(childAlarm.timeInMinutes *60, false, true)
holder.itemView.tv_child_alarm_info.setText(time)
}

holder.itemView.tv_child_alarm_info.setOnClickListener {
if(parentAlarm.timeInMinutes != childAlarm.timeInMinutes)
TimePickerDialog(context, context.getDialogTheme(), timeSetListener, childAlarm.timeInMinutes / 60, childAlarm.timeInMinutes % 60, context.config.use24HourFormat).show()
else
TimePickerDialog(context, context.getDialogTheme(), timeSetListener, parentAlarm.timeInMinutes / 60, parentAlarm.timeInMinutes % 60, context.config.use24HourFormat).show()
}

holder.itemView.ib_delete_child_alarm.setOnClickListener {
items.get(shownItems.get(position)).isEnabled = false
alarmsToDelete.add(items.get(shownItems.get(position)))
updateItems(items, shownItems.minus(position))
notifyDataSetChanged()
}


holder.itemView.switch_toggle_silent_alarm.setOnClickListener {
items[position].isEnabled = !items[position].isEnabled
}


}

fun addItem(alarm: Alarm) {
updateItems(items.plus(alarm), shownItems.plus(shownItems.lastIndex+1))
//updateItems(items.plus(alarm), items.plus(alarm).indices.toList())
}

}

class ViewHolder (view: View) : RecyclerView.ViewHolder(view)
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package com.simplemobiletools.clock.dialogs
import android.app.TimePickerDialog
import android.graphics.drawable.Drawable
import android.media.AudioManager
import androidx.appcompat.app.AlertDialog
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.activities.SimpleActivity
import com.simplemobiletools.clock.adapters.ChildAlarmsAdapter
import com.simplemobiletools.clock.extensions.*
import com.simplemobiletools.clock.helpers.PICK_AUDIO_FILE_INTENT_ID
import com.simplemobiletools.clock.models.Alarm
Expand All @@ -15,23 +18,29 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ALARM_SOUND_TYPE_ALARM
import com.simplemobiletools.commons.models.AlarmSound
import kotlinx.android.synthetic.main.dialog_edit_alarm.view.*
import kotlin.collections.ArrayList

class EditAlarmDialog(val activity: SimpleActivity, val alarm: Alarm, val callback: (alarmId: Int) -> Unit) {
private val view = activity.layoutInflater.inflate(R.layout.dialog_edit_alarm, null)
private val textColor = activity.config.textColor

var childAlarmsAdapter: ChildAlarmsAdapter
private var cachedChildAlarms: ArrayList<Alarm> = arrayListOf()
init {
updateAlarmTime()

view.apply {
edit_alarm_time.setOnClickListener {
TimePickerDialog(context, context.getDialogTheme(), timeSetListener, alarm.timeInMinutes / 60, alarm.timeInMinutes % 60, context.config.use24HourFormat).show()
}

ib_silent_alarm_info.setOnClickListener {
val fm = activity.supportFragmentManager
val infoFragment = SilentAlarmInfoDialog()
infoFragment.show(fm,"SilentAlarmDialog")
}

edit_alarm_sound.colorLeftDrawable(textColor)
edit_alarm_sound.text = alarm.soundTitle
edit_alarm_sound.setOnClickListener {
SelectAlarmSoundDialog(activity, alarm.soundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID, ALARM_SOUND_TYPE_ALARM, true,
edit_alarm_sound.setOnClickListener { SelectAlarmSoundDialog(activity, alarm.soundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID, ALARM_SOUND_TYPE_ALARM, true,
onAlarmPicked = {
if (it != null) {
updateSelectedAlarmSound(it)
Expand All @@ -55,12 +64,24 @@ class EditAlarmDialog(val activity: SimpleActivity, val alarm: Alarm, val callba
edit_alarm_label_image.applyColorFilter(textColor)
edit_alarm_label.setText(alarm.label)

var rcChildAlarm = findViewById<RecyclerView>(R.id.rc_child_alarms)
rcChildAlarm.layoutManager = LinearLayoutManager(this.context)
childAlarmsAdapter = ChildAlarmsAdapter(ArrayList(activity.dbHelper.getChildAlarms(alarm.id)), ArrayList(activity.dbHelper.getChildAlarms(alarm.id)).indices.toList(), alarm, this.context)
cachedChildAlarms = ArrayList(childAlarmsAdapter.items)
rcChildAlarm.adapter = childAlarmsAdapter

button_add_child_alarm.setOnClickListener {
childAlarmsAdapter.addItem(Alarm(0, alarm.timeInMinutes, alarm.days, true, false, "No Sound", "silent", "Child", alarm.id, true))
}

val dayLetters = activity.resources.getStringArray(R.array.week_day_letters).toList() as ArrayList<String>
val dayIndexes = arrayListOf(0, 1, 2, 3, 4, 5, 6)
if (activity.config.isSundayFirst) {
dayIndexes.moveLastItemToFront()
}



dayIndexes.forEach {
val pow = Math.pow(2.0, it.toDouble()).toInt()
val day = activity.layoutInflater.inflate(R.layout.alarm_day, edit_alarm_days_holder, false) as TextView
Expand Down Expand Up @@ -95,18 +116,40 @@ class EditAlarmDialog(val activity: SimpleActivity, val alarm: Alarm, val callba
activity.toast(R.string.no_days_selected)
return@setOnClickListener
}

alarm.label = view.edit_alarm_label.value

var alarmId = alarm.id
if (alarm.id == 0) {

alarmId = activity.dbHelper.insertAlarm(alarm)
childAlarmsAdapter.items.forEach{
it.parentId = alarmId
it.days = alarm.days
activity.dbHelper.insertAlarm(it)
}

if (alarmId == -1) {
activity.toast(R.string.unknown_error_occurred)
activity.toast("Unknown error")
}
} else {
if (!activity.dbHelper.updateAlarm(alarm)) {
activity.toast(R.string.unknown_error_occurred)
activity.toast("Unknown error")
}

val alarmsToDelete = ArrayList(childAlarmsAdapter.alarmsToDelete)
val alarmsToProcess= ArrayList(childAlarmsAdapter.items).filter{ !alarmsToDelete.contains(it) }
activity.dbHelper.deleteAlarms(ArrayList( alarmsToDelete))
alarmsToProcess.forEach{
//Update days of childalarms
it.days = alarm.days

// disable child alarms if parent is disabled
if(!alarm.isEnabled)
it.isEnabled = false

if(activity.dbHelper.getAlarmWithId(it.id) == null)
activity.dbHelper.insertAlarm(it)
else
activity.dbHelper.updateAlarm(it)
}
}
callback(alarmId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.simplemobiletools.clock.dialogs

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.simplemobiletools.clock.R

class SilentAlarmInfoDialog : DialogFragment() {

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(it)
builder.setTitle(R.string.silent_alarm_dialog_title)
builder.setMessage(R.string.silent_alarm_info)
.setNeutralButton(R.string.ok, DialogInterface.OnClickListener{ dialog: DialogInterface?, which: Int -> })
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
Loading

0 comments on commit 1375574

Please sign in to comment.