Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -445,4 +445,7 @@ object PreferenceData {
var SharedPreferences.isTtsBakeryOpen: Boolean
get() = getBoolean(PREF_IS_TTS_BAKERY_OPEN, AppConfig.openDebug)
set(value) = edit { putBoolean(PREF_IS_TTS_BAKERY_OPEN, value) }

const val PREF_SUPPRESS_NOTIFICATION_CHECK = "pref_suppress_notification_permission_check"
const val PREF_SUPPRESS_BATTERY_CHECK = "pref_suppress_battery_optimization_check"
}
12 changes: 12 additions & 0 deletions app-base/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,18 @@

<string name="whitelist_settings_template">System setting %d</string>

<!--Permission checks-->
<string name="permission_notifications_title">Enable Notifications</string>
<string name="permission_notifications_message">Timer Machine needs notification permission to show timer countdowns and alerts when timers expire. Without it, timers will run silently.</string>
<string name="permission_battery_title">Disable Battery Optimization</string>
<string name="permission_battery_message">Battery optimization may stop timers from running in the background. Please disable it for Timer Machine to ensure reliable timer operation.</string>
<string name="permission_enable">Enable</string>
<string name="permission_not_now">Not now</string>
<string name="permission_dont_ask_again">Don\'t ask again</string>
<string name="pref_reset_permission_reminders">Reset permission reminders</string>
<string name="pref_reset_permission_reminders_summary">Re-enable notification and battery optimization prompts</string>
<string name="pref_permission_reminders_reset">Permission reminders re-enabled</string>

<!--Account-->
<string name="account_sign_out_confirmation">Sign out?</string>
<string name="account_sign_out">Sign Out</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.Manifest
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import androidx.core.content.edit
import android.os.Bundle
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
Expand All @@ -12,6 +13,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.github.deweyreed.timer.component.tts.TtsBakery
import com.github.deweyreed.tools.anko.snackbar
import com.github.deweyreed.tools.helper.IntentHelper
import com.github.deweyreed.tools.helper.createChooserIntentIfDead
import com.github.deweyreed.tools.helper.hasPermissions
Expand Down Expand Up @@ -171,6 +173,14 @@ class SettingsFragment :
NavHostFragment.findNavController(this)
.subLevelNavigate(RBase.id.dest_about)
}
KEY_RESET_PERMISSION_REMINDERS -> {
sharedPreferences.edit {
putBoolean(PreferenceData.PREF_SUPPRESS_NOTIFICATION_CHECK, false)
putBoolean(PreferenceData.PREF_SUPPRESS_BATTERY_CHECK, false)
}
(requireActivity() as MainCallback.ActivityCallback).snackbarView
.snackbar(getString(RBase.string.pref_permission_reminders_reset))
}
else -> return false
}
return true
Expand Down Expand Up @@ -221,6 +231,7 @@ class SettingsFragment :
}

findPreference<Preference>(KEY_NOTIF_SETTING)?.onPreferenceClickListener = this
findPreference<Preference>(KEY_RESET_PERMISSION_REMINDERS)?.onPreferenceClickListener = this

findPreference<Preference>(KEY_AUDIO_VOLUME)?.run {
onPreferenceClickListener = this@SettingsFragment
Expand Down Expand Up @@ -330,6 +341,7 @@ private const val KEY_PHONE_CALL = PreferenceData.KEY_PHONE_CALL
private const val KEY_WEEK_START = PreferenceData.KEY_WEEK_START

private const val KEY_NOTIF_SETTING = "key_notif_setting"
private const val KEY_RESET_PERMISSION_REMINDERS = "key_reset_permission_reminders"

private const val KEY_AUDIO_VOLUME = "key_audio_volume"

Expand Down
8 changes: 8 additions & 0 deletions app-settings/src/main/res/xml/pref_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@
app:key="key_media_style_notification"
app:title="@string/pref_media_style_notification" />

<Preference
app:icon="@drawable/settings_notifications"
app:key="key_reset_permission_reminders"
app:persistent="false"
app:singleLineTitle="false"
app:title="@string/pref_reset_permission_reminders"
app:summary="@string/pref_reset_permission_reminders_summary" />

</PreferenceCategory>

<PreferenceCategory app:title="@string/pref_category_title_audio">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ package xyz.aprildown.timer.app.timer.list
import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.os.PowerManager
import android.provider.Settings
import android.text.Spanned
import android.text.style.StyleSpan
import android.widget.CheckBox
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.text.buildSpannedString
import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
Expand Down Expand Up @@ -144,16 +150,23 @@ class TimerFragment :
}
}

private var hasCheckedPermissionsThisResume = false

override fun onResume() {
super.onResume()
ScreenWakeLock.acquireScreenWakeLock(
context = requireActivity(),
screenTiming = getString(RBase.string.pref_screen_timing_value_timer)
)
if (!hasCheckedPermissionsThisResume) {
hasCheckedPermissionsThisResume = true
view?.post { checkPermissions() }
}
}

override fun onPause() {
super.onPause()
hasCheckedPermissionsThisResume = false
ScreenWakeLock.releaseScreenLock(
context = requireActivity(),
screenTiming = getString(RBase.string.pref_screen_timing_value_timer)
Expand Down Expand Up @@ -595,6 +608,94 @@ class TimerFragment :
}
}

private fun checkPermissions() {
val context = context ?: return
if (viewModel.tips.value != TipManager.TIP_NO_MORE) return

val prefs = context.safeSharedPreference

// Check notification permission (Android 13+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!context.hasPermissions(Manifest.permission.POST_NOTIFICATIONS)
) {
if (!prefs.getBoolean(PreferenceData.PREF_SUPPRESS_NOTIFICATION_CHECK, false)) {
if (!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
postNotificationsLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} else {
showPermissionDialog(
title = RBase.string.permission_notifications_title,
message = RBase.string.permission_notifications_message,
suppressKey = PreferenceData.PREF_SUPPRESS_NOTIFICATION_CHECK,
onEnable = {
startActivity(
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
)
}
)
}
return
}
}

// Check battery optimization
val pm = context.getSystemService(PowerManager::class.java)
if (pm != null && !pm.isIgnoringBatteryOptimizations(context.packageName)) {
if (!prefs.getBoolean(PreferenceData.PREF_SUPPRESS_BATTERY_CHECK, false)) {
showPermissionDialog(
title = RBase.string.permission_battery_title,
message = RBase.string.permission_battery_message,
suppressKey = PreferenceData.PREF_SUPPRESS_BATTERY_CHECK,
onEnable = {
startActivity(
@Suppress("BatteryLife")
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.setData("package:${context.packageName}".toUri())
)
}
)
}
}
}

private fun showPermissionDialog(
@StringRes title: Int,
@StringRes message: Int,
suppressKey: String,
onEnable: () -> Unit
) {
val context = context ?: return
val checkBox = CheckBox(context).apply {
setText(RBase.string.permission_dont_ask_again)
}
val container = android.widget.FrameLayout(context).apply {
val horizontalMargin = context.dp(24).toInt()
addView(checkBox, android.widget.FrameLayout.LayoutParams(
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
marginStart = horizontalMargin
marginEnd = horizontalMargin
})
}
MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setView(container)
.setPositiveButton(RBase.string.permission_enable) { _, _ ->
if (checkBox.isChecked) {
context.safeSharedPreference.edit { putBoolean(suppressKey, true) }
}
onEnable()
}
.setNegativeButton(RBase.string.permission_not_now) { _, _ ->
if (checkBox.isChecked) {
context.safeSharedPreference.edit { putBoolean(suppressKey, true) }
}
}
.show()
}

private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
listAdapter?.setPresenter((service as MachineContract.PresenterProvider).getPresenter())
Expand Down