Skip to content

Commit

Permalink
Merge branch 'master' into client-certificate-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
cyb3rko committed Apr 23, 2024
2 parents a3dd80c + d5903b2 commit 2d70b94
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 177 deletions.
13 changes: 7 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ if (project.hasProperty('sign')) {
}

dependencies {
def markwon_version = "4.6.2"
implementation project(':client')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
Expand All @@ -76,12 +77,12 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.1'

implementation 'com.github.cyb3rko:QuickPermissions-Kotlin:1.1.3'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'io.noties.markwon:core:4.6.2'
implementation 'io.noties.markwon:image-picasso:4.6.2'
implementation 'io.noties.markwon:image:4.6.2'
implementation 'io.noties.markwon:ext-tables:4.6.2'
implementation 'io.noties.markwon:ext-strikethrough:4.6.2'
implementation 'io.coil-kt:coil:2.6.0'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:image-coil:$markwon_version"
implementation "io.noties.markwon:image:$markwon_version"
implementation "io.noties.markwon:ext-tables:$markwon_version"
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"

implementation 'org.tinylog:tinylog-api-kotlin:2.6.2'
implementation 'org.tinylog:tinylog-impl:2.6.2'
Expand Down
67 changes: 67 additions & 0 deletions app/src/main/kotlin/com/github/gotify/CoilHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.github.gotify

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import coil.ImageLoader
import coil.annotation.ExperimentalCoilApi
import coil.disk.DiskCache
import coil.executeBlocking
import coil.request.ImageRequest
import com.github.gotify.api.CertUtils
import com.github.gotify.client.model.Application
import java.io.IOException
import okhttp3.OkHttpClient
import org.tinylog.kotlin.Logger

internal class CoilHandler(private val context: Context, private val settings: Settings) {
private val imageLoader = makeImageLoader()

private fun makeImageLoader(): ImageLoader {
val builder = OkHttpClient.Builder()
CertUtils.applySslSettings(builder, settings.sslSettings())
return ImageLoader.Builder(context)
.okHttpClient(builder.build())
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("coil-cache"))
.build()
}
.build()
}

@Throws(IOException::class)
fun getImageFromUrl(url: String?): Bitmap {
val request = ImageRequest.Builder(context)
.data(url)
.build()
return (imageLoader.executeBlocking(request).drawable as BitmapDrawable).bitmap
}

fun getIcon(app: Application?): Bitmap {
if (app == null) {
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
}
try {
return getImageFromUrl(
Utils.resolveAbsoluteUrl("${settings.url}/", app.image)
)
} catch (e: IOException) {
Logger.error(e, "Could not load image for notification")
}
return BitmapFactory.decodeResource(context.resources, R.drawable.gotify)
}

fun get() = imageLoader

@OptIn(ExperimentalCoilApi::class)
fun evict() {
try {
imageLoader.diskCache?.clear()
imageLoader.memoryCache?.clear()
} catch (e: IOException) {
Logger.error(e, "Problem evicting Coil cache")
}
}
}
12 changes: 6 additions & 6 deletions app/src/main/kotlin/com/github/gotify/MarkwonFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import android.text.style.RelativeSizeSpan
import android.text.style.StyleSpan
import android.text.style.TypefaceSpan
import androidx.core.content.ContextCompat
import com.squareup.picasso.Picasso
import coil.ImageLoader
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonSpansFactory
Expand All @@ -22,7 +22,7 @@ import io.noties.markwon.core.MarkwonTheme
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TableAwareMovementMethod
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.image.picasso.PicassoImagesPlugin
import io.noties.markwon.image.coil.CoilImagesPlugin
import io.noties.markwon.movement.MovementMethodPlugin
import org.commonmark.ext.gfm.tables.TableCell
import org.commonmark.ext.gfm.tables.TablesExtension
Expand All @@ -36,11 +36,11 @@ import org.commonmark.node.StrongEmphasis
import org.commonmark.parser.Parser

internal object MarkwonFactory {
fun createForMessage(context: Context, picasso: Picasso): Markwon {
fun createForMessage(context: Context, imageLoader: ImageLoader): Markwon {
return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create()))
.usePlugin(PicassoImagesPlugin.create(picasso))
.usePlugin(CoilImagesPlugin.create(context, imageLoader))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(TablePlugin.create(context))
.usePlugin(object : AbstractMarkwonPlugin() {
Expand All @@ -52,13 +52,13 @@ internal object MarkwonFactory {
.build()
}

fun createForNotification(context: Context, picasso: Picasso): Markwon {
fun createForNotification(context: Context, imageLoader: ImageLoader): Markwon {
val headingSizes = floatArrayOf(2f, 1.5f, 1.17f, 1f, .83f, .67f)
val bulletGapWidth = (8 * context.resources.displayMetrics.density + 0.5f).toInt()

return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(PicassoImagesPlugin.create(picasso))
.usePlugin(CoilImagesPlugin.create(context, imageLoader))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
Expand Down
18 changes: 6 additions & 12 deletions app/src/main/kotlin/com/github/gotify/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@ package com.github.gotify
import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.format.DateUtils
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import coil.target.Target
import com.github.gotify.client.JSON
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import com.squareup.picasso.Picasso.LoadedFrom
import com.squareup.picasso.Target
import java.net.MalformedURLException
import java.net.URI
import java.net.URISyntaxException
Expand Down Expand Up @@ -67,17 +63,15 @@ internal object Utils {
}
}

fun toDrawable(resources: Resources?, drawableReceiver: DrawableReceiver): Target {
fun toDrawable(drawableReceiver: DrawableReceiver): Target {
return object : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: LoadedFrom) {
drawableReceiver.loaded(BitmapDrawable(resources, bitmap))
override fun onSuccess(result: Drawable) {
drawableReceiver.loaded(result)
}

override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
Logger.error(e, "Bitmap failed")
override fun onError(error: Drawable?) {
Logger.error("Bitmap failed")
}

override fun onPrepareLoad(placeHolderDrawable: Drawable) {}
}
}

Expand Down
23 changes: 14 additions & 9 deletions app/src/main/kotlin/com/github/gotify/api/CertUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,14 @@ internal object CertUtils {
if (customManagers || !settings.validateSSL) {
val context = SSLContext.getInstance("TLS")
context.init(keyManagers, trustManagers, SecureRandom())
if (trustManagers != null) {
// Use custom trust manager
builder.sslSocketFactory(
context.socketFactory,
trustManagers[0] as X509TrustManager
)
} else {
if (trustManagers == null) {
// Fall back to system trust managers
@Suppress("DEPRECATION")
builder.sslSocketFactory(context.socketFactory)
trustManagers = defaultSystemTrustManager()
}
builder.sslSocketFactory(
context.socketFactory,
trustManagers[0] as X509TrustManager
)
}
} catch (e: Exception) {
// We shouldn't have issues since the cert is verified on login.
Expand Down Expand Up @@ -124,4 +121,12 @@ internal object CertUtils {
keyManagerFactory.init(keyStore, certPassword.toCharArray())
return keyManagerFactory.keyManagers
}

private fun defaultSystemTrustManager(): Array<TrustManager> {
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
trustManagerFactory.init(null as KeyStore?)
return trustManagerFactory.trustManagers
}
}
6 changes: 3 additions & 3 deletions app/src/main/kotlin/com/github/gotify/login/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.security.cert.X509Certificate
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.tinylog.kotlin.Logger

internal class LoginActivity : AppCompatActivity() {
Expand Down Expand Up @@ -130,13 +130,13 @@ internal class LoginActivity : AppCompatActivity() {

private fun doCheckUrl() {
val url = binding.gotifyUrlEditext.text.toString().trim().trimEnd('/')
val parsedUrl = HttpUrl.parse(url)
val parsedUrl = url.toHttpUrlOrNull()
if (parsedUrl == null) {
Utils.showSnackBar(this, "Invalid URL (include http:// or https://)")
return
}

if ("http" == parsedUrl.scheme()) {
if ("http" == parsedUrl.scheme) {
showHttpWarning()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import coil.ImageLoader
import coil.load
import com.github.gotify.MarkwonFactory
import com.github.gotify.R
import com.github.gotify.Settings
Expand All @@ -26,7 +28,6 @@ import com.github.gotify.client.model.Message
import com.github.gotify.databinding.MessageItemBinding
import com.github.gotify.databinding.MessageItemCompactBinding
import com.github.gotify.messages.provider.MessageWithImage
import com.squareup.picasso.Picasso
import io.noties.markwon.Markwon
import java.text.DateFormat
import java.util.Date
Expand All @@ -35,11 +36,11 @@ import org.threeten.bp.OffsetDateTime
internal class ListMessageAdapter(
private val context: Context,
private val settings: Settings,
private val picasso: Picasso,
private val imageLoader: ImageLoader,
private val delete: Delete
) : ListAdapter<MessageWithImage, ListMessageAdapter.ViewHolder>(DiffCallback) {
private val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val markwon: Markwon = MarkwonFactory.createForMessage(context, picasso)
private val markwon: Markwon = MarkwonFactory.createForMessage(context, imageLoader)

private val timeFormatRelative =
context.resources.getString(R.string.time_format_value_relative)
Expand Down Expand Up @@ -81,10 +82,11 @@ internal class ListMessageAdapter(
}
holder.title.text = message.message.title
if (message.image != null) {
picasso.load(Utils.resolveAbsoluteUrl("${settings.url}/", message.image))
.error(R.drawable.ic_alarm)
.placeholder(R.drawable.ic_placeholder)
.into(holder.image)
val url = Utils.resolveAbsoluteUrl("${settings.url}/", message.image)
holder.image.load(url, imageLoader) {
error(R.drawable.ic_alarm)
placeholder(R.drawable.ic_placeholder)
}
}

val prefs = PreferenceManager.getDefaultSharedPreferences(context)
Expand Down
35 changes: 20 additions & 15 deletions app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.request.ImageRequest
import com.github.gotify.BuildConfig
import com.github.gotify.MissedMessageUtil
import com.github.gotify.R
Expand Down Expand Up @@ -57,7 +58,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.BaseTransientBottomBar.BaseCallback
import com.google.android.material.snackbar.Snackbar
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tinylog.kotlin.Logger
Expand Down Expand Up @@ -102,7 +102,7 @@ internal class MessagesActivity :
listMessageAdapter = ListMessageAdapter(
this,
viewModel.settings,
viewModel.picassoHandler.get()
viewModel.coilHandler.get()
) { message ->
scheduleDeletion(message)
}
Expand Down Expand Up @@ -169,19 +169,20 @@ internal class MessagesActivity :
}

private fun refreshAll() {
try {
viewModel.picassoHandler.evict()
} catch (e: IOException) {
Logger.error(e, "Problem evicting Picasso cache")
}
viewModel.coilHandler.evict()
startActivity(Intent(this, InitializationActivity::class.java))
finish()
}

private fun onRefresh() {
viewModel.coilHandler.evict()
viewModel.messages.clear()
launchCoroutine {
loadMore(viewModel.appId)
loadMore(viewModel.appId).forEachIndexed { index, message ->
if (message.image != null) {
listMessageAdapter.notifyItemChanged(index)
}
}
}
}

Expand All @@ -201,15 +202,18 @@ internal class MessagesActivity :
val item = menu.add(R.id.apps, index, APPLICATION_ORDER, app.name)
item.isCheckable = true
if (app.id == viewModel.appId) selectedItem = item
val t = Utils.toDrawable(resources) { icon -> item.icon = icon }
val t = Utils.toDrawable { icon -> item.icon = icon }
viewModel.targetReferences.add(t)
viewModel.picassoHandler
.get()
.load(Utils.resolveAbsoluteUrl(viewModel.settings.url + "/", app.image))
val request = ImageRequest.Builder(this)
.data(Utils.resolveAbsoluteUrl(viewModel.settings.url + "/", app.image))
.error(R.drawable.ic_alarm)
.placeholder(R.drawable.ic_placeholder)
.resize(100, 100)
.into(t)
.size(100, 100)
.target(t)
.build()
viewModel.coilHandler
.get()
.enqueue(request)
}
selectAppInMenu(selectedItem)
}
Expand Down Expand Up @@ -552,11 +556,12 @@ internal class MessagesActivity :
)
}

private suspend fun loadMore(appId: Long) {
private suspend fun loadMore(appId: Long): List<MessageWithImage> {
val messagesWithImages = viewModel.messages.loadMore(appId)
withContext(Dispatchers.Main) {
updateMessagesAndStopLoading(messagesWithImages)
}
return messagesWithImages
}

private suspend fun updateMessagesForApplication(withLoadingSpinner: Boolean, appId: Long) {
Expand Down
Loading

0 comments on commit 2d70b94

Please sign in to comment.