Add create call link sheet.
This commit is contained in:
parent
d8ac5a390a
commit
9d575650d1
15 changed files with 717 additions and 9 deletions
|
@ -0,0 +1,242 @@
|
|||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Alignment.Companion.End
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Rows
|
||||
import org.thoughtcrime.securesms.R
|
||||
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.sharing.MultiShareArgs
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
|
||||
/**
|
||||
* Bottom sheet for creating call links
|
||||
*/
|
||||
class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
private val viewModel: CreateCallLinkViewModel by viewModels()
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentSize(Alignment.Center)
|
||||
) {
|
||||
val callName: String by viewModel.callName
|
||||
val callLink: String by viewModel.callLink
|
||||
val approveAllMembers: Boolean by viewModel.approveAllMembers
|
||||
|
||||
Handle(modifier = Modifier.align(CenterHorizontally))
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__create_call_link),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
SignalCallRow(
|
||||
callName = callName,
|
||||
callLink = callLink,
|
||||
onJoinClicked = this@CreateCallLinkBottomSheetDialogFragment::onJoinClicked
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__add_call_name),
|
||||
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onAddACallNameClicked)
|
||||
)
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = approveAllMembers,
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__approve_all_members),
|
||||
onCheckChanged = viewModel::setApproveAllMembers,
|
||||
modifier = Modifier.clickable(onClick = viewModel::toggleApproveAllMembers)
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__share_link_via_signal),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.symbol_forward_24),
|
||||
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareViaSignalClicked)
|
||||
)
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__copy_link),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.symbol_copy_android_24),
|
||||
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onCopyLinkClicked)
|
||||
)
|
||||
|
||||
Rows.TextRow(
|
||||
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__share_link),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.symbol_share_android_24),
|
||||
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareLinkClicked)
|
||||
)
|
||||
|
||||
Buttons.MediumTonal(
|
||||
onClick = this@CreateCallLinkBottomSheetDialogFragment::onDoneClicked,
|
||||
modifier = Modifier
|
||||
.padding(end = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
.align(End)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__done))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAddACallNameClicked() {
|
||||
EditCallLinkNameDialogFragment().show(childFragmentManager, null)
|
||||
}
|
||||
|
||||
private fun onJoinClicked() {
|
||||
}
|
||||
|
||||
private fun onDoneClicked() {
|
||||
}
|
||||
|
||||
private fun onShareViaSignalClicked() {
|
||||
val snapshot = viewModel.callLink.value
|
||||
|
||||
MultiselectForwardFragment.showFullScreen(
|
||||
childFragmentManager,
|
||||
MultiselectForwardFragmentArgs(
|
||||
canSendToNonPush = false,
|
||||
multiShareArgs = listOf(
|
||||
MultiShareArgs.Builder()
|
||||
.withDraftText(snapshot)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCopyLinkClicked() {
|
||||
val snapshot = viewModel.callLink.value
|
||||
Util.copyToClipboard(requireContext(), snapshot)
|
||||
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(snapshot)
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(requireContext(), R.string.CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignalCallRow(
|
||||
callName: String,
|
||||
callLink: String,
|
||||
onJoinClicked: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
.border(
|
||||
width = 1.25.dp,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
shape = RoundedCornerShape(18.dp)
|
||||
)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_video_display_bold_40),
|
||||
contentScale = ContentScale.Inside,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(Color(0xFF5151F6)),
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(
|
||||
color = Color(0xFFE5E5FE),
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = callName.ifEmpty { stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__signal_call) }
|
||||
)
|
||||
Text(
|
||||
text = callLink,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
Buttons.Small(
|
||||
onClick = onJoinClicked,
|
||||
modifier = Modifier.align(CenterVertically)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__join))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class CreateCallLinkViewModel : ViewModel() {
|
||||
private val _callName: MutableState<String> = mutableStateOf("")
|
||||
private val _callLink: MutableState<String> = mutableStateOf("")
|
||||
private val _approveAllMembers: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
val callName: State<String> = _callName
|
||||
val callLink: State<String> = _callLink
|
||||
val approveAllMembers: State<Boolean> = _approveAllMembers
|
||||
|
||||
fun setApproveAllMembers(approveAllMembers: Boolean) {
|
||||
_approveAllMembers.value = approveAllMembers
|
||||
}
|
||||
|
||||
fun toggleApproveAllMembers() {
|
||||
_approveAllMembers.value = !_approveAllMembers.value
|
||||
}
|
||||
|
||||
fun setCallName(callName: String) {
|
||||
_callName.value = callName
|
||||
}
|
||||
|
||||
fun setCallLink(callLink: String) {
|
||||
_callLink.value = callLink
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package org.thoughtcrime.securesms.calls.links
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment.Companion.End
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.viewModels
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeDialogFragment
|
||||
|
||||
class EditCallLinkNameDialogFragment : ComposeDialogFragment() {
|
||||
|
||||
private val viewModel: CreateCallLinkViewModel by viewModels(
|
||||
ownerProducer = { requireParentFragment() }
|
||||
)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
return dialog
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@Composable
|
||||
override fun DialogContent() {
|
||||
val viewModelCallName by viewModel.callName
|
||||
var callName by remember {
|
||||
mutableStateOf(
|
||||
TextFieldValue(
|
||||
text = viewModelCallName,
|
||||
selection = TextRange(viewModelCallName.length)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(id = R.string.EditCallLinkNameDialogFragment__edit_call_name),
|
||||
onNavigationClick = this::dismiss,
|
||||
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24),
|
||||
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
|
||||
) { paddingValues ->
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
Surface(modifier = Modifier.padding(paddingValues)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = dimensionResource(id = org.signal.core.ui.R.dimen.core_ui__gutter)
|
||||
)
|
||||
.padding(top = 20.dp, bottom = 16.dp)
|
||||
) {
|
||||
TextField(
|
||||
value = callName,
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.EditCallLinkNameDialogFragment__call_name))
|
||||
},
|
||||
onValueChange = { callName = it },
|
||||
singleLine = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Buttons.MediumTonal(
|
||||
onClick = {
|
||||
viewModel.setCallName(callName.text)
|
||||
dismiss()
|
||||
},
|
||||
modifier = Modifier.align(End)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.EditCallLinkNameDialogFragment__save))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import androidx.core.widget.TextViewCompat
|
|||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.databinding.CallLogAdapterItemBinding
|
||||
import org.thoughtcrime.securesms.databinding.CallLogCreateCallLinkItemBinding
|
||||
import org.thoughtcrime.securesms.databinding.ConversationListItemClearFilterBinding
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -51,6 +52,13 @@ class CallLogAdapter(
|
|||
inflater = ConversationListItemClearFilterBinding::inflate
|
||||
)
|
||||
)
|
||||
registerFactory(
|
||||
CreateCallLinkModel::class.java,
|
||||
BindingFactory(
|
||||
creator = { CreateCallLinkViewHolder(it, callbacks::onCreateACallLinkClicked) },
|
||||
inflater = CallLogCreateCallLinkItemBinding::inflate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun submitCallRows(
|
||||
|
@ -65,6 +73,7 @@ class CallLogAdapter(
|
|||
when (it) {
|
||||
is CallLogRow.Call -> CallModel(it, selectionState, itemCount)
|
||||
is CallLogRow.ClearFilter -> ClearFilterModel()
|
||||
is CallLogRow.CreateCallLink -> CreateCallLinkModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,6 +121,12 @@ class CallLogAdapter(
|
|||
override fun areContentsTheSame(newItem: ClearFilterModel): Boolean = true
|
||||
}
|
||||
|
||||
private class CreateCallLinkModel : MappingModel<CreateCallLinkModel> {
|
||||
override fun areItemsTheSame(newItem: CreateCallLinkModel): Boolean = true
|
||||
|
||||
override fun areContentsTheSame(newItem: CreateCallLinkModel): Boolean = true
|
||||
}
|
||||
|
||||
private class CallModelViewHolder(
|
||||
binding: CallLogAdapterItemBinding,
|
||||
private val onCallClicked: (CallLogRow.Call) -> Unit,
|
||||
|
@ -234,7 +249,23 @@ class CallLogAdapter(
|
|||
override fun bind(model: ClearFilterModel) = Unit
|
||||
}
|
||||
|
||||
private class CreateCallLinkViewHolder(
|
||||
binding: CallLogCreateCallLinkItemBinding,
|
||||
onClick: () -> Unit
|
||||
) : BindingViewHolder<CreateCallLinkModel, CallLogCreateCallLinkItemBinding>(binding) {
|
||||
init {
|
||||
binding.root.setOnClickListener { onClick() }
|
||||
}
|
||||
|
||||
override fun bind(model: CreateCallLinkModel) = Unit
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
/**
|
||||
* Invoked when 'Create a call link' is clicked
|
||||
*/
|
||||
fun onCreateACallLinkClicked()
|
||||
|
||||
/**
|
||||
* Invoked when a call row is clicked
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -268,6 +269,10 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCreateACallLinkClicked() {
|
||||
findNavController().navigate(R.id.createCallLinkBottomSheet)
|
||||
}
|
||||
|
||||
override fun onCallClicked(callLogRow: CallLogRow.Call) {
|
||||
if (viewModel.selectionStateSnapshot.isNotEmpty(binding.recycler.adapter!!.itemCount)) {
|
||||
viewModel.toggleSelected(callLogRow.id)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.calls.log
|
||||
|
||||
import org.signal.paging.PagedDataSource
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
|
||||
class CallLogPagedDataSource(
|
||||
private val query: String?,
|
||||
|
@ -9,16 +10,24 @@ class CallLogPagedDataSource(
|
|||
) : PagedDataSource<CallLogRow.Id, CallLogRow> {
|
||||
|
||||
private val hasFilter = filter == CallLogFilter.MISSED
|
||||
private val hasCallLinkRow = FeatureFlags.adHocCalling() && filter == CallLogFilter.ALL && query.isNullOrEmpty()
|
||||
|
||||
var callsCount = 0
|
||||
private var callsCount = 0
|
||||
|
||||
override fun size(): Int {
|
||||
callsCount = repository.getCallsCount(query, filter)
|
||||
return callsCount + (if (hasFilter) 1 else 0)
|
||||
return callsCount + hasFilter.toInt() + hasCallLinkRow.toInt()
|
||||
}
|
||||
|
||||
override fun load(start: Int, length: Int, cancellationSignal: PagedDataSource.CancellationSignal): MutableList<CallLogRow> {
|
||||
val calls: MutableList<CallLogRow> = repository.getCalls(query, filter, start, length).toMutableList()
|
||||
val calls = mutableListOf<CallLogRow>()
|
||||
val callLimit = length - hasCallLinkRow.toInt()
|
||||
|
||||
if (start == 0 && length >= 1 && hasCallLinkRow) {
|
||||
calls.add(CallLogRow.CreateCallLink)
|
||||
}
|
||||
|
||||
calls.addAll(repository.getCalls(query, filter, start, callLimit).toMutableList())
|
||||
|
||||
if (calls.size < length && hasFilter) {
|
||||
calls.add(CallLogRow.ClearFilter)
|
||||
|
@ -31,6 +40,10 @@ class CallLogPagedDataSource(
|
|||
|
||||
override fun load(key: CallLogRow.Id?): CallLogRow = error("Not supported")
|
||||
|
||||
private fun Boolean.toInt(): Int {
|
||||
return if (this) 1 else 0
|
||||
}
|
||||
|
||||
interface CallRepository {
|
||||
fun getCallsCount(query: String?, filter: CallLogFilter): Int
|
||||
fun getCalls(query: String?, filter: CallLogFilter, start: Int, length: Int): List<CallLogRow>
|
||||
|
|
|
@ -27,8 +27,13 @@ sealed class CallLogRow {
|
|||
override val id: Id = Id.ClearFilter
|
||||
}
|
||||
|
||||
object CreateCallLink : CallLogRow() {
|
||||
override val id: Id = Id.CreateCallLink
|
||||
}
|
||||
|
||||
sealed class Id {
|
||||
data class Call(val callId: Long) : Id()
|
||||
object ClearFilter : Id()
|
||||
object CreateCallLink : Id()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ abstract class ComposeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetD
|
|||
* ```
|
||||
*/
|
||||
@Composable
|
||||
protected fun Handle() {
|
||||
protected fun Handle(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.size(width = 48.dp, height = 22.dp)
|
||||
.padding(vertical = 10.dp)
|
||||
.clip(RoundedCornerShape(1000.dp))
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package org.thoughtcrime.securesms.compose
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme
|
||||
|
||||
/**
|
||||
* Generic ComposeFragment which can be subclassed to build UI with compose.
|
||||
*/
|
||||
abstract class ComposeDialogFragment : DialogFragment() {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
SignalTheme(
|
||||
isDarkMode = DynamicTheme.isDarkTheme(LocalContext.current)
|
||||
) {
|
||||
DialogContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
abstract fun DialogContent()
|
||||
}
|
15
app/src/main/res/drawable/symbol_link_24.xml
Normal file
15
app/src/main/res/drawable/symbol_link_24.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16.1 12.88c-0.35 0.34-0.35 0.9 0 1.24 0.34 0.34 0.89 0.34 1.23 0l3-3c1.03-1.03 1.55-2.38 1.55-3.73 0-2.9-2.36-5.26-5.27-5.26-1.35 0-2.7 0.51-3.73 1.54l-3 3C9.54 7 9.54 7.57 9.88 7.9c0.34 0.34 0.9 0.34 1.24 0l3-3c0.69-0.7 1.58-1.04 2.49-1.04 1.94 0 3.52 1.58 3.52 3.52 0 0.9-0.35 1.8-1.04 2.5l-3 3Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15.6 8.37c0.35 0.34 0.35 0.9 0 1.23l-6 6c-0.34 0.35-0.9 0.35-1.23 0-0.35-0.34-0.35-0.9 0-1.23l6-6c0.34-0.35 0.9-0.35 1.23 0Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.9 9.88c0.35 0.34 0.35 0.9 0 1.24l-3 3C4.23 14.8 3.88 15.7 3.88 16.6c0 1.94 1.58 3.52 3.52 3.52 0.9 0 1.8-0.35 2.5-1.04l3-3c0.33-0.34 0.89-0.34 1.23 0 0.34 0.35 0.34 0.9 0 1.24l-3 3c-1.03 1.03-2.38 1.55-3.73 1.55-2.9 0-5.26-2.36-5.26-5.27 0-1.35 0.51-2.7 1.54-3.73l3-3c0.34-0.34 0.9-0.34 1.24 0Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11.95 6.25h8.1c1.36 0 2.45 0 3.34 0.07 0.9 0.08 1.7 0.23 2.45 0.61 1.17 0.6 2.13 1.56 2.73 2.73 0.38 0.74 0.53 1.54 0.6 2.45 0.08 0.89 0.08 1.98 0.08 3.34v0.03l5.3-5.3c1.74-1.74 4.7-0.5 4.7 1.94v15.76c0 2.45-2.96 3.68-4.7 1.94l-5.3-5.3v0.03c0 1.36 0 2.45-0.07 3.34-0.08 0.9-0.23 1.7-0.61 2.45-0.6 1.17-1.56 2.13-2.73 2.73-0.74 0.38-1.54 0.53-2.45 0.6-0.89 0.08-1.98 0.08-3.34 0.08h-8.1c-1.36 0-2.45 0-3.34-0.07-0.9-0.08-1.7-0.23-2.45-0.61-1.17-0.6-2.13-1.56-2.73-2.73-0.38-0.74-0.53-1.54-0.6-2.45C2.74 27 2.74 25.9 2.74 24.55v-9.1c0-1.36 0-2.45 0.07-3.34 0.08-0.9 0.23-1.7 0.61-2.45C4.03 8.5 5 7.53 6.16 6.93 6.9 6.55 7.7 6.4 8.61 6.33c0.89-0.08 1.98-0.08 3.34-0.08Zm14.8 9.25c0-1.42 0-2.41-0.06-3.18-0.07-0.76-0.18-1.2-0.35-1.52-0.36-0.7-0.93-1.28-1.64-1.64-0.33-0.17-0.76-0.28-1.52-0.35-0.77-0.06-1.76-0.06-3.18-0.06h-8c-1.42 0-2.41 0-3.18 0.06C8.06 8.88 7.62 9 7.3 9.16c-0.7 0.36-1.28 0.93-1.64 1.64-0.17 0.33-0.28 0.76-0.35 1.52-0.06 0.77-0.06 1.76-0.06 3.18v9c0 1.42 0 2.41 0.06 3.18 0.07 0.76 0.18 1.2 0.35 1.52 0.36 0.7 0.93 1.28 1.64 1.64 0.33 0.17 0.76 0.28 1.52 0.35 0.77 0.06 1.76 0.06 3.18 0.06h8c1.42 0 2.41 0 3.18-0.06 0.76-0.07 1.2-0.18 1.52-0.35 0.7-0.36 1.28-0.93 1.64-1.64 0.17-0.33 0.28-0.76 0.35-1.52 0.06-0.77 0.06-1.76 0.06-3.18v-9Zm2.5 4.5c0 0.63 0.25 1.23 0.7 1.68l6.37 6.38c0.05 0.05 0.1 0.06 0.13 0.07 0.04 0 0.1 0 0.15-0.02 0.05-0.02 0.09-0.06 0.11-0.09 0.02-0.03 0.04-0.07 0.04-0.14V12.12c0-0.07-0.02-0.11-0.04-0.14-0.02-0.03-0.06-0.07-0.11-0.09-0.06-0.02-0.1-0.03-0.15-0.02-0.03 0-0.08 0.02-0.13 0.07l-6.38 6.38c-0.44 0.45-0.69 1.05-0.69 1.68Z"/>
|
||||
</vector>
|
62
app/src/main/res/layout/call_log_create_call_link_item.xml
Normal file
62
app/src/main/res/layout/call_log_create_call_link_item.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:layout_marginVertical="2dp"
|
||||
android:background="@drawable/selectable_list_item_background"
|
||||
android:minHeight="60dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/call_link_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@color/signal_colorSecondaryContainer"
|
||||
android:scaleType="centerInside"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Circle"
|
||||
app:srcCompat="@drawable/symbol_link_24"
|
||||
app:tint="@color/signal_colorOnPrimaryContainer" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/call_link_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:maxLines="2"
|
||||
android:text="@string/CreateCallLink__create_a_call_link"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
app:layout_constraintBottom_toTopOf="@+id/call_link_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@+id/call_link_icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/call_link_icon" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/call_link_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center_vertical"
|
||||
android:maxLines="1"
|
||||
android:text="@string/CreateCallLink__share_a_link_for"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/call_link_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@+id/call_link_icon"
|
||||
app:layout_constraintTop_toBottomOf="@+id/call_link_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -44,6 +44,14 @@
|
|||
<action
|
||||
android:id="@+id/action_callLogFragment_to_storiesLandingFragment"
|
||||
app:destination="@id/storiesLandingFragment" />
|
||||
<action
|
||||
android:id="@+id/action_callLogFragment_to_createCallLinkBottomSheet"
|
||||
app:destination="@id/createCallLinkBottomSheet" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/createCallLinkBottomSheet"
|
||||
android:name="org.thoughtcrime.securesms.calls.links.CreateCallLinkBottomSheetDialogFragment"
|
||||
android:label="create_call_link_bottom_sheet" />
|
||||
|
||||
</navigation>
|
|
@ -5859,5 +5859,43 @@
|
|||
<!-- Outgoing group call in call info -->
|
||||
<string name="CallPreference__outgoing_group_call">Outgoing group call</string>
|
||||
|
||||
<!-- CreateCallLink -->
|
||||
<!-- Call link creation item title on calls tab -->
|
||||
<string name="CreateCallLink__create_a_call_link">Create a Call Link</string>
|
||||
<!-- Call link creation item description on calls tab -->
|
||||
<string name="CreateCallLink__share_a_link_for">Share a link for a Signal call</string>
|
||||
|
||||
<!-- CreateCallLinkBottomSheetDialogFragment -->
|
||||
<!-- Fragment title -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__create_call_link">Create call link</string>
|
||||
<!-- Displayed as a default name for the signal call -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__signal_call">Signal call</string>
|
||||
<!-- Displayed on a small button to allow user to instantly join call -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__join">Join</string>
|
||||
<!-- Option to open a full screen dialog to enter a call name -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__add_call_name">Add call name</string>
|
||||
<!-- Toggle to require approval for all members before joining -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__approve_all_members">Approve all members</string>
|
||||
<!-- Row label to share the link via Signal -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__share_link_via_signal">Share link via Signal</string>
|
||||
<!-- Row label to copy the link to the clipboard -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__copy_link">Copy link</string>
|
||||
<!-- Row label to share the link with the external share sheet -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__share_link">Share link</string>
|
||||
<!-- Button text to dismiss the sheet and add it as an upcoming call -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__done">Done</string>
|
||||
<!-- Displayed when we can't find a suitable way to open the system share picker -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__failed_to_open_share_sheet">Failed to open share sheet.</string>
|
||||
<!-- Displayed when we copy the call link to the clipboard -->
|
||||
<string name="CreateCallLinkBottomSheetDialogFragment__copied_to_clipboard">Copied to clipboard</string>
|
||||
|
||||
<!-- EditCallLinkNameDialogFragment -->
|
||||
<!-- App bar title for editing a call name -->
|
||||
<string name="EditCallLinkNameDialogFragment__edit_call_name">Edit call name</string>
|
||||
<!-- Text on button to confirm edit -->
|
||||
<string name="EditCallLinkNameDialogFragment__save">Save</string>
|
||||
<!-- Placeholder text on input field when editing call name -->
|
||||
<string name="EditCallLinkNameDialogFragment__call_name">Call name</string>
|
||||
|
||||
<!-- EOF -->
|
||||
</resources>
|
||||
|
|
|
@ -2,11 +2,16 @@ package org.signal.core.ui
|
|||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -14,7 +19,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -22,6 +29,7 @@ import androidx.compose.ui.unit.sp
|
|||
import org.signal.core.ui.theme.SignalTheme
|
||||
|
||||
object Rows {
|
||||
|
||||
/**
|
||||
* A row consisting of a radio button and text, which takes up the full
|
||||
* width of the screen.
|
||||
|
@ -36,10 +44,7 @@ object Rows {
|
|||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = dimensionResource(id = R.dimen.core_ui__gutter),
|
||||
vertical = 16.dp
|
||||
),
|
||||
.padding(defaultPadding()),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
|
@ -65,6 +70,76 @@ object Rows {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToggleRow(
|
||||
checked: Boolean,
|
||||
text: String,
|
||||
onCheckChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(defaultPadding())
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(CenterVertically)
|
||||
)
|
||||
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckChanged,
|
||||
modifier = Modifier.align(CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextRow(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector? = null
|
||||
) {
|
||||
if (icon != null) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(defaultPadding())
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(24.dp))
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(defaultPadding())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun defaultPadding(): PaddingValues {
|
||||
return PaddingValues(
|
||||
horizontal = dimensionResource(id = R.dimen.core_ui__gutter),
|
||||
vertical = 16.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
@ -83,3 +158,28 @@ private fun RadioRowPreview() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ToggleRowPreview() {
|
||||
SignalTheme(isDarkMode = false) {
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
|
||||
Rows.ToggleRow(
|
||||
checked = checked,
|
||||
text = "ToggleRow",
|
||||
onCheckChanged = {
|
||||
checked = it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TextRowPreview() {
|
||||
SignalTheme(isDarkMode = false) {
|
||||
Rows.TextRow(text = "TextRow")
|
||||
Rows.TextRow(text = "TextRow")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue