feat: handling default group requests and open group api updates for proper image endpoint handling

This commit is contained in:
jubb 2021-04-21 17:00:57 +10:00
parent c601098065
commit f9939aae92
13 changed files with 237 additions and 117 deletions

View file

@ -57,48 +57,49 @@ import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.messaging.messages.visible.Quote;
import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2;
import org.session.libsession.messaging.opengroups.OpenGroupV2;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.ViewUtil;
import org.session.libsession.utilities.concurrent.SimpleTask;
import org.session.libsession.utilities.task.ProgressDialogAsyncTask;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.MessageDetailsActivity;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.ShareActivity;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.session.libsession.utilities.task.ProgressDialogAsyncTask;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.ViewUtil;
import org.session.libsession.utilities.concurrent.SimpleTask;
import java.io.IOException;
import java.io.InputStream;
@ -396,7 +397,8 @@ public class ConversationFragment extends Fragment
if (isGroupChat) {
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
boolean isPublicChat = (publicChat != null);
OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId);
boolean isPublicChat = (publicChat != null || openGroupChat != null);
int selectedMessageCount = messageRecords.size();
boolean areAllSentByUser = true;
Set<String> uniqueUserSet = new HashSet<>();
@ -407,7 +409,11 @@ public class ConversationFragment extends Fragment
menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser);
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
boolean userCanModerate = isPublicChat && OpenGroupAPI.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
boolean userCanModerate =
(isPublicChat &&
(OpenGroupAPI.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer())
|| OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()))
);
boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate);
// allow banning if moderating a public chat and only one user's messages are selected
boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1;

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.activities
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Patterns
import android.view.LayoutInflater
@ -9,14 +10,20 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels
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
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.DefaultGroup
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@ -24,11 +31,13 @@ import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroup
import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroupsViewModel
import org.thoughtcrime.securesms.loki.viewmodel.State
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val viewModel by viewModels<DefaultGroupsViewModel>()
private val adapter = JoinPublicChatActivityAdapter(this)
// region Lifecycle
@ -70,12 +79,24 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
if (!Patterns.WEB_URL.matcher(url).matches() || !url.startsWith("https://")) {
return Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show()
}
val properString = if (!url.startsWith("http")) "http://$url" else url
val httpUrl = HttpUrl.parse(url) ?: return Toast.makeText(this,R.string.invalid_url, Toast.LENGTH_SHORT).show()
val room = httpUrl.pathSegments().firstOrNull()
val publicKey = httpUrl.queryParameter("public_key")
val isV2OpenGroup = !room.isNullOrEmpty()
showLoader()
val channel: Long = 1
lifecycleScope.launch(Dispatchers.IO) {
try {
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
if (isV2OpenGroup) {
val server = httpUrl.newBuilder().removeAllQueryParameters("public_key").removePathSegment(0).build().toString()
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, server, room, publicKey)
} else {
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
}
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
} catch (e: Exception) {
Log.e("JoinPublicChatActivity", "Fialed to join open group.", e)
@ -111,7 +132,7 @@ private class JoinPublicChatActivityAdapter(val activity: JoinPublicChatActivity
}
}
override fun getPageTitle(index: Int): CharSequence? {
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)
@ -124,7 +145,6 @@ private class JoinPublicChatActivityAdapter(val activity: JoinPublicChatActivity
// region Enter Chat URL Fragment
class EnterChatURLFragment : Fragment() {
// factory producer is app scoped because default groups will want to stick around for app lifetime
private val viewModel by activityViewModels<DefaultGroupsViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -132,7 +152,23 @@ class EnterChatURLFragment : Fragment() {
}
private fun populateDefaultGroups(groups: List<DefaultGroup>) {
Log.d("Loki", "Got some default groups $groups")
defaultRoomsGridLayout.removeAllViews()
groups.forEach { defaultGroup ->
val chip = layoutInflater.inflate(R.layout.default_group_chip,defaultRoomsGridLayout, false) as Chip
val drawable = defaultGroup.image?.let { bytes ->
val bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.size)
RoundedBitmapDrawableFactory.create(resources,bitmap).apply {
isCircular = true
}
}
chip.chipIcon = drawable
chip.text = defaultGroup.name
defaultRoomsGridLayout.addView(chip)
}
if (groups.size and 1 != 0) {
// add a filler weight 1 view
layoutInflater.inflate(R.layout.grid_layout_filler, defaultRoomsGridLayout)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -140,6 +176,8 @@ class EnterChatURLFragment : Fragment() {
chatURLEditText.imeOptions = chatURLEditText.imeOptions or 16777216 // Always use incognito keyboard
joinPublicChatButton.setOnClickListener { joinPublicChatIfPossible() }
viewModel.defaultRooms.observe(viewLifecycleOwner) { state ->
defaultRoomsParent.isVisible = state is State.Success
defaultRoomsLoader.isVisible = state is State.Loading
when (state) {
State.Loading -> {
// show a loader here probs

View file

@ -123,7 +123,7 @@ class PublicChatManager(private val context: Context) {
if (threadID < 0) {
val imageID = info.imageID
if (!imageID.isNullOrEmpty()) {
val profilePictureAsByteArray = OpenGroupAPIV2.downloadOpenGroupProfilePicture(imageID)
val profilePictureAsByteArray = OpenGroupAPIV2.downloadOpenGroupProfilePicture(info.id,server)
profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray)
}
val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, info.name)

View file

@ -25,8 +25,10 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
private val friendRequestStatus = "friend_request_status"
private val sessionResetStatus = "session_reset_status"
val publicChat = "public_chat"
@JvmStatic val createSessionResetTableCommand = "CREATE TABLE $sessionResetTable ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
@JvmStatic
val createSessionResetTableCommand = "CREATE TABLE $sessionResetTable ($threadID INTEGER PRIMARY KEY, $sessionResetStatus INTEGER DEFAULT 0);"
@JvmStatic
val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
}
override fun getThreadID(hexEncodedPublicKey: String): Long {
@ -45,11 +47,13 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
val threadID = cursor.getLong(threadID)
val string = cursor.getString(publicChat)
val publicChat = PublicChat.fromJSON(string)
if (publicChat != null) { result[threadID] = publicChat }
if (publicChat != null) {
result[threadID] = publicChat
}
}
} catch (e: Exception) {
// Do nothing
} finally {
} finally {
cursor?.close()
}
return result
@ -79,25 +83,40 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
}
override fun getPublicChat(threadID: Long): PublicChat? {
if (threadID < 0) { return null }
fun getOpenGroupChat(threadID: Long): OpenGroupV2? {
if (threadID < 0) {
return null
}
val database = databaseHelper.readableDatabase
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
return database.get(publicChat, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor ->
val json = cursor.getString(publicChat)
OpenGroupV2.fromJson(json)
}
}
override fun getPublicChat(threadID: Long): PublicChat? {
if (threadID < 0) {
return null
}
val database = databaseHelper.readableDatabase
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor ->
val publicChatAsJSON = cursor.getString(publicChat)
PublicChat.fromJSON(publicChatAsJSON)
}
}
override fun setPublicChat(publicChat: PublicChat, threadID: Long) {
if (threadID < 0) { return }
if (threadID < 0) {
return
}
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID)
contentValues.put(Companion.publicChat, JsonUtil.toJson(publicChat.toJSON()))
database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString()))
}
override fun removePublicChat(threadID: Long) {
databaseHelper.writableDatabase.delete(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
databaseHelper.writableDatabase.delete(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString()))
}
}

View file

@ -5,6 +5,7 @@ import androidx.annotation.WorkerThread
import org.greenrobot.eventbus.EventBus
import org.session.libsession.messaging.opengroups.OpenGroup
import org.session.libsession.messaging.opengroups.OpenGroupAPI
import org.session.libsession.messaging.opengroups.OpenGroupV2
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil
@ -18,6 +19,18 @@ object OpenGroupUtilities {
private const val TAG = "OpenGroupUtilities"
@JvmStatic
@WorkerThread
fun addGroup(context: Context, server: String, room: String, publicKey: String?): OpenGroupV2 {
val groupId = "$server.$room"
val threadID = GroupManager.getOpenGroupThreadID(groupId, context)
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
if (openGroup != null) return openGroup
val application = ApplicationContext.getInstance(context)
val group = application.publicChatManager.addChat(server, room, publicKey)
}
@JvmStatic
@WorkerThread
@Throws(Exception::class)

View file

@ -1,10 +1,13 @@
package org.thoughtcrime.securesms.loki.viewmodel
import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2
import org.session.libsignal.utilities.logging.Log
typealias DefaultGroups = List<OpenGroupAPIV2.DefaultGroup>
typealias GroupState = State<DefaultGroups>
class DefaultGroupsViewModel : ViewModel() {
@ -12,28 +15,10 @@ class DefaultGroupsViewModel : ViewModel() {
OpenGroupAPIV2.getDefaultRoomsIfNeeded()
}
val defaultRooms = OpenGroupAPIV2.defaultRooms.asLiveData().distinctUntilChanged().switchMap { groups ->
liveData {
// load images etc
emit(State.Loading)
val images = groups.filterNot { it.imageID.isNullOrEmpty() }.map { group ->
val image = viewModelScope.async(Dispatchers.IO) {
try {
OpenGroupAPIV2.downloadOpenGroupProfilePicture(group.imageID!!)
} catch (e: Exception) {
Log.e("Loki", "Error getting group profile picture", e)
null
}
}
group.id to image
}.toMap()
val defaultGroups = groups.map { group ->
DefaultGroup(group.id, group.name, images[group.id]?.await())
}
emit(State.Success(defaultGroups))
}
}
val defaultRooms = OpenGroupAPIV2.defaultRooms.map<DefaultGroups, GroupState> {
State.Success(it)
}.onStart {
emit(State.Loading)
}.asLiveData()
}
data class DefaultGroup(val id: String, val name: String, val image: ByteArray?)
}

View file

@ -1,6 +1,6 @@
package org.thoughtcrime.securesms.loki.viewmodel
sealed class State<T> {
sealed class State<out T> {
object Loading : State<Nothing>()
data class Success<T>(val value: T): State<T>()
data class Error(val error: Exception): State<Nothing>()

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -24,47 +23,39 @@
android:inputType="textWebEmailAddress"
android:hint="@string/fragment_enter_chat_url_edit_text_hint" />
<com.github.ybq.android.spinkit.SpinKitView
android:visibility="gone"
android:id="@+id/defaultRoomsLoader"
style="@style/SpinKitView.Small.WanderingCubes"
android:layout_marginVertical="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:visibility="gone"
android:paddingHorizontal="24dp"
android:id="@+id/defaultRoomsParent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_marginVertical="16dp"
android:textSize="18sp"
android:textStyle="bold"
android:text="@string/activity_join_public_chat_join_rooms"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<GridLayout
android:id="@+id/defaultRoomsGridLayout"
android:columnCount="2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<androidx.gridlayout.widget.GridLayout
app:columnCount="2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:theme="@style/Theme.MaterialComponents.DayNight"
style="?attr/chipStyle"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:textStartPadding="10dp"
android:text="Main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.google.android.material.chip.Chip
android:theme="@style/Theme.MaterialComponents.DayNight"
style="?attr/chipStyle"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:textStartPadding="10dp"
android:text="Main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.google.android.material.chip.Chip
android:theme="@style/Theme.MaterialComponents.DayNight"
style="?attr/chipStyle"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:textStartPadding="10dp"
android:text="Main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.google.android.material.chip.Chip
android:theme="@style/Theme.MaterialComponents.DayNight"
style="?attr/chipStyle"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:textStartPadding="10dp"
android:text="Main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.gridlayout.widget.GridLayout>
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip 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:theme="@style/Theme.MaterialComponents.DayNight"
style="?attr/chipStyle"
app:textStartPadding="10dp"
app:textEndPadding="10dp"
android:layout_columnWeight="1"
android:layout_marginHorizontal="2dp"
tools:text="Main Group"
android:ellipsize="end"
tools:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="52dp" />

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -24,19 +23,40 @@
android:inputType="textWebEmailAddress"
android:hint="@string/fragment_enter_chat_url_edit_text_hint" />
<com.github.ybq.android.spinkit.SpinKitView
android:visibility="gone"
android:id="@+id/defaultRoomsLoader"
style="@style/SpinKitView.Small.WanderingCubes"
android:layout_marginVertical="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:visibility="gone"
android:paddingHorizontal="24dp"
android:id="@+id/defaultRoomsParent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_marginVertical="16dp"
android:textSize="18sp"
android:textStyle="bold"
android:text="@string/activity_join_public_chat_join_rooms"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<GridLayout
android:id="@+id/defaultRoomsGridLayout"
android:columnCount="2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.chip.Chip
android:theme="@style/Theme.MaterialComponents.DayNight"
app:closeIconEnabled="true"
style="?attr/chipStyle"
android:text="Main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/joinPublicChatButton"

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_columnWeight="1"
android:layout_height="0dp"/>

View file

@ -1884,5 +1884,6 @@
<string name="activity_backup_restore_passphrase">30-digit passphrase</string>
<!-- LinkDeviceActivity -->
<string name="activity_link_device_skip_prompt">This is taking a while, would you like to skip?</string>
<string name="activity_join_public_chat_join_rooms">Or join one of these...</string>
</resources>

View file

@ -30,10 +30,10 @@ import java.util.*
object OpenGroupAPIV2 {
private val moderators: HashMap<String, Set<String>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
private const val DEFAULT_SERVER = "https://sog.ibolpap.finance"
const val DEFAULT_SERVER = "https://sog.ibolpap.finance"
private const val DEFAULT_SERVER_PUBLIC_KEY = "b464aa186530c97d6bcf663a3a3b7465a5f782beaa67c83bee99468824b4aa10"
val defaultRooms = MutableSharedFlow<List<Info>>(replay = 1)
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
private val sharedContext = Kovenant.createContext()
private val curve = Curve25519.getInstance(Curve25519.BEST)
@ -47,6 +47,10 @@ object OpenGroupAPIV2 {
object NO_PUBLIC_KEY : Error()
}
data class DefaultGroup(val id: String,
val name: String,
val image: ByteArray?)
data class Info(
val id: String,
val name: String,
@ -77,7 +81,7 @@ object OpenGroupAPIV2 {
val urlBuilder = HttpUrl.Builder()
.scheme(parsed.scheme())
.host(parsed.host())
.addPathSegment(request.endpoint)
.addPathSegments(request.endpoint)
if (request.verb == GET) {
for ((key, value) in request.queryParameters) {
@ -139,6 +143,14 @@ object OpenGroupAPIV2 {
}
}
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
return send(request).map(sharedContext) { json ->
val result = json["result"] as? String ?: throw Error.PARSING_FAILED
decode(result)
}
}
fun getAuthToken(room: String, server: String): Promise<String, Exception> {
val storage = MessagingConfiguration.shared.storage
return storage.getAuthToken(room, server)?.let {
@ -312,15 +324,30 @@ object OpenGroupAPIV2 {
}
}
fun isUserModerator(publicKey: String, room: String, server: String): Boolean = moderators["$server.$room"]?.contains(publicKey)
?: false
@JvmStatic
fun isUserModerator(publicKey: String, room: String, server: String): Boolean =
moderators["$server.$room"]?.contains(publicKey) ?: false
// endregion
// region General
fun getDefaultRoomsIfNeeded(): Promise<List<Info>, Exception> {
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
val storage = MessagingConfiguration.shared.storage
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)
return getAllRooms(DEFAULT_SERVER).success { new ->
return getAllRooms(DEFAULT_SERVER).map { groups ->
val images = groups.map { group ->
group.id to downloadOpenGroupProfilePicture(group.id, DEFAULT_SERVER)
}.toMap()
groups.map { group ->
val image = try {
images[group.id]!!.get()
} catch (e: Exception) {
// no image or image failed to download
null
}
DefaultGroup(group.id, group.name, image)
}
}.success { new ->
defaultRooms.tryEmit(new)
}
}