srctree

Robin Linden parent 57b47cbc de715145
Support receiving shared text from other apps

inlinesplit
atox/src/main/AndroidManifest.xml added: 216, removed: 30, total 186
@@ -43,12 +43,19 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
 
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="tox" />
</intent-filter>
 
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
</manifest>
 
atox/src/main/kotlin/Extensions.kt added: 216, removed: 30, total 186
@@ -10,3 +10,10 @@ class NoSuchArgumentException(arg: String) : Exception("No such argument: $arg")
 
fun Fragment.requireStringArg(key: String) =
arguments?.getString(key) ?: throw NoSuchArgumentException(key)
 
fun String.truncated(length: Int): String =
if (this.length > length) {
this.take(length - 1) + "…"
} else {
this
}
 
atox/src/main/kotlin/MainActivity.kt added: 216, removed: 30, total 186
@@ -7,10 +7,12 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.bundleOf
import androidx.core.view.WindowCompat
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import javax.inject.Inject
import ltd.evilcorp.atox.di.ViewModelFactory
import ltd.evilcorp.atox.settings.Settings
import ltd.evilcorp.atox.ui.contactlist.ARG_SHARE
 
private const val TAG = "MainActivity"
private const val SCHEME = "tox:"
@@ -36,22 +38,41 @@ class MainActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
setContentView(R.layout.activity_main)
 
// Handle potential tox link, but only the first time it triggers the app.
if (savedInstanceState == null && intent.action == Intent.ACTION_VIEW) {
val data = intent.dataString ?: ""
Log.i(TAG, "Got uri with data: $data")
if (!data.startsWith(SCHEME) || data.length != SCHEME.length + TOX_ID_LENGTH) {
Log.e(TAG, "Got malformed uri: $data")
return
}
// Only handle intent the first time it triggers the app.
if (savedInstanceState != null) return
when (intent.action) {
// Handle potential tox link
Intent.ACTION_VIEW -> {
val data = intent.dataString ?: ""
Log.i(TAG, "Got uri with data: $data")
if (!data.startsWith(SCHEME) || data.length != SCHEME.length + TOX_ID_LENGTH) {
Log.e(TAG, "Got malformed uri: $data")
return
}
 
supportFragmentManager.findFragmentById(R.id.nav_host_fragment)?.findNavController()?.navigate(
R.id.action_contactListFragment_to_addContactFragment,
bundleOf("toxId" to data.drop(SCHEME.length))
)
supportFragmentManager.findFragmentById(R.id.nav_host_fragment)?.findNavController()?.navigate(
R.id.action_contactListFragment_to_addContactFragment,
bundleOf("toxId" to data.drop(SCHEME.length))
)
}
Intent.ACTION_SEND -> handleShareIntent(intent)
}
}
 
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
 
if (intent?.action != Intent.ACTION_SEND) return
val nav = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)?.findNavController()
if (nav == null) {
Log.e(TAG, "Unable to find navController to handle intent $intent")
return
}
 
nav.popBackStack(R.id.contactListFragment, false)
handleShareIntent(intent)
}
 
override fun onPause() {
super.onPause()
autoAway.onBackground()
@@ -61,4 +82,22 @@ class MainActivity : AppCompatActivity() {
super.onResume()
autoAway.onForeground()
}
 
private fun handleShareIntent(intent: Intent) {
if (intent.type != "text/plain") {
Log.e(TAG, "Got unsupported share type ${intent.type}")
return
}
 
val data = intent.getStringExtra(Intent.EXTRA_TEXT)
if (data.isNullOrEmpty()) {
Log.e(TAG, "Got share intent with no data")
return
}
 
Log.i(TAG, "Got text share: $data")
val navController =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment)?.findNavController() ?: return
navController.setGraph(navController.graph, bundleOf(ARG_SHARE to data))
}
}
 
filename was Deleted added: 216, removed: 30, total 186
@@ -0,0 +1,55 @@
package ltd.evilcorp.atox.ui
 
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Window
import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.DialogReceiveShareBinding
import ltd.evilcorp.atox.truncated
import ltd.evilcorp.atox.ui.contactlist.ContactAdapter
import ltd.evilcorp.core.vo.Contact
 
private const val SHARE_TEXT_PREVIEW_LENGTH = 128
 
class ReceiveShareDialog(
ctx: Context,
private var contacts: List<Contact>,
private val sharePreview: String,
private val contactSelectedFunc: (Contact) -> Unit
) : Dialog(ctx, R.style.DialogSlideAnimation) {
private var _binding: DialogReceiveShareBinding? = null
private val binding get() = _binding!!
 
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
_binding = DialogReceiveShareBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
 
binding.sharingText.text = String.format("%s", sharePreview.truncated(SHARE_TEXT_PREVIEW_LENGTH))
 
binding.contacts.let {
it.adapter = ContactAdapter(layoutInflater, context.resources).apply {
contacts = this@ReceiveShareDialog.contacts
notifyDataSetChanged()
}
it.setOnItemClickListener { _, _, position, _ ->
val contact = binding.contacts.getItemAtPosition(position) as Contact
contactSelectedFunc(contact)
dismiss()
}
}
}
 
fun setContacts(contacts: List<Contact>) {
this.contacts = contacts
(binding.contacts.adapter as ContactAdapter?)?.let {
it.contacts = this.contacts
it.notifyDataSetChanged()
}
}
}
 
atox/src/main/kotlin/ui/chat/ChatFragment.kt added: 216, removed: 30, total 186
@@ -32,6 +32,7 @@ import ltd.evilcorp.atox.BuildConfig
import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.FragmentChatBinding
import ltd.evilcorp.atox.requireStringArg
import ltd.evilcorp.atox.truncated
import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.colorByStatus
import ltd.evilcorp.atox.ui.setAvatarFromContact
@@ -48,13 +49,6 @@ private const val REQUEST_CODE_FT_EXPORT = 1234
private const val REQUEST_CODE_ATTACH = 5678
private const val MAX_CONFIRM_DELETE_STRING_LENGTH = 20
 
private fun trimString(s: String): String =
if (s.length > MAX_CONFIRM_DELETE_STRING_LENGTH) {
s.take(MAX_CONFIRM_DELETE_STRING_LENGTH - 1) + "…"
} else {
s
}
 
class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::inflate) {
private val viewModel: ChatViewModel by viewModels { vmFactory }
 
@@ -251,7 +245,12 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
 
AlertDialog.Builder(requireContext())
.setTitle(R.string.clear_history)
.setMessage(getString(R.string.delete_message_confirm, trimString(message.message)))
.setMessage(
getString(
R.string.delete_message_confirm,
message.message.truncated(MAX_CONFIRM_DELETE_STRING_LENGTH)
)
)
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.delete(message)
}
 
atox/src/main/kotlin/ui/contactlist/ContactListFragment.kt added: 216, removed: 30, total 186
@@ -32,6 +32,7 @@ import ltd.evilcorp.atox.databinding.FragmentContactListBinding
import ltd.evilcorp.atox.databinding.FriendRequestItemBinding
import ltd.evilcorp.atox.databinding.NavHeaderContactListBinding
import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.ReceiveShareDialog
import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY
import ltd.evilcorp.atox.ui.friend_request.FRIEND_REQUEST_PUBLIC_KEY
import ltd.evilcorp.atox.vmFactory
@@ -43,6 +44,7 @@ import ltd.evilcorp.core.vo.UserStatus
import ltd.evilcorp.domain.tox.PublicKey
import ltd.evilcorp.domain.tox.ToxSaveStatus
 
const val ARG_SHARE = "share"
private const val REQUEST_CODE_BACKUP_TOX = 9202
 
private fun User.online(): Boolean =
@@ -59,6 +61,8 @@ class ContactListFragment :
 
private var backupFileNameHint = "something_is_broken.tox"
 
private var shareDialog: ReceiveShareDialog? = null
 
private fun colorFromStatus(status: UserStatus) = when (status) {
UserStatus.None -> ResourcesCompat.getColor(resources, R.color.statusAvailable, null)
UserStatus.Away -> ResourcesCompat.getColor(resources, R.color.statusAway, null)
@@ -139,6 +143,8 @@ class ContactListFragment :
} else {
View.GONE
}
 
shareDialog?.setContacts(contactAdapter.contacts)
}
 
contactList.setOnItemClickListener { _, _, position, _ ->
@@ -170,6 +176,22 @@ class ContactListFragment :
}
}
 
arguments?.getString(ARG_SHARE)?.let { share ->
shareDialog = ReceiveShareDialog(
requireContext(),
(binding.contactList.adapter as ContactAdapter).contacts,
share,
) {
viewModel.onShareText(share, it)
openChat(it)
}
shareDialog?.setOnDismissListener {
shareDialog = null
}
shareDialog?.show()
arguments?.remove(ARG_SHARE)
}
 
activity?.getSystemService<InputMethodManager>().let { imm ->
imm?.hideSoftInputFromWindow(view.windowToken, 0)
}
 
atox/src/main/kotlin/ui/contactlist/ContactListViewModel.kt added: 216, removed: 30, total 186
@@ -20,6 +20,7 @@ import ltd.evilcorp.atox.tox.ToxStarter
import ltd.evilcorp.core.vo.Contact
import ltd.evilcorp.core.vo.FriendRequest
import ltd.evilcorp.core.vo.User
import ltd.evilcorp.domain.feature.ChatManager
import ltd.evilcorp.domain.feature.ContactManager
import ltd.evilcorp.domain.feature.FriendRequestManager
import ltd.evilcorp.domain.feature.UserManager
@@ -33,6 +34,7 @@ import ltd.evilcorp.domain.tox.testToxSave
class ContactListViewModel @Inject constructor(
private val context: Context,
private val resolver: ContentResolver,
private val chatManager: ChatManager,
private val contactManager: ContactManager,
private val friendRequestManager: FriendRequestManager,
private val tox: Tox,
@@ -78,4 +80,7 @@ class ContactListViewModel @Inject constructor(
}
}
}
 
fun onShareText(what: String, to: Contact) =
chatManager.sendMessage(PublicKey(to.publicKey), what)
}
 
filename was Deleted added: 216, removed: 30, total 186
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="270dp"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
android:background="@null"
tools:targetApi="21">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="30dp"
android:elevation="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dialogBackground"
android:orientation="vertical"
android:paddingHorizontal="25dp"
android:paddingTop="20dp"
android:paddingBottom="25dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="8dp"
android:fontFamily="sans-serif-light"
android:gravity="center"
android:text="@string/receive_share_share_to"
android:textColor="@color/textWhiteColor"
android:textSize="20sp" />
 
<TextView android:id="@+id/sharing_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:fontFamily="sans-serif-light"
android:textColor="@color/dialogTextButton" />
 
<!-- TODO(robinlinden): New simplified contact display for this. -->
<ListView android:id="@+id/contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-8dp"
tools:listitem="@layout/contact_list_view_item" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
 
atox/src/main/res/values-ru/strings.xml added: 216, removed: 30, total 186
@@ -135,4 +135,6 @@
<string name="bad_positive_number">Должно быть положительным числом</string>
<string name="warn_node_json_import_failed">Ошибка при импортировании узлов</string>
<string name="error_no_nodes_loaded">Невозможно загрузить начальные узлы, пожалуйста, переключитесь на встроенные узлы или попробуйте импортировать их снова</string>
 
<string name="receive_share_share_to">Поделиться с…</string>
</resources>
 
atox/src/main/res/values/strings.xml added: 216, removed: 30, total 186
@@ -135,4 +135,6 @@
<string name="bad_positive_number">Must be a positive number</string>
<string name="warn_node_json_import_failed">Error importing nodes</string>
<string name="error_no_nodes_loaded">Unable to load bootstrap nodes, please switch to built-in nodes or import nodes again</string>
 
<string name="receive_share_share_to">Share to…</string>
</resources>