session-android/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt

229 lines
9.9 KiB
Kotlin
Raw Normal View History

2020-05-11 08:19:26 +02:00
package org.thoughtcrime.securesms.loki.activities
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.view.isVisible
import androidx.fragment.app.*
import androidx.lifecycle.lifecycleScope
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.activity_join_public_chat.*
import kotlinx.android.synthetic.main.fragment_enter_chat_url.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R
import okhttp3.HttpUrl
Merge remote-tracking branch 'upstream/dev' into open_groups_V2, working on compact poller implementation # Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java # app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt # app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt # app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt # app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt # app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt # app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt # app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt # app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt # app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt # app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt # libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt # libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt # libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt # libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt # libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt # libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt # libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessageV2.kt # libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupV2.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt # libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt # libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt # libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt # libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt # libsession/src/main/java/org/session/libsession/utilities/mentions/MentionsManager.kt # libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt # libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt # libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/MentionsManager.kt
2021-04-28 09:41:30 +02:00
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.DefaultGroup
2021-05-18 08:11:38 +02:00
import org.session.libsession.utilities.Address
2021-05-18 08:03:47 +02:00
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.GroupUtil
2021-05-18 01:12:33 +02:00
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.PublicKeyValidation
import org.thoughtcrime.securesms.BaseActionBarActivity
2020-01-06 04:26:52 +01:00
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
2021-07-09 05:18:48 +02:00
import org.thoughtcrime.securesms.groups.DefaultGroupsViewModel
import org.thoughtcrime.securesms.groups.GroupManager
2021-05-19 03:12:29 +02:00
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
2020-05-11 08:19:26 +02:00
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
2021-07-09 05:18:48 +02:00
import org.thoughtcrime.securesms.util.State
2021-06-02 02:51:36 +02:00
import java.util.*
2020-01-06 04:26:52 +01:00
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val adapter = JoinPublicChatActivityAdapter(this)
// region Lifecycle
2020-01-06 04:26:52 +01:00
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
// Set content view
setContentView(R.layout.activity_join_public_chat)
// Set title
supportActionBar!!.title = resources.getString(R.string.activity_join_public_chat_title)
// Set up view pager
viewPager.adapter = adapter
tabLayout.setupWithViewPager(viewPager)
}
// endregion
// region Updating
private fun showLoader() {
loader.visibility = View.VISIBLE
loader.animate().setDuration(150).alpha(1.0f).start()
}
private fun hideLoader() {
loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
loader.visibility = View.GONE
}
})
}
// endregion
// region Interaction
override fun handleQRCodeScanned(url: String) {
joinPublicChatIfPossible(url)
}
fun joinPublicChatIfPossible(url: String) {
2021-05-14 00:56:25 +02:00
// Add "http" if not entered explicitly
val stringWithExplicitScheme = if (!url.startsWith("http")) "http://$url" else url
val url = HttpUrl.parse(stringWithExplicitScheme) ?: return Toast.makeText(this,R.string.invalid_url, Toast.LENGTH_SHORT).show()
val room = url.pathSegments().firstOrNull()
val publicKey = url.queryParameter("public_key")
val isV2OpenGroup = !room.isNullOrEmpty()
if (isV2OpenGroup && (publicKey == null || !PublicKeyValidation.isValid(publicKey, 64,false))) {
return Toast.makeText(this, R.string.invalid_public_key, Toast.LENGTH_SHORT).show()
}
showLoader()
lifecycleScope.launch(Dispatchers.IO) {
try {
val (threadID, groupID) = if (isV2OpenGroup) {
2021-05-14 00:56:25 +02:00
val server = HttpUrl.Builder().scheme(url.scheme()).host(url.host()).apply {
if (url.port() != 80 || url.port() != 443) { this.port(url.port()) } // Non-standard port; add to server
}.build()
2021-05-19 03:12:29 +02:00
val sanitizedServer = server.toString().removeSuffix("/")
val openGroupID = "$sanitizedServer.${room!!}"
OpenGroupManager.add(sanitizedServer, room, publicKey!!, this@JoinPublicChatActivity)
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, this@JoinPublicChatActivity)
val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())
threadID to groupID
} else {
2021-05-19 03:12:29 +02:00
throw Exception("No longer supported.")
}
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
withContext(Dispatchers.Main) {
2021-05-14 00:56:25 +02:00
val recipient = Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false)
openConversationActivity(this@JoinPublicChatActivity, threadID, recipient)
finish()
}
} catch (e: Exception) {
2021-05-14 00:56:25 +02:00
Log.e("Loki", "Couldn't join open group.", e)
withContext(Dispatchers.Main) {
hideLoader()
Toast.makeText(this@JoinPublicChatActivity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
}
return@launch
}
}
}
// endregion
// region Convenience
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
val intent = Intent(context, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
2021-07-01 01:31:30 +02:00
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
context.startActivity(intent)
}
// endregion
}
// region Adapter
private class JoinPublicChatActivityAdapter(val activity: JoinPublicChatActivity) : FragmentPagerAdapter(activity.supportFragmentManager) {
override fun getCount(): Int {
return 2
}
override fun getItem(index: Int): Fragment {
return when (index) {
0 -> EnterChatURLFragment()
1 -> {
val result = ScanQRCodeWrapperFragment()
result.delegate = activity
result.message = activity.resources.getString(R.string.activity_join_public_chat_scan_qr_code_explanation)
result
}
else -> throw IllegalStateException()
}
}
override fun getPageTitle(index: Int): CharSequence {
return when (index) {
0 -> activity.resources.getString(R.string.activity_join_public_chat_enter_group_url_tab_title)
1 -> activity.resources.getString(R.string.activity_join_public_chat_scan_qr_code_tab_title)
else -> throw IllegalStateException()
}
}
}
// endregion
// region Enter Chat URL Fragment
class EnterChatURLFragment : Fragment() {
private val viewModel by activityViewModels<DefaultGroupsViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_enter_chat_url, container, false)
}
2021-05-14 00:56:25 +02:00
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
chatURLEditText.imeOptions = chatURLEditText.imeOptions or 16777216 // Always use incognito keyboard
joinPublicChatButton.setOnClickListener { joinPublicChatIfPossible() }
viewModel.defaultRooms.observe(viewLifecycleOwner) { state ->
defaultRoomsContainer.isVisible = state is State.Success
2021-06-02 02:51:36 +02:00
defaultRoomsLoaderContainer.isVisible = state is State.Loading
2021-05-14 00:56:25 +02:00
defaultRoomsLoader.isVisible = state is State.Loading
when (state) {
State.Loading -> {
// TODO: Show a loader
}
is State.Error -> {
// TODO: Hide the loader
}
is State.Success -> {
populateDefaultGroups(state.value)
}
}
}
}
private fun populateDefaultGroups(groups: List<DefaultGroup>) {
defaultRoomsGridLayout.removeAllViews()
2021-05-14 02:09:12 +02:00
defaultRoomsGridLayout.useDefaultMargins = false
groups.forEach { defaultGroup ->
2021-05-14 02:09:12 +02:00
val chip = layoutInflater.inflate(R.layout.default_group_chip, defaultRoomsGridLayout, false) as Chip
val drawable = defaultGroup.image?.let { bytes ->
2021-05-14 00:56:25 +02:00
val bitmap = BitmapFactory.decodeByteArray(bytes,0, bytes.size)
RoundedBitmapDrawableFactory.create(resources,bitmap).apply {
isCircular = true
}
}
chip.chipIcon = drawable
chip.text = defaultGroup.name
chip.setOnClickListener {
2021-05-12 06:48:13 +02:00
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(defaultGroup.joinURL)
}
defaultRoomsGridLayout.addView(chip)
}
2021-05-14 02:09:12 +02:00
if ((groups.size and 1) != 0) { // This checks that the number of rooms is even
layoutInflater.inflate(R.layout.grid_layout_filler, defaultRoomsGridLayout)
}
}
// region Convenience
private fun joinPublicChatIfPossible() {
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
2021-06-02 02:51:36 +02:00
val chatURL = chatURLEditText.text.trim().toString().toLowerCase(Locale.US)
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
}
// endregion
}
// endregion