Update gallery permission UI

This commit is contained in:
mtang-signal 2024-05-01 16:38:26 -04:00 committed by Alex Hart
parent 3c380d35fd
commit c95b180728
10 changed files with 156 additions and 16 deletions

View file

@ -29,7 +29,6 @@ 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
@ -71,13 +70,7 @@ class ConversationActivityResultContracts(private val fragment: Fragment, privat
}
fun launchGallery(recipientId: RecipientId, text: CharSequence?, isReply: Boolean) {
Permissions
.with(fragment)
.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)) }
.execute()
mediaGalleryLauncher.launch(MediaSelectionInput(emptyList(), recipientId, text, isReply))
}
fun launchCamera(recipientId: RecipientId, isReply: Boolean) {

View file

@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.conversation.v2.keyboard
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
@ -94,8 +95,10 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
override fun onAttachmentPermissionsRequested() {
Permissions.with(requireParentFragment())
.request(*PermissionCompat.forImagesAndVideos())
.ifNecessary()
.onAllGranted { viewModel.refreshRecentMedia() }
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio), null, R.string.AttachmentManager_signal_allow_storage, R.string.AttachmentManager_signal_to_show_photos, parentFragmentManager)
.onAnyDenied { Toast.makeText(requireContext(), R.string.AttachmentManager_signal_needs_storage_access, Toast.LENGTH_LONG).show() }
.execute()
}

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.mediasend.v2.gallery
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.WindowInsetsCompat
@ -18,6 +19,8 @@ import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.databinding.V2MediaGalleryFragmentBinding
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaRepository
import org.thoughtcrime.securesms.permissions.PermissionCompat
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.ViewUtil
@ -39,6 +42,7 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
private lateinit var callbacks: Callbacks
private var selectedMediaTouchHelper: ItemTouchHelper? = null
private var shouldEnableScrolling: Boolean = true
private val galleryAdapter = MappingAdapter()
private val selectedAdapter = MappingAdapter()
@ -65,6 +69,10 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
height = ViewUtil.getStatusBarHeight(view)
}
binding.mediaGalleryGrid.layoutManager = object : GridLayoutManager(requireContext(), 4) {
override fun canScrollVertically() = shouldEnableScrolling
}
(binding.mediaGalleryGrid.layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val isFolder: Boolean = (binding.mediaGalleryGrid.adapter as MappingAdapter).getModel(position).map { it is MediaGallerySelectableItem.FolderModel }.orElse(false)
@ -143,6 +151,8 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
binding.mediaGalleryToolbar.title = state.bucketTitle ?: requireContext().getString(R.string.AttachmentKeyboard_gallery)
}
binding.mediaGalleryAllowAccess.setOnClickListener { requestRequiredPermissions() }
val galleryItemsWithSelection = LiveDataUtil.combineLatest(
viewModel.state.map { it.items },
viewStateLiveData.map { it.selectedMedia }
@ -157,12 +167,34 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
}
galleryItemsWithSelection.observe(viewLifecycleOwner) {
galleryAdapter.submitList(it)
if (!Permissions.hasAll(requireContext(), *PermissionCompat.forImagesAndVideos())) {
binding.mediaGalleryMissingPermissions.visibility = View.VISIBLE
shouldEnableScrolling = false
galleryAdapter.submitList((1..100).map { MediaGallerySelectableItem.PlaceholderModel() })
} else {
binding.mediaGalleryMissingPermissions.visibility = View.GONE
shouldEnableScrolling = true
galleryAdapter.submitList(it)
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback)
}
private fun refreshMediaGallery() {
viewModel.refreshMediaGallery()
}
private fun requestRequiredPermissions() {
Permissions.with(requireParentFragment())
.request(*PermissionCompat.forImagesAndVideos())
.ifNecessary()
.onAllGranted { refreshMediaGallery() }
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio), null, R.string.AttachmentManager_signal_allow_storage, R.string.AttachmentManager_signal_to_show_photos, parentFragmentManager)
.onAnyDenied { Toast.makeText(requireContext(), R.string.AttachmentManager_signal_needs_storage_access, Toast.LENGTH_LONG).show() }
.execute()
}
fun onBack() {
if (viewModel.pop()) {
onBackPressedCallback.isEnabled = false

View file

@ -43,6 +43,16 @@ object MediaGallerySelectableItem {
) {
mappingAdapter.registerFactory(FolderModel::class.java, LayoutFactory({ FolderViewHolder(it, onMediaFolderClicked) }, R.layout.v2_media_gallery_folder_item))
mappingAdapter.registerFactory(FileModel::class.java, LayoutFactory({ FileViewHolder(it, onMediaClicked) }, if (isMultiselectEnabled) R.layout.v2_media_gallery_item else R.layout.v2_media_gallery_item_no_check))
mappingAdapter.registerFactory(PlaceholderModel::class.java, LayoutFactory({ PlaceholderViewHolder(it) }, R.layout.v2_media_gallery_placeholder_item))
}
class PlaceholderViewHolder(itemView: View) : BaseViewHolder<PlaceholderModel>(itemView) {
override fun bind(model: PlaceholderModel) = Unit
}
class PlaceholderModel : MappingModel<PlaceholderModel> {
override fun areItemsTheSame(newItem: PlaceholderModel): Boolean = true
override fun areContentsTheSame(newItem: PlaceholderModel): Boolean = true
}
class FolderModel(val mediaFolder: MediaFolder) : MappingModel<FolderModel> {
@ -58,7 +68,7 @@ object MediaGallerySelectableItem {
abstract class BaseViewHolder<T : MappingModel<T>>(itemView: View) : MappingViewHolder<T>(itemView) {
protected val imageView: ShapeableImageView = itemView.findViewById(R.id.media_gallery_image)
protected val playOverlay: ImageView = itemView.findViewById(R.id.media_gallery_play_overlay)
protected val playOverlay: ImageView? = itemView.findViewById(R.id.media_gallery_play_overlay)
protected val checkView: TextView? = itemView.findViewById(R.id.media_gallery_check)
protected val title: TextView? = itemView.findViewById(R.id.media_gallery_title)
}
@ -69,7 +79,7 @@ object MediaGallerySelectableItem {
.load(DecryptableStreamUriLoader.DecryptableUri(model.mediaFolder.thumbnailUri))
.into(imageView)
playOverlay.visible = false
playOverlay?.visible = false
itemView.setOnClickListener { onMediaFolderClicked(model.mediaFolder) }
title?.text = model.mediaFolder.title
title?.visible = true
@ -105,7 +115,7 @@ object MediaGallerySelectableItem {
checkView?.visible = model.isSelected
checkView?.text = "${model.selectionOneBasedIndex}"
itemView.setOnClickListener { onMediaClicked(model.media, model.isSelected) }
playOverlay.visible = MediaUtil.isVideo(model.media.mimeType) && !model.media.isVideoGif
playOverlay?.visible = MediaUtil.isVideo(model.media.mimeType) && !model.media.isVideoGif
title?.visible = false
if (PAYLOAD_INDEX_CHANGED in payload) {

View file

@ -29,6 +29,10 @@ class MediaGalleryViewModel(bucketId: String?, bucketTitle: String?, private val
loadItemsForBucket(mediaFolder.bucketId, mediaFolder.title)
}
fun refreshMediaGallery() {
loadItemsForBucket(null, null)
}
private fun loadItemsForBucket(bucketId: String?, bucketTitle: String?) {
if (bucketId == null) {
repository.getFolders { folders ->

View file

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="73dp"
android:viewportWidth="72"
android:viewportHeight="73">
<path
android:pathData="M6.066,26.66C5.518,22.495 5.244,20.413 5.845,18.716C6.374,17.223 7.361,15.935 8.667,15.038C10.151,14.018 12.233,13.744 16.397,13.196L41.679,9.868C45.844,9.319 47.926,9.045 49.623,9.646C51.116,10.175 52.403,11.163 53.301,12.468C54.32,13.952 54.595,16.034 55.143,20.199L57.101,35.07C57.649,39.235 57.923,41.317 57.322,43.014C56.793,44.507 55.806,45.795 54.5,46.692C53.016,47.712 50.934,47.986 46.77,48.534L21.488,51.862C17.323,52.411 15.241,52.685 13.544,52.084C12.051,51.555 10.764,50.567 9.867,49.262C8.847,47.778 8.573,45.696 8.024,41.531L6.066,26.66Z"
android:fillColor="#DDE7FF"/>
<path
android:pathData="M16.136,11.717L41.548,8.372C43.575,8.105 45.188,7.892 46.502,7.828C47.848,7.761 49.014,7.839 50.124,8.232C51.915,8.866 53.46,10.052 54.537,11.618C55.203,12.588 55.581,13.694 55.865,15.012C56.142,16.298 56.354,17.91 56.621,19.938L58.596,34.939C58.863,36.966 59.076,38.578 59.14,39.893C59.207,41.239 59.129,42.405 58.736,43.515C58.101,45.306 56.916,46.851 55.35,47.928C54.38,48.594 53.274,48.972 51.956,49.256C50.67,49.533 49.057,49.745 47.03,50.012L21.618,53.358C19.591,53.625 17.979,53.837 16.665,53.902C15.318,53.968 14.153,53.89 13.043,53.497C11.251,52.863 9.707,51.678 8.63,50.111C7.963,49.141 7.586,48.035 7.302,46.718C7.025,45.431 6.812,43.819 6.545,41.792L4.57,26.79C4.304,24.763 4.091,23.151 4.026,21.836C3.96,20.49 4.038,19.324 4.431,18.215C5.065,16.423 6.251,14.878 7.817,13.802C8.787,13.135 9.893,12.758 11.21,12.474C12.497,12.196 14.109,11.984 16.136,11.717ZM11.843,15.406C10.707,15.651 10.03,15.921 9.516,16.274C8.472,16.992 7.682,18.022 7.259,19.216C7.051,19.804 6.965,20.528 7.023,21.688C7.081,22.866 7.276,24.357 7.553,26.464L9.511,41.335C9.789,43.442 9.986,44.933 10.235,46.085C10.479,47.221 10.749,47.898 11.102,48.412C11.82,49.456 12.85,50.246 14.044,50.669C14.632,50.878 15.356,50.963 16.517,50.906C17.694,50.847 19.185,50.652 21.292,50.375L46.574,47.047C48.681,46.769 50.171,46.572 51.324,46.323C52.459,46.078 53.137,45.808 53.651,45.455C54.695,44.738 55.485,43.708 55.908,42.513C56.116,41.926 56.201,41.202 56.144,40.041C56.086,38.863 55.891,37.373 55.613,35.266L53.655,20.394C53.378,18.287 53.181,16.797 52.932,15.644C52.687,14.509 52.417,13.831 52.064,13.317C51.347,12.273 50.317,11.483 49.122,11.06C48.535,10.852 47.811,10.767 46.65,10.824C45.472,10.882 43.982,11.077 41.875,11.354L16.593,14.683C14.486,14.96 12.996,15.158 11.843,15.406Z"
android:fillColor="#6C7B9D"
android:fillType="evenOdd"/>
<path
android:pathData="M54,23.301C58.2,23.301 60.301,23.301 61.905,24.118C63.316,24.837 64.464,25.985 65.183,27.396C66,29 66,31.1 66,35.301V50.301C66,54.501 66,56.601 65.183,58.206C64.464,59.617 63.316,60.764 61.905,61.483C60.301,62.301 58.2,62.301 54,62.301H28.5C24.3,62.301 22.199,62.301 20.595,61.483C19.184,60.764 18.037,59.617 17.317,58.206C16.5,56.601 16.5,54.501 16.5,50.301L16.5,35.301C16.5,31.1 16.5,29 17.317,27.396C18.037,25.985 19.184,24.837 20.595,24.118C22.199,23.301 24.3,23.301 28.5,23.301L54,23.301Z"
android:fillColor="#FCAF68"/>
<path
android:pathData="M63.064,24.85C63.947,25.526 64.672,26.395 65.183,27.396C66,29 66,31.1 66,35.301V50.301C66,54.501 66,56.601 65.183,58.206C64.464,59.617 63.316,60.764 61.905,61.483C60.301,62.301 58.2,62.301 54,62.301H28.5C24.3,62.301 22.199,62.301 20.595,61.483C19.184,60.764 18.037,59.617 17.317,58.206C16.5,56.601 16.5,54.501 16.5,50.301V35.386C29.52,28.623 44.314,24.801 60,24.801C61.025,24.801 62.047,24.817 63.064,24.85Z"
android:fillColor="#FFCF71"/>
<path
android:pathData="M65.999,34.008C66,34.416 66,34.846 66,35.301V50.301C66,54.501 66,56.601 65.183,58.206C64.464,59.617 63.316,60.764 61.905,61.483C60.301,62.301 58.2,62.301 54,62.301L28.5,62.301C24.3,62.301 22.199,62.301 20.595,61.483C19.184,60.764 18.037,59.617 17.317,58.206C16.5,56.601 16.5,54.501 16.5,50.301V45.678C29.244,38.132 44.116,33.801 60,33.801C62.017,33.801 64.017,33.871 65.999,34.008Z"
android:fillColor="#FFE3A5"/>
<path
android:pathData="M28.5,62.301H54.19C58.264,62.3 60.325,62.288 61.905,61.483C63.316,60.764 64.463,59.617 65.182,58.206C65.919,56.759 65.992,54.91 65.999,51.482L54.979,40.128C53.211,38.306 50.288,38.306 48.521,40.128L38.182,50.78L33.624,46.14C31.834,44.317 28.887,44.349 27.136,46.21L16.897,57.094C17.005,57.498 17.143,57.863 17.317,58.206C18.036,59.617 19.184,60.764 20.595,61.483C22.199,62.301 24.299,62.301 28.5,62.301Z"
android:fillColor="#8997B5"/>
<path
android:pathData="M17.536,58.604C17.459,58.474 17.386,58.341 17.317,58.206C16.908,57.402 16.704,56.474 16.602,55.22L26.044,45.182C28.378,42.701 32.307,42.659 34.694,45.089L38.175,48.633L47.444,39.083C49.801,36.655 53.698,36.655 56.055,39.083L66,49.329V50.301C66,51.586 66,52.674 65.977,53.612L53.902,41.172C52.724,39.958 50.775,39.958 49.597,41.172L29.09,62.301H28.5C27.966,62.301 27.467,62.301 26.998,62.299L25.923,61.256L36.085,50.786L32.554,47.191C31.36,45.976 29.396,45.997 28.229,47.238L17.536,58.604Z"
android:fillColor="#4C5876"/>
<path
android:pathData="M67.5,35.235L67.5,50.366C67.5,52.411 67.5,54.037 67.393,55.349C67.283,56.692 67.053,57.838 66.519,58.887C65.656,60.58 64.279,61.957 62.586,62.82C61.537,63.354 60.391,63.584 59.048,63.694C57.736,63.801 56.11,63.801 54.066,63.801L28.434,63.801C26.39,63.801 24.764,63.801 23.452,63.694C22.108,63.584 20.963,63.354 19.914,62.82C18.221,61.957 16.844,60.58 15.981,58.887C15.446,57.838 15.217,56.692 15.107,55.349C15,54.037 15,52.411 15,50.367L15,35.235C15,33.191 15,31.565 15.107,30.253C15.217,28.909 15.446,27.764 15.981,26.715C16.844,25.021 18.221,23.645 19.914,22.782C20.963,22.247 22.108,22.018 23.452,21.908C24.764,21.801 26.39,21.801 28.434,21.801L54.066,21.801C56.11,21.801 57.736,21.801 59.048,21.908C60.391,22.018 61.537,22.247 62.586,22.782C64.279,23.645 65.656,25.021 66.519,26.715C67.053,27.764 67.283,28.909 67.393,30.253C67.5,31.565 67.5,33.191 67.5,35.235ZM64.403,30.497C64.308,29.339 64.129,28.632 63.846,28.077C63.271,26.948 62.353,26.03 61.224,25.455C60.668,25.172 59.962,24.993 58.804,24.898C57.628,24.802 56.125,24.801 54,24.801L28.5,24.801C26.375,24.801 24.872,24.802 23.696,24.898C22.538,24.993 21.831,25.172 21.276,25.455C20.147,26.03 19.229,26.948 18.654,28.077C18.371,28.632 18.192,29.339 18.097,30.497C18.001,31.672 18,33.176 18,35.301L18,50.301C18,52.426 18.001,53.929 18.097,55.105C18.192,56.263 18.371,56.969 18.654,57.525C19.229,58.654 20.147,59.572 21.276,60.147C21.831,60.43 22.538,60.609 23.696,60.704C24.872,60.8 26.375,60.801 28.5,60.801L54,60.801C56.125,60.801 57.628,60.8 58.804,60.704C59.962,60.609 60.668,60.43 61.224,60.147C62.353,59.572 63.271,58.654 63.846,57.525C64.129,56.969 64.308,56.263 64.403,55.105C64.499,53.929 64.5,52.426 64.5,50.301L64.5,35.301C64.5,33.176 64.499,31.672 64.403,30.497Z"
android:fillColor="#4F5C7D"
android:fillType="evenOdd"/>
</vector>

View file

@ -57,9 +57,11 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/attachment_keyboard_permission_button"
style="@style/Signal.Widget.Button.Large.Primary"
app:backgroundTint="@color/signal_colorPrimaryContainer"
android:textColor="@color/signal_colorOnPrimaryContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/AttachmentKeyboard_give_access"
android:text="@string/AttachmentKeyboard_allow_access"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/attachment_keyboard_button_list"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -34,6 +34,44 @@
tools:itemCount="36"
tools:listitem="@layout/v2_media_gallery_item" />
<LinearLayout
android:id="@+id/media_gallery_missing_permissions"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/media_gallery_bottom_bar_barrier"
app:layout_constraintTop_toBottomOf="@id/media_gallery_toolbar" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/permission_gallery" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:gravity="center"
android:layout_marginTop="20dp"
android:layout_marginHorizontal="30dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/media_gallery_allow_access"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Signal.Widget.Button.Large.Primary"
app:backgroundTint="@color/signal_colorPrimaryContainer"
android:textColor="@color/signal_colorOnPrimaryContainer"
android:layout_marginTop="20dp"
android:text="@string/AttachmentKeyboard_allow_access" />
</LinearLayout>
<View
android:id="@+id/bottom_bar_shade"
android:layout_width="match_parent"

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/signal_colorSurfaceVariant">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/media_gallery_image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -80,8 +80,9 @@
<string name="AttachmentKeyboard_file">File</string>
<string name="AttachmentKeyboard_contact">Contact</string>
<string name="AttachmentKeyboard_location">Location</string>
<string name="AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos">Signal needs permission to show your photos and videos.</string>
<string name="AttachmentKeyboard_give_access">Give Access</string>
<string name="AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos">Signal needs permission to show your photos and videos</string>
<!-- Text for a button prompting users to allow Signal access to their gallery storage -->
<string name="AttachmentKeyboard_allow_access">Allow Access</string>
<string name="AttachmentKeyboard_payment">Payment</string>
<!-- AttachmentManager -->
@ -98,6 +99,12 @@
<string name="AttachmentManager_signal_allow_signal_access_location">Allow Signal access to send your location.</string>
<!-- Toast text explaining Signal\'s need for location access -->
<string name="AttachmentManager_signal_needs_location_access">Signal needs location access to send your location.</string>
<!-- Dialog title asking users for gallery storage permission -->
<string name="AttachmentManager_signal_allow_storage">Allow access to storage</string>
<!-- Dialog description that will explain the steps needed to give gallery storage permission -->
<string name="AttachmentManager_signal_to_show_photos">To show photos and videos:</string>
<!-- Toast text explaining Signal's need for storage access -->
<string name="AttachmentManager_signal_needs_storage_access">Signal needs storage access to show your photos and videos.</string>
<!-- Alert dialog title to show the recipient has not activated payments -->
<string name="AttachmentManager__not_activated_payments">%1$s hasn\'t activated Payments </string>