mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 07:28:30 +00:00
Update linked devices screen.
This commit is contained in:
@@ -160,7 +160,11 @@ class AppSettingsFragment : DSLSettingsFragment(
|
||||
title = DSLSettingsText.from(R.string.preferences__linked_devices),
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_devices_24),
|
||||
onClick = {
|
||||
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_deviceActivity)
|
||||
if (FeatureFlags.linkedDevicesV2()) {
|
||||
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_linkDeviceFragment)
|
||||
} else {
|
||||
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_deviceActivity)
|
||||
}
|
||||
},
|
||||
isEnabled = state.isRegisteredAndUpToDate()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
|
||||
/**
|
||||
* Class that represents a linked device
|
||||
*/
|
||||
data class Device(val id: Long, val name: String, val createdMillis: Long, val lastSeenMillis: Long)
|
||||
@@ -0,0 +1,242 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
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.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dialogs
|
||||
import org.signal.core.ui.Dividers
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.thoughtcrime.securesms.DeviceActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Fragment that shows current linked devices
|
||||
*/
|
||||
class LinkDeviceFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: LinkDeviceViewModel by viewModels()
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state
|
||||
|
||||
LaunchedEffect(state.toastDialog) {
|
||||
if (state.toastDialog.isNotEmpty()) {
|
||||
Toast.makeText(requireContext(), state.toastDialog, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(id = R.string.preferences__linked_devices),
|
||||
onNavigationClick = { findNavController().popBackStack() },
|
||||
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24),
|
||||
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
|
||||
) { contentPadding: PaddingValues ->
|
||||
DeviceDescriptionScreen(
|
||||
state = state,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
onLearnMore = this::openLearnMore,
|
||||
onLinkDevice = this::openLinkNewDevice,
|
||||
setDeviceToRemove = this::setDeviceToRemove,
|
||||
onRemoveDevice = this::onRemoveDevice
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.loadDevices(requireContext())
|
||||
}
|
||||
|
||||
private fun openLearnMore() {
|
||||
LinkDeviceLearnMoreBottomSheetFragment().show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
|
||||
private fun openLinkNewDevice() {
|
||||
// TODO(Michelle): Use linkDeviceAddFragment
|
||||
startActivity(DeviceActivity.getIntentForScanner(requireContext()))
|
||||
// findNavController().safeNavigate(R.id.action_linkDeviceFragment_to_linkDeviceAddFragment)
|
||||
}
|
||||
|
||||
private fun setDeviceToRemove(device: Device?) {
|
||||
viewModel.setDeviceToRemove(device)
|
||||
}
|
||||
|
||||
private fun onRemoveDevice(device: Device) {
|
||||
viewModel.removeDevice(requireContext(), device)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceDescriptionScreen(
|
||||
state: LinkDeviceSettingsState,
|
||||
modifier: Modifier = Modifier,
|
||||
onLearnMore: () -> Unit = {},
|
||||
onLinkDevice: () -> Unit = {},
|
||||
setDeviceToRemove: (Device?) -> Unit = {},
|
||||
onRemoveDevice: (Device) -> Unit = {}
|
||||
) {
|
||||
if (state.progressDialogMessage != -1) {
|
||||
Dialogs.IndeterminateProgressDialog(stringResource(id = state.progressDialogMessage))
|
||||
}
|
||||
if (state.deviceToRemove != null) {
|
||||
val device: Device = state.deviceToRemove
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(id = R.string.DeviceListActivity_unlink_s, device.name),
|
||||
body = stringResource(id = R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive),
|
||||
confirm = stringResource(R.string.LinkDeviceFragment__unlink),
|
||||
dismiss = stringResource(android.R.string.cancel),
|
||||
onConfirm = { onRemoveDevice(device) },
|
||||
onDismiss = { setDeviceToRemove(null) }
|
||||
)
|
||||
}
|
||||
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_devices_intro),
|
||||
contentDescription = stringResource(R.string.preferences__linked_devices),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.LinkDeviceFragment__use_signal_on_desktop_ipad),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 12.dp, bottom = 4.dp)
|
||||
)
|
||||
ClickableText(
|
||||
text = AnnotatedString(stringResource(id = R.string.LearnMoreTextView_learn_more)),
|
||||
style = TextStyle(color = MaterialTheme.colorScheme.primary)
|
||||
) {
|
||||
onLearnMore()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onLinkDevice,
|
||||
modifier = Modifier.width(300.dp)
|
||||
) {
|
||||
Text(stringResource(id = R.string.LinkDeviceFragment__link_a_new_device))
|
||||
}
|
||||
|
||||
if (state.devices.isNotEmpty()) {
|
||||
Dividers.Default()
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.LinkDeviceFragment__my_linked_devices),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(start = 24.dp, top = 12.dp, bottom = 24.dp)
|
||||
)
|
||||
state.devices.forEach { device ->
|
||||
DeviceRow(device, setDeviceToRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 40.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.size(12.dp))
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_lock_24),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(id = R.string.LinkDeviceFragment__messages_and_chat_info_are_protected)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceRow(device: Device, setDeviceToRemove: (Device) -> Unit) {
|
||||
val titleString = device.name.ifEmpty { stringResource(R.string.DeviceListItem_unnamed_device) }
|
||||
val linkedDate = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.createdMillis)
|
||||
val lastActive = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.lastSeenMillis)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { setDeviceToRemove(device) },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.symbol_devices_24),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
|
||||
contentScale = ContentScale.Inside,
|
||||
modifier = Modifier
|
||||
.padding(start = 24.dp)
|
||||
.size(40.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
Column {
|
||||
Text(text = titleString, style = MaterialTheme.typography.bodyLarge)
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
Text(stringResource(R.string.DeviceListItem_linked_s, linkedDate), style = MaterialTheme.typography.bodyMedium)
|
||||
Text(stringResource(R.string.DeviceListItem_last_active_s, lastActive), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun DeviceScreenPreview() {
|
||||
val previewDevices = listOf(
|
||||
Device(1, "Sam's Macbook Pro", 1715793982000, 1716053182000),
|
||||
Device(1, "Sam's iPad", 1715793182000, 1716053122000)
|
||||
)
|
||||
val previewState = LinkDeviceSettingsState(devices = previewDevices)
|
||||
|
||||
Previews.Preview {
|
||||
DeviceDescriptionScreen(previewState)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.Texts
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
|
||||
/**
|
||||
* Bottom sheet dialog displayed when users click 'Learn more' when linking a device
|
||||
*/
|
||||
class LinkDeviceLearnMoreBottomSheetFragment : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 0.8f
|
||||
|
||||
companion object {
|
||||
const val SIGNAL_DOWNLOAD_URL = "https://signal.org/download"
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
LearnMoreSheet()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LearnMoreSheet() {
|
||||
val context = LocalContext.current
|
||||
val downloadUrl = stringResource(id = R.string.LinkDeviceFragment__signal_download_url)
|
||||
val fullString = stringResource(id = R.string.LinkDeviceFragment__on_other_device_visit_signal, downloadUrl)
|
||||
val spanned = SpanUtil.urlSubsequence(fullString, downloadUrl, LinkDeviceLearnMoreBottomSheetFragment.SIGNAL_DOWNLOAD_URL)
|
||||
|
||||
return Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentSize(Alignment.Center)
|
||||
.padding(bottom = 48.dp)
|
||||
) {
|
||||
BottomSheets.Handle()
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_all_devices),
|
||||
contentDescription = null,
|
||||
tint = Color.Unspecified,
|
||||
modifier = Modifier.size(110.dp)
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
text = stringResource(R.string.LinkDeviceFragment__signal_on_desktop_ipad),
|
||||
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
|
||||
)
|
||||
LinkedDeviceInformationRow(
|
||||
painterResource(R.drawable.symbol_lock_24),
|
||||
stringResource(R.string.LinkDeviceFragment__all_messaging_is_private)
|
||||
)
|
||||
LinkedDeviceInformationRow(
|
||||
painterResource(R.drawable.ic_replies_outline_20),
|
||||
stringResource(R.string.LinkDeviceFragment__signal_messages_are_synchronized)
|
||||
)
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp, start = 40.dp, end = 32.dp)) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_save_android_24),
|
||||
contentDescription = stringResource(R.string.preferences__linked_devices),
|
||||
modifier = Modifier.size(24.dp).padding(top = 4.dp)
|
||||
)
|
||||
Texts.LinkifiedText(
|
||||
textWithUrlSpans = spanned,
|
||||
onUrlClick = { CommunicationActions.openBrowserLink(context, it) },
|
||||
style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
modifier = Modifier.padding(start = 20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LinkedDeviceInformationRow(
|
||||
iconPainter: Painter,
|
||||
text: String
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp, start = 40.dp, end = 32.dp)) {
|
||||
Icon(
|
||||
painter = iconPainter,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp).padding(top = 4.dp)
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = text,
|
||||
modifier = Modifier.padding(start = 20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun LearnMorePreview() {
|
||||
Previews.BottomSheetPreview {
|
||||
LearnMoreSheet()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
|
||||
import org.signal.core.util.Base64.decode
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.devicelist.protos.DeviceName
|
||||
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Repository for linked devices and its various actions (linking, unlinking, listing).
|
||||
*/
|
||||
object LinkDeviceRepository {
|
||||
|
||||
private val TAG = Log.tag(LinkDeviceRepository::class)
|
||||
|
||||
fun removeDevice(deviceId: Long): Boolean {
|
||||
return try {
|
||||
val accountManager = AppDependencies.signalServiceAccountManager
|
||||
accountManager.removeDevice(deviceId)
|
||||
LinkedDeviceInactiveCheckJob.enqueue()
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDevices(): List<Device>? {
|
||||
val accountManager = AppDependencies.signalServiceAccountManager
|
||||
return try {
|
||||
val devices: List<Device> = accountManager.getDevices()
|
||||
.filter { d: DeviceInfo -> d.getId() != SignalServiceAddress.DEFAULT_DEVICE_ID }
|
||||
.map { deviceInfo: DeviceInfo -> deviceInfo.toDevice() }
|
||||
.sortedBy { it.createdMillis }
|
||||
.toList()
|
||||
devices
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun DeviceInfo.toDevice(): Device {
|
||||
val defaultDevice = Device(getId().toLong(), getName(), getCreated(), getLastSeen())
|
||||
try {
|
||||
if (getName().isNullOrEmpty() || getName().length < 4) {
|
||||
Log.w(TAG, "Invalid DeviceInfo name.")
|
||||
return defaultDevice
|
||||
}
|
||||
|
||||
val deviceName = DeviceName.ADAPTER.decode(decode(getName()))
|
||||
if (deviceName.ciphertext == null || deviceName.ephemeralPublic == null || deviceName.syntheticIv == null) {
|
||||
Log.w(TAG, "Got a DeviceName that wasn't properly populated.")
|
||||
return defaultDevice
|
||||
}
|
||||
|
||||
val plaintext = DeviceNameCipher.decryptDeviceName(deviceName, SignalStore.account().aciIdentityKey)
|
||||
if (plaintext == null) {
|
||||
Log.w(TAG, "Failed to decrypt device name.")
|
||||
return defaultDevice
|
||||
}
|
||||
|
||||
return Device(getId().toLong(), String(plaintext), getCreated(), getLastSeen())
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed while reading the protobuf.", e)
|
||||
}
|
||||
return defaultDevice
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/**
|
||||
* Information about linked devices. Used in [LinkDeviceViewModel].
|
||||
*/
|
||||
data class LinkDeviceSettingsState(
|
||||
val devices: List<Device> = emptyList(),
|
||||
val deviceToRemove: Device? = null,
|
||||
@StringRes val progressDialogMessage: Int = -1,
|
||||
val toastDialog: String = ""
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
/**
|
||||
* Maintains the state of the [LinkDeviceFragment]
|
||||
*/
|
||||
class LinkDeviceViewModel : ViewModel() {
|
||||
|
||||
private val _state = mutableStateOf(LinkDeviceSettingsState())
|
||||
val state: State<LinkDeviceSettingsState> = _state
|
||||
|
||||
fun onResume() {
|
||||
_state.value = _state.value.copy()
|
||||
}
|
||||
|
||||
fun setDeviceToRemove(device: Device?) {
|
||||
_state.value = _state.value.copy(deviceToRemove = device)
|
||||
}
|
||||
|
||||
fun removeDevice(context: Context, device: Device) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.value = _state.value.copy(
|
||||
progressDialogMessage = R.string.DeviceListActivity_unlinking_device
|
||||
)
|
||||
val success = LinkDeviceRepository.removeDevice(device.id)
|
||||
if (success) {
|
||||
loadDevices(context)
|
||||
_state.value = _state.value.copy(
|
||||
toastDialog = context.getString(R.string.LinkDeviceFragment__s_unlinked, device.name),
|
||||
progressDialogMessage = -1
|
||||
)
|
||||
} else {
|
||||
_state.value = _state.value.copy(
|
||||
progressDialogMessage = -1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDevices(context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val devices = LinkDeviceRepository.loadDevices()
|
||||
if (devices == null) {
|
||||
_state.value = _state.value.copy(
|
||||
toastDialog = context.getString(R.string.DeviceListActivity_network_failed),
|
||||
progressDialogMessage = -1
|
||||
)
|
||||
} else {
|
||||
_state.value = _state.value.copy(
|
||||
devices = devices,
|
||||
progressDialogMessage = -1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,13 +210,7 @@ object DateUtils : android.text.format.DateUtils() {
|
||||
return if (isSameDay(System.currentTimeMillis(), timestamp)) {
|
||||
context.getString(R.string.DeviceListItem_today)
|
||||
} else {
|
||||
val format: String = when {
|
||||
timestamp.isWithin(6.days) -> "EEE "
|
||||
timestamp.isWithin(365.days) -> "MMM d"
|
||||
else -> "MMM d, yyy"
|
||||
}
|
||||
|
||||
timestamp.toDateString(format, locale)
|
||||
timestamp.toDateString("dd/MM/yy", locale)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ public final class FeatureFlags {
|
||||
private static final String RESTORE_POST_REGISTRATION = "android.registration.restorePostRegistration";
|
||||
private static final String LIBSIGNAL_WEB_SOCKET_SHADOW_PCT = "android.libsignalWebSocketShadowingPercentage";
|
||||
private static final String DELETE_SYNC_SEND_RECEIVE = "android.deleteSyncSendReceive";
|
||||
private static final String LINKED_DEVICES_V2 = "android.linkedDevices.v2";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
@@ -211,7 +212,7 @@ public final class FeatureFlags {
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
static final Set<String> NOT_REMOTE_CAPABLE = SetUtil.newHashSet(MESSAGE_BACKUPS, REGISTRATION_V2, RESTORE_POST_REGISTRATION);
|
||||
static final Set<String> NOT_REMOTE_CAPABLE = SetUtil.newHashSet(MESSAGE_BACKUPS, REGISTRATION_V2, RESTORE_POST_REGISTRATION, LINKED_DEVICES_V2);
|
||||
|
||||
/**
|
||||
* Values in this map will take precedence over any value. This should only be used for local
|
||||
@@ -750,6 +751,11 @@ public final class FeatureFlags {
|
||||
return getBoolean(DELETE_SYNC_SEND_RECEIVE, false);
|
||||
}
|
||||
|
||||
/** Whether or not to use V2 of linked devices. */
|
||||
public static boolean linkedDevicesV2() {
|
||||
return getBoolean(LINKED_DEVICES_V2, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
||||
45
app/src/main/res/drawable-night/ic_all_devices.xml
Normal file
45
app/src/main/res/drawable-night/ic_all_devices.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="112dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="112"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M15,31C15,26.58 18.58,23 23,23H71C75.42,23 79,26.58 79,31V71C79,75.42 75.42,79 71,79H23C18.58,79 15,75.42 15,71V31Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M14,31C14,26.03 18.03,22 23,22H71C75.97,22 80,26.03 80,31V71C80,75.97 75.97,80 71,80H23C18.03,80 14,75.97 14,71V31ZM23,24C19.13,24 16,27.13 16,31V71C16,74.87 19.13,78 23,78H71C74.87,78 78,74.87 78,71V31C78,27.13 74.87,24 71,24H23Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M78,71V34.31C76.03,34.11 74.03,34 72,34C47.56,34 26.71,49.39 18.61,71H78Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M5,75C5,72.79 6.79,71 9,71H85C87.21,71 89,72.79 89,75V75C89,77.21 87.21,79 85,79H9C6.79,79 5,77.21 5,75V75Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M4,75C4,72.24 6.24,70 9,70H85C87.76,70 90,72.24 90,75C90,77.76 87.76,80 85,80H9C6.24,80 4,77.76 4,75ZM9,72C7.34,72 6,73.34 6,75C6,76.66 7.34,78 9,78H85C86.66,78 88,76.66 88,75C88,73.34 86.66,72 85,72H9Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M71,37C71,33.69 73.69,31 77,31H101C104.31,31 107,33.69 107,37V73C107,76.31 104.31,79 101,79H77C73.69,79 71,76.31 71,73V37Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M107,53.34V73.5C107,76.81 104.31,79.5 101,79.5H77C73.69,79.5 71,76.81 71,73.5V53.34C76.18,49.96 82.36,48 89,48C95.64,48 101.82,49.96 107,53.34Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M70,37C70,33.13 73.13,30 77,30H101C104.87,30 108,33.13 108,37V73C108,76.87 104.87,80 101,80H77C73.13,80 70,76.87 70,73V37ZM77,32C74.24,32 72,34.24 72,37V73C72,75.76 74.24,78 77,78H101C103.76,78 106,75.76 106,73V37C106,34.24 103.76,32 101,32H77Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M56,57C56,53.69 58.69,51 62,51H74C77.31,51 80,53.69 80,57V85.5C80,88.81 77.31,91.5 74,91.5H62C58.69,91.5 56,88.81 56,85.5V57Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M80,70.56V85.5C80,88.81 77.31,91.5 74,91.5H62C58.69,91.5 56,88.81 56,85.5V70.56C59.45,68.31 63.57,67 68,67C72.43,67 76.55,68.31 80,70.56Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M55,57C55,53.13 58.13,50 62,50H74C77.87,50 81,53.13 81,57V85.5C81,89.37 77.87,92.5 74,92.5H62C58.13,92.5 55,89.37 55,85.5V57ZM62,52C59.24,52 57,54.24 57,57V85.5C57,88.26 59.24,90.5 62,90.5H74C76.76,90.5 79,88.26 79,85.5V57C79,54.24 76.76,52 74,52H62Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
34
app/src/main/res/drawable-night/ic_devices.xml
Normal file
34
app/src/main/res/drawable-night/ic_devices.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="112dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="112"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M20,31C20,26.58 23.58,23 28,23H76C80.42,23 84,26.58 84,31V71C84,75.42 80.42,79 76,79H28C23.58,79 20,75.42 20,71V31Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M19,31C19,26.03 23.03,22 28,22H76C80.97,22 85,26.03 85,31V71C85,75.97 80.97,80 76,80H28C23.03,80 19,75.97 19,71V31ZM28,24C24.13,24 21,27.13 21,31V71C21,74.87 24.13,78 28,78H76C79.87,78 83,74.87 83,71V31C83,27.13 79.87,24 76,24H28Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M83,71V34.31C81.03,34.11 79.03,34 77,34C52.56,34 31.71,49.39 23.61,71H83Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M10,75C10,72.79 11.79,71 14,71H90C92.21,71 94,72.79 94,75V75C94,77.21 92.21,79 90,79H14C11.79,79 10,77.21 10,75V75Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M9,75C9,72.24 11.24,70 14,70H90C92.76,70 95,72.24 95,75C95,77.76 92.76,80 90,80H14C11.24,80 9,77.76 9,75ZM14,72C12.34,72 11,73.34 11,75C11,76.66 12.34,78 14,78H90C91.66,78 93,76.66 93,75C93,73.34 91.66,72 90,72H14Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M66,47C66,43.69 68.69,41 72,41H96C99.31,41 102,43.69 102,47V83C102,86.31 99.31,89 96,89H72C68.69,89 66,86.31 66,83V47Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M102,63.34V83.5C102,86.81 99.31,89.5 96,89.5H72C68.69,89.5 66,86.81 66,83.5V63.34C71.18,59.96 77.36,58 84,58C90.64,58 96.82,59.96 102,63.34Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M65,47C65,43.13 68.13,40 72,40H96C99.87,40 103,43.13 103,47V83C103,86.87 99.87,90 96,90H72C68.13,90 65,86.87 65,83V47ZM72,42C69.24,42 67,44.24 67,47V83C67,85.76 69.24,88 72,88H96C98.76,88 101,85.76 101,83V47C101,44.24 98.76,42 96,42H72Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
66
app/src/main/res/drawable-night/ic_devices_intro.xml
Normal file
66
app/src/main/res/drawable-night/ic_devices_intro.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="184dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="184"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M21,25C21,18.37 26.37,13 33,13H105C111.63,13 117,18.37 117,25V85C117,91.63 111.63,97 105,97H33C26.37,97 21,91.63 21,85V25Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M20,25C20,17.82 25.82,12 33,12H105C112.18,12 118,17.82 118,25V85C118,92.18 112.18,98 105,98H33C25.82,98 20,92.18 20,85V25ZM33,14C26.92,14 22,18.92 22,25V85C22,91.08 26.92,96 33,96H105C111.07,96 116,91.08 116,85V25C116,18.92 111.07,14 105,14H33Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M116,85V26.53C112.85,26.18 109.65,26 106.41,26C68.35,26 36.07,50.75 24.86,85H116Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M6,91C6,87.69 8.69,85 12,85H126C129.31,85 132,87.69 132,91V91C132,94.31 129.31,97 126,97H12C8.69,97 6,94.31 6,91V91Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M5,91C5,87.13 8.13,84 12,84H126C129.87,84 133,87.13 133,91C133,94.87 129.87,98 126,98H12C8.13,98 5,94.87 5,91ZM12,86C9.24,86 7,88.24 7,91C7,93.76 9.24,96 12,96H126C128.76,96 131,93.76 131,91C131,88.24 128.76,86 126,86H12Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M86,45C86,41.69 88.69,39 92,39H129C132.31,39 135,41.69 135,45V50C135,53.31 132.31,56 129,56H92C88.69,56 86,53.31 86,50V45Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M85,45C85,41.13 88.13,38 92,38H129C132.87,38 136,41.13 136,45V50C136,53.87 132.87,57 129,57H92C88.13,57 85,53.87 85,50V45ZM92,40C89.24,40 87,42.24 87,45V50C87,52.76 89.24,55 92,55H129C131.76,55 134,52.76 134,50V45C134,42.24 131.76,40 129,40H92Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M106.5,24.5C106.5,20.63 109.63,17.5 113.5,17.5H148.5C152.37,17.5 155.5,20.63 155.5,24.5V24.5C155.5,28.37 152.37,31.5 148.5,31.5H113.5C109.63,31.5 106.5,28.37 106.5,24.5V24.5Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M105.5,24.5C105.5,20.08 109.08,16.5 113.5,16.5H148.5C152.92,16.5 156.5,20.08 156.5,24.5C156.5,28.92 152.92,32.5 148.5,32.5H113.5C109.08,32.5 105.5,28.92 105.5,24.5ZM113.5,18.5C110.19,18.5 107.5,21.19 107.5,24.5C107.5,27.81 110.19,30.5 113.5,30.5H148.5C151.81,30.5 154.5,27.81 154.5,24.5C154.5,21.19 151.81,18.5 148.5,18.5H113.5Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M141,46C141,41.03 145.03,37 150,37H168C172.97,37 177,41.03 177,46V88C177,92.97 172.97,97 168,97H150C145.03,97 141,92.97 141,88V46Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M177,67.36V88C177,92.97 172.97,97 168,97H150C145.03,97 141,92.97 141,88V68.64C146.37,64.9 152.93,62.7 160,62.7C166.22,62.7 172.04,64.4 177,67.36Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M140,46C140,40.48 144.48,36 150,36H168C173.52,36 178,40.48 178,46V88C178,93.52 173.52,98 168,98H150C144.48,98 140,93.52 140,88V46ZM150,38C145.58,38 142,41.58 142,46V88C142,92.42 145.58,96 150,96H168C172.42,96 176,92.42 176,88V46C176,41.58 172.42,38 168,38H150Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M114,24.5C114,23.95 114.45,23.5 115,23.5H148C148.55,23.5 149,23.95 149,24.5V24.5C149,25.05 148.55,25.5 148,25.5H115C114.45,25.5 114,25.05 114,24.5V24.5Z"
|
||||
android:fillColor="#3B45FD"/>
|
||||
<path
|
||||
android:pathData="M93,50C93,49.45 93.45,49 94,49H127C127.55,49 128,49.45 128,50V50C128,50.55 127.55,51 127,51H94C93.45,51 93,50.55 93,50V50Z"
|
||||
android:fillColor="#3B45FD"/>
|
||||
<path
|
||||
android:pathData="M106.5,71C106.5,67.13 109.63,64 113.5,64H148.5C152.37,64 155.5,67.13 155.5,71V71C155.5,74.87 152.37,78 148.5,78H113.5C109.63,78 106.5,74.87 106.5,71V71Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M105.5,71C105.5,66.58 109.08,63 113.5,63H148.5C152.92,63 156.5,66.58 156.5,71C156.5,75.42 152.92,79 148.5,79H113.5C109.08,79 105.5,75.42 105.5,71ZM113.5,65C110.19,65 107.5,67.69 107.5,71C107.5,74.31 110.19,77 113.5,77H148.5C151.81,77 154.5,74.31 154.5,71C154.5,67.69 151.81,65 148.5,65H113.5Z"
|
||||
android:fillColor="#3B45FD"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M93,45C93,44.45 93.45,44 94,44H127C127.55,44 128,44.45 128,45V45C128,45.55 127.55,46 127,46H94C93.45,46 93,45.55 93,45V45Z"
|
||||
android:fillColor="#3B45FD"/>
|
||||
<path
|
||||
android:pathData="M114,71C114,70.45 114.45,70 115,70H148C148.55,70 149,70.45 149,71V71C149,71.55 148.55,72 148,72H115C114.45,72 114,71.55 114,71V71Z"
|
||||
android:fillColor="#3B45FD"/>
|
||||
</vector>
|
||||
45
app/src/main/res/drawable/ic_all_devices.xml
Normal file
45
app/src/main/res/drawable/ic_all_devices.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="112dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="112"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M15,31C15,26.58 18.58,23 23,23H71C75.42,23 79,26.58 79,31V71C79,75.42 75.42,79 71,79H23C18.58,79 15,75.42 15,71V31Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M14,31C14,26.03 18.03,22 23,22H71C75.97,22 80,26.03 80,31V71C80,75.97 75.97,80 71,80H23C18.03,80 14,75.97 14,71V31ZM23,24C19.13,24 16,27.13 16,31V71C16,74.87 19.13,78 23,78H71C74.87,78 78,74.87 78,71V31C78,27.13 74.87,24 71,24H23Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M78,71V34.31C76.03,34.11 74.03,34 72,34C47.56,34 26.71,49.39 18.61,71H78Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M5,75C5,72.79 6.79,71 9,71H85C87.21,71 89,72.79 89,75C89,77.21 87.21,79 85,79H9C6.79,79 5,77.21 5,75Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M4,75C4,72.24 6.24,70 9,70H85C87.76,70 90,72.24 90,75C90,77.76 87.76,80 85,80H9C6.24,80 4,77.76 4,75ZM9,72C7.34,72 6,73.34 6,75C6,76.66 7.34,78 9,78H85C86.66,78 88,76.66 88,75C88,73.34 86.66,72 85,72H9Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M71,37C71,33.69 73.69,31 77,31H101C104.31,31 107,33.69 107,37V73C107,76.31 104.31,79 101,79H77C73.69,79 71,76.31 71,73V37Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M107,53.34V73.5C107,76.81 104.31,79.5 101,79.5H77C73.69,79.5 71,76.81 71,73.5V53.34C76.18,49.96 82.36,48 89,48C95.64,48 101.82,49.96 107,53.34Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M70,37C70,33.13 73.13,30 77,30H101C104.87,30 108,33.13 108,37V73C108,76.87 104.87,80 101,80H77C73.13,80 70,76.87 70,73V37ZM77,32C74.24,32 72,34.24 72,37V73C72,75.76 74.24,78 77,78H101C103.76,78 106,75.76 106,73V37C106,34.24 103.76,32 101,32H77Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M56,57C56,53.69 58.69,51 62,51H74C77.31,51 80,53.69 80,57V85.5C80,88.81 77.31,91.5 74,91.5H62C58.69,91.5 56,88.81 56,85.5V57Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M80,70.56V85.5C80,88.81 77.31,91.5 74,91.5H62C58.69,91.5 56,88.81 56,85.5V70.56C59.45,68.31 63.57,67 68,67C72.43,67 76.55,68.31 80,70.56Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M55,57C55,53.13 58.13,50 62,50H74C77.87,50 81,53.13 81,57V85.5C81,89.37 77.87,92.5 74,92.5H62C58.13,92.5 55,89.37 55,85.5V57ZM62,52C59.24,52 57,54.24 57,57V85.5C57,88.26 59.24,90.5 62,90.5H74C76.76,90.5 79,88.26 79,85.5V57C79,54.24 76.76,52 74,52H62Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
34
app/src/main/res/drawable/ic_devices.xml
Normal file
34
app/src/main/res/drawable/ic_devices.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="112dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="112"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M20,31C20,26.58 23.58,23 28,23H76C80.42,23 84,26.58 84,31V71C84,75.42 80.42,79 76,79H28C23.58,79 20,75.42 20,71V31Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M19,31C19,26.03 23.03,22 28,22H76C80.97,22 85,26.03 85,31V71C85,75.97 80.97,80 76,80H28C23.03,80 19,75.97 19,71V31ZM28,24C24.13,24 21,27.13 21,31V71C21,74.87 24.13,78 28,78H76C79.87,78 83,74.87 83,71V31C83,27.13 79.87,24 76,24H28Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M83,71V34.31C81.03,34.11 79.03,34 77,34C52.56,34 31.71,49.39 23.61,71H83Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M10,75C10,72.79 11.79,71 14,71H90C92.21,71 94,72.79 94,75C94,77.21 92.21,79 90,79H14C11.79,79 10,77.21 10,75Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M9,75C9,72.24 11.24,70 14,70H90C92.76,70 95,72.24 95,75C95,77.76 92.76,80 90,80H14C11.24,80 9,77.76 9,75ZM14,72C12.34,72 11,73.34 11,75C11,76.66 12.34,78 14,78H90C91.66,78 93,76.66 93,75C93,73.34 91.66,72 90,72H14Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M66,47C66,43.69 68.69,41 72,41H96C99.31,41 102,43.69 102,47V83C102,86.31 99.31,89 96,89H72C68.69,89 66,86.31 66,83V47Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M102,63.34V83.5C102,86.81 99.31,89.5 96,89.5H72C68.69,89.5 66,86.81 66,83.5V63.34C71.18,59.96 77.36,58 84,58C90.64,58 96.82,59.96 102,63.34Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M65,47C65,43.13 68.13,40 72,40H96C99.87,40 103,43.13 103,47V83C103,86.87 99.87,90 96,90H72C68.13,90 65,86.87 65,83V47ZM72,42C69.24,42 67,44.24 67,47V83C67,85.76 69.24,88 72,88H96C98.76,88 101,85.76 101,83V47C101,44.24 98.76,42 96,42H72Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
66
app/src/main/res/drawable/ic_devices_intro.xml
Normal file
66
app/src/main/res/drawable/ic_devices_intro.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="184dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="184"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M21,25C21,18.37 26.37,13 33,13H105C111.63,13 117,18.37 117,25V85C117,91.63 111.63,97 105,97H33C26.37,97 21,91.63 21,85V25Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M20,25C20,17.82 25.82,12 33,12H105C112.18,12 118,17.82 118,25V85C118,92.18 112.18,98 105,98H33C25.82,98 20,92.18 20,85V25ZM33,14C26.92,14 22,18.92 22,25V85C22,91.08 26.92,96 33,96H105C111.07,96 116,91.08 116,85V25C116,18.92 111.07,14 105,14H33Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M116,85V26.53C112.85,26.18 109.65,26 106.41,26C68.35,26 36.07,50.75 24.86,85H116Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M6,91C6,87.69 8.69,85 12,85H126C129.31,85 132,87.69 132,91C132,94.31 129.31,97 126,97H12C8.69,97 6,94.31 6,91Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M5,91C5,87.13 8.13,84 12,84H126C129.87,84 133,87.13 133,91C133,94.87 129.87,98 126,98H12C8.13,98 5,94.87 5,91ZM12,86C9.24,86 7,88.24 7,91C7,93.76 9.24,96 12,96H126C128.76,96 131,93.76 131,91C131,88.24 128.76,86 126,86H12Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M86,45C86,41.69 88.69,39 92,39H129C132.31,39 135,41.69 135,45V50C135,53.31 132.31,56 129,56H92C88.69,56 86,53.31 86,50V45Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M85,45C85,41.13 88.13,38 92,38H129C132.87,38 136,41.13 136,45V50C136,53.87 132.87,57 129,57H92C88.13,57 85,53.87 85,50V45ZM92,40C89.24,40 87,42.24 87,45V50C87,52.76 89.24,55 92,55H129C131.76,55 134,52.76 134,50V45C134,42.24 131.76,40 129,40H92Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M106.5,24.5C106.5,20.63 109.63,17.5 113.5,17.5H148.5C152.37,17.5 155.5,20.63 155.5,24.5C155.5,28.37 152.37,31.5 148.5,31.5H113.5C109.63,31.5 106.5,28.37 106.5,24.5Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M105.5,24.5C105.5,20.08 109.08,16.5 113.5,16.5H148.5C152.92,16.5 156.5,20.08 156.5,24.5C156.5,28.92 152.92,32.5 148.5,32.5H113.5C109.08,32.5 105.5,28.92 105.5,24.5ZM113.5,18.5C110.19,18.5 107.5,21.19 107.5,24.5C107.5,27.81 110.19,30.5 113.5,30.5H148.5C151.81,30.5 154.5,27.81 154.5,24.5C154.5,21.19 151.81,18.5 148.5,18.5H113.5Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M141,46C141,41.03 145.03,37 150,37H168C172.97,37 177,41.03 177,46V88C177,92.97 172.97,97 168,97H150C145.03,97 141,92.97 141,88V46Z"
|
||||
android:fillColor="#E3E8FE"/>
|
||||
<path
|
||||
android:pathData="M177,67.36V88C177,92.97 172.97,97 168,97H150C145.03,97 141,92.97 141,88V68.64C146.37,64.9 152.93,62.7 160,62.7C166.22,62.7 172.04,64.4 177,67.36Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M140,46C140,40.48 144.48,36 150,36H168C173.52,36 178,40.48 178,46V88C178,93.52 173.52,98 168,98H150C144.48,98 140,93.52 140,88V46ZM150,38C145.58,38 142,41.58 142,46V88C142,92.42 145.58,96 150,96H168C172.42,96 176,92.42 176,88V46C176,41.58 172.42,38 168,38H150Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M114,24.5C114,23.95 114.45,23.5 115,23.5H148C148.55,23.5 149,23.95 149,24.5C149,25.05 148.55,25.5 148,25.5H115C114.45,25.5 114,25.05 114,24.5Z"
|
||||
android:fillColor="#020BAC"/>
|
||||
<path
|
||||
android:pathData="M93,50C93,49.45 93.45,49 94,49H127C127.55,49 128,49.45 128,50C128,50.55 127.55,51 127,51H94C93.45,51 93,50.55 93,50Z"
|
||||
android:fillColor="#020BAC"/>
|
||||
<path
|
||||
android:pathData="M106.5,71C106.5,67.13 109.63,64 113.5,64H148.5C152.37,64 155.5,67.13 155.5,71C155.5,74.87 152.37,78 148.5,78H113.5C109.63,78 106.5,74.87 106.5,71Z"
|
||||
android:fillColor="#C1C7FE"/>
|
||||
<path
|
||||
android:pathData="M105.5,71C105.5,66.58 109.08,63 113.5,63H148.5C152.92,63 156.5,66.58 156.5,71C156.5,75.42 152.92,79 148.5,79H113.5C109.08,79 105.5,75.42 105.5,71ZM113.5,65C110.19,65 107.5,67.69 107.5,71C107.5,74.31 110.19,77 113.5,77H148.5C151.81,77 154.5,74.31 154.5,71C154.5,67.69 151.81,65 148.5,65H113.5Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M93,45C93,44.45 93.45,44 94,44H127C127.55,44 128,44.45 128,45C128,45.55 127.55,46 127,46H94C93.45,46 93,45.55 93,45Z"
|
||||
android:fillColor="#020BAC"/>
|
||||
<path
|
||||
android:pathData="M114,71C114,70.45 114.45,70 115,70H148C148.55,70 149,70.45 149,71C149,71.55 148.55,72 148,72H115C114.45,72 114,71.55 114,71Z"
|
||||
android:fillColor="#020BAC"/>
|
||||
</vector>
|
||||
@@ -43,6 +43,13 @@
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
<action
|
||||
android:id="@+id/action_appSettingsFragment_to_linkDeviceFragment"
|
||||
app:destination="@id/linkDeviceFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
<action
|
||||
android:id="@+id/action_appSettingsFragment_to_paymentsActivity"
|
||||
app:destination="@id/paymentsActivity"
|
||||
@@ -218,12 +225,18 @@
|
||||
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- Linked Devices -->
|
||||
<!-- Linked Devices V1-->
|
||||
<activity
|
||||
android:id="@+id/deviceActivity"
|
||||
android:name="org.thoughtcrime.securesms.DeviceActivity"
|
||||
android:label="device_activity" />
|
||||
|
||||
<!-- Linked Devices V2 -->
|
||||
<fragment
|
||||
android:id="@+id/linkDeviceFragment"
|
||||
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceFragment"
|
||||
android:label="link_device_fragment" />
|
||||
|
||||
<!-- Payments -->
|
||||
<activity
|
||||
android:id="@+id/paymentsActivity"
|
||||
|
||||
@@ -43,6 +43,13 @@
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
<action
|
||||
android:id="@+id/action_appSettingsFragment_to_linkDeviceFragment"
|
||||
app:destination="@id/linkDeviceFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
<action
|
||||
android:id="@+id/action_appSettingsFragment_to_paymentsActivity"
|
||||
app:destination="@id/paymentsActivity"
|
||||
@@ -218,12 +225,18 @@
|
||||
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- Linked Devices -->
|
||||
<!-- Linked Devices V1-->
|
||||
<activity
|
||||
android:id="@+id/deviceActivity"
|
||||
android:name="org.thoughtcrime.securesms.DeviceActivity"
|
||||
android:label="device_activity" />
|
||||
|
||||
<!-- Linked Devices V2 -->
|
||||
<fragment
|
||||
android:id="@+id/linkDeviceFragment"
|
||||
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceFragment"
|
||||
android:label="link_device_fragment" />
|
||||
|
||||
<!-- Payments -->
|
||||
<activity
|
||||
android:id="@+id/paymentsActivity"
|
||||
|
||||
1
app/src/main/res/raw-night/linking_device.json
Normal file
1
app/src/main/res/raw-night/linking_device.json
Normal file
File diff suppressed because one or more lines are too long
1
app/src/main/res/raw/linking_device.json
Normal file
1
app/src/main/res/raw/linking_device.json
Normal file
File diff suppressed because one or more lines are too long
@@ -859,6 +859,29 @@
|
||||
<string name="DecryptionFailedDialog_chat_session_refreshed">Chat session refreshed</string>
|
||||
<string name="DecryptionFailedDialog_signal_uses_end_to_end_encryption">Signal uses end-to-end encryption and it may need to refresh your chat session sometimes. This doesn\'t affect your chat\'s security, but you may have missed a message from this contact, and you can ask them to resend it.</string>
|
||||
|
||||
<!-- LinkDeviceFragment -->
|
||||
<!-- Description for how Signal will work with a linked device (eg desktop, iPad) -->
|
||||
<string name="LinkDeviceFragment__use_signal_on_desktop_ipad">Use Signal on desktop or iPad. Your messages will sync to your linked devices.</string>
|
||||
<!-- Button prompting users to link a new device to their account -->
|
||||
<string name="LinkDeviceFragment__link_a_new_device">Link a new device</string>
|
||||
<!-- Text explaining that on linked devices, messages will be encrypted -->
|
||||
<string name="LinkDeviceFragment__messages_and_chat_info_are_protected">Messages and chat info are protected by end-to-end encryption on all devices</string>
|
||||
<!-- Bottom sheet title explaining how Signal works on a linked device -->
|
||||
<string name="LinkDeviceFragment__signal_on_desktop_ipad">Signal on Desktop or iPad</string>
|
||||
<!-- Bottom sheet description explaining that messages on linked devices are private -->
|
||||
<string name="LinkDeviceFragment__all_messaging_is_private">All messaging on linked devices is private</string>
|
||||
<!-- Bottom sheet description explaining that future messages on linked devices will be in sync with your phone but previous messages will not appear -->
|
||||
<string name="LinkDeviceFragment__signal_messages_are_synchronized">Signal messages are synchronized with Signal on your mobile phone after it is linked. Your previous message history will not appear.</string>
|
||||
<!-- Bottom sheet description explaining that for non-desktop/iPad devices, they should go to %s to download Signal where %s is Signal's website -->
|
||||
<string name="LinkDeviceFragment__on_other_device_visit_signal">On your other device, visit %s to install Signal</string>
|
||||
<string name="LinkDeviceFragment__signal_download_url" translatable="false">signal.org/download</string>
|
||||
<!-- Header title listing out current linked devices -->
|
||||
<string name="LinkDeviceFragment__my_linked_devices">My linked devices</string>
|
||||
<!-- Dialog confirmation to unlink a device -->
|
||||
<string name="LinkDeviceFragment__unlink">Unlink</string>
|
||||
<!-- Toast message indicating a device has been unlinked where %s is the name of the device -->
|
||||
<string name="LinkDeviceFragment__s_unlinked">%s unlinked</string>
|
||||
|
||||
<!-- DeviceListActivity -->
|
||||
<string name="DeviceListActivity_unlink_s">Unlink \'%s\'?</string>
|
||||
<string name="DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive">By unlinking this device, it will no longer be able to send or receive messages.</string>
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -128,6 +129,33 @@ object Dialogs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizable progress spinner that shows [message] below the spinner to let users know
|
||||
* an action is completing
|
||||
*/
|
||||
@Composable
|
||||
fun IndeterminateProgressDialog(message: String) {
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {},
|
||||
dismissButton = {},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth().fillMaxHeight()
|
||||
) {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
Text(message)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(200.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun PermissionRationaleDialog(
|
||||
@@ -237,3 +265,9 @@ private fun MessageDialogPreview() {
|
||||
private fun IndeterminateProgressDialogPreview() {
|
||||
Dialogs.IndeterminateProgressDialog()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun IndeterminateProgressDialogMessagePreview() {
|
||||
Dialogs.IndeterminateProgressDialog("Completing...")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user