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