Merge remote-tracking branch 'upstream/dev' into libsession-integration

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt
This commit is contained in:
Morgan Pretty 2023-06-13 09:35:07 +10:00
commit 5ca7f76275
15 changed files with 160 additions and 130 deletions

View File

@ -159,8 +159,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
}
def canonicalVersionCode = 336
def canonicalVersionName = "1.16.8"
def canonicalVersionCode = 338
def canonicalVersionName = "1.16.9"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -69,7 +69,6 @@ class DeleteOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListen
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
window.setDimAmount(0.6f)
}
}

View File

@ -60,8 +60,7 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(),
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
window.setDimAmount(0.6f)
}
override fun onClick(v: View?) {

View File

@ -340,11 +340,9 @@ class VisibleMessageView : LinearLayout {
val container = binding.messageInnerContainer
val content = binding.messageContentView.root
val expiration = binding.expirationTimerView
val spacing = binding.messageContentSpacing
container.removeAllViewsInLayout()
container.addView(if (message.isOutgoing) expiration else content)
container.addView(if (message.isOutgoing) content else expiration)
container.addView(spacing, if (message.isOutgoing) 0 else 2)
val containerParams = container.layoutParams as ConstraintLayout.LayoutParams
containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f
container.layoutParams = containerParams

View File

@ -14,9 +14,7 @@ open class BaseDialog : DialogFragment() {
val builder = AlertDialog.Builder(requireContext())
setContentView(builder)
val result = builder.create()
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
result.window?.setDimAmount(if (isLightMode) 0.1f else 0.75f)
result.window?.setDimAmount(0.6f)
return result
}

View File

@ -15,7 +15,7 @@ import java.util.concurrent.Executors
object OpenGroupManager {
private val executorService = Executors.newScheduledThreadPool(4)
private var pollers = mutableMapOf<String, OpenGroupPoller>() // One for each server
private val pollers = mutableMapOf<String, OpenGroupPoller>() // One for each server
private var isPolling = false
private val pollUpdaterLock = Any()
@ -47,11 +47,11 @@ object OpenGroupManager {
}
val servers = serverGroups.map { it.server }.toSet()
servers.forEach { server ->
pollers[server]?.stop() // Shouldn't be necessary
val poller = OpenGroupPoller(server, executorService)
poller.startIfNeeded()
pollers[server] = poller
synchronized(pollUpdaterLock) {
servers.forEach { server ->
pollers[server]?.stop() // Shouldn't be necessary
pollers[server] = OpenGroupPoller(server, executorService).apply { startIfNeeded() }
}
}
}
@ -66,7 +66,7 @@ object OpenGroupManager {
@WorkerThread
fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> {
val openGroupID = "$server.$room"
var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
val storage = MessagingModuleConfiguration.shared.storage
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
// Check it it's added already
@ -82,13 +82,16 @@ object OpenGroupManager {
// Get capabilities & room info
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get()
storage.setServerCapabilities(server, capabilities.capabilities)
storage.setUserCount(room, server, info.activeUsers)
// Create the group locally if not available already
if (threadID < 0) {
threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId
GroupManager.createOpenGroup(openGroupID, context, null, info.name)
}
val openGroup = OpenGroup(server = server, room = room, publicKey = publicKey, name = info.name, imageId = info.imageId, canWrite = info.write, infoUpdates = info.infoUpdates)
threadDB.setOpenGroupChat(openGroup, threadID)
OpenGroupPoller.handleRoomPollInfo(
server = server,
roomToken = room,
pollInfo = info.toPollInfo(),
createGroupIfMissingWithPublicKey = publicKey
)
return threadID to info
}

View File

@ -95,7 +95,6 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
window.setDimAmount(0.6f)
}
}

View File

@ -123,8 +123,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
window.setDimAmount(0.6f)
}
fun saveNickName(recipient: Recipient) = with(binding) {

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/dialog_background_color" />
<corners android:radius="?dialogCornerRadius" />
</shape>

View File

@ -11,6 +11,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:SpinKit_Color="?android:textColorPrimary" />
app:SpinKit_Color="?colorAccent" />
</RelativeLayout>

View File

@ -79,8 +79,9 @@
<include layout="@layout/view_visible_message_content"
android:id="@+id/messageContentView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
android:id="@+id/expirationTimerView"
@ -90,14 +91,9 @@
android:layout_marginHorizontal="@dimen/small_spacing"
android:contentDescription="@string/AccessibilityId_timer_icon"
android:visibility="invisible"
tools:visibility="visible" />
<View
android:id="@+id/messageContentSpacing"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
android:minWidth="@dimen/very_large_spacing" />
tools:visibility="visible"
tools:src="@drawable/timer60"
tools:tint="@color/black"/>
</LinearLayout>

View File

@ -23,6 +23,7 @@
</style>
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
<item name="android:windowBackground">@drawable/default_dialog_background</item>
<item name="android:colorBackground">?attr/dialog_background_color</item>
<item name="dialog_background_color">?colorPrimary</item>
<item name="android:colorBackgroundFloating">?colorPrimary</item>

View File

@ -37,13 +37,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException())
return
}
// get image
storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey)
val info = storage.addOpenGroup(openGroup.joinUrl())
val imageId = info?.imageId
if (imageId != null && storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId) == null) {
JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId))
}
storage.addOpenGroup(openGroup.joinUrl())
Log.d(KEY, "onOpenGroupAdded(${openGroup.server})")
storage.onOpenGroupAdded(openGroup.server, openGroup.room)
} catch (e: Exception) {

View File

@ -109,7 +109,24 @@ object OpenGroupApi {
val defaultWrite: Boolean = false,
val upload: Boolean = false,
val defaultUpload: Boolean = false,
)
) {
fun toPollInfo() = RoomPollInfo(
token = token,
activeUsers = activeUsers,
admin = admin,
globalAdmin = globalAdmin,
moderator = moderator,
globalModerator = globalModerator,
read = read,
defaultRead = defaultRead,
defaultAccessible = defaultAccessible,
write = write,
defaultWrite = defaultWrite,
upload = upload,
defaultUpload = defaultUpload,
details = this
)
}
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class PinnedMessage(

View File

@ -31,6 +31,7 @@ import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.successBackground
import java.util.UUID
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
@ -40,15 +41,101 @@ class OpenGroupPoller(private val server: String, private val executorService: S
var isCaughtUp = false
var secondToLastJob: MessageReceiveJob? = null
private var future: ScheduledFuture<*>? = null
@Volatile private var runId: UUID = UUID.randomUUID()
companion object {
private const val pollInterval: Long = 4000L
const val maxInactivityPeriod = 14 * 24 * 60 * 60 * 1000
public fun handleRoomPollInfo(
server: String,
roomToken: String,
pollInfo: OpenGroupApi.RoomPollInfo,
createGroupIfMissingWithPublicKey: String? = null
) {
val storage = MessagingModuleConfiguration.shared.storage
val groupId = "$server.$roomToken"
val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray())
val existingOpenGroup = storage.getOpenGroup(roomToken, server)
// If we don't have an existing group and don't have a 'createGroupIfMissingWithPublicKey'
// value then don't process the poll info
val publicKey = existingOpenGroup?.publicKey ?: createGroupIfMissingWithPublicKey
val name = pollInfo.details?.name ?: existingOpenGroup?.name
val infoUpdates = pollInfo.details?.infoUpdates ?: existingOpenGroup?.infoUpdates
if (publicKey == null) return
val openGroup = OpenGroup(
server = server,
room = pollInfo.token,
name = name ?: "",
publicKey = publicKey,
imageId = (pollInfo.details?.imageId ?: existingOpenGroup?.imageId),
canWrite = pollInfo.write,
infoUpdates = infoUpdates ?: 0
)
// - Open Group changes
storage.updateOpenGroup(openGroup)
// - User Count
storage.setUserCount(roomToken, server, pollInfo.activeUsers)
// - Moderators
pollInfo.details?.moderators?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.MODERATOR)
})
}
pollInfo.details?.hiddenModerators?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR)
})
}
// - Admins
pollInfo.details?.admins?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.ADMIN)
})
}
pollInfo.details?.hiddenAdmins?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN)
})
}
// Update the group avatar
if (
(
pollInfo.details != null &&
pollInfo.details.imageId != null && (
pollInfo.details.imageId != existingOpenGroup?.imageId ||
!storage.hasDownloadedProfilePicture(dbGroupId)
) &&
storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null
) || (
pollInfo.details == null &&
existingOpenGroup?.imageId != null &&
!storage.hasDownloadedProfilePicture(dbGroupId) &&
storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null
)
) {
JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, openGroup.imageId))
}
else if (
pollInfo.details != null &&
pollInfo.details.imageId == null &&
existingOpenGroup?.imageId != null
) {
storage.removeProfilePicture(dbGroupId)
}
}
}
fun startIfNeeded() {
if (hasStarted) { return }
hasStarted = true
runId = UUID.randomUUID()
future = executorService?.schedule(::poll, 0, TimeUnit.MILLISECONDS)
}
@ -58,6 +145,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
}
fun poll(isPostCapabilitiesRetry: Boolean = false): Promise<Unit, Exception> {
val currentRunId = runId
val storage = MessagingModuleConfiguration.shared.storage
val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room }
@ -88,24 +176,30 @@ class OpenGroupPoller(private val server: String, private val executorService: S
isCaughtUp = true
}
}
if (hasStarted) {
executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
// Only poll again if it's the same poller run
if (currentRunId == runId) {
future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}.fail {
updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, it)
updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, currentRunId, it)
}.map { }
}
private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, exception: Exception) {
private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, currentRunId: UUID, exception: Exception) {
if (exception is OnionRequestAPI.HTTPRequestFailedBlindingRequiredException) {
if (!isPostCapabilitiesRetry) {
OpenGroupApi.getCapabilities(server).map {
handleCapabilities(server, it)
}
executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS)
// Only poll again if it's the same poller run
if (currentRunId == runId) {
future = executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS)
}
}
} else {
executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
} else if (currentRunId == runId) {
future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}
@ -113,84 +207,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
val storage = MessagingModuleConfiguration.shared.storage
storage.setServerCapabilities(server, capabilities.capabilities)
}
private fun handleRoomPollInfo(
server: String,
roomToken: String,
pollInfo: OpenGroupApi.RoomPollInfo
) {
if (!hasStarted) return
val storage = MessagingModuleConfiguration.shared.storage
val groupId = "$server.$roomToken"
val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray())
val existingOpenGroup = storage.getOpenGroup(roomToken, server)
val publicKey = existingOpenGroup?.publicKey ?: return
val openGroup = OpenGroup(
server = server,
room = pollInfo.token,
name = if (pollInfo.details != null) { pollInfo.details.name } else { existingOpenGroup.name },
publicKey = publicKey,
imageId = if (pollInfo.details != null) { pollInfo.details.imageId } else { existingOpenGroup.imageId },
canWrite = pollInfo.write,
infoUpdates = if (pollInfo.details != null) { pollInfo.details.infoUpdates } else { existingOpenGroup.infoUpdates }
)
// - Open Group changes
storage.updateOpenGroup(openGroup)
// - User Count
storage.setUserCount(roomToken, server, pollInfo.activeUsers)
// - Moderators
pollInfo.details?.moderators?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.MODERATOR)
})
}
pollInfo.details?.hiddenModerators?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR)
})
}
// - Admins
pollInfo.details?.admins?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.ADMIN)
})
}
pollInfo.details?.hiddenAdmins?.let { moderatorList ->
storage.setGroupMemberRoles(moderatorList.map {
GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN)
})
}
// Update the group avatar
if (
(
pollInfo.details != null &&
pollInfo.details.imageId != null && (
pollInfo.details.imageId != existingOpenGroup.imageId ||
!storage.hasDownloadedProfilePicture(dbGroupId)
) &&
storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null
) || (
pollInfo.details == null &&
existingOpenGroup.imageId != null &&
!storage.hasDownloadedProfilePicture(dbGroupId) &&
storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null
)
) {
JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, existingOpenGroup.imageId))
}
else if (
pollInfo.details != null &&
pollInfo.details.imageId == null &&
existingOpenGroup.imageId != null
) {
storage.removeProfilePicture(dbGroupId)
}
}
private fun handleMessages(
server: String,
roomToken: String,