mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 11:20:47 +01:00
Integrate call links create/update/read apis.
This commit is contained in:
committed by
Nicholas Tinsley
parent
4d6d31d624
commit
5a38143987
@@ -1,8 +1,83 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.ringrtc.CallLinkRootKey
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import java.net.URLDecoder
|
||||
|
||||
/**
|
||||
* Utility object for call links to try to keep some common logic in one place.
|
||||
*/
|
||||
object CallLinks {
|
||||
fun url(identifier: String) = "https://calls.signal.org/#$identifier"
|
||||
private const val ROOT_KEY = "key"
|
||||
|
||||
private val TAG = Log.tag(CallLinks::class.java)
|
||||
|
||||
fun url(linkKeyBytes: ByteArray) = "https://signal.link/call/#key=${Hex.dump(linkKeyBytes)}"
|
||||
|
||||
fun watchCallLink(roomId: CallLinkRoomId): Observable<CallLinkTable.CallLink> {
|
||||
return Observable.create { emitter ->
|
||||
|
||||
fun refresh() {
|
||||
val callLink = SignalDatabase.callLinks.getCallLinkByRoomId(roomId)
|
||||
if (callLink != null) {
|
||||
emitter.onNext(callLink)
|
||||
}
|
||||
}
|
||||
|
||||
val observer = DatabaseObserver.Observer {
|
||||
refresh()
|
||||
}
|
||||
|
||||
ApplicationDependencies.getDatabaseObserver().registerCallLinkObserver(roomId, observer)
|
||||
emitter.setCancellable {
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer)
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseUrl(url: String): CallLinkRootKey? {
|
||||
val parts = url.split("#")
|
||||
if (parts.size != 2) {
|
||||
Log.w(TAG, "Invalid fragment delimiter count in url.")
|
||||
return null
|
||||
}
|
||||
|
||||
val fragment = parts[1]
|
||||
val fragmentParts = fragment.split("&")
|
||||
val fragmentQuery = fragmentParts.associate {
|
||||
val kv = it.split("=")
|
||||
if (kv.size != 2) {
|
||||
Log.w(TAG, "Invalid fragment keypair. Skipping.")
|
||||
}
|
||||
|
||||
val key = URLDecoder.decode(kv[0], "utf8")
|
||||
val value = URLDecoder.decode(kv[1], "utf8")
|
||||
|
||||
key to value
|
||||
}
|
||||
|
||||
val key = fragmentQuery[ROOT_KEY]
|
||||
if (key == null) {
|
||||
Log.w(TAG, "Root key not found in fragment query string.")
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO Parse the key into a byte array
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import android.app.Dialog
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -30,21 +35,34 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.ringrtc.CallLinkRootKey
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import java.time.Instant
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SignalCallRowPreview() {
|
||||
val avatarColor = remember { AvatarColor.random() }
|
||||
val callLink = remember {
|
||||
val credentials = CallLinkCredentials.generate()
|
||||
CallLinkTable.CallLink(
|
||||
name = "Call Name",
|
||||
identifier = "blahblahblah",
|
||||
avatarColor = avatarColor,
|
||||
isApprovalRequired = false
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = CallLinkRoomId.fromCallLinkRootKey(CallLinkRootKey(credentials.linkKeyBytes)),
|
||||
credentials = credentials,
|
||||
state = SignalCallLinkState(
|
||||
name = "Call Name",
|
||||
restrictions = org.signal.ringrtc.CallLinkState.Restrictions.NONE,
|
||||
expiration = Instant.MAX,
|
||||
revoked = false
|
||||
),
|
||||
avatarColor = avatarColor
|
||||
)
|
||||
}
|
||||
SignalTheme(false) {
|
||||
@@ -95,10 +113,10 @@ fun SignalCallRow(
|
||||
.align(CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = callLink.name.ifEmpty { stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__signal_call) }
|
||||
text = callLink.state.name.ifEmpty { stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__signal_call) }
|
||||
)
|
||||
Text(
|
||||
text = CallLinks.url(callLink.identifier),
|
||||
text = callLink.credentials?.let { CallLinks.url(it.linkKeyBytes) } ?: "",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.ringrtc.CallLinkState
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
|
||||
/**
|
||||
* Repository for performing update operations on call links:
|
||||
* <ul>
|
||||
* <li>Set name</li>
|
||||
* <li>Set restrictions</li>
|
||||
* <li>Revoke link</li>
|
||||
* </ul>
|
||||
*
|
||||
* All of these will delegate to the [SignalCallLinkManager] but will additionally update the database state.
|
||||
*/
|
||||
class UpdateCallLinkRepository(
|
||||
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
|
||||
) {
|
||||
fun setCallName(credentials: CallLinkCredentials, name: String): Single<UpdateCallLinkResult> {
|
||||
return callLinkManager
|
||||
.updateCallLinkName(
|
||||
credentials = credentials,
|
||||
name = name
|
||||
)
|
||||
.doOnSuccess(updateState(credentials))
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun setCallRestrictions(credentials: CallLinkCredentials, restrictions: CallLinkState.Restrictions): Single<UpdateCallLinkResult> {
|
||||
return callLinkManager
|
||||
.updateCallLinkRestrictions(
|
||||
credentials = credentials,
|
||||
restrictions = restrictions
|
||||
)
|
||||
.doOnSuccess(updateState(credentials))
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun revokeCallLink(credentials: CallLinkCredentials): Single<UpdateCallLinkResult> {
|
||||
return callLinkManager
|
||||
.updateCallLinkRevoked(credentials, true)
|
||||
.doOnSuccess(updateState(credentials))
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
private fun updateState(credentials: CallLinkCredentials): (UpdateCallLinkResult) -> Unit {
|
||||
return { result ->
|
||||
if (result is UpdateCallLinkResult.Success) {
|
||||
SignalDatabase.callLinks.updateCallLinkState(credentials.roomId, result.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.create
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
@@ -28,9 +33,13 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.ringrtc.CallLinkState
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
|
||||
@@ -39,6 +48,7 @@ import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CreateCallLinkResult
|
||||
import org.thoughtcrime.securesms.sharing.MultiShareArgs
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
|
||||
@@ -47,14 +57,20 @@ import org.thoughtcrime.securesms.util.Util
|
||||
*/
|
||||
class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(CreateCallLinkBottomSheetDialogFragment::class.java)
|
||||
}
|
||||
|
||||
private val viewModel: CreateCallLinkViewModel by viewModels()
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
parentFragmentManager.setFragmentResultListener(EditCallLinkNameDialogFragment.RESULT_KEY, viewLifecycleOwner) { resultKey, bundle ->
|
||||
if (bundle.containsKey(resultKey)) {
|
||||
viewModel.setCallName(bundle.getString(resultKey)!!)
|
||||
setCallName(bundle.getString(resultKey)!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,10 +110,10 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
|
||||
)
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = callLink.isApprovalRequired,
|
||||
checked = callLink.state.restrictions == CallLinkState.Restrictions.ADMIN_APPROVAL,
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__approve_all_members),
|
||||
onCheckChanged = viewModel::setApproveAllMembers,
|
||||
modifier = Modifier.clickable(onClick = viewModel::toggleApproveAllMembers)
|
||||
onCheckChanged = this@CreateCallLinkBottomSheetDialogFragment::setApproveAllMembers,
|
||||
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::toggleApproveAllMembers)
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
@@ -133,54 +149,82 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCallName(callName: String) {
|
||||
lifecycleDisposable += viewModel.setCallName(callName).subscribeBy {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setApproveAllMembers(approveAllMembers: Boolean) {
|
||||
lifecycleDisposable += viewModel.setApproveAllMembers(approveAllMembers).subscribeBy {
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleApproveAllMembers() {
|
||||
lifecycleDisposable += viewModel.toggleApproveAllMembers().subscribeBy {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAddACallNameClicked() {
|
||||
val snapshot = viewModel.callLink.value
|
||||
findNavController().navigate(
|
||||
CreateCallLinkBottomSheetDialogFragmentDirections.actionCreateCallLinkBottomSheetToEditCallLinkNameDialogFragment(snapshot.name)
|
||||
CreateCallLinkBottomSheetDialogFragmentDirections.actionCreateCallLinkBottomSheetToEditCallLinkNameDialogFragment(snapshot.state.name)
|
||||
)
|
||||
}
|
||||
|
||||
private fun onJoinClicked() {
|
||||
lifecycleDisposable += viewModel.commitCallLink().subscribeBy {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDoneClicked() {
|
||||
lifecycleDisposable += viewModel.commitCallLink().subscribeBy {
|
||||
when (it) {
|
||||
is EnsureCallLinkCreatedResult.Success -> dismissAllowingStateLoss()
|
||||
is EnsureCallLinkCreatedResult.Failure -> handleCreateCallLinkFailure(it.failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCreateCallLinkFailure(failure: CreateCallLinkResult.Failure) {
|
||||
Log.w(TAG, "Failed to create call link: $failure")
|
||||
}
|
||||
|
||||
private fun onShareViaSignalClicked() {
|
||||
val snapshot = viewModel.callLink.value
|
||||
|
||||
MultiselectForwardFragment.showFullScreen(
|
||||
childFragmentManager,
|
||||
MultiselectForwardFragmentArgs(
|
||||
canSendToNonPush = false,
|
||||
multiShareArgs = listOf(
|
||||
MultiShareArgs.Builder()
|
||||
.withDraftText(snapshot.identifier)
|
||||
.build()
|
||||
lifecycleDisposable += viewModel.commitCallLink().subscribeBy {
|
||||
MultiselectForwardFragment.showFullScreen(
|
||||
childFragmentManager,
|
||||
MultiselectForwardFragmentArgs(
|
||||
canSendToNonPush = false,
|
||||
multiShareArgs = listOf(
|
||||
MultiShareArgs.Builder()
|
||||
.withDraftText(CallLinks.url(viewModel.linkKeyBytes))
|
||||
.build()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCopyLinkClicked() {
|
||||
val snapshot = viewModel.callLink.value
|
||||
Util.copyToClipboard(requireContext(), CallLinks.url(snapshot.identifier))
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__copied_to_clipboard, Toast.LENGTH_LONG).show()
|
||||
lifecycleDisposable += viewModel.commitCallLink().subscribeBy {
|
||||
Util.copyToClipboard(requireContext(), CallLinks.url(viewModel.linkKeyBytes))
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__copied_to_clipboard, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onShareLinkClicked() {
|
||||
val snapshot = viewModel.callLink.value
|
||||
val mimeType = Intent.normalizeMimeType("text/plain")
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireContext())
|
||||
.setText(CallLinks.url(snapshot.identifier))
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
lifecycleDisposable += viewModel.commitCallLink().subscribeBy {
|
||||
val mimeType = Intent.normalizeMimeType("text/plain")
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireContext())
|
||||
.setText(CallLinks.url(viewModel.linkKeyBytes))
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet, Toast.LENGTH_LONG).show()
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.create
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CreateCallLinkResult
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
|
||||
|
||||
/**
|
||||
* Repository for creating new call links. This will delegate to the [SignalCallLinkManager]
|
||||
* but will also ensure the database is updated.
|
||||
*/
|
||||
class CreateCallLinkRepository(
|
||||
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
|
||||
) {
|
||||
fun ensureCallLinkCreated(credentials: CallLinkCredentials, avatarColor: AvatarColor): Single<EnsureCallLinkCreatedResult> {
|
||||
val doesCallLinkExistInLocalDatabase = Single.fromCallable {
|
||||
SignalDatabase.callLinks.callLinkExists(credentials.roomId)
|
||||
}
|
||||
|
||||
return doesCallLinkExistInLocalDatabase.flatMap { exists ->
|
||||
if (exists) {
|
||||
Single.just(EnsureCallLinkCreatedResult.Success)
|
||||
} else {
|
||||
callLinkManager.createCallLink(credentials).map {
|
||||
when (it) {
|
||||
is CreateCallLinkResult.Success -> {
|
||||
SignalDatabase.callLinks.insertCallLink(
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = credentials.roomId,
|
||||
credentials = credentials,
|
||||
state = it.state,
|
||||
avatarColor = avatarColor
|
||||
)
|
||||
)
|
||||
|
||||
EnsureCallLinkCreatedResult.Success
|
||||
}
|
||||
|
||||
is CreateCallLinkResult.Failure -> EnsureCallLinkCreatedResult.Failure(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,98 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.create
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.ringrtc.CallLinkState.Restrictions
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
import java.time.Instant
|
||||
|
||||
class CreateCallLinkViewModel : ViewModel() {
|
||||
class CreateCallLinkViewModel(
|
||||
private val repository: CreateCallLinkRepository = CreateCallLinkRepository(),
|
||||
private val mutationRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
|
||||
) : ViewModel() {
|
||||
private val credentials = CallLinkCredentials.generate()
|
||||
private val avatarColor = AvatarColor.random()
|
||||
private val _callLink: MutableState<CallLinkTable.CallLink> = mutableStateOf(
|
||||
CallLinkTable.CallLink("", "", AvatarColor.random(), false)
|
||||
CallLinkTable.CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = credentials.roomId,
|
||||
credentials = credentials,
|
||||
state = SignalCallLinkState(
|
||||
name = "",
|
||||
restrictions = Restrictions.NONE,
|
||||
revoked = false,
|
||||
expiration = Instant.MAX
|
||||
),
|
||||
avatarColor = avatarColor
|
||||
)
|
||||
)
|
||||
|
||||
val callLink: State<CallLinkTable.CallLink> = _callLink
|
||||
val linkKeyBytes: ByteArray = credentials.linkKeyBytes
|
||||
|
||||
fun setApproveAllMembers(approveAllMembers: Boolean) {
|
||||
_callLink.value = _callLink.value.copy(isApprovalRequired = approveAllMembers)
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
init {
|
||||
disposables += CallLinks.watchCallLink(credentials.roomId)
|
||||
.subscribeBy {
|
||||
_callLink.value = it
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleApproveAllMembers() {
|
||||
_callLink.value = _callLink.value.copy(isApprovalRequired = _callLink.value.isApprovalRequired)
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
fun setCallName(callName: String) {
|
||||
_callLink.value = _callLink.value.copy(name = callName)
|
||||
fun commitCallLink(): Single<EnsureCallLinkCreatedResult> {
|
||||
return repository.ensureCallLinkCreated(credentials, avatarColor)
|
||||
}
|
||||
|
||||
fun setApproveAllMembers(approveAllMembers: Boolean): Single<UpdateCallLinkResult> {
|
||||
return commitCallLink()
|
||||
.flatMap {
|
||||
when (it) {
|
||||
is EnsureCallLinkCreatedResult.Success -> mutationRepository.setCallRestrictions(
|
||||
credentials,
|
||||
if (approveAllMembers) Restrictions.ADMIN_APPROVAL else Restrictions.NONE
|
||||
)
|
||||
is EnsureCallLinkCreatedResult.Failure -> Single.just(UpdateCallLinkResult.Failure(it.failure.status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleApproveAllMembers(): Single<UpdateCallLinkResult> {
|
||||
return setApproveAllMembers(_callLink.value.state.restrictions != Restrictions.ADMIN_APPROVAL)
|
||||
}
|
||||
|
||||
fun setCallName(callName: String): Single<UpdateCallLinkResult> {
|
||||
return commitCallLink()
|
||||
.flatMap {
|
||||
when (it) {
|
||||
is EnsureCallLinkCreatedResult.Success -> mutationRepository.setCallName(
|
||||
credentials,
|
||||
callName
|
||||
)
|
||||
is EnsureCallLinkCreatedResult.Failure -> Single.just(UpdateCallLinkResult.Failure(it.failure.status))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.create
|
||||
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CreateCallLinkResult
|
||||
|
||||
sealed interface EnsureCallLinkCreatedResult {
|
||||
object Success : EnsureCallLinkCreatedResult
|
||||
data class Failure(val failure: CreateCallLinkResult.Failure) : EnsureCallLinkCreatedResult
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.details
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.details
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -16,19 +24,27 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.ringrtc.CallLinkState.Restrictions
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
|
||||
import org.thoughtcrime.securesms.calls.links.SignalCallRow
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Provides detailed info about a call link and allows the owner of that link
|
||||
@@ -36,24 +52,24 @@ import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
*/
|
||||
class CallLinkDetailsFragment : ComposeFragment(), CallLinkDetailsCallback {
|
||||
|
||||
private val viewModel: CallLinkViewModel by viewModels()
|
||||
private val viewModel: CallLinkDetailsViewModel by viewModels()
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
parentFragmentManager.setFragmentResultListener(EditCallLinkNameDialogFragment.RESULT_KEY, viewLifecycleOwner) { resultKey, bundle ->
|
||||
if (bundle.containsKey(resultKey)) {
|
||||
viewModel.setName(bundle.getString(resultKey)!!)
|
||||
setName(bundle.getString(resultKey)!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val isLoading by viewModel.isLoading
|
||||
val callLink by viewModel.callLink
|
||||
val state by viewModel.state
|
||||
|
||||
CallLinkDetails(
|
||||
isLoading,
|
||||
callLink,
|
||||
state,
|
||||
this
|
||||
)
|
||||
}
|
||||
@@ -67,22 +83,39 @@ class CallLinkDetailsFragment : ComposeFragment(), CallLinkDetailsCallback {
|
||||
}
|
||||
|
||||
override fun onEditNameClicked() {
|
||||
val name = viewModel.callLink.value.name
|
||||
val name = viewModel.nameSnapshot
|
||||
findNavController().navigate(
|
||||
CallLinkDetailsFragmentDirections.actionCallLinkDetailsFragmentToEditCallLinkNameDialogFragment(name)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onShareClicked() {
|
||||
// TODO("Not yet implemented")
|
||||
val mimeType = Intent.normalizeMimeType("text/plain")
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireContext())
|
||||
.setText(CallLinks.url(viewModel.rootKeySnapshot))
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteClicked() {
|
||||
// TODO("Not yet implemented")
|
||||
lifecycleDisposable += viewModel.revoke().subscribeBy {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onApproveAllMembersChanged(checked: Boolean) {
|
||||
// TODO("Not yet implemented")
|
||||
lifecycleDisposable += viewModel.setApproveAllMembers(checked).subscribeBy {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setName(name: String) {
|
||||
lifecycleDisposable += viewModel.setName(name).subscribeBy {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,18 +136,26 @@ private fun CallLinkDetailsPreview() {
|
||||
}
|
||||
|
||||
val callLink = remember {
|
||||
val credentials = CallLinkCredentials.generate()
|
||||
CallLinkTable.CallLink(
|
||||
name = "Call Name",
|
||||
identifier = "call-id-1",
|
||||
isApprovalRequired = false,
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = credentials.roomId,
|
||||
credentials = credentials,
|
||||
state = SignalCallLinkState(
|
||||
name = "Call Name",
|
||||
revoked = false,
|
||||
restrictions = Restrictions.NONE,
|
||||
expiration = Instant.MAX
|
||||
),
|
||||
avatarColor = avatarColor
|
||||
)
|
||||
}
|
||||
|
||||
SignalTheme(false) {
|
||||
CallLinkDetails(
|
||||
false,
|
||||
callLink,
|
||||
CallLinkDetailsState(
|
||||
callLink
|
||||
),
|
||||
object : CallLinkDetailsCallback {
|
||||
override fun onNavigationClicked() = Unit
|
||||
override fun onJoinClicked() = Unit
|
||||
@@ -129,8 +170,7 @@ private fun CallLinkDetailsPreview() {
|
||||
|
||||
@Composable
|
||||
private fun CallLinkDetails(
|
||||
isLoading: Boolean,
|
||||
callLink: CallLinkTable.CallLink,
|
||||
state: CallLinkDetailsState,
|
||||
callback: CallLinkDetailsCallback
|
||||
) {
|
||||
Scaffolds.Settings(
|
||||
@@ -138,13 +178,13 @@ private fun CallLinkDetails(
|
||||
onNavigationClick = callback::onNavigationClicked,
|
||||
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24)
|
||||
) { paddingValues ->
|
||||
if (isLoading) {
|
||||
if (state.callLink == null) {
|
||||
return@Settings
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
SignalCallRow(
|
||||
callLink = callLink,
|
||||
callLink = state.callLink,
|
||||
onJoinClicked = callback::onJoinClicked,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 12.dp)
|
||||
)
|
||||
@@ -155,7 +195,7 @@ private fun CallLinkDetails(
|
||||
)
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = callLink.isApprovalRequired,
|
||||
checked = state.callLink.state.restrictions == Restrictions.ADMIN_APPROVAL,
|
||||
text = stringResource(id = R.string.CallLinkDetailsFragment__approve_all_members),
|
||||
onCheckChanged = callback::onApproveAllMembersChanged
|
||||
)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.details
|
||||
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.ReadCallLinkResult
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
|
||||
|
||||
class CallLinkDetailsRepository(
|
||||
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
|
||||
) {
|
||||
fun refreshCallLinkState(callLinkRoomId: CallLinkRoomId): Disposable {
|
||||
return Maybe.fromCallable<CallLinkTable.CallLink> { SignalDatabase.callLinks.getCallLinkByRoomId(callLinkRoomId) }
|
||||
.flatMapSingle { callLinkManager.readCallLink(it.credentials!!) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribeBy {
|
||||
when (it) {
|
||||
is ReadCallLinkResult.Success -> SignalDatabase.callLinks.updateCallLinkState(callLinkRoomId, it.callLinkState)
|
||||
is ReadCallLinkResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.details
|
||||
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
|
||||
data class CallLinkDetailsState(
|
||||
val callLink: CallLinkTable.CallLink? = null
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.links.details
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.ringrtc.CallLinkState
|
||||
import org.thoughtcrime.securesms.calls.links.CallLinks
|
||||
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
|
||||
|
||||
class CallLinkDetailsViewModel(
|
||||
private val callLinkRoomId: CallLinkRoomId,
|
||||
private val repository: CallLinkDetailsRepository = CallLinkDetailsRepository(),
|
||||
private val mutationRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
|
||||
) : ViewModel() {
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private val _state: MutableState<CallLinkDetailsState> = mutableStateOf(CallLinkDetailsState())
|
||||
val state: State<CallLinkDetailsState> = _state
|
||||
val nameSnapshot: String
|
||||
get() = state.value.callLink?.state?.name ?: error("Call link not loaded yet.")
|
||||
|
||||
val rootKeySnapshot: ByteArray
|
||||
get() = state.value.callLink?.credentials?.linkKeyBytes ?: error("Call link not loaded yet.")
|
||||
|
||||
init {
|
||||
disposables += repository.refreshCallLinkState(callLinkRoomId)
|
||||
disposables += CallLinks.watchCallLink(callLinkRoomId).subscribeBy {
|
||||
_state.value = CallLinkDetailsState(
|
||||
callLink = it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
fun setApproveAllMembers(approveAllMembers: Boolean): Single<UpdateCallLinkResult> {
|
||||
val credentials = _state.value.callLink?.credentials ?: error("User cannot change the name of this call.")
|
||||
return mutationRepository.setCallRestrictions(credentials, if (approveAllMembers) CallLinkState.Restrictions.ADMIN_APPROVAL else CallLinkState.Restrictions.NONE)
|
||||
}
|
||||
|
||||
fun setName(name: String): Single<UpdateCallLinkResult> {
|
||||
val credentials = _state.value.callLink?.credentials ?: error("User cannot change the name of this call.")
|
||||
return mutationRepository.setCallName(credentials, name)
|
||||
}
|
||||
|
||||
fun revoke(): Single<UpdateCallLinkResult> {
|
||||
val credentials = _state.value.callLink?.credentials ?: error("User cannot change the name of this call.")
|
||||
return mutationRepository.revokeCallLink(credentials)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.thoughtcrime.securesms.calls.links.details
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable
|
||||
|
||||
class CallLinkViewModel : ViewModel() {
|
||||
private val isLoadingState: MutableState<Boolean> = mutableStateOf(true)
|
||||
val isLoading: State<Boolean> = isLoadingState
|
||||
|
||||
private val callLinkState: MutableState<CallLinkTable.CallLink> = mutableStateOf(
|
||||
CallLinkTable.CallLink("", "", AvatarColor.A120, false)
|
||||
)
|
||||
val callLink: State<CallLinkTable.CallLink> = callLinkState
|
||||
|
||||
fun setName(name: String) {
|
||||
callLinkState.value = callLinkState.value.copy(name = name)
|
||||
}
|
||||
}
|
||||
@@ -153,13 +153,17 @@ class CallLogAdapter(
|
||||
return
|
||||
}
|
||||
|
||||
binding.callRecipientAvatar.setAvatar(GlideApp.with(binding.callRecipientAvatar), model.call.peer, true)
|
||||
binding.callRecipientBadge.setBadgeFromRecipient(model.call.peer)
|
||||
binding.callRecipientName.text = model.call.peer.getDisplayName(context)
|
||||
presentRecipientDetails(model.call.peer)
|
||||
presentCallInfo(model.call, model.call.date)
|
||||
presentCallType(model)
|
||||
}
|
||||
|
||||
private fun presentRecipientDetails(recipient: Recipient) {
|
||||
binding.callRecipientAvatar.setAvatar(GlideApp.with(binding.callRecipientAvatar), recipient, true)
|
||||
binding.callRecipientBadge.setBadgeFromRecipient(recipient)
|
||||
binding.callRecipientName.text = recipient.getDisplayName(context)
|
||||
}
|
||||
|
||||
private fun presentCallInfo(call: CallLogRow.Call, date: Long) {
|
||||
val callState = context.getString(getCallStateStringRes(call.record))
|
||||
binding.callInfo.text = context.getString(
|
||||
@@ -321,11 +325,11 @@ class CallLogAdapter(
|
||||
/**
|
||||
* Invoked when user presses the audio icon
|
||||
*/
|
||||
fun onStartAudioCallClicked(peer: Recipient)
|
||||
fun onStartAudioCallClicked(recipient: Recipient)
|
||||
|
||||
/**
|
||||
* Invoked when user presses the video icon
|
||||
*/
|
||||
fun onStartVideoCallClicked(peer: Recipient)
|
||||
fun onStartVideoCallClicked(recipient: Recipient)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class CallLogContextMenu(
|
||||
}
|
||||
|
||||
private fun getAudioCallActionItem(call: CallLogRow.Call): ActionItem? {
|
||||
if (call.peer.isGroup) {
|
||||
if (call.peer.isCallLink || call.peer.isGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -63,12 +63,15 @@ class CallLogContextMenu(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGoToChatActionItem(call: CallLogRow.Call): ActionItem {
|
||||
return ActionItem(
|
||||
iconRes = R.drawable.symbol_open_24,
|
||||
title = fragment.getString(R.string.CallContextMenu__go_to_chat)
|
||||
) {
|
||||
fragment.startActivity(ConversationIntents.createBuilder(fragment.requireContext(), call.peer.id, -1L).build())
|
||||
private fun getGoToChatActionItem(call: CallLogRow.Call): ActionItem? {
|
||||
return when {
|
||||
call.peer.isCallLink -> null
|
||||
else -> ActionItem(
|
||||
iconRes = R.drawable.symbol_open_24,
|
||||
title = fragment.getString(R.string.CallContextMenu__go_to_chat)
|
||||
) {
|
||||
fragment.startActivity(ConversationIntents.createBuilder(fragment.requireContext(), call.peer.id, -1L).build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +80,10 @@ class CallLogContextMenu(
|
||||
iconRes = R.drawable.symbol_info_24,
|
||||
title = fragment.getString(R.string.CallContextMenu__info)
|
||||
) {
|
||||
val intent = ConversationSettingsActivity.forCall(fragment.requireContext(), call.peer, longArrayOf(call.record.messageId!!))
|
||||
val intent = when {
|
||||
call.peer.isCallLink -> throw NotImplementedError("Launch CallLinkDetailsActivity")
|
||||
else -> ConversationSettingsActivity.forCall(fragment.requireContext(), call.peer, longArrayOf(call.record.messageId!!))
|
||||
}
|
||||
fragment.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,9 +311,15 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
override fun onCallClicked(callLogRow: CallLogRow.Call) {
|
||||
if (viewModel.selectionStateSnapshot.isNotEmpty(binding.recycler.adapter!!.itemCount)) {
|
||||
viewModel.toggleSelected(callLogRow.id)
|
||||
} else {
|
||||
val intent = ConversationSettingsActivity.forCall(requireContext(), callLogRow.peer, (callLogRow.id as CallLogRow.Id.Call).children.toLongArray())
|
||||
} else if (!callLogRow.peer.isCallLink) {
|
||||
val intent = ConversationSettingsActivity.forCall(
|
||||
requireContext(),
|
||||
callLogRow.peer,
|
||||
(callLogRow.id as CallLogRow.Id.Call).children.toLongArray()
|
||||
)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
throw NotImplementedError("On call link event clicked.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,12 +333,12 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
binding.recyclerCoordinatorAppBar.setExpanded(false, true)
|
||||
}
|
||||
|
||||
override fun onStartAudioCallClicked(peer: Recipient) {
|
||||
CommunicationActions.startVoiceCall(this, peer)
|
||||
override fun onStartAudioCallClicked(recipient: Recipient) {
|
||||
CommunicationActions.startVoiceCall(this, recipient)
|
||||
}
|
||||
|
||||
override fun onStartVideoCallClicked(peer: Recipient) {
|
||||
CommunicationActions.startVideoCall(this, peer)
|
||||
override fun onStartVideoCallClicked(recipient: Recipient) {
|
||||
CommunicationActions.startVideoCall(this, recipient)
|
||||
}
|
||||
|
||||
override fun startSelection(call: CallLogRow.Call) {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.calls.log
|
||||
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
|
||||
Reference in New Issue
Block a user