Apply code warnings/hint

This commit is contained in:
Niko Diamadis 2025-12-18 00:42:29 +01:00
parent 395fe72fd6
commit 38ce2dd64e
No known key found for this signature in database
GPG key ID: 27D69B8D68305327
19 changed files with 162 additions and 156 deletions

View file

@ -11,8 +11,9 @@ import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.validUrl
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong
import androidx.core.content.edit
class Repository(private val sharedPrefs: SharedPreferences, private val database: Database) {
class Repository(private val sharedPrefs: SharedPreferences, database: Database) {
private val subscriptionDao = database.subscriptionDao()
private val notificationDao = database.notificationDao()
private val userDao = database.userDao()
@ -190,9 +191,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setPollWorkerVersion(version: Int) {
sharedPrefs.edit()
.putInt(SHARED_PREFS_POLL_WORKER_VERSION, version)
.apply()
sharedPrefs.edit {
putInt(SHARED_PREFS_POLL_WORKER_VERSION, version)
}
}
fun getDeleteWorkerVersion(): Int {
@ -200,9 +201,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setDeleteWorkerVersion(version: Int) {
sharedPrefs.edit()
.putInt(SHARED_PREFS_DELETE_WORKER_VERSION, version)
.apply()
sharedPrefs.edit {
putInt(SHARED_PREFS_DELETE_WORKER_VERSION, version)
}
}
fun getAutoRestartWorkerVersion(): Int {
@ -210,20 +211,20 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setAutoRestartWorkerVersion(version: Int) {
sharedPrefs.edit()
.putInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, version)
.apply()
sharedPrefs.edit {
putInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, version)
}
}
fun setMinPriority(minPriority: Int) {
if (minPriority <= MIN_PRIORITY_ANY) {
sharedPrefs.edit()
.remove(SHARED_PREFS_MIN_PRIORITY)
.apply()
sharedPrefs.edit {
remove(SHARED_PREFS_MIN_PRIORITY)
}
} else {
sharedPrefs.edit()
.putInt(SHARED_PREFS_MIN_PRIORITY, minPriority)
.apply()
sharedPrefs.edit {
putInt(SHARED_PREFS_MIN_PRIORITY, minPriority)
}
}
}
@ -241,9 +242,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setAutoDownloadMaxSize(maxSize: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE, maxSize)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE, maxSize)
}
}
fun getAutoDeleteSeconds(): Long {
@ -251,20 +252,20 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setAutoDeleteSeconds(seconds: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_AUTO_DELETE_SECONDS, seconds)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_AUTO_DELETE_SECONDS, seconds)
}
}
fun setDarkMode(mode: Int) {
if (mode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
sharedPrefs.edit()
.remove(SHARED_PREFS_DARK_MODE)
.apply()
sharedPrefs.edit {
remove(SHARED_PREFS_DARK_MODE)
}
} else {
sharedPrefs.edit()
.putInt(SHARED_PREFS_DARK_MODE, mode)
.apply()
sharedPrefs.edit {
putInt(SHARED_PREFS_DARK_MODE, mode)
}
}
}
@ -273,9 +274,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setDynamicColorsEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_DYNAMIC_COLORS, enabled)
.commit()
sharedPrefs.edit(commit = true) {
putBoolean(SHARED_PREFS_DYNAMIC_COLORS, enabled)
}
}
fun getDynamicColorsEnabled(): Boolean {
@ -283,9 +284,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setConnectionProtocol(connectionProtocol: String) {
sharedPrefs.edit()
.putString(SHARED_PREFS_CONNECTION_PROTOCOL, connectionProtocol)
.apply()
sharedPrefs.edit {
putString(SHARED_PREFS_CONNECTION_PROTOCOL, connectionProtocol)
}
}
fun getConnectionProtocol(): String {
@ -297,9 +298,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setBroadcastEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_BROADCAST_ENABLED, enabled)
.apply()
sharedPrefs.edit {
putBoolean(SHARED_PREFS_BROADCAST_ENABLED, enabled)
}
}
fun getUnifiedPushEnabled(): Boolean {
@ -307,9 +308,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setUnifiedPushEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_UNIFIEDPUSH_ENABLED, enabled)
.apply()
sharedPrefs.edit {
putBoolean(SHARED_PREFS_UNIFIEDPUSH_ENABLED, enabled)
}
}
fun getInsistentMaxPriorityEnabled(): Boolean {
@ -317,9 +318,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setInsistentMaxPriorityEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED, enabled)
.apply()
sharedPrefs.edit {
putBoolean(SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED, enabled)
}
}
fun getRecordLogs(): Boolean {
@ -327,9 +328,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setRecordLogsEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_RECORD_LOGS_ENABLED, enabled)
.apply()
sharedPrefs.edit {
putBoolean(SHARED_PREFS_RECORD_LOGS_ENABLED, enabled)
}
}
fun getBatteryOptimizationsRemindTime(): Long {
@ -337,9 +338,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setBatteryOptimizationsRemindTime(timeMillis: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME, timeMillis)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME, timeMillis)
}
}
fun getWebSocketRemindTime(): Long {
@ -347,9 +348,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setWebSocketRemindTime(timeMillis: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_WEBSOCKET_REMIND_TIME, timeMillis)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_WEBSOCKET_REMIND_TIME, timeMillis)
}
}
fun getWebSocketReconnectRemindTime(): Long {
@ -357,9 +358,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setWebSocketReconnectRemindTime(timeMillis: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME, timeMillis)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME, timeMillis)
}
}
fun getDefaultBaseUrl(): String? {
@ -369,16 +370,15 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
fun setDefaultBaseUrl(baseUrl: String) {
if (baseUrl == "") {
sharedPrefs
.edit()
.remove(SHARED_PREFS_UNIFIED_PUSH_BASE_URL) // Remove legacy key
.remove(SHARED_PREFS_DEFAULT_BASE_URL)
.apply()
sharedPrefs.edit {
remove(SHARED_PREFS_UNIFIED_PUSH_BASE_URL) // Remove legacy key
.remove(SHARED_PREFS_DEFAULT_BASE_URL)
}
} else {
sharedPrefs.edit()
.remove(SHARED_PREFS_UNIFIED_PUSH_BASE_URL) // Remove legacy key
.putString(SHARED_PREFS_DEFAULT_BASE_URL, baseUrl)
.apply()
sharedPrefs.edit {
remove(SHARED_PREFS_UNIFIED_PUSH_BASE_URL) // Remove legacy key
.putString(SHARED_PREFS_DEFAULT_BASE_URL, baseUrl)
}
}
}
@ -392,18 +392,18 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
}
fun setGlobalMutedUntil(mutedUntilTimestamp: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, mutedUntilTimestamp)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, mutedUntilTimestamp)
}
}
fun checkGlobalMutedUntil(): Boolean {
val mutedUntil = sharedPrefs.getLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, 0L)
val expired = mutedUntil > 1L && System.currentTimeMillis()/1000 > mutedUntil
if (expired) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, 0L)
.apply()
sharedPrefs.edit {
putLong(SHARED_PREFS_MUTED_UNTIL_TIMESTAMP, 0L)
}
return true
}
return false
@ -416,9 +416,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
fun addLastShareTopic(topic: String) {
val topics = (getLastShareTopics().filterNot { it == topic } + topic).takeLast(LAST_TOPICS_COUNT)
sharedPrefs.edit()
.putString(SHARED_PREFS_LAST_TOPICS, topics.joinToString(separator = "\n"))
.apply()
sharedPrefs.edit {
putString(SHARED_PREFS_LAST_TOPICS, topics.joinToString(separator = "\n"))
}
}
private fun toSubscriptionList(list: List<SubscriptionWithMetadata>): List<Subscription> {

View file

@ -94,8 +94,8 @@ class ApiService {
if (!response.isSuccessful) {
throw Exception("Unexpected response ${response.code} when polling topic $url")
}
val body = response.body?.string()?.trim()
if (body.isNullOrEmpty()) return emptyList()
val body = response.body.string().trim()
if (body.isEmpty()) return emptyList()
val notifications = body.lines().mapNotNull { line ->
parser.parse(line, subscriptionId = subscriptionId, notificationId = 0) // No notification when we poll
}
@ -124,7 +124,7 @@ class ApiService {
if (!response.isSuccessful) {
throw Exception("Unexpected response ${response.code} when subscribing to topic $url")
}
val source = response.body?.source() ?: throw Exception("Unexpected response for $url: body is empty")
val source = response.body.source()
while (!source.exhausted()) {
val line = source.readUtf8Line() ?: throw Exception("Unexpected response for $url: line is null")
val notification = parser.parseWithTopic(line, notificationId = Random.nextInt(), subscriptionId = 0) // subscriptionId to be set downstream

View file

@ -67,7 +67,7 @@ class DownloadAttachmentWorker(private val context: Context, params: WorkerParam
.build()
client.newCall(request).execute().use { response ->
Log.d(TAG, "Download: headers received: $response")
if (!response.isSuccessful || response.body == null) {
if (!response.isSuccessful) {
throw Exception("Unexpected response: ${response.code}")
}
save(updateAttachmentFromResponse(response))
@ -84,7 +84,7 @@ class DownloadAttachmentWorker(private val context: Context, params: WorkerParam
val outFile = resolver.openOutputStream(uri) ?: throw Exception("Cannot open output stream")
val downloadLimit = getDownloadLimit(userAction)
outFile.use { fileOut ->
val fileIn = response.body!!.byteStream()
val fileIn = response.body.byteStream()
val buffer = ByteArray(BUFFER_SIZE)
var bytes = fileIn.read(buffer)
var lastProgress = 0L

View file

@ -70,7 +70,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
.build()
client.newCall(request).execute().use { response ->
Log.d(TAG, "Headers received: $response, Content-Length: ${response.headers["Content-Length"]}")
if (!response.isSuccessful || response.body == null) {
if (!response.isSuccessful) {
throw Exception("Unexpected response: ${response.code}")
} else if (shouldAbortDownload(response)) {
Log.d(TAG, "Aborting download: Content-Length is larger than auto-download setting")
@ -85,7 +85,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
val outFile = resolver.openOutputStream(uri) ?: throw Exception("Cannot open output stream")
val downloadLimit = getDownloadLimit()
outFile.use { fileOut ->
val fileIn = response.body!!.byteStream()
val fileIn = response.body.byteStream()
val buffer = ByteArray(BUFFER_SIZE)
var bytes = fileIn.read(buffer)
while (bytes >= 0) {

View file

@ -20,6 +20,7 @@ import io.heckel.ntfy.ui.DetailActivity
import io.heckel.ntfy.ui.MainActivity
import io.heckel.ntfy.util.*
import java.util.*
import androidx.core.net.toUri
class NotificationService(val context: Context) {
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -184,10 +185,10 @@ class NotificationService(val context: Context) {
builder.setContentIntent(detailActivityIntent(subscription))
} else {
try {
val uri = Uri.parse(notification.click)
val uri = notification.click.toUri()
val viewIntent = PendingIntent.getActivity(context, Random().nextInt(), Intent(Intent.ACTION_VIEW, uri), PendingIntent.FLAG_IMMUTABLE)
builder.setContentIntent(viewIntent)
} catch (e: Exception) {
} catch (_: Exception) {
builder.setContentIntent(detailActivityIntent(subscription))
}
}
@ -207,7 +208,7 @@ class NotificationService(val context: Context) {
return
}
if (notification.attachment?.contentUri != null) {
val contentUri = Uri.parse(notification.attachment.contentUri)
val contentUri = notification.attachment.contentUri.toUri()
val intent = Intent(Intent.ACTION_VIEW, contentUri).apply {
setDataAndType(contentUri, notification.attachment.type ?: "application/octet-stream") // Required for Android <= P
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
@ -275,7 +276,7 @@ class NotificationService(val context: Context) {
private fun addViewUserActionWithoutClear(builder: NotificationCompat.Builder, action: Action) {
try {
val url = action.url ?: return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
val intent = Intent(Intent.ACTION_VIEW, url.toUri()).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
val pendingIntent = PendingIntent.getActivity(context, Random().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE)
@ -473,7 +474,7 @@ class NotificationService(val context: Context) {
// Immediately start the actual activity
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
val intent = Intent(Intent.ACTION_VIEW, url.toUri()).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(intent)

View file

@ -28,6 +28,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import java.util.concurrent.ConcurrentHashMap
import androidx.core.content.edit
/**
* The subscriber service manages the foreground service for instant delivery.
@ -120,7 +121,7 @@ class SubscriberService : Service() {
Log.d(TAG, "Starting the foreground service task")
isServiceStarted = true
saveServiceState(this, ServiceState.STARTED)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
wakeLock = (getSystemService(POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG)
}
refreshConnections()
@ -276,7 +277,7 @@ class SubscriberService : Service() {
}
private fun createNotificationChannel(): NotificationManager {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val channelName = getString(R.string.channel_subscriber_service_name) // Show's up in UI
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW).let {
it.setShowBadge(false) // Don't show long-press badge
@ -313,8 +314,8 @@ class SubscriberService : Service() {
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
applicationContext.getSystemService(ALARM_SERVICE)
val alarmService: AlarmManager = applicationContext.getSystemService(ALARM_SERVICE) as AlarmManager
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent)
}
@ -361,14 +362,14 @@ class SubscriberService : Service() {
private const val SHARED_PREFS_SERVICE_STATE = "ServiceState"
fun saveServiceState(context: Context, state: ServiceState) {
val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
sharedPrefs.edit()
.putString(SHARED_PREFS_SERVICE_STATE, state.name)
.apply()
val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, MODE_PRIVATE)
sharedPrefs.edit {
putString(SHARED_PREFS_SERVICE_STATE, state.name)
}
}
fun readServiceState(context: Context): ServiceState {
val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, MODE_PRIVATE)
val value = sharedPrefs.getString(SHARED_PREFS_SERVICE_STATE, ServiceState.STOPPED.name)
return ServiceState.valueOf(value!!)
}

View file

@ -7,7 +7,6 @@ import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.fragment.app.DialogFragment
@ -23,6 +22,8 @@ import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import androidx.core.view.isVisible
import androidx.core.view.isGone
class AddFragment : DialogFragment() {
private val api = ApiService()
@ -206,9 +207,9 @@ class AddFragment : DialogFragment() {
private fun onActionButtonClick() {
val topic = subscribeTopicText.text.toString()
val baseUrl = getBaseUrl()
if (subscribeView.visibility == View.VISIBLE) {
if (subscribeView.isVisible) {
checkReadAndMaybeShowLogin(baseUrl, topic)
} else if (loginView.visibility == View.VISIBLE) {
} else if (loginView.isVisible) {
loginAndMaybeDismiss(baseUrl, topic)
}
}
@ -349,7 +350,7 @@ class AddFragment : DialogFragment() {
if (!this::actionMenuItem.isInitialized || !this::loginUsernameText.isInitialized || !this::loginPasswordText.isInitialized) {
return // As per crash seen in Google Play
}
if (loginUsernameText.visibility == View.GONE) {
if (loginUsernameText.isGone) {
actionMenuItem.isEnabled = true
} else {
actionMenuItem.isEnabled = (loginUsernameText.text?.isNotEmpty() ?: false)

View file

@ -55,6 +55,7 @@ import java.util.Date
import kotlin.random.Random
import androidx.core.view.size
import androidx.core.view.get
import androidx.core.net.toUri
class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSettingsListener {
private val viewModel by viewModels<DetailViewModel> {
@ -726,7 +727,7 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet
handleActionModeClick(notification)
} else if (notification.click != "") {
try {
startActivity(Intent(ACTION_VIEW, Uri.parse(notification.click)))
startActivity(Intent(ACTION_VIEW, notification.click.toUri()))
} catch (e: Exception) {
Log.w(TAG, "Cannot open click URL", e)
runOnUiThread {

View file

@ -5,11 +5,9 @@ import android.app.Activity
import android.content.*
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.view.LayoutInflater
import android.view.View
@ -42,6 +40,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import me.saket.bettermovementmethod.BetterLinkMovementMethod
import androidx.core.net.toUri
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
@ -71,7 +70,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
selected.add(notificationId)
}
if (selected.size != 0) {
if (selected.isNotEmpty()) {
val listIds = currentList.map { notification -> notification.id }
val notificationPosition = listIds.indexOf(notificationId)
notifyItemChanged(notificationPosition)
@ -205,7 +204,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
}
val attachment = notification.attachment
val image = attachment.contentUri != null && supportedImage(attachment.type) && previewableImage(attachmentFileStat)
val bitmap = if (image) attachment.contentUri?.readBitmapFromUriOrNull(context) else null
val bitmap = if (image) attachment.contentUri.readBitmapFromUriOrNull(context) else null
maybeRenderAttachmentImage(context, bitmap, attachment)
maybeRenderAttachmentBox(context, notification, attachment, attachmentFileStat, bitmap)
}
@ -351,7 +350,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
if (expired) {
infos.add(context.getString(R.string.detail_item_download_info_not_downloaded_expired))
} else if (expires) {
infos.add(context.getString(R.string.detail_item_download_info_not_downloaded_expires_x, formatDateShort(attachment.expires!!)))
infos.add(context.getString(R.string.detail_item_download_info_not_downloaded_expires_x, formatDateShort(attachment.expires)))
} else {
infos.add(context.getString(R.string.detail_item_download_info_not_downloaded))
}
@ -361,7 +360,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
if (expired) {
infos.add(context.getString(R.string.detail_item_download_info_deleted_expired))
} else if (expires) {
infos.add(context.getString(R.string.detail_item_download_info_deleted_expires_x, formatDateShort(attachment.expires!!)))
infos.add(context.getString(R.string.detail_item_download_info_deleted_expires_x, formatDateShort(attachment.expires)))
} else {
infos.add(context.getString(R.string.detail_item_download_info_deleted))
}
@ -369,12 +368,12 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
if (expired) {
infos.add(context.getString(R.string.detail_item_download_info_download_failed_expired))
} else if (expires) {
infos.add(context.getString(R.string.detail_item_download_info_download_failed_expires_x, formatDateShort(attachment.expires!!)))
infos.add(context.getString(R.string.detail_item_download_info_download_failed_expires_x, formatDateShort(attachment.expires)))
} else {
infos.add(context.getString(R.string.detail_item_download_info_download_failed))
}
}
return if (infos.size > 0) {
return if (infos.isNotEmpty()) {
"$name\n${infos.joinToString(", ")}"
} else {
name
@ -389,7 +388,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
try {
Glide.with(context).load(attachment.contentUri).fitCenter().into(attachmentImageView)
attachmentImageView.setOnClickListener {
StfalconImageViewer.Builder<Any?>(context, listOf(bitmap)) { imageView, image ->
StfalconImageViewer.Builder<Any?>(context, listOf(bitmap)) { imageView, _ ->
Glide.with(context).load(attachment.contentUri).into(imageView)
}
.allowZooming(true)
@ -412,12 +411,12 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
}
Log.d(TAG, "Opening file ${attachment.contentUri}")
try {
val contentUri = Uri.parse(attachment.contentUri)
val contentUri = attachment.contentUri?.toUri()
val intent = Intent(Intent.ACTION_VIEW, contentUri)
intent.setDataAndType(contentUri, attachment.type ?: "application/octet-stream") // Required for Android <= P
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
Toast
.makeText(context, context.getString(R.string.detail_item_cannot_open_not_found), Toast.LENGTH_LONG)
.show()
@ -444,7 +443,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
put(MediaStore.MediaColumns.IS_PENDING, 1) // While downloading
}
}
val inUri = Uri.parse(attachment.contentUri)
val inUri = attachment.contentUri!!.toUri()
val inFile = resolver.openInputStream(inUri) ?: throw Exception("Cannot open input stream")
val outUri = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
val file = ensureSafeNewFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), attachment.name)
@ -475,7 +474,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
private fun deleteFile(context: Context, notification: Notification, attachment: Attachment): Boolean {
try {
val contentUri = Uri.parse(attachment.contentUri)
val contentUri = attachment.contentUri!!.toUri()
val resolver = context.applicationContext.contentResolver
val deleted = resolver.delete(contentUri, null, null) > 0
if (!deleted) throw Exception("no rows deleted")
@ -537,7 +536,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
private fun runViewAction(context: Context, action: Action) {
try {
val url = action.url ?: return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
val intent = Intent(Intent.ACTION_VIEW, url.toUri()).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(intent)

View file

@ -32,6 +32,7 @@ import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.util.*
import androidx.core.net.toUri
/**
* Subscription settings
@ -143,10 +144,8 @@ class DetailSettingsActivity : AppCompatActivity() {
loadInsistentMaxPriorityPref()
loadIconSetPref()
loadIconRemovePref()
if (notificationService.channelsSupported()) {
loadDedicatedChannelsPrefs()
loadOpenChannelsPrefs()
}
loadDedicatedChannelsPrefs()
loadOpenChannelsPrefs()
} else {
val notificationsHeaderId = context?.getString(R.string.detail_settings_notifications_header_key) ?: return
val notificationsHeader: PreferenceCategory? = findPreference(notificationsHeaderId)
@ -507,7 +506,7 @@ class DetailSettingsActivity : AppCompatActivity() {
return
}
try {
resolver.delete(Uri.parse(uri), null, null)
resolver.delete(uri.toUri(), null, null)
} catch (e: Exception) {
Log.w(TAG, "Unable to delete $uri", e)
}

View file

@ -8,7 +8,6 @@ import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
@ -75,6 +74,7 @@ import java.util.concurrent.TimeUnit
import kotlin.random.Random
import androidx.core.view.size
import androidx.core.view.get
import androidx.core.net.toUri
class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, NotificationFragment.NotificationSettingsListener {
private val viewModel by viewModels<SubscriptionsViewModel> {
@ -258,17 +258,17 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
}
fixNowButton.setOnClickListener {
try {
Log.d(TAG, Uri.parse("package:$packageName").toString())
Log.d(TAG, "package:$packageName".toUri().toString())
startActivity(
Intent(
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:$packageName")
"package:$packageName".toUri()
)
)
} catch (e: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
try {
startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
} catch (e2: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
@ -558,19 +558,27 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
true
}
R.id.main_menu_report_bug -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_report_bug_url))))
startActivity(
Intent(Intent.ACTION_VIEW, getString(R.string.main_menu_report_bug_url).toUri())
)
true
}
R.id.main_menu_rate -> {
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName")))
} catch (e: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageName")))
startActivity(
Intent(Intent.ACTION_VIEW, "market://details?id=$packageName".toUri())
)
} catch (_: ActivityNotFoundException) {
startActivity(
Intent(Intent.ACTION_VIEW, "https://play.google.com/store/apps/details?id=$packageName".toUri())
)
}
true
}
R.id.main_menu_docs -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url))))
startActivity(
Intent(Intent.ACTION_VIEW, getString(R.string.main_menu_docs_url).toUri())
)
true
}
else -> super.onOptionsItemSelected(item)
@ -683,7 +691,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
var errorMessage = "" // First error
var newNotificationsCount = 0
repository.getSubscriptions().forEach { subscription ->
Log.d(TAG, "subscription: ${subscription}")
Log.d(TAG, "subscription: $subscription")
try {
val user = repository.getUser(subscription.baseUrl) // May be null
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user, subscription.lastNotificationId)

View file

@ -51,7 +51,7 @@ class MainAdapter(
selected.add(subscriptionId)
}
if (selected.size != 0) {
if (selected.isNotEmpty()) {
val listIds = currentList.map { subscription -> subscription.id }
val subscriptionPosition = listIds.indexOf(subscriptionId)
notifyItemChanged(subscriptionPosition)

View file

@ -1,7 +1,6 @@
package io.heckel.ntfy.ui
import android.content.Context
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
@ -10,6 +9,7 @@ import io.heckel.ntfy.db.*
import io.heckel.ntfy.up.Distributor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import androidx.core.net.toUri
class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
fun list(): LiveData<List<Subscription>> {
@ -35,7 +35,7 @@ class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
if (subscription.icon != null) {
val resolver = context.applicationContext.contentResolver
try {
resolver.delete(Uri.parse(subscription.icon), null, null)
resolver.delete(subscription.icon.toUri(), null, null)
} catch (_: Exception) {
// Don't care
}

View file

@ -1,6 +1,5 @@
package io.heckel.ntfy.ui
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle

View file

@ -4,7 +4,6 @@ import android.Manifest
import android.app.AlarmManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
@ -619,7 +618,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
versionPref?.summary = version
versionPref?.onPreferenceClickListener = OnPreferenceClickListener {
val context = context ?: return@OnPreferenceClickListener false
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("ntfy version", version)
clipboard.setPrimaryClip(clip)
Toast
@ -647,7 +646,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
val context = context ?: return@launch
val log = Log.getFormatted(context, scrub = scrub)
requireActivity().runOnUiThread {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("ntfy logs", log)
clipboard.setPrimaryClip(clip)
if (scrub) {
@ -689,12 +688,12 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
if (!response.isSuccessful) {
throw Exception("Unexpected response ${response.code}")
}
val body = response.body?.string()?.trim()
if (body.isNullOrEmpty()) throw Exception("Return body is empty")
val body = response.body.string().trim()
if (body.isEmpty()) throw Exception("Return body is empty")
Log.d(TAG, "Logs uploaded successfully: $body")
val resp = gson.fromJson(body.toString(), NopasteResponse::class.java)
val resp = gson.fromJson(body, NopasteResponse::class.java)
requireActivity().runOnUiThread {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("logs URL", resp.url)
clipboard.setPrimaryClip(clip)
if (scrub) {

View file

@ -1,6 +1,6 @@
package io.heckel.ntfy.util;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
@ -16,11 +16,7 @@ public class Emoji {
protected Emoji(List<String> aliases, byte... bytes) {
this.aliases = Collections.unmodifiableList(aliases);
try {
this.unicode = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
this.unicode = new String(bytes, StandardCharsets.UTF_8);
}
public List<String> getAliases() {

View file

@ -5,6 +5,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -37,7 +38,7 @@ public class EmojiLoader {
InputStream stream
) throws IOException {
StringBuilder sb = new StringBuilder();
InputStreamReader isr = new InputStreamReader(stream, "UTF-8");
InputStreamReader isr = new InputStreamReader(stream, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String read;
while((read = br.readLine()) != null) {
@ -49,12 +50,12 @@ public class EmojiLoader {
protected static Emoji buildEmojiFromJSON(
JSONObject json
) throws UnsupportedEncodingException, JSONException {
) throws JSONException {
if (!json.has("emoji")) {
return null;
}
byte[] bytes = json.getString("emoji").getBytes("UTF-8");
byte[] bytes = json.getString("emoji").getBytes(StandardCharsets.UTF_8);
List<String> aliases = jsonArrayToStringList(json.getJSONArray("aliases"));
return new Emoji(aliases, bytes);
}

View file

@ -49,6 +49,7 @@ import java.text.StringCharacterIterator
import java.util.Date
import kotlin.math.abs
import kotlin.math.absoluteValue
import androidx.core.net.toUri
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush
@ -168,7 +169,7 @@ fun decodeMessage(notification: Notification): String {
} else {
notification.message
}
} catch (e: IllegalArgumentException) {
} catch (_: IllegalArgumentException) {
notification.message + "(invalid base64)"
}
}
@ -180,7 +181,7 @@ fun decodeBytesMessage(notification: Notification): ByteArray {
} else {
notification.message.toByteArray()
}
} catch (e: IllegalArgumentException) {
} catch (_: IllegalArgumentException) {
notification.message.toByteArray()
}
}
@ -230,7 +231,7 @@ fun maybeAppendActionErrors(message: CharSequence, notification: Notification):
// Queries the filename of a content URI
fun fileName(context: Context, contentUri: String?, fallbackName: String): String {
return try {
val info = fileStat(context, Uri.parse(contentUri))
val info = fileStat(context, contentUri?.toUri())
info.filename
} catch (_: Exception) {
fallbackName
@ -264,7 +265,7 @@ fun fileStat(context: Context, contentUri: Uri?): FileInfo {
fun maybeFileStat(context: Context, contentUri: String?): FileInfo? {
return try {
fileStat(context, Uri.parse(contentUri)) // Throws if the file does not exist
fileStat(context, contentUri?.toUri()) // Throws if the file does not exist
} catch (_: Exception) {
null
}
@ -427,7 +428,7 @@ fun Uri.readBitmapFromUri(context: Context): Bitmap {
}
fun String.readBitmapFromUri(context: Context): Bitmap {
return Uri.parse(this).readBitmapFromUri(context)
return this.toUri().readBitmapFromUri(context)
}
fun String.readBitmapFromUriOrNull(context: Context): Bitmap? {

View file

@ -1,7 +1,6 @@
package io.heckel.ntfy.work
import android.content.Context
import android.net.Uri
import androidx.core.content.FileProvider
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
@ -15,6 +14,7 @@ import io.heckel.ntfy.util.topicShortUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import androidx.core.net.toUri
/**
* Deletes notifications marked for deletion and attachments for deleted notifications.
@ -59,7 +59,7 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
notifications.forEach { notification ->
try {
val attachment = notification.attachment ?: return
val contentUri = Uri.parse(attachment.contentUri ?: return)
val contentUri = (attachment.contentUri ?: return).toUri()
Log.d(TAG, "Deleting attachment for notification ${notification.id}: ${attachment.contentUri} (${attachment.name})")
val deleted = resolver.delete(contentUri, null, null) > 0
if (!deleted) {