AIRA-android/app/src/main/java/sushi/hardcore/aira/MainActivity.kt

371 lines
15 KiB
Kotlin

package sushi.hardcore.aira
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AbsListView
import android.widget.AdapterView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import sushi.hardcore.aira.adapters.Session
import sushi.hardcore.aira.adapters.SessionAdapter
import sushi.hardcore.aira.background_service.AIRAService
import sushi.hardcore.aira.background_service.FilesReceiver
import sushi.hardcore.aira.databinding.ActivityMainBinding
import sushi.hardcore.aira.databinding.DialogIpAddressesBinding
import sushi.hardcore.aira.utils.FileUtils
import sushi.hardcore.aira.utils.StringUtils
import java.net.NetworkInterface
class MainActivity : ServiceBoundActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var onlineSessionAdapter: SessionAdapter
private var offlineSessionAdapter: SessionAdapter? = null
private val onSessionsItemClickSendFile = AdapterView.OnItemClickListener { adapter, _, position, _ ->
askShareFileTo(adapter.getItemAtPosition(position) as Session)
}
private val onSessionsScrollListener = object : AbsListView.OnScrollListener {
override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {}
override fun onScroll(listView: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
if (listView.getChildAt(0) != null) {
binding.refresher.isEnabled = listView.firstVisiblePosition == 0 && listView.getChildAt(0).top == 0
}
}
}
private val uiCallbacks = object : AIRAService.UiCallbacks {
override fun onConnectFailed(ip: String, errorMsg: String?) {
var msg = getString(R.string.unable_to_connect_to, ip)
errorMsg?.let {
msg += ": $it"
}
runOnUiThread {
Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
}
}
override fun onNewSession(sessionId: Int, ip: String) {
runOnUiThread {
handleNewSession(sessionId, ip)
}
}
override fun onSessionDisconnect(sessionId: Int) {
runOnUiThread {
onlineSessionAdapter.remove(sessionId)?.let { session ->
if (session.isContact) {
offlineSessionAdapter?.add(session)
}
}
}
}
override fun onNameTold(sessionId: Int, name: String) {
runOnUiThread {
onlineSessionAdapter.setName(sessionId, name)
}
}
override fun onAvatarChanged(sessionId: Int, avatar: ByteArray?) {
runOnUiThread {
onlineSessionAdapter.setAvatar(sessionId, avatar)
}
}
override fun onNewMessage(sessionId: Int, data: ByteArray): Boolean {
runOnUiThread {
onlineSessionAdapter.setSeen(sessionId, false)
}
return false
}
override fun onAskLargeFiles(sessionId: Int, filesReceiver: FilesReceiver): Boolean {
runOnUiThread {
filesReceiver.ask(this@MainActivity, airaService.getNameOf(sessionId))
}
return true
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar.toolbar)
val identityName = intent.getStringExtra(LoginActivity.NAME_ARG)
identityName?.let {
initToolbar(it)
}
val openedToShareFile = intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE
onlineSessionAdapter = SessionAdapter(this)
binding.onlineSessions.apply {
adapter = onlineSessionAdapter
onItemClickListener = if (openedToShareFile) {
onSessionsItemClickSendFile
} else {
AdapterView.OnItemClickListener { _, _, position, _ ->
if (isSelecting()) {
changeSelection(onlineSessionAdapter, position)
} else {
launchChatActivity(onlineSessionAdapter.getItem(position))
}
}
}
onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, position, _ ->
changeSelection(onlineSessionAdapter, position)
true
}
setOnScrollListener(onSessionsScrollListener)
}
if (openedToShareFile) {
binding.offlineSessions.visibility = View.GONE
binding.textOfflineSessions.visibility = View.GONE
} else {
offlineSessionAdapter = SessionAdapter(this)
binding.offlineSessions.apply {
adapter = offlineSessionAdapter
onItemClickListener = if (openedToShareFile) {
onSessionsItemClickSendFile
} else {
AdapterView.OnItemClickListener { _, _, position, _ ->
if (isSelecting()) {
changeSelection(offlineSessionAdapter!!, position)
} else {
launchChatActivity(offlineSessionAdapter!!.getItem(position))
}
}
}
onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, position, _ ->
changeSelection(offlineSessionAdapter!!, position)
true
}
setOnScrollListener(onSessionsScrollListener)
}
}
serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder) {
val binder = service as AIRAService.AIRABinder
airaService = binder.getService()
airaService.uiCallbacks = uiCallbacks
airaService.isAppInBackground = false
refreshSessions()
if (AIRAService.isServiceRunning) {
airaService.identityName?.let { initToolbar(it) }
} else {
airaService.identityName = identityName
startService(serviceIntent)
}
}
override fun onServiceDisconnected(name: ComponentName?) {}
}
binding.refresher.setOnRefreshListener {
if (isServiceInitialized()) {
airaService.restartDiscovery()
}
binding.refresher.isRefreshing = false
}
binding.buttonShowIp.setOnClickListener {
val ipAddresses = StringBuilder()
for (iface in NetworkInterface.getNetworkInterfaces()) {
for (addr in iface.inetAddresses) {
if (!addr.isLoopbackAddress) {
ipAddresses.appendLine(StringUtils.getIpFromInetAddress(addr)+" ("+iface.displayName+')')
}
}
}
val dialogBinding = DialogIpAddressesBinding.inflate(layoutInflater)
dialogBinding.textIpAddresses.text = ipAddresses.substring(0, ipAddresses.length-1) //remove last LF
AlertDialog.Builder(this, R.style.CustomAlertDialog)
.setTitle(R.string.your_addresses)
.setView(dialogBinding.root)
.setPositiveButton(R.string.ok, null)
.show()
}
binding.editPeerIp.setOnEditorActionListener { _, _, _ ->
if (isServiceInitialized()){
airaService.connectTo(binding.editPeerIp.text.toString())
}
binding.editPeerIp.text.clear()
true
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_activity, menu)
menu.findItem(R.id.remove_contact).isVisible = isSelecting()
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.settings -> {
startActivity(Intent(this, SettingsActivity::class.java))
true
}
R.id.close -> {
if (isServiceInitialized()) {
AlertDialog.Builder(this, R.style.CustomAlertDialog)
.setTitle(R.string.warning)
.setMessage(R.string.ask_log_out)
.setPositiveButton(R.string.yes) { _, _ ->
airaService.logOut()
if (AIRADatabase.isIdentityProtected(Constants.getDatabaseFolder(this))) {
startActivity(Intent(this, LoginActivity::class.java))
}
finish()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
true
}
R.id.remove_contact -> {
AlertDialog.Builder(this, R.style.CustomAlertDialog)
.setTitle(R.string.warning)
.setMessage(R.string.ask_remove_contacts)
.setPositiveButton(R.string.delete) { _, _ ->
Thread {
for (sessionId in onlineSessionAdapter.getSelectedSessionIds()) {
airaService.removeContact(sessionId)
}
offlineSessionAdapter?.let {
for (sessionId in it.getSelectedSessionIds()) {
airaService.removeContact(sessionId)
}
}
runOnUiThread {
unSelectAll()
refreshSessions()
}
}.start()
}
.setNegativeButton(R.string.cancel, null)
.show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onPause() {
super.onPause()
if (isServiceInitialized()) {
airaService.isAppInBackground = true
}
}
override fun onBackPressed() {
if (isSelecting()) {
unSelectAll()
} else {
super.onBackPressed()
}
}
private fun initToolbar(identityName: String) {
val avatar = AIRADatabase.getIdentityAvatar(Constants.getDatabaseFolder(this))
if (avatar == null) {
binding.toolbar.avatar.setTextAvatar(identityName)
} else {
binding.toolbar.avatar.setImageAvatar(avatar)
}
binding.toolbar.title.text = identityName
}
private fun refreshSessions() {
onlineSessionAdapter.reset()
offlineSessionAdapter?.reset()
loadContacts()
loadSessions()
}
private fun unSelectAll() {
onlineSessionAdapter.unSelectAll()
offlineSessionAdapter?.unSelectAll()
invalidateOptionsMenu()
}
private fun changeSelection(adapter: SessionAdapter, position: Int) {
val wasSelecting = adapter.selectedItems.isNotEmpty()
adapter.onSelectionChanged(position)
val isSelecting = adapter.selectedItems.isNotEmpty()
if (wasSelecting != isSelecting) {
invalidateOptionsMenu()
}
}
private fun isSelecting(): Boolean {
return onlineSessionAdapter.selectedItems.isNotEmpty() || offlineSessionAdapter?.selectedItems?.isNotEmpty() == true
}
private fun loadContacts() {
if (offlineSessionAdapter != null) {
for ((sessionId, contact) in airaService.contacts) {
offlineSessionAdapter!!.add(Session(sessionId, true, contact.verified, contact.seen, null, contact.name, AIRADatabase.loadAvatar(contact.avatar)))
}
}
}
private fun loadSessions() {
for ((sessionId, session) in airaService.sessions) {
handleNewSession(sessionId, session.ip)
}
}
private fun handleNewSession(sessionId: Int, ip: String) {
val seen = !airaService.notSeen.contains(sessionId)
val contact = airaService.contacts[sessionId]
if (contact == null) {
onlineSessionAdapter.add(Session(sessionId, false, false, seen, ip, airaService.savedNames[sessionId], AIRADatabase.loadAvatar(airaService.savedAvatars[sessionId])))
} else {
onlineSessionAdapter.add(Session(sessionId, true, contact.verified, seen, ip, contact.name, AIRADatabase.loadAvatar(contact.avatar)))
offlineSessionAdapter?.remove(sessionId)
}
}
private fun launchChatActivity(session: Session) {
startActivity(Intent(this, ChatActivity::class.java).apply {
putExtra("sessionId", session.sessionId)
})
}
private fun askShareFileTo(session: Session) {
var uris: ArrayList<Uri>? = null
when (intent.action) {
Intent.ACTION_SEND -> intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let {
uris = arrayListOf(it)
}
Intent.ACTION_SEND_MULTIPLE -> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
}
if (uris == null) {
Toast.makeText(this, R.string.open_uri_failed, Toast.LENGTH_SHORT).show()
} else {
val msg = if (uris!!.size == 1) {
val sendFile = FileUtils.openFileFromUri(this, uris!![0])
if (sendFile == null) {
Toast.makeText(this, R.string.open_uri_failed, Toast.LENGTH_SHORT).show()
return
} else {
sendFile.inputStream.close()
getString(R.string.ask_send_single_file, sendFile.fileName, FileUtils.formatSize(sendFile.fileSize), session.name ?: session.ip)
}
} else {
getString(R.string.ask_send_multiple_files, uris!!.size, session.name ?: session.ip)
}
AlertDialog.Builder(this, R.style.CustomAlertDialog)
.setTitle(R.string.warning)
.setMessage(msg)
.setPositiveButton(R.string.yes) { _, _ ->
airaService.sendFilesFromUris(session.sessionId, uris!!)
finish()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
}