commit
62e96705a4
@ -0,0 +1,79 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.*
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
|
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||||
|
import org.session.libsignal.utilities.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
|
||||||
|
|
||||||
|
class OpenGroupInvitationView : FrameLayout {
|
||||||
|
private val joinButton: ImageView
|
||||||
|
private val openGroupIconContainer: RelativeLayout
|
||||||
|
private val openGroupIconImageView: ImageView
|
||||||
|
private val nameTextView: TextView
|
||||||
|
private val urlTextView: TextView
|
||||||
|
private var url: String = ""
|
||||||
|
|
||||||
|
constructor(context: Context): this(context, null)
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
|
||||||
|
View.inflate(context, R.layout.open_group_invitation_view, this)
|
||||||
|
joinButton = findViewById(R.id.join_open_group_button)
|
||||||
|
openGroupIconContainer = findViewById(R.id.open_group_icon_image_view_container)
|
||||||
|
openGroupIconImageView = findViewById(R.id.open_group_icon_image_view)
|
||||||
|
nameTextView = findViewById(R.id.name_text_view)
|
||||||
|
urlTextView = findViewById(R.id.url_text_view)
|
||||||
|
joinButton.setOnClickListener { joinOpenGroup(url) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOpenGroup(name: String, url: String, isOutgoing: Boolean = false) {
|
||||||
|
nameTextView.text = name
|
||||||
|
urlTextView.text = OpenGroupUrlParser.trimQueryParameter(url)
|
||||||
|
this.url = url
|
||||||
|
joinButton.visibility = if (isOutgoing) View.GONE else View.VISIBLE
|
||||||
|
openGroupIconContainer.visibility = if (isOutgoing) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun joinOpenGroup(url: String) {
|
||||||
|
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
builder.setTitle(context.getString(R.string.ConversationActivity_join_open_group, nameTextView.text.toString()))
|
||||||
|
builder.setCancelable(true)
|
||||||
|
val message: String =
|
||||||
|
context.getString(R.string.ConversationActivity_join_open_group_confirmation_message, nameTextView.text.toString())
|
||||||
|
builder.setMessage(message)
|
||||||
|
builder.setPositiveButton(R.string.yes) { dialog, _ ->
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
dialog.dismiss()
|
||||||
|
val group = OpenGroupUtilities.addGroup(
|
||||||
|
context,
|
||||||
|
openGroup.server,
|
||||||
|
openGroup.room,
|
||||||
|
openGroup.serverPublicKey
|
||||||
|
)
|
||||||
|
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(context)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Loki", "Failed to join open group.", e)
|
||||||
|
Toast.makeText(context, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.setNegativeButton(R.string.no, null)
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.loki.views.OpenGroupInvitationView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/open_group_invitation_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.loki.views.OpenGroupInvitationView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/open_group_invitation_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context="org.thoughtcrime.securesms.loki.views.OpenGroupInvitationView">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="@dimen/medium_spacing"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/join_open_group_button"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/circle_tintable"
|
||||||
|
android:backgroundTint="#00E076"
|
||||||
|
android:contentDescription="@string/open_group_invitation_view__join_accessibility_description"
|
||||||
|
app:srcCompat="@drawable/ic_add_white_original_24dp" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/open_group_icon_image_view_container"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/circle_tintable"
|
||||||
|
android:backgroundTint="#00E076">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/open_group_icon_image_view"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:contentDescription="@string/open_group_invitation_view__join_accessibility_description"
|
||||||
|
android:src="@drawable/ic_globe" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/small_spacing"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name_text_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="@dimen/large_font_size"
|
||||||
|
android:maxLines="1"
|
||||||
|
tools:text="Open group" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/small_font_size"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/open_group_invitation_view__open_group_invitation" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/url_text_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/small_font_size"
|
||||||
|
android:maxLines="1"
|
||||||
|
tools:text="http://1.1.1.1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</merge>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:title="@string/ConversationActivity_invite_to_open_group"
|
||||||
|
android:id="@+id/menu_invite_to_open_group"
|
||||||
|
android:icon="?menu_accept_icon" />
|
||||||
|
|
||||||
|
</menu>
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.session.libsession.messaging.messages.visible
|
||||||
|
|
||||||
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
|
import org.session.libsignal.utilities.logging.Log
|
||||||
|
|
||||||
|
class OpenGroupInvitation() {
|
||||||
|
var url: String? = null
|
||||||
|
var name: String? = null
|
||||||
|
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
return (url != null && name != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "OpenGroupInvitation"
|
||||||
|
|
||||||
|
fun fromProto(proto: SignalServiceProtos.DataMessage.OpenGroupInvitation): OpenGroupInvitation {
|
||||||
|
return OpenGroupInvitation(proto.url, proto.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(url: String?, serverName: String?): this() {
|
||||||
|
this.url = url
|
||||||
|
this.name = serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toProto(): SignalServiceProtos.DataMessage.OpenGroupInvitation? {
|
||||||
|
val openGroupInvitationProto = SignalServiceProtos.DataMessage.OpenGroupInvitation.newBuilder()
|
||||||
|
openGroupInvitationProto.url = url
|
||||||
|
openGroupInvitationProto.name = name
|
||||||
|
return try {
|
||||||
|
openGroupInvitationProto.build()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Couldn't construct open group invitation proto from: $this.")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
object OpenGroupUrlParser {
|
||||||
|
|
||||||
|
sealed class Error(val description: String) : Exception(description) {
|
||||||
|
object MalformedURL : Error("Malformed URL.")
|
||||||
|
object NoRoom : Error("No room specified in the URL.")
|
||||||
|
object NoPublicKey : Error("No public key specified in the URL.")
|
||||||
|
object InvalidPublicKey : Error("Invalid public key provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val suffix = "/"
|
||||||
|
private const val queryPrefix = "public_key"
|
||||||
|
|
||||||
|
fun parseUrl(string: String): V2OpenGroupInfo {
|
||||||
|
// URL has to start with 'http://'
|
||||||
|
val urlWithPrefix = if (!string.startsWith("http")) "http://$string" else string
|
||||||
|
// If the URL is malformed, throw an exception
|
||||||
|
val url = HttpUrl.parse(urlWithPrefix) ?: throw Error.MalformedURL
|
||||||
|
// Parse components
|
||||||
|
val server = HttpUrl.Builder().scheme(url.scheme()).host(url.host()).port(url.port()).build().toString().removeSuffix(suffix)
|
||||||
|
val room = url.pathSegments().firstOrNull { !it.isNullOrEmpty() } ?: throw Error.NoRoom
|
||||||
|
val publicKey = url.queryParameter(queryPrefix) ?: throw Error.NoPublicKey
|
||||||
|
if (publicKey.length != 64) throw Error.InvalidPublicKey
|
||||||
|
// Return
|
||||||
|
return V2OpenGroupInfo(server,room,publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun trimQueryParameter(string: String): String {
|
||||||
|
return string.substringBefore("?$queryPrefix")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class V2OpenGroupInfo(val server: String, val room: String, val serverPublicKey: String)
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
class OpenGroupUrlParserTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseUrlTest() {
|
||||||
|
val inputUrl = "https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val expectedHost = "https://sessionopengroup.co"
|
||||||
|
val expectedRoom = "main"
|
||||||
|
val expectedPublicKey = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val result = OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
assertEquals(expectedHost, result.server)
|
||||||
|
assertEquals(expectedRoom, result.room)
|
||||||
|
assertEquals(expectedPublicKey, result.serverPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseUrlNoHttpTest() {
|
||||||
|
val inputUrl = "sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val expectedHost = "http://sessionopengroup.co"
|
||||||
|
val expectedRoom = "main"
|
||||||
|
val expectedPublicKey = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val result = OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
assertEquals(expectedHost, result.server)
|
||||||
|
assertEquals(expectedRoom, result.room)
|
||||||
|
assertEquals(expectedPublicKey, result.serverPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseUrlWithIpTest() {
|
||||||
|
val inputUrl = "https://143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val expectedHost = "https://143.198.213.255:80"
|
||||||
|
val expectedRoom = "main"
|
||||||
|
val expectedPublicKey = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val result = OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
assertEquals(expectedHost, result.server)
|
||||||
|
assertEquals(expectedRoom, result.room)
|
||||||
|
assertEquals(expectedPublicKey, result.serverPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseUrlWithIpAndNoHttpTest() {
|
||||||
|
val inputUrl = "143.198.213.255/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val expectedHost = "http://143.198.213.255"
|
||||||
|
val expectedRoom = "main"
|
||||||
|
val expectedPublicKey = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
|
||||||
|
val result = OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
assertEquals(expectedHost, result.server)
|
||||||
|
assertEquals(expectedRoom, result.room)
|
||||||
|
assertEquals(expectedPublicKey, result.serverPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OpenGroupUrlParser.Error.MalformedURL::class)
|
||||||
|
fun parseUrlMalformedUrlTest() {
|
||||||
|
val inputUrl = "file:sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OpenGroupUrlParser.Error.NoRoom::class)
|
||||||
|
fun parseUrlNoRoomSpecifiedTest() {
|
||||||
|
val inputUrl = "https://sessionopengroup.comain?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
|
||||||
|
OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OpenGroupUrlParser.Error.NoPublicKey::class)
|
||||||
|
fun parseUrlNoPublicKeySpecifiedTest() {
|
||||||
|
val inputUrl = "https://sessionopengroup.co/main"
|
||||||
|
OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = OpenGroupUrlParser.Error.InvalidPublicKey::class)
|
||||||
|
fun parseUrlInvalidPublicKeyProviedTest() {
|
||||||
|
val inputUrl = "https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adff"
|
||||||
|
OpenGroupUrlParser.parseUrl(inputUrl)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue