Files
NNNet/android/app/src/main/java/pro/nnnteam/nnnet/SettingsActivity.kt

238 lines
9.4 KiB
Kotlin

package pro.nnnteam.nnnet
import android.os.Build
import android.os.Bundle
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButton
import com.google.android.material.switchmaterial.SwitchMaterial
import kotlinx.coroutines.launch
import pro.nnnteam.nnnet.data.MeshDatabase
import pro.nnnteam.nnnet.data.MeshRepository
import pro.nnnteam.nnnet.data.ProfileEntity
import pro.nnnteam.nnnet.mesh.MeshServiceContract
import pro.nnnteam.nnnet.update.UpdateInfo
import pro.nnnteam.nnnet.update.UpdateManager
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
class SettingsActivity : AppCompatActivity() {
private lateinit var repository: MeshRepository
private lateinit var firstNameInput: EditText
private lateinit var lastNameInput: EditText
private lateinit var usernameInput: EditText
private lateinit var descriptionInput: EditText
private lateinit var searchInput: EditText
private lateinit var profileResultCard: android.view.View
private lateinit var resultNameText: TextView
private lateinit var resultUsernameText: TextView
private lateinit var resultDescriptionText: TextView
private lateinit var resultPeerIdText: TextView
private var receiverRegistered = false
private val prefs by lazy {
getSharedPreferences(UpdateManager.PREFS_NAME, MODE_PRIVATE)
}
private val meshEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: android.content.Context?, intent: Intent?) {
if (intent?.action != MeshServiceContract.ACTION_EVENT) return
val eventType = intent.getStringExtra(MeshServiceContract.EXTRA_EVENT_TYPE) ?: return
if (eventType == MeshServiceContract.EVENT_PROFILES_CHANGED) {
val query = searchInput.text.toString().trim()
if (query.isNotEmpty()) {
lookupProfile(query)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val database = MeshDatabase.getInstance(applicationContext)
repository = MeshRepository(database.messageDao(), database.outboundQueueDao(), database.profileDao())
findViewById<ImageButton>(R.id.backButton).setOnClickListener { finish() }
firstNameInput = findViewById(R.id.firstNameInput)
lastNameInput = findViewById(R.id.lastNameInput)
usernameInput = findViewById(R.id.usernameInput)
descriptionInput = findViewById(R.id.descriptionInput)
searchInput = findViewById(R.id.searchInput)
profileResultCard = findViewById(R.id.profileResultCard)
resultNameText = findViewById(R.id.resultNameText)
resultUsernameText = findViewById(R.id.resultUsernameText)
resultDescriptionText = findViewById(R.id.resultDescriptionText)
resultPeerIdText = findViewById(R.id.resultPeerIdText)
val autoUpdateSwitch = findViewById<SwitchMaterial>(R.id.autoUpdateSwitch)
val versionText = findViewById<TextView>(R.id.versionText)
autoUpdateSwitch.isChecked = prefs.getBoolean(UpdateManager.KEY_AUTO_UPDATE, false)
autoUpdateSwitch.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean(UpdateManager.KEY_AUTO_UPDATE, isChecked).apply()
}
versionText.text = getString(
R.string.current_version,
packageManager.getPackageInfo(packageName, 0).versionName,
currentVersionCode()
)
findViewById<MaterialButton>(R.id.saveProfileButton).setOnClickListener { saveProfile() }
findViewById<MaterialButton>(R.id.searchButton).setOnClickListener {
val query = searchInput.text.toString().trim()
if (query.isEmpty()) {
Toast.makeText(this, R.string.enter_username_to_search, Toast.LENGTH_SHORT).show()
} else {
lookupProfile(query)
}
}
findViewById<MaterialButton>(R.id.checkUpdatesButton).setOnClickListener { checkForUpdates() }
loadLocalProfile()
}
override fun onStart() {
super.onStart()
registerMeshReceiver()
}
override fun onStop() {
if (receiverRegistered) {
unregisterReceiver(meshEventReceiver)
receiverRegistered = false
}
super.onStop()
}
private fun registerMeshReceiver() {
if (receiverRegistered) return
val filter = IntentFilter(MeshServiceContract.ACTION_EVENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(meshEventReceiver, filter, RECEIVER_NOT_EXPORTED)
} else {
@Suppress("DEPRECATION")
registerReceiver(meshEventReceiver, filter)
}
receiverRegistered = true
}
private fun loadLocalProfile() {
lifecycleScope.launch {
val localProfile = repository.localProfile()
if (localProfile != null) {
firstNameInput.setText(localProfile.firstName)
lastNameInput.setText(localProfile.lastName)
usernameInput.setText(localProfile.username)
descriptionInput.setText(localProfile.description)
}
}
}
private fun saveProfile() {
val firstName = firstNameInput.text.toString().trim()
val lastName = lastNameInput.text.toString().trim()
val username = usernameInput.text.toString().trim().removePrefix("@")
val description = descriptionInput.text.toString().trim()
if (username.isBlank()) {
Toast.makeText(this, R.string.username_required, Toast.LENGTH_SHORT).show()
return
}
lifecycleScope.launch {
repository.saveLocalProfile(
firstName = firstName,
lastName = lastName,
username = username,
description = description
)
Toast.makeText(this@SettingsActivity, R.string.profile_saved, Toast.LENGTH_SHORT).show()
}
}
private fun lookupProfile(username: String) {
lifecycleScope.launch {
val profile = repository.profileByUsername(username.removePrefix("@"))
renderSearchResult(profile)
if (profile == null) {
Toast.makeText(this@SettingsActivity, R.string.profile_not_found_locally, Toast.LENGTH_SHORT).show()
}
}
}
private fun renderSearchResult(profile: ProfileEntity?) {
if (profile == null) {
profileResultCard.visibility = android.view.View.GONE
return
}
profileResultCard.visibility = android.view.View.VISIBLE
resultNameText.text = profile.displayName()
resultUsernameText.text = "@${profile.username}"
resultDescriptionText.text = profile.description.ifBlank { getString(R.string.no_profile_description) }
resultPeerIdText.text = if (profile.peerId.isBlank()) {
getString(R.string.peer_id_unknown)
} else {
getString(R.string.peer_id_value, profile.peerId)
}
}
private fun checkForUpdates() {
lifecycleScope.launch {
val updateInfo = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { UpdateManager.fetchUpdateInfo() }
if (updateInfo == null) {
Toast.makeText(this@SettingsActivity, R.string.update_check_failed, Toast.LENGTH_SHORT).show()
return@launch
}
if (updateInfo.versionCode > currentVersionCode()) {
showUpdateDialog(updateInfo)
} else {
Toast.makeText(this@SettingsActivity, R.string.latest_version_installed, Toast.LENGTH_SHORT).show()
}
}
}
private fun showUpdateDialog(updateInfo: UpdateInfo) {
lifecycleScope.launch {
val releaseNotes = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
UpdateManager.fetchReleaseNotes(updateInfo.releaseNotesPath)
}
androidx.appcompat.app.AlertDialog.Builder(this@SettingsActivity)
.setTitle(updateInfo.releaseNotesTitle)
.setMessage(
buildString {
append(getString(R.string.update_available_message, updateInfo.versionName))
if (!releaseNotes.isNullOrBlank()) {
append("\n\n")
append(releaseNotes.trim())
}
}
)
.setPositiveButton(R.string.download_update) { _, _ ->
val url = UpdateManager.buildDownloadUrl(updateInfo.apkPath)
startActivity(Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url)))
}
.setNegativeButton(R.string.later, null)
.show()
}
}
private fun currentVersionCode(): Int {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode.toInt()
} else {
@Suppress("DEPRECATION")
packageInfo.versionCode
}
}
}