parent
68af560709
commit
7e2f91e3c4
6 changed files with 142 additions and 3 deletions
|
|
@ -58,6 +58,8 @@
|
|||
android:parentActivityName=".ui.home.HomeActivity"
|
||||
android:windowSoftInputMode="stateVisible" />
|
||||
|
||||
<activity android:name=".ui.home.ShortcutActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.vnc.VncActivity"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ interface ServerProfileDao {
|
|||
@Query("SELECT * FROM profiles")
|
||||
suspend fun getList(): List<ServerProfile>
|
||||
|
||||
@Query("SELECT * FROM profiles WHERE ID = :id")
|
||||
suspend fun getByID(id: Long): ServerProfile?
|
||||
|
||||
@Query("SELECT * FROM profiles WHERE name = :name")
|
||||
suspend fun getByName(name: String): List<ServerProfile>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,16 @@ import android.app.ActivityOptions
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Window
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.gaurav.avnc.R
|
||||
import com.gaurav.avnc.databinding.ActivityHomeBinding
|
||||
import com.gaurav.avnc.model.ServerProfile
|
||||
|
|
@ -27,6 +33,8 @@ import com.gaurav.avnc.util.MsgDialog
|
|||
import com.gaurav.avnc.viewmodel.HomeViewModel
|
||||
import com.gaurav.avnc.vnc.VncClient
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Primary activity of the app.
|
||||
|
|
@ -56,9 +64,10 @@ class HomeActivity : AppCompatActivity() {
|
|||
//Observers
|
||||
viewModel.editProfileEvent.observe(this) { showProfileEditor() }
|
||||
viewModel.profileInsertedEvent.observe(this) { onProfileInserted(it) }
|
||||
viewModel.profileDeletedEvent.observe(this) { showProfileDeletedMsg(it) }
|
||||
viewModel.profileDeletedEvent.observe(this) { onProfileDeleted(it) }
|
||||
viewModel.newConnectionEvent.observe(this) { startNewConnection(it) }
|
||||
viewModel.discovery.servers.observe(this) { updateDiscoveryBadge(it) }
|
||||
viewModel.serverProfiles.observe(this) { updateShortcuts(it) }
|
||||
|
||||
showWelcomeMsg()
|
||||
}
|
||||
|
|
@ -136,14 +145,26 @@ class HomeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Shows delete confirmation snackbar.
|
||||
* Shows delete confirmation snackbar, allowing the user to Undo deletion.
|
||||
*/
|
||||
private fun showProfileDeletedMsg(profile: ServerProfile) {
|
||||
private fun onProfileDeleted(profile: ServerProfile) {
|
||||
val callback = object : Snackbar.Callback() {
|
||||
override fun onDismissed(snackbar: Snackbar?, event: Int) {
|
||||
if (event != DISMISS_EVENT_ACTION)
|
||||
onProfileDeleteConfirmed(profile)
|
||||
}
|
||||
}
|
||||
|
||||
Snackbar.make(binding.root, R.string.msg_server_profile_deleted, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.title_undo)) { viewModel.insertProfile(profile) }
|
||||
.addCallback(callback)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onProfileDeleteConfirmed(profile: ServerProfile) {
|
||||
disableShortcut(profile)
|
||||
}
|
||||
|
||||
private fun updateDiscoveryBadge(list: List<ServerProfile>) {
|
||||
tabs.updateDiscoveryBadge(list.size)
|
||||
}
|
||||
|
|
@ -172,4 +193,36 @@ class HomeActivity : AppCompatActivity() {
|
|||
MsgDialog.show(supportFragmentManager, "Native library is missing!", msg)
|
||||
}.isSuccess
|
||||
}
|
||||
|
||||
private fun createShortcutId(profile: ServerProfile) = "shortcut:pid:${profile.ID}"
|
||||
|
||||
private fun updateShortcuts(profiles: List<ServerProfile>) {
|
||||
val context = this
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context)
|
||||
val shortcutProfiles = profiles.filter { it.name.isNotBlank() }.take(maxShortcuts)
|
||||
val shortcuts = shortcutProfiles.map { p ->
|
||||
val intent = ShortcutActivity.createIntent(context, p.ID)
|
||||
ShortcutInfoCompat.Builder(context, createShortcutId(p))
|
||||
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_computer_shortcut))
|
||||
.setShortLabel(p.name)
|
||||
.setLongLabel("${p.name} (${p.host})")
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
}
|
||||
ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts)
|
||||
|
||||
}.onFailure {
|
||||
Log.e("Shortcuts", "Unable to update shortcuts", it)
|
||||
Toast.makeText(context, "Unable to update shortcuts", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableShortcut(profile: ServerProfile) {
|
||||
val shortcutId = createShortcutId(profile)
|
||||
val msg = getString(R.string.msg_shortcut_server_deleted)
|
||||
ShortcutManagerCompat.disableShortcuts(this, listOf(shortcutId), msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Gaurav Ujjwal.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* See COPYING.txt for more details.
|
||||
*/
|
||||
|
||||
package com.gaurav.avnc.ui.home
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.gaurav.avnc.model.db.MainDb
|
||||
import com.gaurav.avnc.ui.vnc.startVncActivity
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
|
||||
/**
|
||||
* Used for launching app shortcuts.
|
||||
*/
|
||||
class ShortcutActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val PROFILE_ID_KEY = "com.gaurav.avnc.SHORTCUT_PROFILE_ID"
|
||||
|
||||
fun createIntent(context: Context, profileId: Long) = Intent(context, ShortcutActivity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
putExtra(PROFILE_ID_KEY, profileId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val id = intent?.getLongExtra(PROFILE_ID_KEY, 0) ?: 0
|
||||
|
||||
if (id == 0L)
|
||||
toast("Invalid shortcut ID")
|
||||
else
|
||||
runCatching {
|
||||
val dao = MainDb.getInstance(this).serverProfileDao
|
||||
val profile = runBlocking { dao.getByID(id) } // Blocking IO on Main thread
|
||||
if (profile == null)
|
||||
toast("This server has been deleted")
|
||||
else
|
||||
startVncActivity(this, profile)
|
||||
}.onFailure {
|
||||
Log.e(javaClass.simpleName, "Unable to launch shortcut [ID: $id]", it)
|
||||
toast("Unable to launch shortcut")
|
||||
}
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
22
app/src/main/res/drawable/ic_computer_shortcut.xml
Normal file
22
app/src/main/res/drawable/ic_computer_shortcut.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#f5f5f5" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="16dp"
|
||||
android:left="16dp"
|
||||
android:right="16dp"
|
||||
android:top="16dp">
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#2128ab"
|
||||
android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -80,6 +80,7 @@
|
|||
<string name="msg_key_compat_mode_help">Some servers have limited Unicode support. Legacy key events can help input non-English text.</string>
|
||||
<string name="msg_button_up_delay_help">Delaying click events can help in some rare cases if an app is not responding to clicks.</string>
|
||||
<string name="msg_drag_gesture_help">Assigning an action to this gesture will change Long press detection:\n\n<b>Press-hold-release</b> → Long press\n<b>Press-hold-swipe</b> → Long press and swipe</string>
|
||||
<string name="msg_shortcut_server_deleted">This server has been deleted</string>
|
||||
<string name="tip_empty_server_list">Server list is empty.\nClick \'<b>+</b>\' to add a server, or\nuse the top address bar to connect directly.</string>
|
||||
<string name="tip_urlbar">You can enter IP address, hostname, or \'vnc://\' URI.</string>
|
||||
<string name="tip_discovery">Discovery can automatically find supported servers\nrunning on your network.</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue