diff --git a/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt b/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt index 01c104c97..1a9c9bc7a 100644 --- a/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt +++ b/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt @@ -1,16 +1,25 @@ package com.todoroo.astrid.service +import android.app.NotificationManager +import android.app.NotificationManager.INTERRUPTION_FILTER_ALL +import android.content.Context +import android.media.MediaPlayer import com.todoroo.andlib.utility.DateUtilities import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.data.Task +import dagger.hilt.android.qualifiers.ApplicationContext import org.tasks.data.GoogleTaskDao +import org.tasks.preferences.Preferences import timber.log.Timber import javax.inject.Inject class TaskCompleter @Inject internal constructor( - private val taskDao: TaskDao, - private val googleTaskDao: GoogleTaskDao) { - + @ApplicationContext private val context: Context, + private val taskDao: TaskDao, + private val googleTaskDao: GoogleTaskDao, + private val preferences: Preferences, + private val notificationManager: NotificationManager, +) { suspend fun setComplete(taskId: Long) = taskDao .fetch(taskId) @@ -43,5 +52,15 @@ class TaskCompleter @Inject internal constructor( } taskDao.save(task) } + if ( + tasks.size == 1 && + completionDate > 0 && + notificationManager.currentInterruptionFilter == INTERRUPTION_FILTER_ALL + ) { + preferences + .completionSound + ?.takeUnless { preferences.isCurrentlyQuietHours } + ?.let { MediaPlayer.create(context, it).start() } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/extensions/Context.kt b/app/src/main/java/org/tasks/extensions/Context.kt index 73cb53648..f45cb50cf 100644 --- a/app/src/main/java/org/tasks/extensions/Context.kt +++ b/app/src/main/java/org/tasks/extensions/Context.kt @@ -1,11 +1,13 @@ package org.tasks.extensions import android.content.ActivityNotFoundException +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.Intent.ACTION_VIEW import android.net.Uri import android.widget.Toast +import androidx.annotation.AnyRes import androidx.browser.customtabs.CustomTabsIntent import org.tasks.R @@ -46,4 +48,11 @@ object Context { fun Context.toast(text: String?, duration: Int = Toast.LENGTH_LONG) = text?.let { Toast.makeText(this, it, duration).show() } + + fun Context.getResourceUri(@AnyRes res: Int) = + Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(packageName) + .path(res.toString()) + .build() } diff --git a/app/src/main/java/org/tasks/injection/ApplicationModule.kt b/app/src/main/java/org/tasks/injection/ApplicationModule.kt index 0358b9fdb..0a7cec470 100644 --- a/app/src/main/java/org/tasks/injection/ApplicationModule.kt +++ b/app/src/main/java/org/tasks/injection/ApplicationModule.kt @@ -1,5 +1,6 @@ package org.tasks.injection +import android.app.NotificationManager import android.content.Context import com.todoroo.astrid.dao.Database import dagger.Module @@ -113,4 +114,8 @@ class ApplicationModule { fun providesCoroutineScope( @DefaultDispatcher defaultDispatcher: CoroutineDispatcher ): CoroutineScope = CoroutineScope(SupervisorJob() + defaultDispatcher) + + @Provides + fun providesNotificationManager(@ApplicationContext context: Context) = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/preferences/Preferences.kt b/app/src/main/java/org/tasks/preferences/Preferences.kt index 6b7d86b54..448af898d 100644 --- a/app/src/main/java/org/tasks/preferences/Preferences.kt +++ b/app/src/main/java/org/tasks/preferences/Preferences.kt @@ -10,6 +10,7 @@ import android.media.RingtoneManager import android.net.Uri import android.os.Binder import androidx.core.app.NotificationCompat +import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import androidx.preference.PreferenceManager import com.todoroo.andlib.utility.DateUtilities @@ -25,6 +26,7 @@ import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.billing.Purchase import org.tasks.data.TaskAttachment +import org.tasks.extensions.Context.getResourceUri import org.tasks.themes.ColorProvider import org.tasks.themes.ThemeBase import org.tasks.time.DateTime @@ -141,13 +143,25 @@ class Preferences @JvmOverloads constructor( } val ringtone: Uri? - get() { - val ringtone = getStringValue(R.string.p_rmd_ringtone) - ?: return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) - return if ("" == ringtone) { - null - } else Uri.parse(ringtone) + get() = getRingtone( + R.string.p_rmd_ringtone, + RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + ) + + val completionSound: Uri? + get() = getRingtone( + R.string.p_completion_ringtone, + context.getResourceUri(R.raw.long_rising_tone) + ) + + private fun getRingtone(pref: Int, default: Uri): Uri? { + val ringtone = getStringValue(pref) + return when { + ringtone == null -> default + ringtone.isNotBlank() -> ringtone.toUri() + else -> null } + } val isTrackingEnabled: Boolean get() = getBoolean(R.string.p_collect_statistics, true) diff --git a/app/src/main/java/org/tasks/preferences/fragments/Notifications.kt b/app/src/main/java/org/tasks/preferences/fragments/Notifications.kt index c2607bdf3..206db5310 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/Notifications.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/Notifications.kt @@ -9,6 +9,7 @@ import android.os.Bundle import android.os.PowerManager import android.provider.Settings import android.speech.tts.TextToSpeech +import androidx.core.net.toUri import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat @@ -21,6 +22,7 @@ import org.tasks.LocalBroadcastManager import org.tasks.R import org.tasks.activities.FilterSelectionActivity import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker +import org.tasks.extensions.Context.getResourceUri import org.tasks.injection.InjectingPreferenceFragment import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.Preferences @@ -37,6 +39,7 @@ private const val REQUEST_DEFAULT_REMIND = 10003 private const val REQUEST_BADGE_LIST = 10004 private const val REQUEST_CODE_ALERT_RINGTONE = 10005 private const val REQUEST_CODE_TTS_CHECK = 10006 +private const val REQUEST_CODE_COMPLETION_SOUND = 10007 @AndroidEntryPoint class Notifications : InjectingPreferenceFragment() { @@ -59,6 +62,7 @@ class Notifications : InjectingPreferenceFragment() { rescheduleNotificationsOnChange(true, R.string.p_bundle_notifications) initializeRingtonePreference() + initializeCompletionSoundPreference() initializeTimePreference(getDefaultRemindTimePreference()!!, REQUEST_DEFAULT_REMIND) initializeTimePreference(getQuietStartPreference()!!, REQUEST_QUIET_START) initializeTimePreference(getQuietEndPreference()!!, REQUEST_QUIET_END) @@ -175,19 +179,27 @@ class Notifications : InjectingPreferenceFragment() { } override fun onPreferenceTreeClick(preference: Preference?): Boolean = - if (preference!!.key == getString(R.string.p_rmd_ringtone)) { + when (preference!!.key) { + getString(R.string.p_rmd_ringtone), + getString(R.string.p_completion_ringtone) -> { val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER) - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION) + intent.putExtra( + RingtoneManager.EXTRA_RINGTONE_TYPE, + RingtoneManager.TYPE_NOTIFICATION + ) intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true) intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true) intent.putExtra( RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI ) - val existingValue: String? = preferences.getStringValue(R.string.p_rmd_ringtone) + val existingValue: String? = preferences.getStringValue(preference.key) if (existingValue != null) { if (existingValue.isEmpty()) { - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, null as Uri?) + intent.putExtra( + RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, + null as Uri? + ) } else { intent.putExtra( RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, @@ -200,11 +212,20 @@ class Notifications : InjectingPreferenceFragment() { Settings.System.DEFAULT_NOTIFICATION_URI ) } - startActivityForResult(intent, REQUEST_CODE_ALERT_RINGTONE) + startActivityForResult( + intent, + if (preference.key == getString(R.string.p_rmd_ringtone)) { + REQUEST_CODE_ALERT_RINGTONE + } else { + REQUEST_CODE_COMPLETION_SOUND + } + ) true - } else { + } + else -> { super.onPreferenceTreeClick(preference) } + } private fun getQuietStartPreference(): TimePreference? = getTimePreference(R.string.p_rmd_quietStart) @@ -227,29 +248,41 @@ class Notifications : InjectingPreferenceFragment() { } } - private fun initializeRingtonePreference() { + private fun initializeRingtonePreference() = + initializeRingtonePreference( + R.string.p_rmd_ringtone, + R.string.silent, + ) + + private fun initializeCompletionSoundPreference() = + initializeRingtonePreference( + R.string.p_completion_ringtone, + R.string.none, + requireContext().getResourceUri(R.raw.long_rising_tone) + ) + + private fun initializeRingtonePreference(pref: Int, noneRes: Int, default: Uri? = null) { val ringtoneChangedListener = Preference.OnPreferenceChangeListener { preference: Preference, value: Any? -> if ("" == value) { - preference.setSummary(R.string.silent) + preference.setSummary(noneRes) } else { - val ringtone = RingtoneManager.getRingtone( - context, - if (value == null) Settings.System.DEFAULT_NOTIFICATION_URI else Uri.parse( - value as String? - ) - ) - preference.summary = if (ringtone == null) "" else ringtone.getTitle(context) + val uri = + (value as? String?)?.toUri() + ?: default + ?: Settings.System.DEFAULT_RINGTONE_URI + preference.summary = if (uri == default) { + getString(R.string.settings_default) + } else { + RingtoneManager.getRingtone(context, uri).getTitle(context) + } } true } - val ringtoneKey = R.string.p_rmd_ringtone - val ringtonePreference: Preference = findPreference(ringtoneKey) + val ringtonePreference = findPreference(pref) ringtonePreference.onPreferenceChangeListener = ringtoneChangedListener - ringtoneChangedListener.onPreferenceChange( - ringtonePreference, - preferences.getStringValue(ringtoneKey) - ) + ringtoneChangedListener + .onPreferenceChange(ringtonePreference, preferences.getStringValue(pref)) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -264,6 +297,16 @@ class Notifications : InjectingPreferenceFragment() { } initializeRingtonePreference() } + REQUEST_CODE_COMPLETION_SOUND -> if (resultCode == RESULT_OK && data != null) { + val ringtone: Uri? = + data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + if (ringtone != null) { + preferences.setString(R.string.p_completion_ringtone, ringtone.toString()) + } else { + preferences.setString(R.string.p_completion_ringtone, "") + } + initializeCompletionSoundPreference() + } REQUEST_QUIET_START -> if (resultCode == RESULT_OK) { getQuietStartPreference()!!.handleTimePickerActivityIntent(data) } diff --git a/app/src/main/res/raw/long_rising_tone.m4a b/app/src/main/res/raw/long_rising_tone.m4a new file mode 100644 index 000000000..ef870a296 Binary files /dev/null and b/app/src/main/res/raw/long_rising_tone.m4a differ diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index bf65d1f65..2f4f24682 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -72,6 +72,7 @@ notification_ringtone + completion_ringtone notif_default_reminder diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f4c8a2ac..841b84b79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -712,4 +712,5 @@ File %1$s contained %2$s.\n\n More options Markdown Enable Markdown in title and description + Play completion sound diff --git a/app/src/main/res/xml/preferences_notifications.xml b/app/src/main/res/xml/preferences_notifications.xml index 5e3354f47..2fd77a633 100644 --- a/app/src/main/res/xml/preferences_notifications.xml +++ b/app/src/main/res/xml/preferences_notifications.xml @@ -60,6 +60,11 @@ android:key="@string/p_rmd_ringtone" android:title="@string/sound" /> + +