Add distributed user profiles and username directory
This commit is contained in:
@@ -1,33 +1,78 @@
|
||||
package pro.nnnteam.nnnet
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
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.AlertDialog
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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)
|
||||
@@ -41,14 +86,108 @@ class SettingsActivity : AppCompatActivity() {
|
||||
currentVersionCode()
|
||||
)
|
||||
|
||||
findViewById<android.view.View>(R.id.checkUpdatesButton).setOnClickListener {
|
||||
checkForUpdates()
|
||||
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 = withContext(Dispatchers.IO) { UpdateManager.fetchUpdateInfo() }
|
||||
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
|
||||
@@ -63,10 +202,10 @@ class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private fun showUpdateDialog(updateInfo: UpdateInfo) {
|
||||
lifecycleScope.launch {
|
||||
val releaseNotes = withContext(Dispatchers.IO) {
|
||||
val releaseNotes = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
UpdateManager.fetchReleaseNotes(updateInfo.releaseNotesPath)
|
||||
}
|
||||
AlertDialog.Builder(this@SettingsActivity)
|
||||
androidx.appcompat.app.AlertDialog.Builder(this@SettingsActivity)
|
||||
.setTitle(updateInfo.releaseNotesTitle)
|
||||
.setMessage(
|
||||
buildString {
|
||||
@@ -79,7 +218,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
)
|
||||
.setPositiveButton(R.string.download_update) { _, _ ->
|
||||
val url = UpdateManager.buildDownloadUrl(updateInfo.apkPath)
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
startActivity(Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url)))
|
||||
}
|
||||
.setNegativeButton(R.string.later, null)
|
||||
.show()
|
||||
|
||||
Reference in New Issue
Block a user