Update target API to 33
This commit is contained in:
parent
b9449a798b
commit
a3e36d2453
38 changed files with 1236 additions and 203 deletions
|
@ -596,6 +596,7 @@ dependencies {
|
|||
testImplementation testLibs.robolectric.shadows.multidex
|
||||
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
|
||||
testImplementation (testLibs.bouncycastle.bcpkix.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
|
||||
testImplementation testLibs.conscrypt.openjdk.uber // Used by robolectric
|
||||
testImplementation testLibs.hamcrest.hamcrest
|
||||
testImplementation testLibs.mockk
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
||||
|
@ -94,6 +96,10 @@
|
|||
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
<application android:name=".ApplicationContext"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
|
|||
import org.thoughtcrime.securesms.groups.ParcelableGroupId
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
@ -238,7 +239,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
|
|||
@Suppress("DEPRECATION")
|
||||
private fun openGallery() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(*PermissionCompat.forImages())
|
||||
.ifNecessary()
|
||||
.onAllGranted {
|
||||
val intent = AvatarSelectionActivity.getIntentForGallery(requireContext())
|
||||
|
|
|
@ -31,8 +31,11 @@ import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
|||
import org.thoughtcrime.securesms.components.settings.RadioListPreference
|
||||
import org.thoughtcrime.securesms.components.settings.RadioListPreferenceViewHolder
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.models.Banner
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.RingtoneUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
|
@ -62,6 +65,11 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
|||
|
||||
private lateinit var viewModel: NotificationsSettingsViewModel
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.refresh()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == MESSAGE_SOUND_SELECT && resultCode == Activity.RESULT_OK && data != null) {
|
||||
val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
||||
|
@ -78,6 +86,8 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
|||
LayoutFactory(::LedColorPreferenceViewHolder, R.layout.dsl_preference_item)
|
||||
)
|
||||
|
||||
Banner.register(adapter)
|
||||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val factory = NotificationsSettingsViewModel.Factory(sharedPreferences)
|
||||
|
||||
|
@ -90,10 +100,23 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
|||
|
||||
private fun getConfiguration(state: NotificationsSettingsState): DSLConfiguration {
|
||||
return configure {
|
||||
if (!state.messageNotificationsState.canEnableNotifications) {
|
||||
customPref(
|
||||
Banner.Model(
|
||||
textId = R.string.NotificationSettingsFragment__to_enable_notifications,
|
||||
actionId = R.string.NotificationSettingsFragment__turn_on,
|
||||
onClick = {
|
||||
TurnOnNotificationsBottomSheet().show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
sectionHeaderPref(R.string.NotificationsSettingsFragment__messages)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__notifications),
|
||||
isEnabled = state.messageNotificationsState.canEnableNotifications,
|
||||
isChecked = state.messageNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setMessageNotificationsEnabled(!state.messageNotificationsState.notificationsEnabled)
|
||||
|
@ -223,6 +246,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
|
|||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.preferences__notifications),
|
||||
isEnabled = state.callNotificationsState.canEnableNotifications,
|
||||
isChecked = state.callNotificationsState.notificationsEnabled,
|
||||
onClick = {
|
||||
viewModel.setCallNotificationsEnabled(!state.callNotificationsState.notificationsEnabled)
|
||||
|
|
|
@ -10,6 +10,7 @@ data class NotificationsSettingsState(
|
|||
|
||||
data class MessageNotificationsState(
|
||||
val notificationsEnabled: Boolean,
|
||||
val canEnableNotifications: Boolean,
|
||||
val sound: Uri,
|
||||
val vibrateEnabled: Boolean,
|
||||
val ledColor: String,
|
||||
|
@ -23,6 +24,7 @@ data class MessageNotificationsState(
|
|||
|
||||
data class CallNotificationsState(
|
||||
val notificationsEnabled: Boolean,
|
||||
val canEnableNotifications: Boolean,
|
||||
val ringtone: Uri,
|
||||
val vibrateEnabled: Boolean
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.notifications
|
|||
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
@ -26,78 +27,83 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
|
|||
|
||||
val state: LiveData<NotificationsSettingsState> = store.stateLiveData
|
||||
|
||||
fun refresh() {
|
||||
store.update { getState() }
|
||||
}
|
||||
|
||||
fun setMessageNotificationsEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageNotificationsEnabled = enabled
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationsSound(sound: Uri?) {
|
||||
val messageSound = sound ?: Uri.EMPTY
|
||||
SignalStore.settings().messageNotificationSound = messageSound
|
||||
NotificationChannels.getInstance().updateMessageRingtone(messageSound)
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationVibration(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageVibrateEnabled = enabled
|
||||
NotificationChannels.getInstance().updateMessageVibrate(enabled)
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationLedColor(color: String) {
|
||||
SignalStore.settings().messageLedColor = color
|
||||
NotificationChannels.getInstance().updateMessagesLedColor(color)
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationLedBlink(blink: String) {
|
||||
SignalStore.settings().messageLedBlinkPattern = blink
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationInChatSoundsEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isMessageNotificationsInChatSoundsEnabled = enabled
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageRepeatAlerts(repeats: Int) {
|
||||
SignalStore.settings().messageNotificationsRepeatAlerts = repeats
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationPrivacy(preference: String) {
|
||||
SignalStore.settings().messageNotificationsPrivacy = NotificationPrivacyPreference(preference)
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setMessageNotificationPriority(priority: Int) {
|
||||
sharedPreferences.edit().putString(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF, priority.toString()).apply()
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setCallNotificationsEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isCallNotificationsEnabled = enabled
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setCallRingtone(ringtone: Uri?) {
|
||||
SignalStore.settings().callRingtone = ringtone ?: Uri.EMPTY
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setCallVibrateEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().isCallVibrateEnabled = enabled
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setNotifyWhenContactJoinsSignal(enabled: Boolean) {
|
||||
SignalStore.settings().isNotifyWhenContactJoinsSignal = enabled
|
||||
store.update { getState() }
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun getState(): NotificationsSettingsState = NotificationsSettingsState(
|
||||
messageNotificationsState = MessageNotificationsState(
|
||||
notificationsEnabled = SignalStore.settings().isMessageNotificationsEnabled,
|
||||
notificationsEnabled = SignalStore.settings().isMessageNotificationsEnabled && canEnableNotifications(),
|
||||
canEnableNotifications = canEnableNotifications(),
|
||||
sound = SignalStore.settings().messageNotificationSound,
|
||||
vibrateEnabled = SignalStore.settings().isMessageVibrateEnabled,
|
||||
ledColor = SignalStore.settings().messageLedColor,
|
||||
|
@ -109,13 +115,24 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
|
|||
troubleshootNotifications = SlowNotificationHeuristics.isPotentiallyCausedByBatteryOptimizations() && SlowNotificationHeuristics.isHavingDelayedNotifications()
|
||||
),
|
||||
callNotificationsState = CallNotificationsState(
|
||||
notificationsEnabled = SignalStore.settings().isCallNotificationsEnabled,
|
||||
notificationsEnabled = SignalStore.settings().isCallNotificationsEnabled && canEnableNotifications(),
|
||||
canEnableNotifications = canEnableNotifications(),
|
||||
ringtone = SignalStore.settings().callRingtone,
|
||||
vibrateEnabled = SignalStore.settings().isCallVibrateEnabled
|
||||
),
|
||||
notifyWhenContactJoinsSignal = SignalStore.settings().isNotifyWhenContactJoinsSignal
|
||||
)
|
||||
|
||||
private fun canEnableNotifications(): Boolean {
|
||||
val areNotificationsDisabledBySystem = Build.VERSION.SDK_INT >= 26 && (
|
||||
!NotificationChannels.getInstance().isMessageChannelEnabled ||
|
||||
!NotificationChannels.getInstance().isMessagesChannelGroupEnabled ||
|
||||
!NotificationChannels.getInstance().areNotificationsEnabled()
|
||||
)
|
||||
|
||||
return !areNotificationsDisabledBySystem
|
||||
}
|
||||
|
||||
class Factory(private val sharedPreferences: SharedPreferences) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(NotificationsSettingsViewModel(sharedPreferences)))
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.models
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.thoughtcrime.securesms.databinding.DslBannerBinding
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
|
||||
/**
|
||||
* Displays a banner to notify the user of certain state or action that needs to be taken.
|
||||
*/
|
||||
object Banner {
|
||||
fun register(mappingAdapter: MappingAdapter) {
|
||||
mappingAdapter.registerFactory(Model::class.java, BindingFactory(::ViewHolder, DslBannerBinding::inflate))
|
||||
}
|
||||
|
||||
class Model(
|
||||
@StringRes val textId: Int,
|
||||
@StringRes val actionId: Int,
|
||||
val onClick: () -> Unit
|
||||
) : MappingModel<Model> {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||
return textId == newItem.textId && actionId == newItem.actionId
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder(binding: DslBannerBinding) : BindingViewHolder<Model, DslBannerBinding>(binding) {
|
||||
override fun bind(model: Model) {
|
||||
binding.bannerText.setText(model.textId)
|
||||
binding.bannerAction.setText(model.actionId)
|
||||
binding.bannerAction.setOnClickListener { model.onClick() }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.maps.PlacePickerActivity
|
|||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
|
@ -72,7 +73,7 @@ class ConversationActivityResultContracts(private val fragment: Fragment, privat
|
|||
fun launchGallery(recipientId: RecipientId, text: CharSequence?, isReply: Boolean) {
|
||||
Permissions
|
||||
.with(fragment)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted { mediaGalleryLauncher.launch(MediaSelectionInput(emptyList(), recipientId, text, isReply)) }
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package org.thoughtcrime.securesms.conversation.v2.keyboard
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.os.bundleOf
|
||||
|
@ -23,6 +22,7 @@ import org.thoughtcrime.securesms.conversation.AttachmentKeyboardButton
|
|||
import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import java.util.function.Predicate
|
||||
|
@ -93,7 +93,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
|
|||
|
||||
override fun onAttachmentPermissionsRequested() {
|
||||
Permissions.with(requireParentFragment())
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.onAllGranted { viewModel.refreshRecentMedia() }
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.execute()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.location.LocationManager;
|
||||
|
@ -16,7 +15,6 @@ import androidx.activity.OnBackPressedCallback;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
@ -100,7 +98,7 @@ public abstract class DeviceTransferSetupFragment extends LoggingFragment {
|
|||
case INITIAL:
|
||||
status.setText("");
|
||||
case PERMISSIONS_CHECK:
|
||||
requestLocationPermission();
|
||||
requestRequiredPermission();
|
||||
break;
|
||||
case PERMISSIONS_DENIED:
|
||||
error.setText(getErrorTextForStep(step));
|
||||
|
@ -280,9 +278,9 @@ public abstract class DeviceTransferSetupFragment extends LoggingFragment {
|
|||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void requestLocationPermission() {
|
||||
private void requestRequiredPermission() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
.request(WifiDirect.requiredPermission())
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(getErrorTextForStep(SetupStep.PERMISSIONS_DENIED)), false, R.drawable.ic_location_on_white_24dp)
|
||||
.withPermanentDenialDialog(getString(getErrorTextForStep(SetupStep.PERMISSIONS_DENIED)))
|
||||
|
|
|
@ -78,7 +78,7 @@ public final class DeviceTransferSetupViewModel extends ViewModel {
|
|||
|
||||
public void onWifiDirectUnavailable(WifiDirect.AvailableStatus availability) {
|
||||
Log.i(TAG, "Wifi Direct unavailable: " + availability);
|
||||
if (availability == WifiDirect.AvailableStatus.FINE_LOCATION_PERMISSION_NOT_GRANTED) {
|
||||
if (availability == WifiDirect.AvailableStatus.REQUIRED_PERMISSION_NOT_GRANTED) {
|
||||
store.update(s -> s.updateStep(SetupStep.PERMISSIONS_CHECK));
|
||||
} else {
|
||||
store.update(s -> s.updateStep(SetupStep.WIFI_DIRECT_UNAVAILABLE));
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.widget.Toast
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
|
@ -46,7 +47,7 @@ class MediaSelectionNavigator(
|
|||
onGranted: () -> Unit
|
||||
) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos))
|
||||
.onAllGranted(onGranted)
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
|||
import org.thoughtcrime.securesms.lock.v2.SvrMigrationActivity;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -232,17 +233,8 @@ public final class Megaphones {
|
|||
.setBody(R.string.NotificationsMegaphone_never_miss_a_message)
|
||||
.setImage(R.drawable.megaphone_notifications_64)
|
||||
.setActionButton(R.string.NotificationsMegaphone_turn_on, (megaphone, controller) -> {
|
||||
if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.getInstance().isMessageChannelEnabled()) {
|
||||
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getInstance().getMessagesChannel());
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
|
||||
controller.onMegaphoneNavigationRequested(intent);
|
||||
} else if (Build.VERSION.SDK_INT >= 26 &&
|
||||
(!NotificationChannels.getInstance().areNotificationsEnabled() || !NotificationChannels.getInstance().isMessagesChannelGroupEnabled()))
|
||||
{
|
||||
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
|
||||
controller.onMegaphoneNavigationRequested(intent);
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
controller.onMegaphoneDialogFragmentRequested(new TurnOnNotificationsBottomSheet());
|
||||
} else {
|
||||
controller.onMegaphoneNavigationRequested(AppSettingsActivity.notifications(context));
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ import org.thoughtcrime.securesms.payments.create.CreatePaymentFragmentArgs;
|
|||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
|
||||
import org.thoughtcrime.securesms.payments.preferences.RecipientHasNotEnabledPaymentsDialog;
|
||||
import org.thoughtcrime.securesms.payments.preferences.model.PayeeParcelable;
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||
|
@ -407,7 +408,7 @@ public class AttachmentManager {
|
|||
|
||||
public static void selectGallery(Fragment fragment, int requestCode, @NonNull Recipient recipient, @NonNull CharSequence body, @NonNull MessageSendType messageSendType, boolean hasQuote) {
|
||||
Permissions.with(fragment)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(PermissionCompat.forImagesAndVideos())
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted(() -> fragment.startActivityForResult(MediaSelectionActivity.gallery(fragment.requireContext(), messageSendType, Collections.emptyList(), recipient.getId(), body, hasQuote), requestCode))
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
|
||||
private const val PLACEHOLDER = "__TOGGLE_PLACEHOLDER__"
|
||||
|
||||
/**
|
||||
* Sheet explaining how to turn on notifications and providing an action to do so.
|
||||
*/
|
||||
class TurnOnNotificationsBottomSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
TurnOnNotificationsSheetContent(this::goToSystemNotificationSettings)
|
||||
}
|
||||
|
||||
private fun goToSystemNotificationSettings() {
|
||||
if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.getInstance().isMessageChannelEnabled) {
|
||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getInstance().messagesChannel)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
startActivity(intent)
|
||||
} else if (Build.VERSION.SDK_INT >= 26 && (!NotificationChannels.getInstance().areNotificationsEnabled() || !NotificationChannels.getInstance().isMessagesChannelGroupEnabled)) {
|
||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
startActivity(AppSettingsActivity.notifications(requireContext()))
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TurnOnNotificationsSheetContentPreview() {
|
||||
SignalTheme(isDarkMode = false) {
|
||||
Surface {
|
||||
TurnOnNotificationsSheetContent {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TurnOnNotificationsSheetContent(
|
||||
onGoToSettingsClicked: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
.padding(bottom = 32.dp)
|
||||
) {
|
||||
BottomSheets.Handle(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.TurnOnNotificationsBottomSheet__turn_on_notifications),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 12.dp, top = 10.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.TurnOnNotificationsBottomSheet__to_receive_notifications),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.TurnOnNotificationsBottomSheet__1_tap_settings_below),
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
val step2String = stringResource(id = R.string.TurnOnNotificationsBottomSheet__2_s_turn_on_notifications, PLACEHOLDER)
|
||||
val (step2Text, step2InlineContent) = remember(step2String) {
|
||||
val parts = step2String.split(PLACEHOLDER)
|
||||
val annotatedString = buildAnnotatedString {
|
||||
append(parts[0])
|
||||
appendInlineContent("toggle")
|
||||
append(parts[1])
|
||||
}
|
||||
|
||||
val inlineContentMap = mapOf(
|
||||
"toggle" to InlineTextContent(Placeholder(36.sp, 22.sp, PlaceholderVerticalAlign.Center)) {
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.illustration_toggle_switch),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
annotatedString to inlineContentMap
|
||||
}
|
||||
|
||||
Text(
|
||||
text = step2Text,
|
||||
inlineContent = step2InlineContent,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onGoToSettingsClicked,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.fillMaxWidth(1f)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.TurnOnNotificationsBottomSheet__settings))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.permissions
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
|
||||
/**
|
||||
* Compatibility object for requesting specific permissions that have become more
|
||||
* granular as the APIs have evolved.
|
||||
*/
|
||||
object PermissionCompat {
|
||||
@JvmStatic
|
||||
fun forImages(): Array<String> {
|
||||
return if (Build.VERSION.SDK_INT >= 33) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun forVideos(): Array<String> {
|
||||
return if (Build.VERSION.SDK_INT >= 33) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_VIDEO)
|
||||
} else {
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forImagesAndVideos(): Array<String> {
|
||||
return setOf(*(forImages() + forVideos())).toTypedArray()
|
||||
}
|
||||
}
|
|
@ -49,6 +49,10 @@ public class Permissions {
|
|||
return new PermissionsBuilder(new FragmentPermissionObject(fragment));
|
||||
}
|
||||
|
||||
public static boolean isRuntimePermissionsRequired() {
|
||||
return Build.VERSION.SDK_INT >= 23;
|
||||
}
|
||||
|
||||
public static class PermissionsBuilder {
|
||||
|
||||
private final PermissionObject permissionObject;
|
||||
|
@ -239,13 +243,13 @@ public class Permissions {
|
|||
}
|
||||
|
||||
public static boolean hasAny(@NonNull Context context, String... permissions) {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
|
||||
return !isRuntimePermissionsRequired() ||
|
||||
Stream.of(permissions).anyMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
}
|
||||
|
||||
public static boolean hasAll(@NonNull Context context, String... permissions) {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
|
||||
return !isRuntimePermissionsRequired() ||
|
||||
Stream.of(permissions).allMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registration.fragments
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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.activityViewModels
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel
|
||||
import org.thoughtcrime.securesms.util.BackupUtil
|
||||
|
||||
/**
|
||||
* Fragment displayed during registration which allows a user to read through
|
||||
* what permissions are granted to Signal and why, and a means to either skip
|
||||
* granting those permissions or continue to grant via system dialogs.
|
||||
*/
|
||||
class GrantPermissionsFragment : ComposeFragment() {
|
||||
|
||||
private val args by navArgs<GrantPermissionsFragmentArgs>()
|
||||
private val viewModel by activityViewModels<RegistrationViewModel>()
|
||||
private val isSearchingForBackup = mutableStateOf(false)
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val isSearchingForBackup by this.isSearchingForBackup
|
||||
|
||||
GrantPermissionsScreen(
|
||||
deviceBuildVersion = Build.VERSION.SDK_INT,
|
||||
isSearchingForBackup = isSearchingForBackup,
|
||||
isBackupSelectionRequired = BackupUtil.isUserSelectionRequired(LocalContext.current),
|
||||
onNextClicked = this::onNextClicked,
|
||||
onNotNowClicked = this::onNotNowClicked
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun onNextClicked() {
|
||||
when (args.welcomeAction) {
|
||||
WelcomeAction.CONTINUE -> {
|
||||
WelcomeFragment.continueClicked(
|
||||
this,
|
||||
viewModel,
|
||||
{ isSearchingForBackup.value = true },
|
||||
{ isSearchingForBackup.value = false },
|
||||
GrantPermissionsFragmentDirections.actionSkipRestore(),
|
||||
GrantPermissionsFragmentDirections.actionRestore()
|
||||
)
|
||||
}
|
||||
|
||||
WelcomeAction.RESTORE_BACKUP -> {
|
||||
WelcomeFragment.restoreFromBackupClicked(
|
||||
this,
|
||||
viewModel,
|
||||
GrantPermissionsFragmentDirections.actionTransferOrRestore()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNotNowClicked() {
|
||||
when (args.welcomeAction) {
|
||||
WelcomeAction.CONTINUE -> {
|
||||
WelcomeFragment.gatherInformationAndContinue(
|
||||
this,
|
||||
viewModel,
|
||||
{ isSearchingForBackup.value = true },
|
||||
{ isSearchingForBackup.value = false },
|
||||
GrantPermissionsFragmentDirections.actionSkipRestore(),
|
||||
GrantPermissionsFragmentDirections.actionRestore()
|
||||
)
|
||||
}
|
||||
|
||||
WelcomeAction.RESTORE_BACKUP -> {
|
||||
WelcomeFragment.gatherInformationAndChooseBackup(
|
||||
this,
|
||||
viewModel,
|
||||
GrantPermissionsFragmentDirections.actionTransferOrRestore()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Which welcome action the user selected which prompted this
|
||||
* screen.
|
||||
*/
|
||||
enum class WelcomeAction {
|
||||
CONTINUE,
|
||||
RESTORE_BACKUP
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun GrantPermissionsScreenPreview() {
|
||||
SignalTheme(isDarkMode = false) {
|
||||
GrantPermissionsScreen(
|
||||
deviceBuildVersion = 33,
|
||||
isBackupSelectionRequired = true,
|
||||
isSearchingForBackup = true,
|
||||
{},
|
||||
{}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GrantPermissionsScreen(
|
||||
deviceBuildVersion: Int,
|
||||
isBackupSelectionRequired: Boolean,
|
||||
isSearchingForBackup: Boolean,
|
||||
onNextClicked: () -> Unit,
|
||||
onNotNowClicked: () -> Unit
|
||||
) {
|
||||
Surface {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(top = 40.dp, bottom = 24.dp)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(id = R.string.GrantPermissionsFragment__allow_permissions),
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(id = R.string.GrantPermissionsFragment__to_help_you_message_people_you_know),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 12.dp, bottom = 41.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (deviceBuildVersion >= 33) {
|
||||
item {
|
||||
PermissionRow(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.permission_notification),
|
||||
title = stringResource(id = R.string.GrantPermissionsFragment__notifications),
|
||||
subtitle = stringResource(id = R.string.GrantPermissionsFragment__get_notified_when)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
PermissionRow(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.permission_contact),
|
||||
title = stringResource(id = R.string.GrantPermissionsFragment__contacts),
|
||||
subtitle = stringResource(id = R.string.GrantPermissionsFragment__find_people_you_know)
|
||||
)
|
||||
}
|
||||
|
||||
if (deviceBuildVersion < 29 || !isBackupSelectionRequired) {
|
||||
item {
|
||||
PermissionRow(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.permission_file),
|
||||
title = stringResource(id = R.string.GrantPermissionsFragment__storage),
|
||||
subtitle = stringResource(id = R.string.GrantPermissionsFragment__send_photos_videos_and_files)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
PermissionRow(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.permission_phone),
|
||||
title = stringResource(id = R.string.GrantPermissionsFragment__phone_calls),
|
||||
subtitle = stringResource(id = R.string.GrantPermissionsFragment__make_registering_easier)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
TextButton(onClick = onNotNowClicked) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.GrantPermissionsFragment__not_now)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (isSearchingForBackup) {
|
||||
Box {
|
||||
NextButton(
|
||||
isSearchingForBackup = true,
|
||||
onNextClicked = onNextClicked
|
||||
)
|
||||
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
NextButton(
|
||||
isSearchingForBackup = false,
|
||||
onNextClicked = onNextClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PermissionRowPreview() {
|
||||
PermissionRow(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.permission_notification),
|
||||
title = stringResource(id = R.string.GrantPermissionsFragment__notifications),
|
||||
subtitle = stringResource(id = R.string.GrantPermissionsFragment__get_notified_when)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PermissionRow(
|
||||
imageVector: ImageVector,
|
||||
title: String,
|
||||
subtitle: String
|
||||
) {
|
||||
Row(modifier = Modifier.padding(bottom = 32.dp)) {
|
||||
Image(
|
||||
imageVector = imageVector,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(32.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NextButton(
|
||||
isSearchingForBackup: Boolean,
|
||||
onNextClicked: () -> Unit
|
||||
) {
|
||||
val alpha = if (isSearchingForBackup) {
|
||||
0f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onNextClicked,
|
||||
enabled = !isSearchingForBackup,
|
||||
modifier = Modifier.alpha(alpha)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.GrantPermissionsFragment__next)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -14,11 +14,11 @@ import android.widget.TextView;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.ActivityNavigator;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
|
@ -49,29 +49,6 @@ public final class WelcomeFragment extends LoggingFragment {
|
|||
|
||||
private static final String TAG = Log.tag(WelcomeFragment.class);
|
||||
|
||||
private static final String[] PERMISSIONS = { Manifest.permission.WRITE_CONTACTS,
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_PHONE_STATE };
|
||||
@RequiresApi(26)
|
||||
private static final String[] PERMISSIONS_API_26 = { Manifest.permission.WRITE_CONTACTS,
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_PHONE_STATE,
|
||||
Manifest.permission.READ_PHONE_NUMBERS };
|
||||
@RequiresApi(26)
|
||||
private static final String[] PERMISSIONS_API_29 = { Manifest.permission.WRITE_CONTACTS,
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.READ_PHONE_STATE,
|
||||
Manifest.permission.READ_PHONE_NUMBERS };
|
||||
|
||||
private static final @StringRes int RATIONALE = R.string.RegistrationActivity_signal_needs_access_to_your_contacts_and_media_in_order_to_connect_with_friends;
|
||||
private static final @StringRes int RATIONALE_API_29 = R.string.RegistrationActivity_signal_needs_access_to_your_contacts_in_order_to_connect_with_friends;
|
||||
private static final int[] HEADERS = { R.drawable.ic_contacts_white_48dp, R.drawable.ic_folder_white_48dp };
|
||||
private static final int[] HEADERS_API_29 = { R.drawable.ic_contacts_white_48dp };
|
||||
|
||||
private CircularProgressMaterialButton continueButton;
|
||||
private RegistrationViewModel viewModel;
|
||||
|
||||
|
@ -97,7 +74,7 @@ public final class WelcomeFragment extends LoggingFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
initializeNumber();
|
||||
initializeNumber(requireContext(), viewModel);
|
||||
|
||||
Log.i(TAG, "Skipping restore because this is a reregistration.");
|
||||
viewModel.setWelcomeSkippedOnRestore();
|
||||
|
@ -109,10 +86,10 @@ public final class WelcomeFragment extends LoggingFragment {
|
|||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.title));
|
||||
|
||||
continueButton = view.findViewById(R.id.welcome_continue_button);
|
||||
continueButton.setOnClickListener(this::continueClicked);
|
||||
continueButton.setOnClickListener(v -> onContinueClicked());
|
||||
|
||||
Button restoreFromBackup = view.findViewById(R.id.welcome_transfer_or_restore);
|
||||
restoreFromBackup.setOnClickListener(this::restoreFromBackupClicked);
|
||||
restoreFromBackup.setOnClickListener(v -> onRestoreFromBackupClicked());
|
||||
|
||||
TextView welcomeTermsButton = view.findViewById(R.id.welcome_terms_button);
|
||||
welcomeTermsButton.setOnClickListener(v -> onTermsClicked());
|
||||
|
@ -139,70 +116,116 @@ public final class WelcomeFragment extends LoggingFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void continueClicked(@NonNull View view) {
|
||||
boolean isUserSelectionRequired = BackupUtil.isUserSelectionRequired(requireContext());
|
||||
private void onContinueClicked() {
|
||||
if (Permissions.isRuntimePermissionsRequired()) {
|
||||
NavHostFragment.findNavController(this)
|
||||
.navigate(WelcomeFragmentDirections.actionWelcomeFragmentToGrantPermissionsFragment(GrantPermissionsFragment.WelcomeAction.CONTINUE));
|
||||
} else {
|
||||
gatherInformationAndContinue(
|
||||
this,
|
||||
viewModel,
|
||||
() -> continueButton.setSpinning(),
|
||||
() -> continueButton.cancelSpinning(),
|
||||
WelcomeFragmentDirections.actionSkipRestore(),
|
||||
WelcomeFragmentDirections.actionRestore()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Permissions.with(this)
|
||||
.request(getContinuePermissions(isUserSelectionRequired))
|
||||
private void onRestoreFromBackupClicked() {
|
||||
if (Permissions.isRuntimePermissionsRequired()) {
|
||||
NavHostFragment.findNavController(this)
|
||||
.navigate(WelcomeFragmentDirections.actionWelcomeFragmentToGrantPermissionsFragment(GrantPermissionsFragment.WelcomeAction.RESTORE_BACKUP));
|
||||
} else {
|
||||
gatherInformationAndChooseBackup(this, viewModel, WelcomeFragmentDirections.actionTransferOrRestore());
|
||||
}
|
||||
}
|
||||
|
||||
static void continueClicked(@NonNull Fragment fragment,
|
||||
@NonNull RegistrationViewModel viewModel,
|
||||
@NonNull Runnable onSearchForBackupStarted,
|
||||
@NonNull Runnable onSearchForBackupFinished,
|
||||
@NonNull NavDirections actionSkipRestore,
|
||||
@NonNull NavDirections actionRestore)
|
||||
{
|
||||
boolean isUserSelectionRequired = BackupUtil.isUserSelectionRequired(fragment.requireContext());
|
||||
|
||||
Permissions.with(fragment)
|
||||
.request(WelcomePermissions.getWelcomePermissions(isUserSelectionRequired))
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(getContinueRationale(isUserSelectionRequired)), getContinueHeaders(isUserSelectionRequired))
|
||||
.onAnyResult(() -> gatherInformationAndContinue(continueButton))
|
||||
.onAnyResult(() -> gatherInformationAndContinue(fragment,
|
||||
viewModel,
|
||||
onSearchForBackupStarted,
|
||||
onSearchForBackupFinished,
|
||||
actionSkipRestore,
|
||||
actionRestore))
|
||||
.execute();
|
||||
}
|
||||
|
||||
private void restoreFromBackupClicked(@NonNull View view) {
|
||||
boolean isUserSelectionRequired = BackupUtil.isUserSelectionRequired(requireContext());
|
||||
static void restoreFromBackupClicked(@NonNull Fragment fragment,
|
||||
@NonNull RegistrationViewModel viewModel,
|
||||
@NonNull NavDirections actionTransferOrRestore)
|
||||
{
|
||||
boolean isUserSelectionRequired = BackupUtil.isUserSelectionRequired(fragment.requireContext());
|
||||
|
||||
Permissions.with(this)
|
||||
.request(getContinuePermissions(isUserSelectionRequired))
|
||||
Permissions.with(fragment)
|
||||
.request(WelcomePermissions.getWelcomePermissions(isUserSelectionRequired))
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(getContinueRationale(isUserSelectionRequired)), getContinueHeaders(isUserSelectionRequired))
|
||||
.onAnyResult(() -> gatherInformationAndChooseBackup(continueButton))
|
||||
.onAnyResult(() -> gatherInformationAndChooseBackup(fragment, viewModel, actionTransferOrRestore))
|
||||
.execute();
|
||||
}
|
||||
|
||||
private void gatherInformationAndContinue(@NonNull View view) {
|
||||
continueButton.setSpinning();
|
||||
static void gatherInformationAndContinue(
|
||||
@NonNull Fragment fragment,
|
||||
@NonNull RegistrationViewModel viewModel,
|
||||
@NonNull Runnable onSearchForBackupStarted,
|
||||
@NonNull Runnable onSearchForBackupFinished,
|
||||
@NonNull NavDirections actionSkipRestore,
|
||||
@NonNull NavDirections actionRestore
|
||||
) {
|
||||
onSearchForBackupStarted.run();
|
||||
|
||||
RestoreBackupFragment.searchForBackup(backup -> {
|
||||
Context context = getContext();
|
||||
Context context = fragment.getContext();
|
||||
if (context == null) {
|
||||
Log.i(TAG, "No context on fragment, must have navigated away.");
|
||||
return;
|
||||
}
|
||||
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(requireContext(), true);
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(fragment.requireContext(), true);
|
||||
|
||||
initializeNumber();
|
||||
initializeNumber(fragment.requireContext(), viewModel);
|
||||
|
||||
continueButton.cancelSpinning();
|
||||
onSearchForBackupFinished.run();
|
||||
|
||||
if (backup == null) {
|
||||
Log.i(TAG, "Skipping backup. No backup found, or no permission to look.");
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this),
|
||||
WelcomeFragmentDirections.actionSkipRestore());
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(fragment),
|
||||
actionSkipRestore);
|
||||
} else {
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this),
|
||||
WelcomeFragmentDirections.actionRestore());
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(fragment),
|
||||
actionRestore);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void gatherInformationAndChooseBackup(@NonNull View view) {
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(requireContext(), true);
|
||||
static void gatherInformationAndChooseBackup(@NonNull Fragment fragment,
|
||||
@NonNull RegistrationViewModel viewModel,
|
||||
@NonNull NavDirections actionTransferOrRestore) {
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(fragment.requireContext(), true);
|
||||
|
||||
initializeNumber();
|
||||
initializeNumber(fragment.requireContext(), viewModel);
|
||||
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this),
|
||||
WelcomeFragmentDirections.actionTransferOrRestore());
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(fragment),
|
||||
actionTransferOrRestore);
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private void initializeNumber() {
|
||||
private static void initializeNumber(@NonNull Context context, @NonNull RegistrationViewModel viewModel) {
|
||||
Optional<Phonenumber.PhoneNumber> localNumber = Optional.empty();
|
||||
|
||||
if (Permissions.hasAll(requireContext(), Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) {
|
||||
localNumber = Util.getDeviceNumber(requireContext());
|
||||
if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS)) {
|
||||
localNumber = Util.getDeviceNumber(context);
|
||||
} else {
|
||||
Log.i(TAG, "No phone permission");
|
||||
}
|
||||
|
@ -215,7 +238,7 @@ public final class WelcomeFragment extends LoggingFragment {
|
|||
viewModel.onNumberDetected(phoneNumber.getCountryCode(), nationalNumber);
|
||||
} else {
|
||||
Log.i(TAG, "No number detected");
|
||||
Optional<String> simCountryIso = Util.getSimCountryIso(requireContext());
|
||||
Optional<String> simCountryIso = Util.getSimCountryIso(context);
|
||||
|
||||
if (simCountryIso.isPresent() && !TextUtils.isEmpty(simCountryIso.get())) {
|
||||
viewModel.onNumberDetected(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get()), "");
|
||||
|
@ -232,23 +255,4 @@ public final class WelcomeFragment extends LoggingFragment {
|
|||
!viewModel.isReregister() &&
|
||||
!SignalStore.settings().isBackupEnabled();
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private static String[] getContinuePermissions(boolean isUserSelectionRequired) {
|
||||
if (isUserSelectionRequired) {
|
||||
return PERMISSIONS_API_29;
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
return PERMISSIONS_API_26;
|
||||
} else {
|
||||
return PERMISSIONS;
|
||||
}
|
||||
}
|
||||
|
||||
private static @StringRes int getContinueRationale(boolean isUserSelectionRequired) {
|
||||
return isUserSelectionRequired ? RATIONALE_API_29 : RATIONALE;
|
||||
}
|
||||
|
||||
private static int[] getContinueHeaders(boolean isUserSelectionRequired) {
|
||||
return isUserSelectionRequired ? HEADERS_API_29 : HEADERS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registration.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
|
||||
/**
|
||||
* Handles welcome permissions instead of having to do weird giant if statements.
|
||||
*/
|
||||
object WelcomePermissions {
|
||||
private enum class Permissions {
|
||||
POST_NOTIFICATIONS {
|
||||
override fun getPermissions(isUserBackupSelectionRequired: Boolean): List<String> {
|
||||
return if (Build.VERSION.SDK_INT >= 33) {
|
||||
listOf(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
},
|
||||
CONTACTS {
|
||||
override fun getPermissions(isUserBackupSelectionRequired: Boolean): List<String> {
|
||||
return listOf(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
|
||||
}
|
||||
},
|
||||
STORAGE {
|
||||
override fun getPermissions(isUserBackupSelectionRequired: Boolean): List<String> {
|
||||
return if (Build.VERSION.SDK_INT < 29 || !isUserBackupSelectionRequired) {
|
||||
listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
},
|
||||
PHONE {
|
||||
override fun getPermissions(isUserBackupSelectionRequired: Boolean): List<String> {
|
||||
return listOf(Manifest.permission.READ_PHONE_STATE) +
|
||||
(if (Build.VERSION.SDK_INT >= 26) listOf(Manifest.permission.READ_PHONE_NUMBERS) else emptyList())
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun getPermissions(isUserBackupSelectionRequired: Boolean): List<String>
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getWelcomePermissions(isUserBackupSelectionRequired: Boolean): Array<String> {
|
||||
return Permissions.values().map { it.getPermissions(isUserBackupSelectionRequired) }.flatten().toTypedArray()
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.BuildConfig;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -82,10 +83,6 @@ public class StorageUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static File getBackupCacheDirectory(Context context) {
|
||||
return context.getExternalCacheDir();
|
||||
}
|
||||
|
||||
private static File getSignalStorageDir() throws NoExternalStorageException {
|
||||
final File storage = Environment.getExternalStorageDirectory();
|
||||
|
||||
|
@ -108,17 +105,13 @@ public class StorageUtil {
|
|||
return storage.canWrite();
|
||||
}
|
||||
|
||||
public static File getLegacyBackupDirectory() throws NoExternalStorageException {
|
||||
return getSignalStorageDir();
|
||||
}
|
||||
|
||||
public static boolean canWriteToMediaStore() {
|
||||
return Build.VERSION.SDK_INT > 28 ||
|
||||
Permissions.hasAll(ApplicationDependencies.getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
public static boolean canReadFromMediaStore() {
|
||||
return Permissions.hasAll(ApplicationDependencies.getApplication(), Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
return Permissions.hasAll(ApplicationDependencies.getApplication(), PermissionCompat.forImagesAndVideos());
|
||||
}
|
||||
|
||||
public static @NonNull Uri getVideoUri() {
|
||||
|
|
|
@ -18,6 +18,7 @@ import androidx.navigation.Navigation;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.wallpaper.crop.WallpaperImageSelectionActivity;
|
||||
|
||||
|
@ -76,7 +77,7 @@ public class ChatWallpaperSelectionFragment extends Fragment {
|
|||
|
||||
private void askForPermissionIfNeededAndLaunchPhotoSelection() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(PermissionCompat.forImages())
|
||||
.ifNecessary()
|
||||
.onAllGranted(() -> {
|
||||
startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.thoughtcrime.securesms.wallpaper.crop;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
@ -8,7 +7,6 @@ import android.view.WindowManager;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
|
@ -23,7 +21,6 @@ public final class WallpaperImageSelectionActivity extends AppCompatActivity
|
|||
private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID";
|
||||
private static final int CROP = 901;
|
||||
|
||||
@RequiresPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
public static Intent getIntent(@NonNull Context context,
|
||||
@Nullable RecipientId recipientId)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="36"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M11,0L25,0A11,11 0,0 1,36 11L36,11A11,11 0,0 1,25 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||
android:fillColor="#B6C5FA"/>
|
||||
<path
|
||||
android:pathData="M25,11m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
|
||||
android:fillColor="#1E2438"/>
|
||||
</vector>
|
12
app/src/main/res/drawable/illustration_toggle_switch.xml
Normal file
12
app/src/main/res/drawable/illustration_toggle_switch.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="36"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M11,0L25,0A11,11 0,0 1,36 11L36,11A11,11 0,0 1,25 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||
android:fillColor="#2C58C3"/>
|
||||
<path
|
||||
android:pathData="M25,11m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
20
app/src/main/res/drawable/permission_contact.xml
Normal file
20
app/src/main/res/drawable/permission_contact.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M24,24m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M24,27.75c3.59,0 6.5,-3.772 6.5,-8.121C30.5,15.279 27.59,12 24,12s-6.5,3.28 -6.5,7.629c0,4.35 2.91,8.121 6.5,8.121ZM24,44a19.92,19.92 0,0 0,13.139 -4.921c-2.82,-3.513 -7.652,-5.829 -13.14,-5.829 -5.486,0 -10.318,2.316 -13.138,5.829A19.923,19.923 0,0 0,24 44Z"
|
||||
android:fillColor="#E3E9F5"/>
|
||||
<path
|
||||
android:pathData="M24,11c-4.276,0 -7.5,3.871 -7.5,8.629 0,2.39 0.798,4.642 2.12,6.312 1.321,1.67 3.214,2.809 5.38,2.809s4.059,-1.139 5.38,-2.81c1.322,-1.67 2.12,-3.92 2.12,-6.311C31.5,14.87 28.276,11 24,11ZM18.5,19.629C18.5,15.689 21.096,13 24,13s5.5,2.688 5.5,6.629c0,1.958 -0.657,3.768 -1.688,5.07 -1.03,1.304 -2.388,2.051 -3.812,2.051s-2.781,-0.747 -3.812,-2.05c-1.031,-1.303 -1.688,-3.113 -1.688,-5.071Z"
|
||||
android:fillColor="#7282A5"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M3,24C3,12.402 12.402,3 24,3s21,9.402 21,21 -9.402,21 -21,21S3,35.598 3,24ZM24,5C13.507,5 5,13.507 5,24a18.944,18.944 0,0 0,5.78 13.646c3.085,-3.31 7.884,-5.396 13.22,-5.396s10.135,2.087 13.22,5.396A18.944,18.944 0,0 0,43 24c0,-10.493 -8.507,-19 -19,-19ZM24,43a18.918,18.918 0,0 1,-11.712 -4.038C14.964,36.122 19.19,34.25 24,34.25s9.036,1.872 11.712,4.712A18.918,18.918 0,0 1,24 43Z"
|
||||
android:fillColor="#7282A5"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
19
app/src/main/res/drawable/permission_file.xml
Normal file
19
app/src/main/res/drawable/permission_file.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M33.4,12c3.36,0 5.04,0 6.324,0.654a6,6 0,0 1,2.622 2.622C43,16.56 43,18.24 43,21.6v8.8c0,3.36 0,5.04 -0.654,6.324a6,6 0,0 1,-2.622 2.622C38.44,40 36.76,40 33.4,40H14.6c-3.36,0 -5.04,0 -6.324,-0.654a6,6 0,0 1,-2.622 -2.622C5,35.44 5,33.76 5,30.4v-8.8c0,-3.36 0,-5.04 0.654,-6.324a6,6 0,0 1,2.622 -2.622C9.56,12 11.24,12 14.6,12h18.8Z"
|
||||
android:fillColor="#FFEABC"/>
|
||||
<path
|
||||
android:pathData="M5,21.6v8.8c0,3.36 0,5.04 0.654,6.324a6,6 0,0 0,2.622 2.622C9.56,40 11.24,40 14.6,40h18.943c3.265,0 4.916,-0.01 6.18,-0.654 0.452,-0.23 0.869,-0.514 1.244,-0.844L7.033,13.498a5.998,5.998 0,0 0,-1.379 1.778C5,16.56 5,18.24 5,21.6Z"
|
||||
android:fillColor="#FFE3A5"/>
|
||||
<path
|
||||
android:pathData="M5.026,18.681c0.051,-1.548 0.199,-2.562 0.628,-3.405a6,6 0,0 1,2.622 -2.622C9.56,12 11.24,12 14.6,12h7.891c-0.073,-0.163 -0.166,-0.35 -0.32,-0.658 -0.495,-0.99 -0.743,-1.485 -1.048,-1.9a6,6 0,0 0,-3.81 -2.354C16.806,7 16.253,7 15.146,7H14c-2.796,0 -4.193,0 -5.296,0.457a6,6 0,0 0,-3.247 3.247C5,11.806 5,13.204 5,16c0,1.082 0,1.954 0.026,2.681Z"
|
||||
android:fillColor="#E9C576"/>
|
||||
<path
|
||||
android:pathData="M4,21.473v-4.94c0,-1.632 0,-2.918 0.084,-3.953 0.086,-1.055 0.264,-1.937 0.67,-2.74A7,7 0,0 1,7.84 6.755c0.802,-0.406 1.684,-0.584 2.74,-0.67C11.615,6 12.9,6 14.533,6h2.551a6.15,6.15 0,0 1,5.274 2.986A4.15,4.15 0,0 0,25.912 11h7.532c1.643,0 2.937,0 3.978,0.085 1.063,0.087 1.95,0.267 2.756,0.678a7,7 0,0 1,3.059 3.06c0.41,0.805 0.591,1.692 0.678,2.755 0.085,1.041 0.085,2.335 0.085,3.978v8.888c0,1.643 0,2.937 -0.085,3.978 -0.087,1.063 -0.267,1.95 -0.678,2.756a7,7 0,0 1,-3.06 3.059c-0.805,0.41 -1.692,0.591 -2.755,0.678 -1.041,0.085 -2.335,0.085 -3.978,0.085L14.556,41c-1.643,0 -2.937,0 -3.978,-0.085 -1.063,-0.087 -1.95,-0.267 -2.756,-0.678a7,7 0,0 1,-3.059 -3.06c-0.41,-0.805 -0.591,-1.692 -0.678,-2.755C4,33.381 4,32.087 4,30.444v-8.97ZM25.912,13L14.6,13c-1.697,0 -2.909,0 -3.86,0.078 -0.938,0.077 -1.533,0.224 -2.01,0.467a5,5 0,0 0,-2.185 2.185c-0.243,0.477 -0.39,1.072 -0.467,2.01 -0.076,0.931 -0.078,2.112 -0.078,3.754L6,30.4c0,1.697 0,2.909 0.078,3.86 0.077,0.938 0.224,1.533 0.467,2.01a5,5 0,0 0,2.185 2.185c0.477,0.243 1.072,0.39 2.01,0.467 0.951,0.077 2.163,0.078 3.86,0.078h18.8c1.697,0 2.909,0 3.86,-0.078 0.938,-0.077 1.533,-0.224 2.01,-0.467a5,5 0,0 0,2.185 -2.185c0.243,-0.477 0.39,-1.072 0.467,-2.01 0.077,-0.951 0.078,-2.163 0.078,-3.86v-8.8c0,-1.697 0,-2.909 -0.078,-3.86 -0.077,-0.938 -0.224,-1.533 -0.467,-2.01a5,5 0,0 0,-2.185 -2.185c-0.477,-0.243 -1.072,-0.39 -2.01,-0.467 -0.951,-0.077 -2.163,-0.078 -3.86,-0.078h-7.488ZM21.377,11c-0.274,-0.3 -0.52,-0.63 -0.735,-0.986A4.15,4.15 0,0 0,17.084 8h-2.507c-1.685,0 -2.89,0 -3.835,0.077 -0.933,0.076 -1.524,0.221 -1.999,0.461a5,5 0,0 0,-2.205 2.205c-0.24,0.475 -0.385,1.066 -0.46,2a15.71,15.71 0,0 0,-0.022 0.301,7 7,0 0,1 1.766,-1.281c0.806,-0.41 1.693,-0.591 2.756,-0.678C11.619,11 12.913,11 14.556,11h6.82Z"
|
||||
android:fillColor="#9C7A2D"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
22
app/src/main/res/drawable/permission_notification.xml
Normal file
22
app/src/main/res/drawable/permission_notification.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M17,36.5c0,3.967 3.033,7 7,7s7,-3.033 7,-7"
|
||||
android:fillColor="#E9C576"/>
|
||||
<path
|
||||
android:pathData="M5.25,33.403c0,1.71 1.361,3.097 3.04,3.097h31.42c1.679,0 3.04,-1.386 3.04,-3.097 0,-4.325 -5.557,-1.548 -6.081,-16.516C36.416,9.647 30.717,4.5 24,4.5c-6.717,0 -12.416,5.147 -12.669,12.387 -0.524,14.968 -6.081,12.19 -6.081,16.516Z"
|
||||
android:fillColor="#FFEABC"/>
|
||||
<path
|
||||
android:pathData="M8.29,36.5c-1.679,0 -3.04,-1.386 -3.04,-3.097 0,-1.528 0.694,-2.17 1.615,-3.021 1.53,-1.415 3.687,-3.41 4.323,-11.022A20.908,20.908 0,0 1,24 15c4.823,0 9.267,1.626 12.812,4.36 0.636,7.613 2.793,9.607 4.323,11.022 0.921,0.851 1.615,1.493 1.615,3.021 0,1.71 -1.361,3.097 -3.04,3.097H8.29Z"
|
||||
android:fillColor="#FFE3A5"/>
|
||||
<path
|
||||
android:pathData="M10.707,3.293a1,1 0,0 1,0 1.414c-2.355,2.356 -4.24,5.65 -4.713,9.903a1,1 0,0 1,-1.988 -0.22c0.528,-4.748 2.642,-8.453 5.287,-11.097a1,1 0,0 1,1.414 0ZM37.293,3.293a1,1 0,0 0,0 1.414c2.355,2.356 4.24,5.65 4.713,9.903a1,1 0,0 0,1.988 -0.22c-0.527,-4.748 -2.642,-8.453 -5.287,-11.097a1,1 0,0 0,-1.414 0Z"
|
||||
android:fillColor="#9C7A2D"/>
|
||||
<path
|
||||
android:pathData="M10.332,16.852C10.604,9.064 16.755,3.5 24,3.5c7.245,0 13.396,5.564 13.668,13.352 0.258,7.364 1.747,10.157 3,11.638 0.326,0.383 0.651,0.698 0.977,1.001l0.155,0.144 0.004,0.003c0.27,0.25 0.558,0.516 0.81,0.792 0.67,0.738 1.136,1.608 1.136,2.973 0,2.245 -1.792,4.097 -4.04,4.097h-7.768c-0.471,4.022 -3.763,7 -7.942,7 -4.18,0 -7.471,-2.978 -7.942,-7L8.29,37.5c-2.25,0 -4.041,-1.852 -4.041,-4.097 0,-1.365 0.466,-2.235 1.137,-2.973 0.25,-0.276 0.54,-0.543 0.809,-0.792l0.159,-0.147c0.326,-0.303 0.651,-0.618 0.976,-1.001 1.254,-1.48 2.743,-4.274 3,-11.638ZM24,5.5c-6.189,0 -11.435,4.731 -11.67,11.422 -0.266,7.604 -1.817,10.906 -3.473,12.86a13.45,13.45 0,0 1,-1.14 1.173l-0.184,0.171c-0.268,0.248 -0.48,0.444 -0.667,0.65 -0.387,0.425 -0.616,0.83 -0.616,1.627 0,1.176 0.93,2.097 2.04,2.097h31.42c1.11,0 2.04,-0.921 2.04,-2.097 0,-0.797 -0.23,-1.202 -0.616,-1.627 -0.187,-0.206 -0.399,-0.402 -0.667,-0.65l-0.184,-0.17c-0.344,-0.32 -0.738,-0.699 -1.14,-1.174 -1.656,-1.954 -3.207,-5.256 -3.474,-12.86C35.437,10.232 30.19,5.5 24,5.5ZM24,42.5c-3.072,0 -5.473,-2.093 -5.924,-5h11.848c-0.45,2.907 -2.852,5 -5.924,5Z"
|
||||
android:fillColor="#9C7A2D"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
16
app/src/main/res/drawable/permission_phone.xml
Normal file
16
app/src/main/res/drawable/permission_phone.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M15.847,7.103a4.058,4.058 0,0 0,-6.1 -0.414l-0.871,0.87C6.09,10.347 4.7,14.355 5.978,18.083a38.396,38.396 0,0 0,9.208 14.732,38.395 38.395,0 0,0 14.732,9.208c3.728,1.277 7.736,-0.112 10.522,-2.898l0.871,-0.871a4.058,4.058 0,0 0,-0.414 -6.1l-5.706,-4.337a4.058,4.058 0,0 0,-5.325 0.361l-2.484,2.485c-0.793,0.792 -3.683,-0.814 -6.457,-3.587 -2.773,-2.774 -4.379,-5.664 -3.587,-6.457l2.485,-2.484a4.058,4.058 0,0 0,0.361 -5.325l-4.337,-5.706Z"
|
||||
android:fillColor="#DDE7FF"/>
|
||||
<path
|
||||
android:pathData="M20.083,17.848a4.058,4.058 0,0 0,0.101 -5.039l-4.337,-5.706a4.059,4.059 0,0 0,-5.839 -0.654c0.213,5.773 4.503,10.504 10.075,11.399ZM29.524,28.519 L29.866,28.177a4.058,4.058 0,0 1,5.325 -0.361l5.706,4.337a4.06,4.06 0,0 1,1.294 4.789c-0.392,0.038 -0.79,0.058 -1.19,0.058 -5.404,0 -9.973,-3.57 -11.477,-8.481Z"
|
||||
android:fillColor="#C3CFE9"/>
|
||||
<path
|
||||
android:pathData="M15.051,7.708a3.058,3.058 0,0 0,-4.597 -0.312l-0.871,0.87c-2.585,2.586 -3.784,6.209 -2.659,9.492a37.396,37.396 0,0 0,8.97 14.348,37.397 37.397,0 0,0 14.348,8.97c3.283,1.125 6.906,-0.074 9.491,-2.659l0.871,-0.871a3.058,3.058 0,0 0,-0.312 -4.597l-5.706,-4.337a3.058,3.058 0,0 0,-4.013 0.272l-2.484,2.485c-0.557,0.556 -1.32,0.54 -1.85,0.438 -0.572,-0.11 -1.2,-0.378 -1.828,-0.728 -1.27,-0.705 -2.76,-1.864 -4.193,-3.297 -1.433,-1.433 -2.592,-2.922 -3.297,-4.193 -0.35,-0.629 -0.617,-1.256 -0.728,-1.827 -0.103,-0.53 -0.119,-1.294 0.438,-1.85l2.485,-2.485a3.058,3.058 0,0 0,0.272 -4.013l-4.337,-5.706ZM9.04,5.982a5.058,5.058 0,0 1,7.603 0.516l4.337,5.706a5.058,5.058 0,0 1,-0.45 6.637l-2.394,2.394c0.002,0.033 0.008,0.08 0.02,0.146 0.055,0.28 0.213,0.697 0.513,1.237 0.592,1.066 1.623,2.41 2.963,3.75 1.34,1.34 2.684,2.37 3.75,2.963 0.54,0.3 0.957,0.458 1.237,0.513 0.065,0.012 0.113,0.018 0.146,0.02l2.394,-2.394a5.058,5.058 0,0 1,6.637 -0.45l5.706,4.337a5.058,5.058 0,0 1,0.516 7.603l-0.87,0.871c-2.988,2.988 -7.381,4.567 -11.554,3.137a39.395,39.395 0,0 1,-15.115 -9.447,39.395 39.395,0 0,1 -9.447,-15.115c-1.43,-4.173 0.15,-8.566 3.137,-11.553l0.87,-0.871 0.708,0.707 -0.707,-0.707ZM26.828,29.862 L26.817,29.864 26.827,29.862ZM18.138,21.172 L18.136,21.183c0,-0.008 0.002,-0.011 0.002,-0.01Z"
|
||||
android:fillColor="#6C7B9D"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
51
app/src/main/res/layout/dsl_banner.xml
Normal file
51
app/src/main/res/layout/dsl_banner.xml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeColor="@color/signal_colorOutline_38"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/banner_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16.57dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorOnSurface"
|
||||
app:layout_constraintBottom_toTopOf="@id/banner_action"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Banner text will go here and probably be about something important" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/banner_action"
|
||||
style="@style/Widget.Signal.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/banner_text"
|
||||
tools:text="Action" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
|
@ -43,6 +43,49 @@
|
|||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_welcomeFragment_to_grantPermissionsFragment"
|
||||
app:destination="@id/grantPermissionsFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/grantPermissionsFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.GrantPermissionsFragment"
|
||||
android:label="fragment_grant_permissions">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_restore"
|
||||
app:destination="@id/restoreBackupFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_skip_restore"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_transfer_or_restore"
|
||||
app:destination="@id/transferOrRestore"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<argument
|
||||
android:name="welcomeAction"
|
||||
app:argType="org.thoughtcrime.securesms.registration.fragments.GrantPermissionsFragment$WelcomeAction" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
|
@ -202,4 +202,5 @@
|
|||
<color name="message_request_bar_background_wallpaper">@color/signal_colorNeutralVariant</color>
|
||||
<color name="message_request_bar_denyForeground_wallpaper">@color/signal_colorError</color>
|
||||
<color name="message_request_bar_acceptForeground_wallpaper">@color/signal_colorNeutralInverse</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -2246,6 +2246,18 @@
|
|||
|
||||
<string name="TurnOffContactJoinedNotificationsActivity__turn_off_contact_joined_signal">Turn off contact joined Signal notifications? You can enable them again in Signal > Settings > Notifications.</string>
|
||||
|
||||
<!-- TurnOnNotificationsBottomSheet -->
|
||||
<!-- Title for sheet explaining how to turn on app notifications -->
|
||||
<string name="TurnOnNotificationsBottomSheet__turn_on_notifications">Turn on notifications</string>
|
||||
<!-- Subtitle for sheet explaining how to turn on app notifications -->
|
||||
<string name="TurnOnNotificationsBottomSheet__to_receive_notifications">To receive notifications for new messages:</string>
|
||||
<!-- Sheet step 1 for sheet explaining how to turn on app notifications-->
|
||||
<string name="TurnOnNotificationsBottomSheet__1_tap_settings_below">1. Tap “Settings” below</string>
|
||||
<!-- Sheet step 2 with placeholder which will be replaced with an image of a toggle for sheet explaining how to turn on app notifications -->
|
||||
<string name="TurnOnNotificationsBottomSheet__2_s_turn_on_notifications">2. %1$s Turn on notifications</string>
|
||||
<!-- Label for button at the bottom of the sheet which opens system app notification settings for sheet explaining how to turn on app notifications -->
|
||||
<string name="TurnOnNotificationsBottomSheet__settings">Settings</string>
|
||||
|
||||
<!-- Notification Channels -->
|
||||
<string name="NotificationChannel_channel_messages">Messages</string>
|
||||
<string name="NotificationChannel_calls">Calls</string>
|
||||
|
@ -3117,6 +3129,32 @@
|
|||
<!-- Alert dialog button to update now -->
|
||||
<string name="PaymentsHomeFragment__update_now">Update now</string>
|
||||
|
||||
<!-- GrantPermissionsFragment -->
|
||||
<!-- Displayed as a text-only action button at the bottom start of the screen -->
|
||||
<string name="GrantPermissionsFragment__not_now">Not now</string>
|
||||
<!-- Displayed as an action button at the bottom end of the screen -->
|
||||
<string name="GrantPermissionsFragment__next">Next</string>
|
||||
<!-- Displayed as a title at the top of the screen -->
|
||||
<string name="GrantPermissionsFragment__allow_permissions">Allow permissions</string>
|
||||
<!-- Displayed as a subtitle at the top of the screen -->
|
||||
<string name="GrantPermissionsFragment__to_help_you_message_people_you_know">To help you message people you know, Signal will request these permissions. </string>
|
||||
<!-- Notifications permission row title -->
|
||||
<string name="GrantPermissionsFragment__notifications">Notifications</string>
|
||||
<!-- Notifications permission row description -->
|
||||
<string name="GrantPermissionsFragment__get_notified_when">Get notified when new messages arrive.</string>
|
||||
<!-- Contacts permission row title -->
|
||||
<string name="GrantPermissionsFragment__contacts">Contacts</string>
|
||||
<!-- Contacts permission row description -->
|
||||
<string name="GrantPermissionsFragment__find_people_you_know">Find people you know. Your contacts are encrypted and not visible to the Signal service.</string>
|
||||
<!-- Phone calls permission row title -->
|
||||
<string name="GrantPermissionsFragment__phone_calls">Phone calls</string>
|
||||
<!-- Phone calls permission row description -->
|
||||
<string name="GrantPermissionsFragment__make_registering_easier">Make registering easier and enable additional calling features.</string>
|
||||
<!-- Storage permission row title -->
|
||||
<string name="GrantPermissionsFragment__storage">Storage</string>
|
||||
<!-- Storage permission row description -->
|
||||
<string name="GrantPermissionsFragment__send_photos_videos_and_files">Send photos, videos and files from your device.</string>
|
||||
|
||||
<!-- PaymentsSecuritySetupFragment -->
|
||||
<!-- Toolbar title -->
|
||||
<string name="PaymentsSecuritySetupFragment__security_setup">Security setup</string>
|
||||
|
@ -4991,6 +5029,10 @@
|
|||
|
||||
<!-- Displayed in a toast when we fail to open the ringtone picker -->
|
||||
<string name="NotificationSettingsFragment__failed_to_open_picker">Failed to open picker.</string>
|
||||
<!-- Banner title when notification permission is disabled -->
|
||||
<string name="NotificationSettingsFragment__to_enable_notifications">To enable notifications, Signal needs permission to display them.</string>
|
||||
<!-- Banner action when notification permission is disabled -->
|
||||
<string name="NotificationSettingsFragment__turn_on">Turn on</string>
|
||||
|
||||
<!-- Description shown for the Signal Release Notes channel -->
|
||||
<string name="ReleaseNotes__signal_release_notes_and_news">Signal Release Notes & News</string>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registration.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Ignore("Causing OOM errors.")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class WelcomePermissionsTest {
|
||||
@Test
|
||||
@Config(sdk = [33])
|
||||
fun givenApi33_whenIGetWelcomePermissions_thenIExpectPostNotifications() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertTrue(Manifest.permission.POST_NOTIFICATIONS in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [23, 26, 29])
|
||||
fun givenApiUnder33_whenIGetWelcomePermissions_thenIExpectNoPostNotifications() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertFalse(Manifest.permission.POST_NOTIFICATIONS in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [23, 26, 29, 33])
|
||||
fun givenAnyApi_whenIGetWelcomePermissions_thenIExpectContacts() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertTrue(Manifest.permission.WRITE_CONTACTS in result)
|
||||
assertTrue(Manifest.permission.READ_CONTACTS in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [23, 26, 29, 33])
|
||||
fun givenAnyApi_whenIGetWelcomePermissions_thenIExpectReadPhoneState() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertTrue(Manifest.permission.READ_PHONE_STATE in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [26, 29, 33])
|
||||
fun givenApi26Plus_whenIGetWelcomePermissions_thenIExpectReadPhoneNumbers() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertTrue(Manifest.permission.READ_PHONE_NUMBERS in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [23])
|
||||
fun givenApiUnder26_whenIGetWelcomePermissions_thenIExpectNoReadPhoneNumbers() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertFalse(Manifest.permission.READ_PHONE_NUMBERS in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [23, 26])
|
||||
fun givenApiUnder29_whenIGetWelcomePermissions_thenIExpectPhoneStorage() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertTrue(Manifest.permission.WRITE_EXTERNAL_STORAGE in result)
|
||||
assertTrue(Manifest.permission.READ_EXTERNAL_STORAGE in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [29, 33])
|
||||
fun givenApi29Plus_whenIGetWelcomePermissionsAndSelectionNotRequired_thenIExpectPhoneStorage() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(false)
|
||||
|
||||
assertTrue(Manifest.permission.WRITE_EXTERNAL_STORAGE in result)
|
||||
assertTrue(Manifest.permission.READ_EXTERNAL_STORAGE in result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [29, 33])
|
||||
fun givenApi29Plus_whenIGetWelcomePermissionsAndSelectionRequired_thenIExpectNoPhoneStorage() {
|
||||
val result = WelcomePermissions.getWelcomePermissions(true)
|
||||
|
||||
assertFalse(Manifest.permission.WRITE_EXTERNAL_STORAGE in result)
|
||||
assertFalse(Manifest.permission.READ_EXTERNAL_STORAGE in result)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
val signalBuildToolsVersion by extra("34.0.0")
|
||||
val signalCompileSdkVersion by extra("android-34")
|
||||
val signalTargetSdkVersion by extra(31)
|
||||
val signalTargetSdkVersion by extra(33)
|
||||
val signalMinSdkVersion by extra(21)
|
||||
val signalJavaVersion by extra(JavaVersion.VERSION_17)
|
||||
val signalKotlinJvmTarget by extra("17")
|
|
@ -172,7 +172,7 @@ dependencyResolutionManagement {
|
|||
testLibs {
|
||||
version('androidx-test', '1.4.0')
|
||||
version('androidx-test-ext-junit', '1.1.1')
|
||||
version('robolectric', '4.8.1')
|
||||
version('robolectric', '4.10.3')
|
||||
|
||||
library('junit-junit', 'junit:junit:4.13.2')
|
||||
library('androidx-test-core', 'androidx.test', 'core').versionRef('androidx-test')
|
||||
|
|
|
@ -65,6 +65,14 @@ public final class WifiDirect {
|
|||
private WifiP2pDnsSdServiceRequest serviceRequest;
|
||||
private final HandlerThread wifiDirectCallbacksHandler;
|
||||
|
||||
public static @NonNull String requiredPermission() {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
return Manifest.permission.NEARBY_WIFI_DEVICES;
|
||||
} else {
|
||||
return Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the ability to use WiFi Direct by checking if the device supports WiFi Direct
|
||||
* and the appropriate permissions have been granted.
|
||||
|
@ -81,9 +89,12 @@ public final class WifiDirect {
|
|||
return AvailableStatus.WIFI_MANAGER_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23 && context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
if (Build.VERSION.SDK_INT >= 33 && context.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.i(TAG, "Nearby Wifi permission required");
|
||||
return AvailableStatus.REQUIRED_PERMISSION_NOT_GRANTED;
|
||||
} else if (Build.VERSION.SDK_INT >= 23 && context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.i(TAG, "Fine location permission required");
|
||||
return AvailableStatus.FINE_LOCATION_PERMISSION_NOT_GRANTED;
|
||||
return AvailableStatus.REQUIRED_PERMISSION_NOT_GRANTED;
|
||||
}
|
||||
|
||||
return Build.VERSION.SDK_INT <= 23 || wifiManager.isP2pSupported() ? AvailableStatus.AVAILABLE
|
||||
|
@ -464,7 +475,7 @@ public final class WifiDirect {
|
|||
public enum AvailableStatus {
|
||||
FEATURE_NOT_AVAILABLE,
|
||||
WIFI_MANAGER_NOT_AVAILABLE,
|
||||
FINE_LOCATION_PERMISSION_NOT_GRANTED,
|
||||
REQUIRED_PERMISSION_NOT_GRANTED,
|
||||
WIFI_DIRECT_NOT_AVAILABLE,
|
||||
AVAILABLE
|
||||
}
|
||||
|
|
|
@ -1538,6 +1538,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="c0754928effe1968c3a9a7b55d1dfc7ceb1e1e7c9f3f09f98afd42431f712492" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="annotation" version="1.0.1">
|
||||
<artifact name="annotation-1.0.1.aar">
|
||||
<sha256 value="c0754928effe1968c3a9a7b55d1dfc7ceb1e1e7c9f3f09f98afd42431f712492" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="core" version="1.4.0">
|
||||
<artifact name="core-1.4.0.aar">
|
||||
<sha256 value="671284e62e393f16ceae1a99a3a9a07bf1aacda29f8fe7b6b884355ef34c09cf" origin="Generated by Gradle"/>
|
||||
|
@ -1558,6 +1563,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="10b1723c436beecb5884c69f8473504bc59611f9463ae549c48b3cf8e73b09c0" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="monitor" version="1.6.1">
|
||||
<artifact name="monitor-1.6.1.aar">
|
||||
<sha256 value="2985ce8556989baf7c84342e7f687713c037a39a922e614d1a3ddf1ca3777079" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="orchestrator" version="1.4.1">
|
||||
<artifact name="orchestrator-1.4.1.apk">
|
||||
<sha256 value="0c7a02b619e4a97ee8ab55983ab19c6559d02aaff55710e6d2c1b8581832d4c1" origin="Generated by Gradle"/>
|
||||
|
@ -1588,6 +1598,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="8845d93979f09fffcc974c0be7d6b6ce4cf4275a4e3ba26bf0f83402e7f0cca5" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test.espresso" name="espresso-idling-resource" version="3.5.1">
|
||||
<artifact name="espresso-idling-resource-3.5.1.aar">
|
||||
<sha256 value="84fb8e2f5eda937771bee28582f5d2cfa61b0e9438d02041ca61b81e3dac3c87" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test.ext" name="junit" version="1.1.1">
|
||||
<artifact name="junit-1.1.1.aar">
|
||||
<sha256 value="449df418d2916a0f86fe7dafb1edb09480fafb6e995d5c751c7d0d1970d4ae72" origin="Generated by Gradle"/>
|
||||
|
@ -2606,6 +2621,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="5ce71656118618731e34a5d4c61aa3a031be23446dc7de8b5a5e77b66ebcd6ef" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.auto.value" name="auto-value-annotations" version="1.10.1">
|
||||
<artifact name="auto-value-annotations-1.10.1.jar">
|
||||
<sha256 value="a4fe0a211925e938a8510d741763ee1171a11bf931f5891ef4d4ee84fca72be2" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.auto.value" name="auto-value-annotations" version="1.6.2">
|
||||
<artifact name="auto-value-annotations-1.6.2.jar">
|
||||
<sha256 value="b48b04ddba40e8ac33bf036f06fc43995fc5084bd94bdaace807ce27d3bea3fb" origin="Generated by Gradle"/>
|
||||
|
@ -2970,6 +2990,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="2b4d8d4e098e86aa5f905ec81c46751d218b16afd3f7fc02b64f80dd20fffa20" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.ibm.icu" name="icu4j" version="72.1">
|
||||
<artifact name="icu4j-72.1.jar">
|
||||
<sha256 value="3df572b240a68d13b5cd778ad2393e885d26411434cd8f098ac5987ea2e64ce3" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.jakewharton.android.repackaged" name="dalvik-dx" version="9.0.0_r3">
|
||||
<artifact name="dalvik-dx-9.0.0_r3.jar">
|
||||
<sha256 value="b29c1c21e52ed6238cd3fed39d880a17ecf2360118604548cea8821be6801e1c" origin="Generated by Gradle"/>
|
||||
|
@ -4229,6 +4254,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="8f3c20e3e2d565d26f33e8d4857a37d0d7f8ac39b62a7026496fcab1bdac30d4" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.bouncycastle" name="bcprov-jdk18on" version="1.72">
|
||||
<artifact name="bcprov-jdk18on-1.72.jar">
|
||||
<sha256 value="39287f2208a753db419f5ca529d6c80f094614aa74d790331126b3c9c6b85fda" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.bouncycastle" name="bcutil-jdk15on" version="1.70">
|
||||
<artifact name="bcutil-jdk15on-1.70.jar">
|
||||
<sha256 value="52dc5551b0257666526c5095424567fed7dc7b00d2b1ba7bd52298411112b1d0" origin="Generated by Gradle"/>
|
||||
|
@ -5051,6 +5081,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="b9d4fe4d71938df38839f0eca42aaaa64cf8b313d678da036f0cb3ca199b47f5" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm" version="9.5">
|
||||
<artifact name="asm-9.5.jar">
|
||||
<sha256 value="b62e84b5980729751b0458c534cf1366f727542bb8d158621335682a460f0353" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-analysis" version="9.1">
|
||||
<artifact name="asm-analysis-9.1.jar">
|
||||
<sha256 value="81a88041b1b8beda5a8a99646098046c48709538270c49def68abff25ac3be34" origin="Generated by Gradle"/>
|
||||
|
@ -5061,11 +5096,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="878fbe521731c072d14d2d65b983b1beae6ad06fda0007b6a8bae81f73f433c4" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-analysis" version="9.5">
|
||||
<artifact name="asm-analysis-9.5.jar">
|
||||
<sha256 value="39f1cf1791335701c3b02cae7b2bc21057ec9a55b2240789cb6d552b2b2c62fa" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-commons" version="9.2">
|
||||
<artifact name="asm-commons-9.2.jar">
|
||||
<sha256 value="be4ce53138a238bb522cd781cf91f3ba5ce2f6ca93ec62d46a162a127225e0a6" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-commons" version="9.5">
|
||||
<artifact name="asm-commons-9.5.jar">
|
||||
<sha256 value="72eee9fbafb9de8d9463f20dd584a48ceeb7e5152ad4c987bfbe17dd4811c9ae" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-tree" version="9.1">
|
||||
<artifact name="asm-tree-9.1.jar">
|
||||
<sha256 value="fd00afa49e9595d7646205b09cecb4a776a8ff0ba06f2d59b8f7bf9c704b4a73" origin="Generated by Gradle"/>
|
||||
|
@ -5076,118 +5121,127 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="aabf9bd23091a4ebfc109c1f3ee7cf3e4b89f6ba2d3f51c5243f16b3cffae011" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-tree" version="9.5">
|
||||
<artifact name="asm-tree-9.5.jar">
|
||||
<sha256 value="3c33a648191079aeaeaeb7c19a49b153952f9e40fe86fbac5205554ddd9acd94" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-util" version="9.2">
|
||||
<artifact name="asm-util-9.2.jar">
|
||||
<sha256 value="ff5b3cd331ae8a9a804768280da98f50f424fef23dd3c788bb320e08c94ee598" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.ow2.asm" name="asm-util" version="9.5">
|
||||
<artifact name="asm-util-9.5.jar">
|
||||
<sha256 value="c467f1bb3c08888f47243e2d475209b34a772d627e44fca06752e18bb038bd74" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.reactivestreams" name="reactive-streams" version="1.0.3">
|
||||
<artifact name="reactive-streams-1.0.3.jar">
|
||||
<sha256 value="1dee0481072d19c929b623e155e14d2f6085dc011529a0a0dbefc84cf571d865" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="annotations" version="4.8.1">
|
||||
<artifact name="annotations-4.8.1.jar">
|
||||
<sha256 value="d3924abb4c9f7c20df582e4553ac13203bb40b7c367bf5bf776a9acbb252e181" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="annotations" version="4.10.3">
|
||||
<artifact name="annotations-4.10.3.jar">
|
||||
<sha256 value="f3d6b921b7bf9d541577414c3b3124293eb09ced71f939e0c325c8d8abad0b6f" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="annotations-4.8.1.module">
|
||||
<sha256 value="a5e9f2ee5794c9ac22921e12646869025540924abfeacc5f39152dcee5b4a628" origin="Generated by Gradle"/>
|
||||
<artifact name="annotations-4.10.3.module">
|
||||
<sha256 value="a8a2a9b49333cba29c6598fdd18f111c562e5c2c37a7e92826ae0093e812bb7d" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="junit" version="4.8.1">
|
||||
<artifact name="junit-4.8.1.jar">
|
||||
<sha256 value="1475e1a271dce425d95ef53e837c07542023d718b0a320a9af6b879fa26c49ac" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="junit" version="4.10.3">
|
||||
<artifact name="junit-4.10.3.jar">
|
||||
<sha256 value="815f0bae88eb198889e1878ef65b904c4ec59131be2458829bcc942bd7b9f6da" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="junit-4.8.1.module">
|
||||
<sha256 value="e4b95ac9e273333dfa3938e50790fc5d3ba8bfe78f38ad61376b1baf6fa50b70" origin="Generated by Gradle"/>
|
||||
<artifact name="junit-4.10.3.module">
|
||||
<sha256 value="fc951c7170ebe9b8da140bffdb3822c158ef4ace7b21d1e81d8752cd7ed92cda" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="nativeruntime" version="4.8.1">
|
||||
<artifact name="nativeruntime-4.8.1.jar">
|
||||
<sha256 value="62e3e2de0e205a5a9a017f71de661d9529ac1a1f6bf4014609493a0ec95178f8" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="nativeruntime" version="4.10.3">
|
||||
<artifact name="nativeruntime-4.10.3.jar">
|
||||
<sha256 value="71fd2d1e8e78f2d70cc4879f5aa6910bf05a68274d3ca87179fb6f9447db5fb9" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="nativeruntime-4.8.1.module">
|
||||
<sha256 value="796f67b8d2a5a93dd9917034089a8dfae1eb96c59e61223ac319a298475022c8" origin="Generated by Gradle"/>
|
||||
<artifact name="nativeruntime-4.10.3.module">
|
||||
<sha256 value="989c43aacf186fc92edd59ee7be4d562a658130b7d3bfba3df9a5115a14f105e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="pluginapi" version="4.8.1">
|
||||
<artifact name="pluginapi-4.8.1.jar">
|
||||
<sha256 value="bea89e22d02b946563cf320312bb3eb479f8f31aa9cf96f65ce35c2b80cd02bc" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="pluginapi-4.8.1.module">
|
||||
<sha256 value="d2580abc08a992fe0256e209305dacdcc8e523d2c8d2ff0ddc7b25eaa7a39ac3" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="nativeruntime-dist-compat" version="1.0.1">
|
||||
<artifact name="nativeruntime-dist-compat-1.0.1.jar">
|
||||
<sha256 value="2dd7aae2332b8f57932e1ef78fb8d973aac1da631ec9fb471752280df50d140c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="plugins-maven-dependency-resolver" version="4.8.1">
|
||||
<artifact name="plugins-maven-dependency-resolver-4.8.1.jar">
|
||||
<sha256 value="dc87b3d6c55f5a6691246038ef4ba53311f406c784c8d9469d1f1112f885f72d" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="pluginapi" version="4.10.3">
|
||||
<artifact name="pluginapi-4.10.3.jar">
|
||||
<sha256 value="56be2717854add52e3437bb3be1b898dfea8ce8c6fcd26c4d0de68bf605274b0" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="plugins-maven-dependency-resolver-4.8.1.module">
|
||||
<sha256 value="e47aca957baeb036487497d52242c2fc5300435e6d31a106773baa9fae85b302" origin="Generated by Gradle"/>
|
||||
<artifact name="pluginapi-4.10.3.module">
|
||||
<sha256 value="2581c1cdc15888ce414f719763a8c784035189833f28debe049f9d686d1aeb0e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="resources" version="4.8.1">
|
||||
<artifact name="resources-4.8.1.jar">
|
||||
<sha256 value="fd15dedf3721e46412c23c238a0b905166fbfba65628db546b1c121546d4712a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="resources-4.8.1.module">
|
||||
<sha256 value="e8625ecc2f8c4476bc0a67bdf41824fd8a4dd871246d2eaa06a5cf01ea8d89c2" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="plugins-maven-dependency-resolver" version="4.10.3">
|
||||
<artifact name="plugins-maven-dependency-resolver-4.10.3.jar">
|
||||
<sha256 value="54618c67214824dd5ebd72c5ed9c56fb62b776902455d0b0efc0e0940d8ebcf6" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="robolectric" version="4.8.1">
|
||||
<artifact name="robolectric-4.8.1.jar">
|
||||
<sha256 value="c17a5358ca9f1adb5d182bda0cf276efa01a0cc1a7ba456325ecffd6e9c70fef" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="resources" version="4.10.3">
|
||||
<artifact name="resources-4.10.3.jar">
|
||||
<sha256 value="8decd0518e147c1038d38f6d33632e3310886194d7a8afeeb62849495f36e5f7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="robolectric-4.8.1.module">
|
||||
<sha256 value="34b34fdcf60982097fcad301036e379378a1c3d2cced824318fa43ed0b2fbcc3" origin="Generated by Gradle"/>
|
||||
<artifact name="resources-4.10.3.module">
|
||||
<sha256 value="723823f373052ed4cb4c660039880f21ca88dc731260333bb8c7f01eb95f7039" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="sandbox" version="4.8.1">
|
||||
<artifact name="sandbox-4.8.1.jar">
|
||||
<sha256 value="ec132754fbd4a9b0c6658a2d3b12a2c6c2f2f56dfbd4ab1bd429441853697a41" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="robolectric" version="4.10.3">
|
||||
<artifact name="robolectric-4.10.3.jar">
|
||||
<sha256 value="e61c4733bd64f57ba9884bf232b293fdd19b233608dd3481cd0e3c99f0f7c0fc" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="sandbox-4.8.1.module">
|
||||
<sha256 value="09f45b4a6240134d8859245017b8d18f654dc1e0e56605b4840cf49dbd01f8ee" origin="Generated by Gradle"/>
|
||||
<artifact name="robolectric-4.10.3.module">
|
||||
<sha256 value="f4491fc2ede8245609502e1bf6f41c0eaad532eec16c568e63091583fb30ece0" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="shadowapi" version="4.8.1">
|
||||
<artifact name="shadowapi-4.8.1.jar">
|
||||
<sha256 value="48ea81a61603d316f971a1c59f74b66d5914a8a5f96f657c8a57a3a5104455fb" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="sandbox" version="4.10.3">
|
||||
<artifact name="sandbox-4.10.3.jar">
|
||||
<sha256 value="59611ce3f110f21d464003a7a812dc8155f4132173cb13cf4e246da496cf17d0" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="shadowapi-4.8.1.module">
|
||||
<sha256 value="ecc1e0e90857cb1dcde67b0ca27cf3cbc7be84a09c4a6dd107b45b40588b7b9c" origin="Generated by Gradle"/>
|
||||
<artifact name="sandbox-4.10.3.module">
|
||||
<sha256 value="3a2c82c681484a66288016006ec30dca02c48cc7a9855ca5ece8538fbf062c75" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="shadows-framework" version="4.8.1">
|
||||
<artifact name="shadows-framework-4.8.1.jar">
|
||||
<sha256 value="5fda3468ab58877ade2fab5c0a954a947a7b89d67c4e9eeda939eb8a91e1bf34" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="shadowapi" version="4.10.3">
|
||||
<artifact name="shadowapi-4.10.3.jar">
|
||||
<sha256 value="1ba648a76968f1bb9f4fc64321af70c4eeed94b2a3fa1b2a848a7706ec25c75a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="shadows-framework-4.8.1.module">
|
||||
<sha256 value="13e1f7e83dec909ef0f08130b4433e47a15821ccb8af1dad51fd344103ba7cfc" origin="Generated by Gradle"/>
|
||||
<artifact name="shadowapi-4.10.3.module">
|
||||
<sha256 value="09790eeb56f1a2ffa997fb3373d0b62ca763487e9540d7ae83198ab999f793ee" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="shadows-multidex" version="4.8.1">
|
||||
<artifact name="shadows-multidex-4.8.1.jar">
|
||||
<sha256 value="010216f7e8b05598e2b15c1091afc93e8e23f4258e87413ef28f38831afc352b" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="shadows-framework" version="4.10.3">
|
||||
<artifact name="shadows-framework-4.10.3.jar">
|
||||
<sha256 value="106f6a19abc9d5786a18461a2554afbf782a30e799f867d7f1a9f26bcbb873a7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="shadows-multidex-4.8.1.module">
|
||||
<sha256 value="fbc182dca3e86887e055dff179eaaf68caf04f9c7532b29cdaa85b52b851be5a" origin="Generated by Gradle"/>
|
||||
<artifact name="shadows-framework-4.10.3.module">
|
||||
<sha256 value="126f485b5f1570021ab79a2b819bf44a2f64405bd248f7e819236f13728b9ce7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="utils" version="4.8.1">
|
||||
<artifact name="utils-4.8.1.jar">
|
||||
<sha256 value="c40afc0a140aada6830b3c2f7a331ad96d8e2f2a905458b6624b8fb12f7552d4" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="shadows-multidex" version="4.10.3">
|
||||
<artifact name="shadows-multidex-4.10.3.jar">
|
||||
<sha256 value="34d1da27044528c07ca43c6334af3890868f2570d48114459c25ec4331ea6966" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="utils-4.8.1.module">
|
||||
<sha256 value="816c8a178a9346c9e7a9ecf3081f81df42ecb5e7b77ded0e36c6944635079aba" origin="Generated by Gradle"/>
|
||||
<artifact name="shadows-multidex-4.10.3.module">
|
||||
<sha256 value="ad1276a3820bbe37672624ee412b14bf35660604fb4802ac9b2ce8de494b72ff" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.robolectric" name="utils-reflector" version="4.8.1">
|
||||
<artifact name="utils-reflector-4.8.1.jar">
|
||||
<sha256 value="d17357e5254e1e14ee258fc1604bbe011aa425fe5e7c768a04fd043476e41267" origin="Generated by Gradle"/>
|
||||
<component group="org.robolectric" name="utils" version="4.10.3">
|
||||
<artifact name="utils-4.10.3.jar">
|
||||
<sha256 value="0081b1a65c2c6d7cf56a56f6b4ed85b35a91f5a9f40a4b81c6771b497265518e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="utils-reflector-4.8.1.module">
|
||||
<sha256 value="5c319c569301724ed105416a0246f88ba186a04ec54c4d136aca468bc694b664" origin="Generated by Gradle"/>
|
||||
</component>
|
||||
<component group="org.robolectric" name="utils-reflector" version="4.10.3">
|
||||
<artifact name="utils-reflector-4.10.3.jar">
|
||||
<sha256 value="fcd2dde7623a5b47caa7efcbdf7dd2a95429e640b42490db5bc645367f0a0e1a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="utils-reflector-4.10.3.module">
|
||||
<sha256 value="6afe78a35a9ce7f2b65f22b605a028b5a6ed43083a3c76a299f37a648ca3cf25" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.signal" name="aesgcmprovider" version="0.0.3">
|
||||
|
|
Loading…
Add table
Reference in a new issue