Files
gotify-android-client/app/src/main/kotlin/com/github/gotify/init/InitializationActivity.kt
2024-04-23 20:12:51 +02:00

222 lines
7.7 KiB
Kotlin

package com.github.gotify.init
import android.Manifest
import android.app.AlarmManager
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.github.gotify.R
import com.github.gotify.Settings
import com.github.gotify.api.ApiException
import com.github.gotify.api.Callback
import com.github.gotify.api.Callback.SuccessCallback
import com.github.gotify.api.ClientFactory
import com.github.gotify.client.model.User
import com.github.gotify.client.model.VersionInfo
import com.github.gotify.login.LoginActivity
import com.github.gotify.messages.MessagesActivity
import com.github.gotify.service.WebSocketService
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.livinglifetechway.quickpermissionskotlin.runWithPermissions
import com.livinglifetechway.quickpermissionskotlin.util.QuickPermissionsOptions
import com.livinglifetechway.quickpermissionskotlin.util.QuickPermissionsRequest
import org.tinylog.kotlin.Logger
internal class InitializationActivity : AppCompatActivity() {
private lateinit var settings: Settings
private var splashScreenActive = true
@RequiresApi(Build.VERSION_CODES.S)
private val activityResultLauncher =
registerForActivityResult(StartActivityForResult()) {
requestAlarmPermissionOrAuthenticate()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
settings = Settings(this)
Logger.info("Entering ${javaClass.simpleName}")
installSplashScreen().setKeepOnScreenCondition { splashScreenActive }
if (settings.tokenExists()) {
runWithPostNotificationsPermission {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {
// Android 14 and above
requestAlarmPermissionOrAuthenticate()
} else {
// Android 13 and below
tryAuthenticate()
}
}
} else {
showLogin()
}
}
@RequiresApi(Build.VERSION_CODES.S)
private fun requestAlarmPermissionOrAuthenticate() {
val manager = ContextCompat.getSystemService(this, AlarmManager::class.java)
if (manager?.canScheduleExactAlarms() == true) {
tryAuthenticate()
} else {
stopSlashScreen()
alarmDialog()
}
}
private fun showLogin() {
splashScreenActive = false
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
private fun tryAuthenticate() {
ClientFactory.userApiWithToken(settings)
.currentUser()
.enqueue(
Callback.callInUI(
this,
onSuccess = Callback.SuccessBody { user -> authenticated(user) },
onError = { exception -> failed(exception) }
)
)
}
private fun failed(exception: ApiException) {
stopSlashScreen()
when (exception.code) {
0 -> {
dialog(getString(R.string.not_available, settings.url))
return
}
401 -> {
dialog(getString(R.string.auth_failed))
return
}
}
var response = exception.body
response = response.take(200)
dialog(getString(R.string.other_error, settings.url, exception.code, response))
}
private fun dialog(message: String) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.oops)
.setMessage(message)
.setPositiveButton(R.string.retry) { _, _ -> tryAuthenticate() }
.setNegativeButton(R.string.logout) { _, _ -> showLogin() }
.setCancelable(false)
.show()
}
@RequiresApi(Build.VERSION_CODES.S)
private fun alarmDialog() {
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.permissions_alarm_prompt))
.setPositiveButton(getString(R.string.permissions_dialog_grant)) { _, _ ->
Intent(
android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
Uri.parse("package:$packageName")
).apply {
activityResultLauncher.launch(this)
}
}
.setCancelable(false)
.show()
}
private fun authenticated(user: User) {
Logger.info("Authenticated as ${user.name}")
settings.setUser(user.name, user.isAdmin)
requestVersion {
splashScreenActive = false
startActivity(Intent(this, MessagesActivity::class.java))
finish()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(Intent(this, WebSocketService::class.java))
} else {
startService(Intent(this, WebSocketService::class.java))
}
}
private fun requestVersion(runnable: Runnable) {
requestVersion(
callback = Callback.SuccessBody { version: VersionInfo ->
Logger.info("Server version: ${version.version}@${version.buildDate}")
settings.serverVersion = version.version
runnable.run()
},
errorCallback = { runnable.run() }
)
}
private fun requestVersion(
callback: SuccessCallback<VersionInfo>,
errorCallback: Callback.ErrorCallback
) {
ClientFactory.versionApi(settings)
.version
.enqueue(Callback.callInUI(this, callback, errorCallback))
}
private fun runWithPostNotificationsPermission(action: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13 and above
val quickPermissionsOption = QuickPermissionsOptions(
handleRationale = true,
handlePermanentlyDenied = true,
preRationaleAction = { stopSlashScreen() },
rationaleMethod = { req -> processPermissionRationale(req) },
permissionsDeniedMethod = { req -> processPermissionRationale(req) },
permanentDeniedMethod = { req -> processPermissionsPermanentDenied(req) }
)
runWithPermissions(
Manifest.permission.POST_NOTIFICATIONS,
options = quickPermissionsOption,
callback = action
)
} else {
// Android 12 and below
action()
}
}
private fun stopSlashScreen() {
splashScreenActive = false
setContentView(R.layout.splash)
}
private fun processPermissionRationale(req: QuickPermissionsRequest) {
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.permissions_notification_denied_temp))
.setPositiveButton(getString(R.string.permissions_dialog_grant)) { _, _ ->
req.proceed()
}
.setCancelable(false)
.show()
}
private fun processPermissionsPermanentDenied(req: QuickPermissionsRequest) {
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.permissions_notification_denied_permanent))
.setPositiveButton(getString(R.string.permissions_dialog_grant)) { _, _ ->
req.openAppSettings()
}
.setCancelable(false)
.show()
}
}