Fix Android 5 crashes on unsubscribing
This commit is contained in:
parent
6a8d222e12
commit
e64cd79c28
11 changed files with 43 additions and 59 deletions
|
@ -14,8 +14,8 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
|
||||
versionCode 31
|
||||
versionName "1.15.2"
|
||||
versionCode 32
|
||||
versionName "1.16.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.heckel.ntfy.firebase
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class FirebaseMessenger {
|
||||
fun subscribe(topic: String) {
|
||||
// Dummy to keep F-Droid flavor happy
|
||||
|
|
|
@ -89,7 +89,7 @@ class Backuper(val context: Context) {
|
|||
|
||||
private suspend fun applySubscriptions(subscriptions: List<Subscription>?) {
|
||||
if (subscriptions == null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
val appBaseUrl = context.getString(R.string.app_base_url)
|
||||
subscriptions.forEach { s ->
|
||||
|
@ -119,7 +119,7 @@ class Backuper(val context: Context) {
|
|||
|
||||
private suspend fun applyNotifications(notifications: List<Notification>?) {
|
||||
if (notifications == null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
notifications.forEach { n ->
|
||||
try {
|
||||
|
@ -188,7 +188,7 @@ class Backuper(val context: Context) {
|
|||
|
||||
private suspend fun applyUsers(users: List<User>?) {
|
||||
if (users == null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
users.forEach { u ->
|
||||
try {
|
||||
|
|
|
@ -310,11 +310,11 @@ class SubscriberService : Service() {
|
|||
override fun onTaskRemoved(rootIntent: Intent) {
|
||||
val restartServiceIntent = Intent(applicationContext, SubscriberService::class.java).also {
|
||||
it.setPackage(packageName)
|
||||
};
|
||||
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE);
|
||||
applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager;
|
||||
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent);
|
||||
}
|
||||
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)
|
||||
applicationContext.getSystemService(Context.ALARM_SERVICE)
|
||||
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent)
|
||||
}
|
||||
|
||||
/* This re-starts the service on reboot; see manifest */
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.ActionMode
|
||||
|
@ -178,7 +179,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
howToExample.linksClickable = true
|
||||
|
||||
val howToText = getString(R.string.detail_how_to_example, topicUrl)
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
howToExample.text = Html.fromHtml(howToText, Html.FROM_HTML_MODE_LEGACY)
|
||||
} else {
|
||||
howToExample.text = Html.fromHtml(howToText)
|
||||
|
@ -241,7 +242,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
if (positionStart == 0) {
|
||||
Log.d(TAG, "$itemCount item(s) inserted at $positionStart, scrolling to the top")
|
||||
Log.d(TAG, "$itemCount item(s) inserted at 0, scrolling to the top")
|
||||
mainList.scrollToPosition(positionStart)
|
||||
}
|
||||
}
|
||||
|
@ -571,7 +572,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
dialog.setOnShowListener {
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
.setTextAppearance(R.style.DangerText)
|
||||
.dangerButton(this)
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
@ -609,7 +610,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
dialog.setOnShowListener {
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
.setTextAppearance(R.style.DangerText)
|
||||
.dangerButton(this)
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
@ -619,7 +620,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
handleActionModeClick(notification)
|
||||
} else if (notification.click != "") {
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click)))
|
||||
startActivity(Intent(ACTION_VIEW, Uri.parse(notification.click)))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Cannot open click URL", e)
|
||||
runOnUiThread {
|
||||
|
@ -720,7 +721,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
dialog.setOnShowListener {
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
.setTextAppearance(R.style.DangerText)
|
||||
.dangerButton(this)
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import io.heckel.ntfy.app.Application
|
|||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.Subscription
|
||||
import io.heckel.ntfy.firebase.FirebaseMessenger
|
||||
import io.heckel.ntfy.util.Log
|
||||
import io.heckel.ntfy.msg.ApiService
|
||||
import io.heckel.ntfy.msg.DownloadManager
|
||||
import io.heckel.ntfy.msg.DownloadType
|
||||
|
@ -48,7 +47,6 @@ import io.heckel.ntfy.work.PollWorker
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
@ -622,7 +620,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
|||
dialog.setOnShowListener {
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
.setTextAppearance(R.style.DangerText)
|
||||
.dangerButton(this)
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
|
|||
if (selected.contains(subscription.id)) {
|
||||
itemView.setBackgroundResource(Colors.itemSelectedBackground(context))
|
||||
} else {
|
||||
itemView.setBackgroundColor(Color.TRANSPARENT);
|
||||
itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.heckel.ntfy.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
|
@ -295,7 +294,7 @@ class ShareActivity : AppCompatActivity() {
|
|||
.show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val message = if (e is ApiService.UnauthorizedException) {
|
||||
val errorMessage = if (e is ApiService.UnauthorizedException) {
|
||||
if (e.user != null) {
|
||||
getString(R.string.detail_test_message_error_unauthorized_user, e.user.username)
|
||||
} else {
|
||||
|
@ -308,7 +307,7 @@ class ShareActivity : AppCompatActivity() {
|
|||
}
|
||||
runOnUiThread {
|
||||
progress.visibility = View.GONE
|
||||
errorText.text = message
|
||||
errorText.text = errorMessage
|
||||
errorImage.visibility = View.VISIBLE
|
||||
errorText.visibility = View.VISIBLE
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import android.app.Dialog
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Button
|
||||
|
@ -16,6 +14,8 @@ import androidx.fragment.app.DialogFragment
|
|||
import com.google.android.material.textfield.TextInputEditText
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.db.User
|
||||
import io.heckel.ntfy.util.AfterChangedTextWatcher
|
||||
import io.heckel.ntfy.util.dangerButton
|
||||
import io.heckel.ntfy.util.validUrl
|
||||
|
||||
class UserFragment : DialogFragment() {
|
||||
|
@ -98,28 +98,14 @@ class UserFragment : DialogFragment() {
|
|||
|
||||
// Delete button should be red
|
||||
if (user != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
.setTextAppearance(R.style.DangerText)
|
||||
} else {
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
.setTextColor(ContextCompat.getColor(requireContext(), Colors.dangerText(requireContext())))
|
||||
}
|
||||
dialog
|
||||
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
.dangerButton(requireContext())
|
||||
}
|
||||
|
||||
// Validate input when typing
|
||||
val textWatcher = object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
validateInput()
|
||||
}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
// Nothing
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||
// Nothing
|
||||
}
|
||||
val textWatcher = AfterChangedTextWatcher {
|
||||
validateInput()
|
||||
}
|
||||
baseUrlView.addTextChangedListener(textWatcher)
|
||||
usernameView.addTextChangedListener(textWatcher)
|
||||
|
@ -140,7 +126,7 @@ class UserFragment : DialogFragment() {
|
|||
}
|
||||
|
||||
// Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785)
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
|
|
@ -22,13 +22,16 @@ import android.util.Base64
|
|||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.heckel.ntfy.BuildConfig
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.db.*
|
||||
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
|
||||
import io.heckel.ntfy.ui.Colors
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -218,16 +221,6 @@ fun maybeAppendActionErrors(message: String, notification: Notification): String
|
|||
}
|
||||
}
|
||||
|
||||
// Checks in the most horrible way if a content URI exists; I couldn't find a better way
|
||||
fun fileExists(context: Context, contentUri: String?): Boolean {
|
||||
return try {
|
||||
fileStat(context, Uri.parse(contentUri)) // Throws if the file does not exist
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Queries the filename of a content URI
|
||||
fun fileName(context: Context, contentUri: String?, fallbackName: String): String {
|
||||
return try {
|
||||
|
@ -455,10 +448,6 @@ fun String.readBitmapFromUriOrNull(context: Context): Bitmap? {
|
|||
}
|
||||
}
|
||||
|
||||
fun Long.nullIfZero(): Long? {
|
||||
return if (this == 0L) return null else this
|
||||
}
|
||||
|
||||
// TextWatcher that only implements the afterTextChanged method
|
||||
class AfterChangedTextWatcher(val afterTextChangedFn: (s: Editable?) -> Unit) : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
|
@ -506,3 +495,11 @@ fun String.sha256(): String {
|
|||
val digest = md.digest(this.toByteArray())
|
||||
return digest.fold("") { str, it -> str + "%02x".format(it) }
|
||||
}
|
||||
|
||||
fun Button.dangerButton(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
setTextAppearance(R.style.DangerText)
|
||||
} else {
|
||||
setTextColor(ContextCompat.getColor(context, Colors.dangerText(context)))
|
||||
}
|
||||
}
|
||||
|
|
2
fastlane/metadata/android/en-US/changelog/32.txt
Normal file
2
fastlane/metadata/android/en-US/changelog/32.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Bug fixes:
|
||||
* Android 5 (SDK 21): Fix crash on unsubscribing (#528, thanks to Roger M.)
|
Loading…
Reference in a new issue