Simplify and optimize Kotlin code snippets
This commit is contained in:
@@ -28,69 +28,65 @@ internal object MarkwonFactory {
|
|||||||
.usePlugin(PicassoImagesPlugin.create(picasso))
|
.usePlugin(PicassoImagesPlugin.create(picasso))
|
||||||
.usePlugin(StrikethroughPlugin.create())
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
.usePlugin(TablePlugin.create(context))
|
.usePlugin(TablePlugin.create(context))
|
||||||
.usePlugin(
|
.usePlugin(object : AbstractMarkwonPlugin() {
|
||||||
object : AbstractMarkwonPlugin() {
|
override fun configureTheme(builder: MarkwonTheme.Builder) {
|
||||||
override fun configureTheme(builder: MarkwonTheme.Builder) {
|
builder.linkColor(ContextCompat.getColor(context, R.color.hyperLink))
|
||||||
builder.linkColor(ContextCompat.getColor(context, R.color.hyperLink))
|
.isLinkUnderlined(true)
|
||||||
.isLinkUnderlined(true)
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createForNotification(context: Context, picasso: Picasso): Markwon {
|
fun createForNotification(context: Context, picasso: Picasso): Markwon {
|
||||||
val headingSizes = floatArrayOf(
|
val headingSizes = floatArrayOf(2f, 1.5f, 1.17f, 1f, .83f, .67f)
|
||||||
2f, 1.5f, 1.17f, 1f, .83f, .67f
|
|
||||||
)
|
|
||||||
val bulletGapWidth = (8 * context.resources.displayMetrics.density + 0.5f).toInt()
|
val bulletGapWidth = (8 * context.resources.displayMetrics.density + 0.5f).toInt()
|
||||||
|
|
||||||
return Markwon.builder(context)
|
return Markwon.builder(context)
|
||||||
.usePlugin(CorePlugin.create())
|
.usePlugin(CorePlugin.create())
|
||||||
.usePlugin(PicassoImagesPlugin.create(picasso))
|
.usePlugin(PicassoImagesPlugin.create(picasso))
|
||||||
.usePlugin(StrikethroughPlugin.create())
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
.usePlugin(
|
.usePlugin(object : AbstractMarkwonPlugin() {
|
||||||
object : AbstractMarkwonPlugin() {
|
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
|
||||||
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
|
builder.setFactory(Heading::class.java) { _, props: RenderProps? ->
|
||||||
builder.setFactory(Heading::class.java) { _, props: RenderProps? ->
|
arrayOf<Any>(
|
||||||
|
RelativeSizeSpan(
|
||||||
|
headingSizes[CoreProps.HEADING_LEVEL.require(props!!) - 1]
|
||||||
|
),
|
||||||
|
StyleSpan(Typeface.BOLD)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setFactory(Emphasis::class.java) { _, _ ->
|
||||||
|
StyleSpan(Typeface.ITALIC)
|
||||||
|
}
|
||||||
|
.setFactory(StrongEmphasis::class.java) { _, _ ->
|
||||||
|
StyleSpan(Typeface.BOLD)
|
||||||
|
}
|
||||||
|
.setFactory(BlockQuote::class.java) { _, _ -> QuoteSpan() }
|
||||||
|
.setFactory(Code::class.java) { _, _ ->
|
||||||
arrayOf<Any>(
|
arrayOf<Any>(
|
||||||
RelativeSizeSpan(
|
BackgroundColorSpan(Color.LTGRAY),
|
||||||
headingSizes[CoreProps.HEADING_LEVEL.require(props!!) - 1]
|
TypefaceSpan("monospace")
|
||||||
),
|
|
||||||
StyleSpan(Typeface.BOLD)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.setFactory(Emphasis::class.java) { _, _ ->
|
.setFactory(ListItem::class.java) { _, _ ->
|
||||||
StyleSpan(Typeface.ITALIC)
|
BulletSpan(bulletGapWidth)
|
||||||
}
|
|
||||||
.setFactory(StrongEmphasis::class.java) { _, _ ->
|
|
||||||
StyleSpan(Typeface.BOLD)
|
|
||||||
}
|
|
||||||
.setFactory(BlockQuote::class.java) { _, _ -> QuoteSpan() }
|
|
||||||
.setFactory(Code::class.java) { _, _ ->
|
|
||||||
arrayOf<Any>(
|
|
||||||
BackgroundColorSpan(Color.LTGRAY),
|
|
||||||
TypefaceSpan("monospace")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.setFactory(ListItem::class.java) { _, _ ->
|
|
||||||
BulletSpan(bulletGapWidth)
|
|
||||||
}
|
|
||||||
.setFactory(Link::class.java) { _, _ -> null }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureParser(builder: Parser.Builder) {
|
|
||||||
builder.extensions(setOf(TablesExtension.create()))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
|
|
||||||
builder.on(
|
|
||||||
TableCell::class.java
|
|
||||||
) { visitor: MarkwonVisitor, node: TableCell? ->
|
|
||||||
visitor.visitChildren(node!!)
|
|
||||||
visitor.builder().append(' ')
|
|
||||||
}
|
}
|
||||||
|
.setFactory(Link::class.java) { _, _ -> null }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureParser(builder: Parser.Builder) {
|
||||||
|
builder.extensions(setOf(TablesExtension.create()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
|
||||||
|
builder.on(
|
||||||
|
TableCell::class.java
|
||||||
|
) { visitor: MarkwonVisitor, node: TableCell? ->
|
||||||
|
visitor.visitChildren(node!!)
|
||||||
|
visitor.builder().append(' ')
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
})
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ internal class MissedMessageUtil(private val api: MessageApi) {
|
|||||||
val messages = pagedMessages!!.messages
|
val messages = pagedMessages!!.messages
|
||||||
val filtered = filter(messages, till)
|
val filtered = filter(messages, till)
|
||||||
result.addAll(filtered)
|
result.addAll(filtered)
|
||||||
if (messages.size != filtered.size
|
if (messages.size != filtered.size ||
|
||||||
|| messages.size == 0
|
messages.size == 0 ||
|
||||||
|| pagedMessages.paging.next == null) {
|
pagedMessages.paging.next == null) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
since = pagedMessages.paging.since
|
since = pagedMessages.paging.since
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package com.github.gotify
|
package com.github.gotify
|
||||||
|
|
||||||
internal class SSLSettings(val validateSSL: Boolean, val cert: String)
|
internal class SSLSettings(val validateSSL: Boolean, val cert: String?)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ internal class Settings(context: Context) {
|
|||||||
var url: String
|
var url: String
|
||||||
get() = sharedPreferences.getString("url", "")!!
|
get() = sharedPreferences.getString("url", "")!!
|
||||||
set(value) = sharedPreferences.edit().putString("url", value).apply()
|
set(value) = sharedPreferences.edit().putString("url", value).apply()
|
||||||
var token: String
|
var token: String?
|
||||||
get() = sharedPreferences.getString("token", "")!!
|
get() = sharedPreferences.getString("token", null)
|
||||||
set(value) = sharedPreferences.edit().putString("token", value).apply()
|
set(value) = sharedPreferences.edit().putString("token", value).apply()
|
||||||
var user: User? = null
|
var user: User? = null
|
||||||
get() {
|
get() {
|
||||||
@@ -26,8 +26,8 @@ internal class Settings(context: Context) {
|
|||||||
var serverVersion: String
|
var serverVersion: String
|
||||||
get() = sharedPreferences.getString("version", "UNKNOWN")!!
|
get() = sharedPreferences.getString("version", "UNKNOWN")!!
|
||||||
set(value) = sharedPreferences.edit().putString("version", value).apply()
|
set(value) = sharedPreferences.edit().putString("version", value).apply()
|
||||||
var cert: String
|
var cert: String?
|
||||||
get() = sharedPreferences.getString("cert", "")!!
|
get() = sharedPreferences.getString("cert", null)
|
||||||
set(value) = sharedPreferences.edit().putString("cert", value).apply()
|
set(value) = sharedPreferences.edit().putString("cert", value).apply()
|
||||||
var validateSSL: Boolean
|
var validateSSL: Boolean
|
||||||
get() = sharedPreferences.getBoolean("validateSSL", true)
|
get() = sharedPreferences.getBoolean("validateSSL", true)
|
||||||
@@ -37,13 +37,13 @@ internal class Settings(context: Context) {
|
|||||||
sharedPreferences = context.getSharedPreferences("gotify", Context.MODE_PRIVATE)
|
sharedPreferences = context.getSharedPreferences("gotify", Context.MODE_PRIVATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tokenExists(): Boolean = token.isNotEmpty()
|
fun tokenExists(): Boolean = !token.isNullOrEmpty()
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
url = ""
|
url = ""
|
||||||
token = ""
|
token = null
|
||||||
validateSSL = true
|
validateSSL = true
|
||||||
cert = ""
|
cert = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setUser(name: String?, admin: Boolean) {
|
fun setUser(name: String?, admin: Boolean) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ internal object Utils {
|
|||||||
.toString()
|
.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resolveAbsoluteUrl(baseURL: String?, target: String?): String? {
|
fun resolveAbsoluteUrl(baseURL: String, target: String?): String? {
|
||||||
return if (target == null) {
|
return if (target == null) {
|
||||||
null
|
null
|
||||||
} else try {
|
} else try {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.github.gotify.api
|
package com.github.gotify.api
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
internal class ApiException : Exception {
|
internal class ApiException : Exception {
|
||||||
@@ -23,12 +22,5 @@ internal class ApiException : Exception {
|
|||||||
code = 0
|
code = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString() = "Code($code) Response: ${body.take(200)}"
|
||||||
return String.format(
|
|
||||||
Locale.ENGLISH,
|
|
||||||
"Code(%d) Response: %s",
|
|
||||||
code,
|
|
||||||
body.substring(0, body.length.coerceAtMost(200))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,7 @@ internal class Callback<T> private constructor(
|
|||||||
fun onError(t: ApiException)
|
fun onError(t: ApiException)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RetrofitCallback<T>(callback: Callback<T>) : retrofit2.Callback<T> {
|
private class RetrofitCallback<T>(private val callback: Callback<T>) : retrofit2.Callback<T> {
|
||||||
private val callback: Callback<T>
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
callback.onSuccess.onSuccess(response.body())
|
callback.onSuccess.onSuccess(response.body())
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ import okhttp3.OkHttpClient
|
|||||||
|
|
||||||
internal object CertUtils {
|
internal object CertUtils {
|
||||||
@SuppressLint("CustomX509TrustManager")
|
@SuppressLint("CustomX509TrustManager")
|
||||||
private val trustAll: X509TrustManager = object : X509TrustManager {
|
private val trustAll = object : X509TrustManager {
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
|
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
|
||||||
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
|
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseCertificate(cert: String): Certificate {
|
fun parseCertificate(cert: String): Certificate {
|
||||||
@@ -45,8 +45,9 @@ internal object CertUtils {
|
|||||||
builder.hostnameVerifier { _, _ -> true }
|
builder.hostnameVerifier { _, _ -> true }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (settings.cert != null) {
|
val cert = settings.cert
|
||||||
val trustManagers = certToTrustManager(settings.cert)
|
if (cert != null) {
|
||||||
|
val trustManagers = certToTrustManager(cert)
|
||||||
if (trustManagers.isNotEmpty()) {
|
if (trustManagers.isNotEmpty()) {
|
||||||
val context = SSLContext.getInstance("TLS")
|
val context = SSLContext.getInstance("TLS")
|
||||||
context.init(arrayOf(), trustManagers, SecureRandom())
|
context.init(arrayOf(), trustManagers, SecureRandom())
|
||||||
@@ -66,7 +67,7 @@ internal object CertUtils {
|
|||||||
private fun certToTrustManager(cert: String): Array<TrustManager> {
|
private fun certToTrustManager(cert: String): Array<TrustManager> {
|
||||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
val certificates = certificateFactory.generateCertificates(Utils.stringToInputStream(cert))
|
val certificates = certificateFactory.generateCertificates(Utils.stringToInputStream(cert))
|
||||||
require(!certificates.isEmpty()) { "expected non-empty set of trusted certificates" }
|
require(certificates.isNotEmpty()) { "expected non-empty set of trusted certificates" }
|
||||||
|
|
||||||
val caKeyStore = newEmptyKeyStore()
|
val caKeyStore = newEmptyKeyStore()
|
||||||
certificates.forEachIndexed { index, certificate ->
|
certificates.forEachIndexed { index, certificate ->
|
||||||
|
|||||||
@@ -9,23 +9,23 @@ import com.github.gotify.client.auth.ApiKeyAuth
|
|||||||
import com.github.gotify.client.auth.HttpBasicAuth
|
import com.github.gotify.client.auth.HttpBasicAuth
|
||||||
|
|
||||||
internal object ClientFactory {
|
internal object ClientFactory {
|
||||||
private fun unauthorized(baseUrl: String, sslSettings: SSLSettings
|
private fun unauthorized(baseUrl: String, sslSettings: SSLSettings): ApiClient {
|
||||||
): ApiClient {
|
return defaultClient(arrayOf(), "$baseUrl/", sslSettings)
|
||||||
return defaultClient(arrayOfNulls(0), "$baseUrl/", sslSettings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun basicAuth(
|
fun basicAuth(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
sslSettings: SSLSettings,
|
sslSettings: SSLSettings,
|
||||||
username: String?,
|
username: String,
|
||||||
password: String?
|
password: String
|
||||||
): ApiClient {
|
): ApiClient {
|
||||||
val client = defaultClient(
|
val client = defaultClient(
|
||||||
arrayOf("basicAuth"),
|
arrayOf("basicAuth"),
|
||||||
"$baseUrl/", sslSettings
|
"$baseUrl/",
|
||||||
|
sslSettings
|
||||||
)
|
)
|
||||||
val auth = client.apiAuthorizations["basicAuth"] as HttpBasicAuth?
|
val auth = client.apiAuthorizations["basicAuth"] as HttpBasicAuth
|
||||||
auth!!.username = username
|
auth.username = username
|
||||||
auth.password = password
|
auth.password = password
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ internal class InitializationActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var response = exception.body
|
var response = exception.body
|
||||||
response = response.substring(0, 200.coerceAtMost(response.length))
|
response = response.take(200)
|
||||||
dialog(getString(R.string.other_error, settings.url, exception.code, response))
|
dialog(getString(R.string.other_error, settings.url, exception.code, response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.github.gotify.log
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.hypertrack.hyperlog.LogFormat
|
import com.hypertrack.hyperlog.LogFormat
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal class Format(context: Context) : LogFormat(context) {
|
internal class Format(context: Context) : LogFormat(context) {
|
||||||
override fun getFormattedLogMessage(
|
override fun getFormattedLogMessage(
|
||||||
@@ -13,5 +12,5 @@ internal class Format(context: Context) : LogFormat(context) {
|
|||||||
senderName: String,
|
senderName: String,
|
||||||
osVersion: String,
|
osVersion: String,
|
||||||
deviceUuid: String
|
deviceUuid: String
|
||||||
) = String.format(Locale.ENGLISH, "%s %s: %s", timeStamp, logLevelName, message)
|
) = "$timeStamp $logLevelName: $message"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ internal class LoginActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun doCheckUrl() {
|
private fun doCheckUrl() {
|
||||||
val url = binding.gotifyUrl.text.toString()
|
var url = binding.gotifyUrl.text.toString()
|
||||||
val parsedUrl = HttpUrl.parse(url)
|
val parsedUrl = HttpUrl.parse(url)
|
||||||
if (parsedUrl == null) {
|
if (parsedUrl == null) {
|
||||||
Utils.showSnackBar(this, "Invalid URL (include http:// or https://)")
|
Utils.showSnackBar(this, "Invalid URL (include http:// or https://)")
|
||||||
@@ -97,19 +97,17 @@ internal class LoginActivity : AppCompatActivity() {
|
|||||||
binding.checkurlProgress.visibility = View.VISIBLE
|
binding.checkurlProgress.visibility = View.VISIBLE
|
||||||
binding.checkurl.visibility = View.GONE
|
binding.checkurl.visibility = View.GONE
|
||||||
|
|
||||||
val trimmedUrl = url.trim()
|
url = url.trim()
|
||||||
val fixedUrl = if (trimmedUrl.endsWith("/")) {
|
if (url.endsWith("/")) url.dropLast(1)
|
||||||
trimmedUrl.substring(0, trimmedUrl.length - 1)
|
|
||||||
} else trimmedUrl
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ClientFactory.versionApi(fixedUrl, tempSslSettings())
|
ClientFactory.versionApi(url, tempSslSettings())
|
||||||
?.version
|
?.version
|
||||||
?.enqueue(Callback.callInUI(this, onValidUrl(fixedUrl), onInvalidUrl(fixedUrl)))
|
?.enqueue(Callback.callInUI(this, onValidUrl(url), onInvalidUrl(url)))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
binding.checkurlProgress.visibility = View.GONE
|
binding.checkurlProgress.visibility = View.GONE
|
||||||
binding.checkurl.visibility = View.VISIBLE
|
binding.checkurl.visibility = View.VISIBLE
|
||||||
val errorMsg = getString(R.string.version_failed, "$fixedUrl/version", e.message)
|
val errorMsg = getString(R.string.version_failed, "$url/version", e.message)
|
||||||
Utils.showSnackBar(this, errorMsg)
|
Utils.showSnackBar(this, errorMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +167,7 @@ internal class LoginActivity : AppCompatActivity() {
|
|||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
try {
|
try {
|
||||||
if (requestCode == FILE_SELECT_CODE) {
|
if (requestCode == FILE_SELECT_CODE) {
|
||||||
require(resultCode == RESULT_OK) { String.format("result was %d", resultCode) }
|
require(resultCode == RESULT_OK) { "result was $resultCode" }
|
||||||
requireNotNull(data) { "file path was null" }
|
requireNotNull(data) { "file path was null" }
|
||||||
|
|
||||||
val uri = data.data ?: throw IllegalArgumentException("file path was null")
|
val uri = data.data ?: throw IllegalArgumentException("file path was null")
|
||||||
@@ -268,7 +266,7 @@ internal class LoginActivity : AppCompatActivity() {
|
|||||||
private fun onCreatedClient(client: Client) {
|
private fun onCreatedClient(client: Client) {
|
||||||
settings.token = client.token
|
settings.token = client.token
|
||||||
settings.validateSSL = !disableSslValidation
|
settings.validateSSL = !disableSslValidation
|
||||||
settings.cert = caCertContents.toString()
|
settings.cert = caCertContents
|
||||||
|
|
||||||
Utils.showSnackBar(this, getString(R.string.created_client))
|
Utils.showSnackBar(this, getString(R.string.created_client))
|
||||||
startActivity(Intent(this, InitializationActivity::class.java))
|
startActivity(Intent(this, InitializationActivity::class.java))
|
||||||
@@ -291,6 +289,6 @@ internal class LoginActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun tempSslSettings(): SSLSettings {
|
private fun tempSslSettings(): SSLSettings {
|
||||||
return SSLSettings(!disableSslValidation, caCertContents.toString())
|
return SSLSettings(!disableSslValidation, caCertContents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package com.github.gotify.messages
|
|||||||
import com.github.gotify.client.model.Message
|
import com.github.gotify.client.model.Message
|
||||||
|
|
||||||
internal object Extras {
|
internal object Extras {
|
||||||
fun useMarkdown(message: Message): Boolean {
|
fun useMarkdown(message: Message): Boolean = useMarkdown(message.extras)
|
||||||
return useMarkdown(message.extras)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun useMarkdown(extras: Map<String, Any>?): Boolean {
|
fun useMarkdown(extras: Map<String, Any>?): Boolean {
|
||||||
if (extras == null) {
|
if (extras == null) {
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ import com.github.gotify.databinding.MessageItemCompactBinding
|
|||||||
import com.github.gotify.messages.provider.MessageWithImage
|
import com.github.gotify.messages.provider.MessageWithImage
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import org.threeten.bp.OffsetDateTime
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import org.threeten.bp.OffsetDateTime
|
||||||
|
|
||||||
internal class ListMessageAdapter(
|
internal class ListMessageAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -72,13 +72,13 @@ internal class ListMessageAdapter(
|
|||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val message = items[position]
|
val message = items[position]
|
||||||
if (Extras.useMarkdown(message.message)) {
|
if (Extras.useMarkdown(message.message)) {
|
||||||
holder.message!!.autoLinkMask = 0
|
holder.message.autoLinkMask = 0
|
||||||
markwon.setMarkdown(holder.message!!, message.message.message)
|
markwon.setMarkdown(holder.message, message.message.message)
|
||||||
} else {
|
} else {
|
||||||
holder.message!!.autoLinkMask = Linkify.WEB_URLS
|
holder.message.autoLinkMask = Linkify.WEB_URLS
|
||||||
holder.message!!.text = message.message.message
|
holder.message.text = message.message.message
|
||||||
}
|
}
|
||||||
holder.title!!.text = message.message.title
|
holder.title.text = message.message.title
|
||||||
picasso.load(Utils.resolveAbsoluteUrl("${settings.url}/", message.image))
|
picasso.load(Utils.resolveAbsoluteUrl("${settings.url}/", message.image))
|
||||||
.error(R.drawable.ic_alarm)
|
.error(R.drawable.ic_alarm)
|
||||||
.placeholder(R.drawable.ic_placeholder)
|
.placeholder(R.drawable.ic_placeholder)
|
||||||
@@ -87,10 +87,10 @@ internal class ListMessageAdapter(
|
|||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val timeFormat = prefs.getString(timeFormatPrefsKey, timeFormatRelative)
|
val timeFormat = prefs.getString(timeFormatPrefsKey, timeFormatRelative)
|
||||||
holder.setDateTime(message.message.date, timeFormat == timeFormatRelative)
|
holder.setDateTime(message.message.date, timeFormat == timeFormatRelative)
|
||||||
holder.date!!.setOnClickListener { holder.switchTimeFormat() }
|
holder.date.setOnClickListener { holder.switchTimeFormat() }
|
||||||
|
|
||||||
holder.delete!!.setOnClickListener {
|
holder.delete.setOnClickListener {
|
||||||
delete.delete(holder.adapterPosition, message.message!!, false)
|
delete.delete(holder.adapterPosition, message.message, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,14 +102,14 @@ internal class ListMessageAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
|
class ViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
var image: ImageView? = null
|
lateinit var image: ImageView
|
||||||
var message: TextView? = null
|
lateinit var message: TextView
|
||||||
var title: TextView? = null
|
lateinit var title: TextView
|
||||||
var date: TextView? = null
|
lateinit var date: TextView
|
||||||
var delete: ImageButton? = null
|
lateinit var delete: ImageButton
|
||||||
|
|
||||||
private var relativeTimeFormat = true
|
private var relativeTimeFormat = true
|
||||||
private var dateTime: OffsetDateTime? = null
|
private lateinit var dateTime: OffsetDateTime
|
||||||
|
|
||||||
init {
|
init {
|
||||||
enableCopyToClipboard()
|
enableCopyToClipboard()
|
||||||
@@ -133,38 +133,35 @@ internal class ListMessageAdapter(
|
|||||||
updateDate()
|
updateDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDateTime(dateTime: OffsetDateTime?, relativeTimeFormatPreference: Boolean) {
|
fun setDateTime(dateTime: OffsetDateTime, relativeTimeFormatPreference: Boolean) {
|
||||||
this.dateTime = dateTime
|
this.dateTime = dateTime
|
||||||
relativeTimeFormat = relativeTimeFormatPreference
|
relativeTimeFormat = relativeTimeFormatPreference
|
||||||
updateDate()
|
updateDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDate() {
|
private fun updateDate() {
|
||||||
var text = "?"
|
val text = if (relativeTimeFormat) {
|
||||||
if (dateTime != null) {
|
// Relative time format
|
||||||
text = if (relativeTimeFormat) {
|
Utils.dateToRelative(dateTime)
|
||||||
// Relative time format
|
} else {
|
||||||
Utils.dateToRelative(dateTime!!)
|
// Absolute time format
|
||||||
|
val time = dateTime.toInstant().toEpochMilli()
|
||||||
|
val date = Date(time)
|
||||||
|
if (DateUtils.isToday(time)) {
|
||||||
|
DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
||||||
} else {
|
} else {
|
||||||
// Absolute time format
|
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(date)
|
||||||
val time = dateTime!!.toInstant().toEpochMilli()
|
|
||||||
val date = Date(time)
|
|
||||||
if (DateUtils.isToday(time)) {
|
|
||||||
DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
|
||||||
} else {
|
|
||||||
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT)
|
|
||||||
.format(date)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
date!!.text = text
|
|
||||||
|
date.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableCopyToClipboard() {
|
private fun enableCopyToClipboard() {
|
||||||
super.itemView.setOnLongClickListener { view: View ->
|
super.itemView.setOnLongClickListener { view: View ->
|
||||||
val clipboard = view.context
|
val clipboard = view.context
|
||||||
.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
|
.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
|
||||||
val clip = ClipData.newPlainText("GotifyMessageContent", message!!.text.toString())
|
val clip = ClipData.newPlainText("GotifyMessageContent", message.text.toString())
|
||||||
if (clipboard != null) {
|
if (clipboard != null) {
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.setPrimaryClip(clip)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@@ -178,7 +175,7 @@ internal class ListMessageAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Delete {
|
fun interface Delete {
|
||||||
fun delete(position: Int, message: Message, listAnimation: Boolean)
|
fun delete(position: Int, message: Message, listAnimation: Boolean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,8 @@ import com.github.gotify.init.InitializationActivity
|
|||||||
import com.github.gotify.log.Log
|
import com.github.gotify.log.Log
|
||||||
import com.github.gotify.log.LogsActivity
|
import com.github.gotify.log.LogsActivity
|
||||||
import com.github.gotify.login.LoginActivity
|
import com.github.gotify.login.LoginActivity
|
||||||
import com.github.gotify.messages.ListMessageAdapter.Delete
|
|
||||||
import com.github.gotify.messages.provider.*
|
import com.github.gotify.messages.provider.*
|
||||||
import com.github.gotify.service.WebSocketService
|
import com.github.gotify.service.WebSocketService
|
||||||
import com.github.gotify.service.WebSocketService.Companion.NEW_MESSAGE_BROADCAST
|
|
||||||
import com.github.gotify.settings.SettingsActivity
|
import com.github.gotify.settings.SettingsActivity
|
||||||
import com.github.gotify.sharing.ShareActivity
|
import com.github.gotify.sharing.ShareActivity
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
@@ -86,8 +84,7 @@ internal class MessagesActivity :
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMessagesBinding.inflate(layoutInflater)
|
binding = ActivityMessagesBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
viewModel = ViewModelProvider(this, MessagesModelFactory(this))
|
viewModel = ViewModelProvider(this, MessagesModelFactory(this))[MessagesModel::class.java]
|
||||||
.get(MessagesModel::class.java)
|
|
||||||
Log.i("Entering " + javaClass.simpleName)
|
Log.i("Entering " + javaClass.simpleName)
|
||||||
initDrawer()
|
initDrawer()
|
||||||
val layoutManager = LinearLayoutManager(this)
|
val layoutManager = LinearLayoutManager(this)
|
||||||
@@ -99,16 +96,14 @@ internal class MessagesActivity :
|
|||||||
this,
|
this,
|
||||||
viewModel.settings,
|
viewModel.settings,
|
||||||
viewModel.picassoHandler.get(),
|
viewModel.picassoHandler.get(),
|
||||||
emptyList(),
|
emptyList()
|
||||||
object : Delete {
|
) { position, message, listAnimation ->
|
||||||
override fun delete(position: Int, message: Message, listAnimation: Boolean) {
|
scheduleDeletion(
|
||||||
scheduleDeletion(
|
position,
|
||||||
position,
|
message,
|
||||||
message,
|
listAnimation
|
||||||
listAnimation
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
messagesView.addItemDecoration(dividerItemDecoration)
|
messagesView.addItemDecoration(dividerItemDecoration)
|
||||||
messagesView.setHasFixedSize(true)
|
messagesView.setHasFixedSize(true)
|
||||||
messagesView.layoutManager = layoutManager
|
messagesView.layoutManager = layoutManager
|
||||||
@@ -191,23 +186,18 @@ internal class MessagesActivity :
|
|||||||
viewModel.targetReferences.clear()
|
viewModel.targetReferences.clear()
|
||||||
updateMessagesAndStopLoading(viewModel.messages[viewModel.appId])
|
updateMessagesAndStopLoading(viewModel.messages[viewModel.appId])
|
||||||
var selectedItem = menu.findItem(R.id.nav_all_messages)
|
var selectedItem = menu.findItem(R.id.nav_all_messages)
|
||||||
for (i in applications.indices) {
|
applications.indices.forEach {
|
||||||
val app = applications[i]
|
val app = applications[it]
|
||||||
val item = menu.add(R.id.apps, i, APPLICATION_ORDER, app.name)
|
val item = menu.add(R.id.apps, it, APPLICATION_ORDER, app.name)
|
||||||
item.isCheckable = true
|
item.isCheckable = true
|
||||||
if (app.id == viewModel.appId) selectedItem = item
|
if (app.id == viewModel.appId) selectedItem = item
|
||||||
val t = Utils.toDrawable(
|
val t = Utils.toDrawable(resources) { icon: Drawable? ->
|
||||||
resources
|
item.icon = icon
|
||||||
) { icon: Drawable? -> item.icon = icon }
|
}
|
||||||
viewModel.targetReferences.add(t)
|
viewModel.targetReferences.add(t)
|
||||||
viewModel
|
viewModel.picassoHandler
|
||||||
.picassoHandler
|
|
||||||
.get()
|
.get()
|
||||||
.load(
|
.load(Utils.resolveAbsoluteUrl(viewModel.settings.url + "/", app.image))
|
||||||
Utils.resolveAbsoluteUrl(
|
|
||||||
viewModel.settings.url + "/", app.image
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.error(R.drawable.ic_alarm)
|
.error(R.drawable.ic_alarm)
|
||||||
.placeholder(R.drawable.ic_placeholder)
|
.placeholder(R.drawable.ic_placeholder)
|
||||||
.resize(100, 100)
|
.resize(100, 100)
|
||||||
@@ -239,10 +229,8 @@ internal class MessagesActivity :
|
|||||||
version.text =
|
version.text =
|
||||||
getString(R.string.versions, BuildConfig.VERSION_NAME, settings.serverVersion)
|
getString(R.string.versions, BuildConfig.VERSION_NAME, settings.serverVersion)
|
||||||
val refreshAll = headerView.findViewById<ImageButton>(R.id.refresh_all)
|
val refreshAll = headerView.findViewById<ImageButton>(R.id.refresh_all)
|
||||||
refreshAll.setOnClickListener { view: View? ->
|
refreshAll.setOnClickListener {
|
||||||
onRefreshAll(
|
onRefreshAll(it)
|
||||||
view
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +247,7 @@ internal class MessagesActivity :
|
|||||||
val id = item.itemId
|
val id = item.itemId
|
||||||
if (item.groupId == R.id.apps) {
|
if (item.groupId == R.id.apps) {
|
||||||
val app = viewModel.appsHolder.get()[id]
|
val app = viewModel.appsHolder.get()[id]
|
||||||
updateAppOnDrawerClose = if (app != null) app.id else MessageState.ALL_MESSAGES
|
updateAppOnDrawerClose = app.id
|
||||||
startLoading()
|
startLoading()
|
||||||
binding.appBarDrawer.toolbar.subtitle = item.title
|
binding.appBarDrawer.toolbar.subtitle = item.title
|
||||||
} else if (id == R.id.nav_all_messages) {
|
} else if (id == R.id.nav_all_messages) {
|
||||||
@@ -273,7 +261,7 @@ internal class MessagesActivity :
|
|||||||
.setPositiveButton(R.string.yes) { _, _ ->
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
doLogout()
|
doLogout()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { a, b -> }
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
} else if (id == R.id.nav_logs) {
|
} else if (id == R.id.nav_logs) {
|
||||||
startActivity(Intent(this, LogsActivity::class.java))
|
startActivity(Intent(this, LogsActivity::class.java))
|
||||||
@@ -309,22 +297,22 @@ internal class MessagesActivity :
|
|||||||
val nManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
val nManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
nManager.cancelAll()
|
nManager.cancelAll()
|
||||||
val filter = IntentFilter()
|
val filter = IntentFilter()
|
||||||
filter.addAction(NEW_MESSAGE_BROADCAST)
|
filter.addAction(WebSocketService.NEW_MESSAGE_BROADCAST)
|
||||||
registerReceiver(receiver, filter)
|
registerReceiver(receiver, filter)
|
||||||
launchCoroutine {
|
launchCoroutine {
|
||||||
updateMissedMessages(viewModel.messages.getLastReceivedMessage())
|
updateMissedMessages(viewModel.messages.getLastReceivedMessage())
|
||||||
}
|
}
|
||||||
var selectedIndex: Int = R.id.nav_all_messages
|
var selectedIndex = R.id.nav_all_messages
|
||||||
val appId = viewModel.appId
|
val appId = viewModel.appId
|
||||||
if (appId != MessageState.ALL_MESSAGES) {
|
if (appId != MessageState.ALL_MESSAGES) {
|
||||||
val apps = viewModel.appsHolder.get()
|
val apps = viewModel.appsHolder.get()
|
||||||
for (i in apps.indices) {
|
apps.indices.forEach {
|
||||||
if (apps[i].id == appId) {
|
if (apps[it].id == appId) {
|
||||||
selectedIndex = i
|
selectedIndex = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listMessageAdapter!!.notifyDataSetChanged()
|
listMessageAdapter.notifyDataSetChanged()
|
||||||
selectAppInMenu(binding.navView.menu.findItem(selectedIndex))
|
selectAppInMenu(binding.navView.menu.findItem(selectedIndex))
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
@@ -337,17 +325,26 @@ internal class MessagesActivity :
|
|||||||
private fun selectAppInMenu(appItem: MenuItem?) {
|
private fun selectAppInMenu(appItem: MenuItem?) {
|
||||||
if (appItem != null) {
|
if (appItem != null) {
|
||||||
appItem.isChecked = true
|
appItem.isChecked = true
|
||||||
if (appItem.itemId != R.id.nav_all_messages) binding.appBarDrawer.toolbar.subtitle =
|
if (appItem.itemId != R.id.nav_all_messages) {
|
||||||
appItem.title
|
binding.appBarDrawer.toolbar.subtitle = appItem.title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scheduleDeletion(position: Int, message: Message, listAnimation: Boolean) {
|
private fun scheduleDeletion(
|
||||||
|
position: Int,
|
||||||
|
message: Message,
|
||||||
|
listAnimation: Boolean
|
||||||
|
) {
|
||||||
val adapter = binding.messagesView.adapter as ListMessageAdapter
|
val adapter = binding.messagesView.adapter as ListMessageAdapter
|
||||||
val messages = viewModel.messages
|
val messages = viewModel.messages
|
||||||
messages.deleteLocal(message)
|
messages.deleteLocal(message)
|
||||||
adapter.items = messages[viewModel.appId]
|
adapter.items = messages[viewModel.appId]
|
||||||
if (listAnimation) adapter.notifyItemRemoved(position) else adapter.notifyDataSetChanged()
|
if (listAnimation) {
|
||||||
|
adapter.notifyItemRemoved(position)
|
||||||
|
} else {
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
showDeletionSnackbar()
|
showDeletionSnackbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,17 +355,19 @@ internal class MessagesActivity :
|
|||||||
val adapter = binding.messagesView.adapter as ListMessageAdapter
|
val adapter = binding.messagesView.adapter as ListMessageAdapter
|
||||||
val appId = viewModel.appId
|
val appId = viewModel.appId
|
||||||
adapter.items = messages[appId]
|
adapter.items = messages[appId]
|
||||||
val insertPosition =
|
val insertPosition = if (appId == MessageState.ALL_MESSAGES) {
|
||||||
if (appId == MessageState.ALL_MESSAGES) deletion.allPosition else deletion.appPosition
|
deletion.allPosition
|
||||||
|
} else {
|
||||||
|
deletion.appPosition
|
||||||
|
}
|
||||||
adapter.notifyItemInserted(insertPosition)
|
adapter.notifyItemInserted(insertPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDeletionSnackbar() {
|
private fun showDeletionSnackbar() {
|
||||||
val view: View = binding.swipeRefresh
|
val view: View = binding.swipeRefresh
|
||||||
val snackbar: Snackbar =
|
val snackbar = Snackbar.make(view, R.string.snackbar_deleted, Snackbar.LENGTH_LONG)
|
||||||
Snackbar.make(view, R.string.snackbar_deleted, Snackbar.LENGTH_LONG)
|
snackbar.setAction(R.string.snackbar_undo) { undoDelete() }
|
||||||
snackbar.setAction(R.string.snackbar_undo) { v -> undoDelete() }
|
|
||||||
snackbar.addCallback(SnackbarCallback())
|
snackbar.addCallback(SnackbarCallback())
|
||||||
snackbar.show()
|
snackbar.show()
|
||||||
}
|
}
|
||||||
@@ -389,8 +388,9 @@ internal class MessagesActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class SwipeToDeleteCallback(private val adapter: ListMessageAdapter) :
|
private inner class SwipeToDeleteCallback(
|
||||||
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
private val adapter: ListMessageAdapter
|
||||||
|
) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||||
private var icon: Drawable?
|
private var icon: Drawable?
|
||||||
private val background: ColorDrawable
|
private val background: ColorDrawable
|
||||||
|
|
||||||
@@ -411,9 +411,7 @@ internal class MessagesActivity :
|
|||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
): Boolean {
|
) = false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.adapterPosition
|
val position = viewHolder.adapterPosition
|
||||||
@@ -475,14 +473,15 @@ internal class MessagesActivity :
|
|||||||
|
|
||||||
private inner class MessageListOnScrollListener : RecyclerView.OnScrollListener() {
|
private inner class MessageListOnScrollListener : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrollStateChanged(view: RecyclerView, scrollState: Int) {}
|
override fun onScrollStateChanged(view: RecyclerView, scrollState: Int) {}
|
||||||
|
|
||||||
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
|
||||||
val linearLayoutManager = view.layoutManager as LinearLayoutManager?
|
val linearLayoutManager = view.layoutManager as LinearLayoutManager?
|
||||||
if (linearLayoutManager != null) {
|
if (linearLayoutManager != null) {
|
||||||
val lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition()
|
val lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition()
|
||||||
val totalItemCount = view.adapter!!.itemCount
|
val totalItemCount = view.adapter!!.itemCount
|
||||||
if (lastVisibleItem > totalItemCount - 15 && totalItemCount != 0 && viewModel.messages.canLoadMore(
|
if (lastVisibleItem > totalItemCount - 15 &&
|
||||||
viewModel.appId
|
totalItemCount != 0 &&
|
||||||
)
|
viewModel.messages.canLoadMore(viewModel.appId)
|
||||||
) {
|
) {
|
||||||
if (!isLoadMore) {
|
if (!isLoadMore) {
|
||||||
isLoadMore = true
|
isLoadMore = true
|
||||||
@@ -540,13 +539,8 @@ internal class MessagesActivity :
|
|||||||
client.createService(ApplicationApi::class.java)
|
client.createService(ApplicationApi::class.java)
|
||||||
.deleteApp(appId)
|
.deleteApp(appId)
|
||||||
.enqueue(
|
.enqueue(
|
||||||
Callback.callInUI(
|
Callback.callInUI(this, { refreshAll() }) {
|
||||||
this,
|
Utils.showSnackBar(this, getString(R.string.error_delete_app))
|
||||||
{ refreshAll() }
|
|
||||||
) {
|
|
||||||
Utils.showSnackBar(
|
|
||||||
this, getString(R.string.error_delete_app)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.app.Activity
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.github.gotify.Settings
|
import com.github.gotify.Settings
|
||||||
import com.github.gotify.api.ClientFactory
|
import com.github.gotify.api.ClientFactory
|
||||||
import com.github.gotify.client.ApiClient
|
|
||||||
import com.github.gotify.client.api.MessageApi
|
import com.github.gotify.client.api.MessageApi
|
||||||
import com.github.gotify.messages.provider.ApplicationHolder
|
import com.github.gotify.messages.provider.ApplicationHolder
|
||||||
import com.github.gotify.messages.provider.MessageFacade
|
import com.github.gotify.messages.provider.MessageFacade
|
||||||
@@ -13,22 +12,14 @@ import com.github.gotify.picasso.PicassoHandler
|
|||||||
import com.squareup.picasso.Target
|
import com.squareup.picasso.Target
|
||||||
|
|
||||||
internal class MessagesModel(parentView: Activity) : ViewModel() {
|
internal class MessagesModel(parentView: Activity) : ViewModel() {
|
||||||
val settings: Settings
|
val settings = Settings(parentView)
|
||||||
val picassoHandler: PicassoHandler
|
val picassoHandler = PicassoHandler(parentView, settings)
|
||||||
val client: ApiClient
|
val client = ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token)
|
||||||
val appsHolder: ApplicationHolder
|
val appsHolder = ApplicationHolder(parentView, client)
|
||||||
val messages: MessageFacade
|
val messages = MessageFacade(client.createService(MessageApi::class.java), appsHolder)
|
||||||
|
|
||||||
// we need to keep the target references otherwise they get gc'ed before they can be called.
|
// we need to keep the target references otherwise they get gc'ed before they can be called.
|
||||||
val targetReferences = mutableListOf<Target>()
|
val targetReferences = mutableListOf<Target>()
|
||||||
|
|
||||||
var appId = MessageState.ALL_MESSAGES
|
var appId = MessageState.ALL_MESSAGES
|
||||||
|
|
||||||
init {
|
|
||||||
settings = Settings(parentView)
|
|
||||||
picassoHandler = PicassoHandler(parentView, settings)
|
|
||||||
client = ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token)
|
|
||||||
appsHolder = ApplicationHolder(parentView, client)
|
|
||||||
messages = MessageFacade(client.createService(MessageApi::class.java), appsHolder)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ import com.github.gotify.client.api.ApplicationApi
|
|||||||
import com.github.gotify.client.model.Application
|
import com.github.gotify.client.model.Application
|
||||||
|
|
||||||
internal class ApplicationHolder(private val activity: Activity, private val client: ApiClient) {
|
internal class ApplicationHolder(private val activity: Activity, private val client: ApiClient) {
|
||||||
private var state: List<Application> = listOf()
|
private var state = listOf<Application>()
|
||||||
private var onUpdate: Runnable? = null
|
private var onUpdate: Runnable? = null
|
||||||
private var onUpdateFailed: Runnable? = null
|
private var onUpdateFailed: Runnable? = null
|
||||||
|
|
||||||
fun wasRequested(): Boolean {
|
fun wasRequested() = state.isNotEmpty()
|
||||||
return state.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun request() {
|
fun request() {
|
||||||
client.createService(ApplicationApi::class.java)
|
client.createService(ApplicationApi::class.java)
|
||||||
|
|||||||
@@ -4,13 +4,8 @@ import com.github.gotify.client.api.MessageApi
|
|||||||
import com.github.gotify.client.model.Message
|
import com.github.gotify.client.model.Message
|
||||||
|
|
||||||
internal class MessageFacade(api: MessageApi, private val applicationHolder: ApplicationHolder) {
|
internal class MessageFacade(api: MessageApi, private val applicationHolder: ApplicationHolder) {
|
||||||
private val requester: MessageRequester
|
private val requester = MessageRequester(api)
|
||||||
private val state: MessageStateHolder
|
private val state = MessageStateHolder()
|
||||||
|
|
||||||
init {
|
|
||||||
requester = MessageRequester(api)
|
|
||||||
state = MessageStateHolder()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
operator fun get(appId: Long): List<MessageWithImage> {
|
operator fun get(appId: Long): List<MessageWithImage> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.github.gotify.log.Log
|
|||||||
internal class MessageRequester(private val messageApi: MessageApi) {
|
internal class MessageRequester(private val messageApi: MessageApi) {
|
||||||
fun loadMore(state: MessageState): PagedMessages? {
|
fun loadMore(state: MessageState): PagedMessages? {
|
||||||
return try {
|
return try {
|
||||||
Log.i("Loading more messages for " + state.appId)
|
Log.i("Loading more messages for ${state.appId}")
|
||||||
if (MessageState.ALL_MESSAGES == state.appId) {
|
if (MessageState.ALL_MESSAGES == state.appId) {
|
||||||
Api.execute(messageApi.getMessages(LIMIT, state.nextSince))
|
Api.execute(messageApi.getMessages(LIMIT, state.nextSince))
|
||||||
} else {
|
} else {
|
||||||
@@ -24,7 +24,7 @@ internal class MessageRequester(private val messageApi: MessageApi) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun asyncRemoveMessage(message: Message) {
|
fun asyncRemoveMessage(message: Message) {
|
||||||
Log.i("Removing message with id " + message.id)
|
Log.i("Removing message with id ${message.id}")
|
||||||
messageApi.deleteMessage(message.id).enqueue(Callback.call())
|
messageApi.deleteMessage(message.id).enqueue(Callback.call())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ internal class PicassoDataRequestHandler : RequestHandler() {
|
|||||||
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||||
|
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
val show = if (uri.length > 50) uri.substring(0, 49) + "..." else uri
|
val show = if (uri.length > 50) uri.take(50) + "..." else uri
|
||||||
val malformed = RuntimeException("Malformed data uri: $show")
|
val malformed = RuntimeException("Malformed data uri: $show")
|
||||||
Log.e("Could not load image", malformed)
|
Log.e("Could not load image", malformed)
|
||||||
throw malformed
|
throw malformed
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import com.github.gotify.log.Log
|
|||||||
import com.github.gotify.messages.provider.MessageImageCombiner
|
import com.github.gotify.messages.provider.MessageImageCombiner
|
||||||
import com.squareup.picasso.OkHttp3Downloader
|
import com.squareup.picasso.OkHttp3Downloader
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import okhttp3.Cache
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import okhttp3.Cache
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
internal class PicassoHandler(private val context: Context, private val settings: Settings) {
|
internal class PicassoHandler(private val context: Context, private val settings: Settings) {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -31,7 +31,7 @@ internal class PicassoHandler(private val context: Context, private val settings
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val picasso: Picasso = makePicasso()
|
private val picasso: Picasso = makePicasso()
|
||||||
private val appIdToAppImage: MutableMap<Long, String> = mutableMapOf()
|
private val appIdToAppImage = mutableMapOf<Long, String>()
|
||||||
|
|
||||||
private fun makePicasso(): Picasso {
|
private fun makePicasso(): Picasso {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
@@ -45,9 +45,7 @@ internal class PicassoHandler(private val context: Context, private val settings
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getImageFromUrl(url: String?): Bitmap {
|
fun getImageFromUrl(url: String?): Bitmap = picasso.load(url).get()
|
||||||
return picasso.load(url).get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getIcon(appId: Long): Bitmap {
|
fun getIcon(appId: Long): Bitmap {
|
||||||
if (appId == -1L) {
|
if (appId == -1L) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import okhttp3.*
|
|||||||
internal class WebSocketConnection(
|
internal class WebSocketConnection(
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
settings: SSLSettings,
|
settings: SSLSettings,
|
||||||
private val token: String,
|
private val token: String?,
|
||||||
private val connectivityManager: ConnectivityManager,
|
private val connectivityManager: ConnectivityManager,
|
||||||
private val alarmManager: AlarmManager
|
private val alarmManager: AlarmManager
|
||||||
) {
|
) {
|
||||||
@@ -33,12 +33,12 @@ internal class WebSocketConnection(
|
|||||||
private var errorCount = 0
|
private var errorCount = 0
|
||||||
|
|
||||||
private var webSocket: WebSocket? = null
|
private var webSocket: WebSocket? = null
|
||||||
private var onMessage: SuccessCallback<Message>? = null
|
private lateinit var onMessage: SuccessCallback<Message>
|
||||||
private var onClose: Runnable? = null
|
private lateinit var onClose: Runnable
|
||||||
private var onOpen: Runnable? = null
|
private lateinit var onOpen: Runnable
|
||||||
private var onBadRequest: BadRequestRunnable? = null
|
private lateinit var onBadRequest: BadRequestRunnable
|
||||||
private var onNetworkFailure: OnNetworkFailureRunnable? = null
|
private lateinit var onNetworkFailure: OnNetworkFailureRunnable
|
||||||
private var onReconnected: Runnable? = null
|
private lateinit var onReconnected: Runnable
|
||||||
private var state: State? = null
|
private var state: State? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -149,10 +149,10 @@ internal class WebSocketConnection(
|
|||||||
syncExec {
|
syncExec {
|
||||||
state = State.Connected
|
state = State.Connected
|
||||||
Log.i("WebSocket($id): opened")
|
Log.i("WebSocket($id): opened")
|
||||||
onOpen!!.run()
|
onOpen.run()
|
||||||
|
|
||||||
if (errorCount > 0) {
|
if (errorCount > 0) {
|
||||||
onReconnected!!.run()
|
onReconnected.run()
|
||||||
errorCount = 0
|
errorCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ internal class WebSocketConnection(
|
|||||||
syncExec {
|
syncExec {
|
||||||
Log.i("WebSocket($id): received message $text")
|
Log.i("WebSocket($id): received message $text")
|
||||||
val message = Utils.JSON.fromJson(text, Message::class.java)
|
val message = Utils.JSON.fromJson(text, Message::class.java)
|
||||||
onMessage!!.onSuccess(message)
|
onMessage.onSuccess(message)
|
||||||
}
|
}
|
||||||
super.onMessage(webSocket, text)
|
super.onMessage(webSocket, text)
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ internal class WebSocketConnection(
|
|||||||
syncExec {
|
syncExec {
|
||||||
if (state == State.Connected) {
|
if (state == State.Connected) {
|
||||||
Log.w("WebSocket($id): closed")
|
Log.w("WebSocket($id): closed")
|
||||||
onClose!!.run()
|
onClose.run()
|
||||||
}
|
}
|
||||||
state = State.Disconnected
|
state = State.Disconnected
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ internal class WebSocketConnection(
|
|||||||
syncExec {
|
syncExec {
|
||||||
state = State.Disconnected
|
state = State.Disconnected
|
||||||
if (response != null && response.code() >= 400 && response.code() <= 499) {
|
if (response != null && response.code() >= 400 && response.code() <= 499) {
|
||||||
onBadRequest!!.execute(message)
|
onBadRequest.execute(message)
|
||||||
close()
|
close()
|
||||||
return@syncExec
|
return@syncExec
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ internal class WebSocketConnection(
|
|||||||
|
|
||||||
val minutes = (errorCount * 2 - 1).coerceAtMost(20)
|
val minutes = (errorCount * 2 - 1).coerceAtMost(20)
|
||||||
|
|
||||||
onNetworkFailure!!.execute(minutes)
|
onNetworkFailure.execute(minutes)
|
||||||
scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes.toLong()))
|
scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes.toLong()))
|
||||||
}
|
}
|
||||||
super.onFailure(webSocket, t, response)
|
super.onFailure(webSocket, t, response)
|
||||||
@@ -215,11 +215,11 @@ internal class WebSocketConnection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface BadRequestRunnable {
|
internal fun interface BadRequestRunnable {
|
||||||
fun execute(message: String)
|
fun execute(message: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface OnNetworkFailureRunnable {
|
internal fun interface OnNetworkFailureRunnable {
|
||||||
fun execute(minutes: Int)
|
fun execute(minutes: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,16 +95,8 @@ internal class WebSocketService : Service() {
|
|||||||
)
|
)
|
||||||
.onOpen { onOpen() }
|
.onOpen { onOpen() }
|
||||||
.onClose { onClose() }
|
.onClose { onClose() }
|
||||||
.onBadRequest(object : BadRequestRunnable {
|
.onBadRequest { message -> onBadRequest(message) }
|
||||||
override fun execute(message: String) {
|
.onNetworkFailure { minutes -> onNetworkFailure(minutes) }
|
||||||
onBadRequest(message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onNetworkFailure(object : OnNetworkFailureRunnable {
|
|
||||||
override fun execute(minutes: Int) {
|
|
||||||
onNetworkFailure(minutes)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onMessage { if (it != null) onMessage(it) }
|
.onMessage { if (it != null) onMessage(it) }
|
||||||
.onReconnected { notifyMissedNotifications() }
|
.onReconnected { notifyMissedNotifications() }
|
||||||
.start()
|
.start()
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
|
|||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||||
if (getString(R.string.setting_key_theme) == key) {
|
if (getString(R.string.setting_key_theme) == key) {
|
||||||
ThemeHelper.setTheme(
|
ThemeHelper.setTheme(
|
||||||
this, sharedPreferences.getString(key, getString(R.string.theme_default))!!)
|
this,
|
||||||
|
sharedPreferences.getString(key, getString(R.string.theme_default))!!
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,11 @@ internal class ShareActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.tokenExists()) {
|
if (!settings.tokenExists()) {
|
||||||
Toast.makeText(applicationContext, R.string.not_loggedin_share, Toast.LENGTH_SHORT)
|
Toast.makeText(
|
||||||
.show()
|
applicationContext,
|
||||||
|
R.string.not_loggedin_share,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user