Strings updates

Latest strings
Removed the LEGACY disappearing messages
New title for Share activity (also formatted the lines)
This commit is contained in:
ThomasSession 2024-09-04 10:15:58 +10:00 committed by fanchao
parent ffbb7d8a1b
commit 2f4c605613
13 changed files with 306 additions and 412 deletions

View File

@ -17,6 +17,8 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -29,14 +31,21 @@ import android.provider.OpenableColumns;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import com.squareup.phrase.Phrase;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.DistributionTypes; import org.session.libsession.utilities.DistributionTypes;
import org.session.libsession.utilities.ViewUtil; import org.session.libsession.utilities.ViewUtil;
@ -57,261 +66,261 @@ import org.thoughtcrime.securesms.util.MediaUtil;
* @author Jake McGinty * @author Jake McGinty
*/ */
public class ShareActivity extends PassphraseRequiredActionBarActivity public class ShareActivity extends PassphraseRequiredActionBarActivity
implements ContactSelectionListFragment.OnContactSelectedListener implements ContactSelectionListFragment.OnContactSelectedListener {
{ private static final String TAG = ShareActivity.class.getSimpleName();
private static final String TAG = ShareActivity.class.getSimpleName();
public static final String EXTRA_THREAD_ID = "thread_id"; public static final String EXTRA_THREAD_ID = "thread_id";
public static final String EXTRA_ADDRESS_MARSHALLED = "address_marshalled"; public static final String EXTRA_ADDRESS_MARSHALLED = "address_marshalled";
public static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type"; public static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type";
private ContactSelectionListFragment contactsFragment; private ContactSelectionListFragment contactsFragment;
private SearchToolbar searchToolbar; private SearchToolbar searchToolbar;
private ImageView searchAction; private ImageView searchAction;
private View progressWheel; private View progressWheel;
private Uri resolvedExtra; private Uri resolvedExtra;
private CharSequence resolvedPlaintext; private CharSequence resolvedPlaintext;
private String mimeType; private String mimeType;
private boolean isPassingAlongMedia; private boolean isPassingAlongMedia;
@Override @Override
protected void onCreate(Bundle icicle, boolean ready) { protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_ALL); getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_ALL);
}
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
setContentView(R.layout.share_activity);
// initializeToolbar();
initializeResources();
initializeSearch();
initializeMedia();
}
@Override
protected void onNewIntent(Intent intent) {
Log.i(TAG, "onNewIntent()");
super.onNewIntent(intent);
setIntent(intent);
initializeMedia();
}
@Override
public void onPause() {
super.onPause();
if (!isPassingAlongMedia && resolvedExtra != null) {
BlobProvider.getInstance().delete(this, resolvedExtra);
if (!isFinishing()) {
finish();
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (searchToolbar.isVisible()) searchToolbar.collapse();
else super.onBackPressed();
}
/* private void initializeToolbar() {
SearchToolbar toolbar = findViewById(R.id.search_toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}*/
private void initializeResources() {
progressWheel = findViewById(R.id.progress_wheel);
searchToolbar = findViewById(R.id.search_toolbar);
searchAction = findViewById(R.id.search_action);
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(this);
}
private void initializeSearch() {
searchAction.setOnClickListener(v -> searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
searchAction.getY() + (searchAction.getHeight() / 2)));
searchToolbar.setListener(new SearchToolbar.SearchListener() {
@Override
public void onSearchTextChange(String text) {
if (contactsFragment != null) {
contactsFragment.setQueryFilter(text);
} }
}
@Override getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
public void onSearchClosed() {
if (contactsFragment != null) {
contactsFragment.resetQueryFilter();
}
}
});
}
private void initializeMedia() { setContentView(R.layout.share_activity);
final Context context = this;
isPassingAlongMedia = false;
Uri streamExtra = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); initializeToolbar();
CharSequence charSequenceExtra = getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT); initializeResources();
mimeType = getMimeType(streamExtra); initializeSearch();
initializeMedia();
if (streamExtra != null && PartAuthority.isLocalUri(streamExtra)) {
isPassingAlongMedia = true;
resolvedExtra = streamExtra;
handleResolvedMedia(getIntent(), false);
} else if (charSequenceExtra != null && mimeType != null && mimeType.startsWith("text/")) {
resolvedPlaintext = charSequenceExtra;
handleResolvedMedia(getIntent(), false);
} else {
contactsFragment.getView().setVisibility(View.GONE);
progressWheel.setVisibility(View.VISIBLE);
new ResolveMediaTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, streamExtra);
}
}
private void handleResolvedMedia(Intent intent, boolean animate) {
long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1);
int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1);
Address address = null;
if (intent.hasExtra(EXTRA_ADDRESS_MARSHALLED)) {
Parcel parcel = Parcel.obtain();
byte[] marshalled = intent.getByteArrayExtra(EXTRA_ADDRESS_MARSHALLED);
parcel.unmarshall(marshalled, 0, marshalled.length);
parcel.setDataPosition(0);
address = parcel.readParcelable(getClassLoader());
parcel.recycle();
}
boolean hasResolvedDestination = threadId != -1 && address != null && distributionType != -1;
if (!hasResolvedDestination && animate) {
ViewUtil.fadeIn(contactsFragment.getView(), 300);
ViewUtil.fadeOut(progressWheel, 300);
} else if (!hasResolvedDestination) {
contactsFragment.getView().setVisibility(View.VISIBLE);
progressWheel.setVisibility(View.GONE);
} else {
createConversation(threadId, address, distributionType);
}
}
private void createConversation(long threadId, Address address, int distributionType) {
final Intent intent = getBaseShareIntent(ConversationActivityV2.class);
intent.putExtra(ConversationActivityV2.ADDRESS, address);
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
isPassingAlongMedia = true;
startActivity(intent);
}
private Intent getBaseShareIntent(final @NonNull Class<?> target) {
final Intent intent = new Intent(this, target);
if (resolvedExtra != null) {
intent.setDataAndType(resolvedExtra, mimeType);
} else if (resolvedPlaintext != null) {
intent.putExtra(Intent.EXTRA_TEXT, resolvedPlaintext);
intent.setType("text/plain");
}
return intent;
}
private String getMimeType(@Nullable Uri uri) {
if (uri != null) {
final String mimeType = MediaUtil.getMimeType(getApplicationContext(), uri);
if (mimeType != null) return mimeType;
}
return MediaUtil.getCorrectedMimeType(getIntent().getType());
}
@Override
public void onContactSelected(String number) {
Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true);
long existingThread = DatabaseComponent.get(this).threadDatabase().getThreadIdIfExistsFor(recipient);
createConversation(existingThread, recipient.getAddress(), DistributionTypes.DEFAULT);
}
@Override
public void onContactDeselected(String number) {
}
@SuppressLint("StaticFieldLeak")
private class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
private final Context context;
ResolveMediaTask(Context context) {
this.context = context;
} }
@Override @Override
protected Uri doInBackground(Uri... uris) { protected void onNewIntent(Intent intent) {
try { Log.i(TAG, "onNewIntent()");
if (uris.length != 1 || uris[0] == null) { super.onNewIntent(intent);
return null; setIntent(intent);
} initializeMedia();
}
InputStream inputStream; @Override
public void onPause() {
super.onPause();
if (!isPassingAlongMedia && resolvedExtra != null) {
BlobProvider.getInstance().delete(this, resolvedExtra);
if ("file".equals(uris[0].getScheme())) { if (!isFinishing()) {
inputStream = new FileInputStream(uris[0].getPath()); finish();
} else {
inputStream = context.getContentResolver().openInputStream(uris[0]);
}
if (inputStream == null) {
return null;
}
Cursor cursor = getContentResolver().query(uris[0], new String[] {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null);
String fileName = null;
Long fileSize = null;
try {
if (cursor != null && cursor.moveToFirst()) {
try {
fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
} catch (IllegalArgumentException e) {
Log.w(TAG, e);
} }
}
} finally {
if (cursor != null) cursor.close();
} }
return BlobProvider.getInstance()
.forData(inputStream, fileSize == null ? 0 : fileSize)
.withMimeType(mimeType)
.withFileName(fileName)
.createForMultipleSessionsOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e));
} catch (IOException ioe) {
Log.w(TAG, ioe);
return null;
}
} }
@Override @Override
protected void onPostExecute(Uri uri) { public boolean onOptionsItemSelected(MenuItem item) {
resolvedExtra = uri; switch (item.getItemId()) {
handleResolvedMedia(getIntent(), true); case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (searchToolbar.isVisible()) searchToolbar.collapse();
else super.onBackPressed();
}
private void initializeToolbar() {
TextView tootlbarTitle = findViewById(R.id.title);
tootlbarTitle.setText(
Phrase.from(getApplicationContext(), R.string.shareToSession)
.put(APP_NAME_KEY, getString(R.string.app_name))
.format().toString()
);
}
private void initializeResources() {
progressWheel = findViewById(R.id.progress_wheel);
searchToolbar = findViewById(R.id.search_toolbar);
searchAction = findViewById(R.id.search_action);
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(this);
}
private void initializeSearch() {
searchAction.setOnClickListener(v -> searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
searchAction.getY() + (searchAction.getHeight() / 2)));
searchToolbar.setListener(new SearchToolbar.SearchListener() {
@Override
public void onSearchTextChange(String text) {
if (contactsFragment != null) {
contactsFragment.setQueryFilter(text);
}
}
@Override
public void onSearchClosed() {
if (contactsFragment != null) {
contactsFragment.resetQueryFilter();
}
}
});
}
private void initializeMedia() {
final Context context = this;
isPassingAlongMedia = false;
Uri streamExtra = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
CharSequence charSequenceExtra = getIntent().getCharSequenceExtra(Intent.EXTRA_TEXT);
mimeType = getMimeType(streamExtra);
if (streamExtra != null && PartAuthority.isLocalUri(streamExtra)) {
isPassingAlongMedia = true;
resolvedExtra = streamExtra;
handleResolvedMedia(getIntent(), false);
} else if (charSequenceExtra != null && mimeType != null && mimeType.startsWith("text/")) {
resolvedPlaintext = charSequenceExtra;
handleResolvedMedia(getIntent(), false);
} else {
contactsFragment.getView().setVisibility(View.GONE);
progressWheel.setVisibility(View.VISIBLE);
new ResolveMediaTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, streamExtra);
}
}
private void handleResolvedMedia(Intent intent, boolean animate) {
long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1);
int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1);
Address address = null;
if (intent.hasExtra(EXTRA_ADDRESS_MARSHALLED)) {
Parcel parcel = Parcel.obtain();
byte[] marshalled = intent.getByteArrayExtra(EXTRA_ADDRESS_MARSHALLED);
parcel.unmarshall(marshalled, 0, marshalled.length);
parcel.setDataPosition(0);
address = parcel.readParcelable(getClassLoader());
parcel.recycle();
}
boolean hasResolvedDestination = threadId != -1 && address != null && distributionType != -1;
if (!hasResolvedDestination && animate) {
ViewUtil.fadeIn(contactsFragment.getView(), 300);
ViewUtil.fadeOut(progressWheel, 300);
} else if (!hasResolvedDestination) {
contactsFragment.getView().setVisibility(View.VISIBLE);
progressWheel.setVisibility(View.GONE);
} else {
createConversation(threadId, address, distributionType);
}
}
private void createConversation(long threadId, Address address, int distributionType) {
final Intent intent = getBaseShareIntent(ConversationActivityV2.class);
intent.putExtra(ConversationActivityV2.ADDRESS, address);
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
isPassingAlongMedia = true;
startActivity(intent);
}
private Intent getBaseShareIntent(final @NonNull Class<?> target) {
final Intent intent = new Intent(this, target);
if (resolvedExtra != null) {
intent.setDataAndType(resolvedExtra, mimeType);
} else if (resolvedPlaintext != null) {
intent.putExtra(Intent.EXTRA_TEXT, resolvedPlaintext);
intent.setType("text/plain");
}
return intent;
}
private String getMimeType(@Nullable Uri uri) {
if (uri != null) {
final String mimeType = MediaUtil.getMimeType(getApplicationContext(), uri);
if (mimeType != null) return mimeType;
}
return MediaUtil.getCorrectedMimeType(getIntent().getType());
}
@Override
public void onContactSelected(String number) {
Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true);
long existingThread = DatabaseComponent.get(this).threadDatabase().getThreadIdIfExistsFor(recipient);
createConversation(existingThread, recipient.getAddress(), DistributionTypes.DEFAULT);
}
@Override
public void onContactDeselected(String number) {
}
@SuppressLint("StaticFieldLeak")
private class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
private final Context context;
ResolveMediaTask(Context context) {
this.context = context;
}
@Override
protected Uri doInBackground(Uri... uris) {
try {
if (uris.length != 1 || uris[0] == null) {
return null;
}
InputStream inputStream;
if ("file".equals(uris[0].getScheme())) {
inputStream = new FileInputStream(uris[0].getPath());
} else {
inputStream = context.getContentResolver().openInputStream(uris[0]);
}
if (inputStream == null) {
return null;
}
Cursor cursor = getContentResolver().query(uris[0], new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null);
String fileName = null;
Long fileSize = null;
try {
if (cursor != null && cursor.moveToFirst()) {
try {
fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
} catch (IllegalArgumentException e) {
Log.w(TAG, e);
}
}
} finally {
if (cursor != null) cursor.close();
}
return BlobProvider.getInstance()
.forData(inputStream, fileSize == null ? 0 : fileSize)
.withMimeType(mimeType)
.withFileName(fileName)
.createForMultipleSessionsOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e));
} catch (IOException ioe) {
Log.w(TAG, ioe);
return null;
}
}
@Override
protected void onPostExecute(Uri uri) {
resolvedExtra = uri;
handleResolvedMedia(getIntent(), true);
}
} }
}
} }

View File

@ -58,7 +58,7 @@ class DisappearingMessagesViewModel(
init { init {
viewModelScope.launch { viewModelScope.launch {
val expiryMode = storage.getExpirationConfiguration(threadId)?.expiryMode?.maybeConvertToLegacy(isNewConfigEnabled) ?: ExpiryMode.NONE val expiryMode = storage.getExpirationConfiguration(threadId)?.expiryMode ?: ExpiryMode.NONE
val recipient = threadDb.getRecipientForThreadId(threadId) val recipient = threadDb.getRecipientForThreadId(threadId)
val groupRecord = recipient?.takeIf { it.isClosedGroupRecipient } val groupRecord = recipient?.takeIf { it.isClosedGroupRecipient }
?.run { groupDb.getGroup(address.toGroupString()).orNull() } ?.run { groupDb.getGroup(address.toGroupString()).orNull() }
@ -80,7 +80,7 @@ class DisappearingMessagesViewModel(
override fun onSetClick() = viewModelScope.launch { override fun onSetClick() = viewModelScope.launch {
val state = _state.value val state = _state.value
val mode = state.expiryMode?.coerceLegacyToAfterSend() val mode = state.expiryMode
val address = state.address val address = state.address
if (address == null || mode == null) { if (address == null || mode == null) {
_event.send(Event.FAIL) _event.send(Event.FAIL)
@ -92,8 +92,6 @@ class DisappearingMessagesViewModel(
_event.send(Event.SUCCESS) _event.send(Event.SUCCESS)
} }
private fun ExpiryMode.coerceLegacyToAfterSend() = takeUnless { it is ExpiryMode.Legacy } ?: ExpiryMode.AfterSend(expirySeconds)
@dagger.assisted.AssistedFactory @dagger.assisted.AssistedFactory
interface AssistedFactory { interface AssistedFactory {
fun create(threadId: Long): Factory fun create(threadId: Long): Factory
@ -125,5 +123,3 @@ class DisappearingMessagesViewModel(
) as T ) as T
} }
} }
private fun ExpiryMode.maybeConvertToLegacy(isNewConfigEnabled: Boolean): ExpiryMode = takeIf { isNewConfigEnabled } ?: ExpiryMode.Legacy(expirySeconds)

View File

@ -18,7 +18,7 @@ data class State(
val isSelfAdmin: Boolean = true, val isSelfAdmin: Boolean = true,
val address: Address? = null, val address: Address? = null,
val isNoteToSelf: Boolean = false, val isNoteToSelf: Boolean = false,
val expiryMode: ExpiryMode? = null, val expiryMode: ExpiryMode = ExpiryMode.NONE,
val isNewConfigEnabled: Boolean = true, val isNewConfigEnabled: Boolean = true,
val persistedMode: ExpiryMode? = null, val persistedMode: ExpiryMode? = null,
val showDebugOptions: Boolean = false val showDebugOptions: Boolean = false
@ -30,16 +30,10 @@ data class State(
val typeOptionsHidden get() = isNoteToSelf || (isGroup && isNewConfigEnabled) val typeOptionsHidden get() = isNoteToSelf || (isGroup && isNewConfigEnabled)
val nextType get() = when { val duration get() = expiryMode.duration
expiryType == ExpiryType.AFTER_READ -> ExpiryType.AFTER_READ val expiryType get() = expiryMode.type
isNewConfigEnabled -> ExpiryType.AFTER_SEND
else -> ExpiryType.LEGACY
}
val duration get() = expiryMode?.duration val isTimeOptionsEnabled = isNoteToSelf || isSelfAdmin && isNewConfigEnabled
val expiryType get() = expiryMode?.type
val isTimeOptionsEnabled = isNoteToSelf || isSelfAdmin && (isNewConfigEnabled || expiryType == ExpiryType.LEGACY)
} }
@ -54,11 +48,6 @@ enum class ExpiryType(
R.string.off, R.string.off,
contentDescription = R.string.AccessibilityId_disappearingMessagesOff, contentDescription = R.string.AccessibilityId_disappearingMessagesOff,
), ),
LEGACY(
ExpiryMode::Legacy,
R.string.expiration_type_disappear_legacy,
contentDescription = R.string.AccessibilityId_disappearingMessagesLegacy
),
AFTER_READ( AFTER_READ(
ExpiryMode::AfterRead, ExpiryMode::AfterRead,
R.string.disappearingMessagesDisappearAfterRead, R.string.disappearingMessagesDisappearAfterRead,
@ -83,7 +72,6 @@ enum class ExpiryType(
} }
val ExpiryMode.type: ExpiryType get() = when(this) { val ExpiryMode.type: ExpiryType get() = when(this) {
is ExpiryMode.Legacy -> ExpiryType.LEGACY
is ExpiryMode.AfterSend -> ExpiryType.AFTER_SEND is ExpiryMode.AfterSend -> ExpiryType.AFTER_SEND
is ExpiryMode.AfterRead -> ExpiryType.AFTER_READ is ExpiryMode.AfterRead -> ExpiryType.AFTER_READ
else -> ExpiryType.NONE else -> ExpiryType.NONE

View File

@ -23,7 +23,6 @@ fun State.toUiState() = UiState(
private fun State.typeOptions(): List<ExpiryRadioOption>? = if (typeOptionsHidden) null else { private fun State.typeOptions(): List<ExpiryRadioOption>? = if (typeOptionsHidden) null else {
buildList { buildList {
add(offTypeOption()) add(offTypeOption())
if (!isNewConfigEnabled) add(legacyTypeOption())
if (!isGroup) add(afterReadTypeOption()) if (!isGroup) add(afterReadTypeOption())
add(afterSendTypeOption()) add(afterSendTypeOption())
} }
@ -33,7 +32,7 @@ private fun State.timeOptions(): List<ExpiryRadioOption>? {
// Don't show times card if we have a types card, and type is off. // Don't show times card if we have a types card, and type is off.
if (!typeOptionsHidden && expiryType == ExpiryType.NONE) return null if (!typeOptionsHidden && expiryType == ExpiryType.NONE) return null
return nextType.let { type -> return expiryType.let { type ->
when (type) { when (type) {
ExpiryType.AFTER_READ -> afterReadTimes ExpiryType.AFTER_READ -> afterReadTimes
else -> afterSendTimes else -> afterSendTimes
@ -48,7 +47,6 @@ private fun State.timeOptions(): List<ExpiryRadioOption>? {
} }
private fun State.offTypeOption() = typeOption(ExpiryType.NONE) private fun State.offTypeOption() = typeOption(ExpiryType.NONE)
private fun State.legacyTypeOption() = typeOption(ExpiryType.LEGACY)
private fun State.afterReadTypeOption() = newTypeOption(ExpiryType.AFTER_READ) private fun State.afterReadTypeOption() = newTypeOption(ExpiryType.AFTER_READ)
private fun State.afterSendTypeOption() = newTypeOption(ExpiryType.AFTER_SEND) private fun State.afterSendTypeOption() = newTypeOption(ExpiryType.AFTER_SEND)
private fun State.newTypeOption(type: ExpiryType) = typeOption(type, isNewConfigEnabled && isSelfAdmin) private fun State.newTypeOption(type: ExpiryType) = typeOption(type, isNewConfigEnabled && isSelfAdmin)
@ -71,7 +69,7 @@ private fun debugModes(isDebug: Boolean, type: ExpiryType) =
debugTimes(isDebug).map { type.mode(it.inWholeSeconds) } debugTimes(isDebug).map { type.mode(it.inWholeSeconds) }
private fun State.debugOptions(): List<ExpiryRadioOption> = private fun State.debugOptions(): List<ExpiryRadioOption> =
debugModes(showDebugOptions, nextType).map { timeOption(it, subtitle = GetString("for testing purposes")) } debugModes(showDebugOptions, expiryType).map { timeOption(it, subtitle = GetString("for testing purposes")) }
// Standard list of available disappearing message times // Standard list of available disappearing message times
private val afterSendTimes = listOf(12.hours, 1.days, 7.days, 14.days) private val afterSendTimes = listOf(12.hours, 1.days, 7.days, 14.days)
@ -96,6 +94,6 @@ private fun State.timeOption(
title = title, title = title,
subtitle = subtitle, subtitle = subtitle,
contentDescription = title, contentDescription = title,
selected = mode.duration == expiryMode?.duration, selected = mode.duration == expiryMode.duration,
enabled = isTimeOptionsEnabled enabled = isTimeOptionsEnabled
) )

View File

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.ui.Callbacks
import org.thoughtcrime.securesms.ui.NoOpCallbacks import org.thoughtcrime.securesms.ui.NoOpCallbacks
import org.thoughtcrime.securesms.ui.OptionsCard import org.thoughtcrime.securesms.ui.OptionsCard
import org.thoughtcrime.securesms.ui.RadioOption import org.thoughtcrime.securesms.ui.RadioOption
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.fadingEdges import org.thoughtcrime.securesms.ui.fadingEdges
@ -71,13 +72,15 @@ fun DisappearingMessages(
} }
} }
if (state.showSetButton) SlimOutlineButton( if (state.showSetButton){
stringResource(R.string.set), PrimaryOutlineButton(
modifier = Modifier stringResource(R.string.set),
.contentDescription(R.string.AccessibilityId_setButton) modifier = Modifier
.align(Alignment.CenterHorizontally) .contentDescription(R.string.AccessibilityId_setButton)
.padding(bottom = LocalDimensions.current.spacing), .align(Alignment.CenterHorizontally)
onClick = callbacks::onSetClick .padding(bottom = LocalDimensions.current.spacing),
) onClick = callbacks::onSetClick
)
}
} }
} }

View File

@ -27,21 +27,18 @@ fun PreviewStates(
} }
class StatePreviewParameterProvider : PreviewParameterProvider<State> { class StatePreviewParameterProvider : PreviewParameterProvider<State> {
override val values = newConfigValues.filter { it.expiryType != ExpiryType.LEGACY } + newConfigValues.map { it.copy(isNewConfigEnabled = false) } override val values = newConfigValues + newConfigValues.map { it.copy(isNewConfigEnabled = false) }
private val newConfigValues get() = sequenceOf( private val newConfigValues get() = sequenceOf(
// new 1-1 // new 1-1
State(expiryMode = ExpiryMode.NONE), State(expiryMode = ExpiryMode.NONE),
State(expiryMode = ExpiryMode.Legacy(43200)),
State(expiryMode = ExpiryMode.AfterRead(300)), State(expiryMode = ExpiryMode.AfterRead(300)),
State(expiryMode = ExpiryMode.AfterSend(43200)), State(expiryMode = ExpiryMode.AfterSend(43200)),
// new group non-admin // new group non-admin
State(isGroup = true, isSelfAdmin = false), State(isGroup = true, isSelfAdmin = false),
State(isGroup = true, isSelfAdmin = false, expiryMode = ExpiryMode.Legacy(43200)),
State(isGroup = true, isSelfAdmin = false, expiryMode = ExpiryMode.AfterSend(43200)), State(isGroup = true, isSelfAdmin = false, expiryMode = ExpiryMode.AfterSend(43200)),
// new group admin // new group admin
State(isGroup = true), State(isGroup = true),
State(isGroup = true, expiryMode = ExpiryMode.Legacy(43200)),
State(isGroup = true, expiryMode = ExpiryMode.AfterSend(43200)), State(isGroup = true, expiryMode = ExpiryMode.AfterSend(43200)),
// new note-to-self // new note-to-self
State(isNoteToSelf = true), State(isNoteToSelf = true),

View File

@ -404,7 +404,7 @@ class VisibleMessageView : FrameLayout {
MessageStatusInfo( MessageStatusInfo(
R.drawable.ic_delivery_status_sending, R.drawable.ic_delivery_status_sending,
context.getColorFromAttr(R.attr.message_status_color), context.getColorFromAttr(R.attr.message_status_color),
R.string.messageStatusUploading R.string.uploading
) )
} }
message.isSyncing || message.isResyncing -> message.isSyncing || message.isResyncing ->

View File

@ -21,9 +21,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView android:layout_width="wrap_content" <TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/share" android:text="@string/shareToSession"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
android:textSize="@dimen/very_large_font_size" android:textSize="@dimen/very_large_font_size"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"

View File

@ -99,45 +99,6 @@ class DisappearingMessagesViewModelTest {
) )
} }
@Test
fun `note to self, off, old config`() = runTest {
mock1on1(ExpiryMode.NONE, LOCAL_ADDRESS)
val viewModel = createViewModel(isNewConfigEnabled = false)
advanceUntilIdle()
assertThat(
viewModel.state.value
).isEqualTo(
State(
isGroup = false,
isSelfAdmin = true,
address = LOCAL_ADDRESS,
isNoteToSelf = true,
expiryMode = ExpiryMode.Legacy(0),
isNewConfigEnabled = false,
persistedMode = ExpiryMode.Legacy(0),
showDebugOptions = false
)
)
assertThat(
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCardData(
R.string.disappearingMessagesTimer,
typeOption(ExpiryMode.NONE, selected = false),
timeOption(ExpiryType.LEGACY, 12.hours),
timeOption(ExpiryType.LEGACY, 1.days),
timeOption(ExpiryType.LEGACY, 7.days),
timeOption(ExpiryType.LEGACY, 14.days)
)
)
)
}
@Test @Test
fun `group, off, admin, new config`() = runTest { fun `group, off, admin, new config`() = runTest {
mockGroup(ExpiryMode.NONE, isAdmin = true) mockGroup(ExpiryMode.NONE, isAdmin = true)
@ -303,53 +264,6 @@ class DisappearingMessagesViewModelTest {
) )
} }
@Test
fun `1-1 conversation, 12 hours legacy, old config`() = runTest {
val time = 12.hours
val someAddress = Address.fromSerialized("05---SOME---ADDRESS")
mock1on1(ExpiryType.LEGACY.mode(time), someAddress)
val viewModel = createViewModel(isNewConfigEnabled = false)
advanceUntilIdle()
assertThat(
viewModel.state.value
).isEqualTo(
State(
isGroup = false,
isSelfAdmin = true,
address = someAddress,
isNoteToSelf = false,
expiryMode = ExpiryType.LEGACY.mode(12.hours),
isNewConfigEnabled = false,
persistedMode = ExpiryType.LEGACY.mode(12.hours),
showDebugOptions = false
)
)
assertThat(
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCardData(
R.string.disappearingMessagesDeleteType,
typeOption(ExpiryMode.NONE),
typeOption(time, ExpiryType.LEGACY, selected = true),
typeOption(12.hours, ExpiryType.AFTER_READ, enabled = false),
typeOption(1.days, ExpiryType.AFTER_SEND, enabled = false)
),
OptionsCardData(
R.string.disappearingMessagesTimer,
timeOption(ExpiryType.LEGACY, 12.hours, selected = true),
timeOption(ExpiryType.LEGACY, 1.days),
timeOption(ExpiryType.LEGACY, 7.days),
timeOption(ExpiryType.LEGACY, 14.days)
)
)
)
}
@Test @Test
fun `1-1 conversation, 1 day after send, new config`() = runTest { fun `1-1 conversation, 1 day after send, new config`() = runTest {
val time = 1.days val time = 1.days

View File

@ -156,8 +156,4 @@
<string name="AccessibilityId_expand">Expand</string> <string name="AccessibilityId_expand">Expand</string>
<string name="AccessibilityId_mediaMessage">Media message</string> <string name="AccessibilityId_mediaMessage">Media message</string>
<!-- LEGACY DISAPPEARING MESSAGES WILL BE NO MORE & THIS CAN BE REMOVED AFTER ONBOARDING GOES OUT! -ACL -->
<!-- Also, this is associated with the string: expiration_type_disappear_legacy -->
<string name="AccessibilityId_disappearingMessagesLegacy">Original version of disappearing messages.</string>
</resources> </resources>

View File

@ -4,7 +4,6 @@ import kotlin.time.Duration.Companion.seconds
sealed class ExpiryMode(val expirySeconds: Long) { sealed class ExpiryMode(val expirySeconds: Long) {
object NONE: ExpiryMode(0) object NONE: ExpiryMode(0)
data class Legacy(private val seconds: Long = 0L): ExpiryMode(seconds)
data class AfterSend(private val seconds: Long = 0L): ExpiryMode(seconds) data class AfterSend(private val seconds: Long = 0L): ExpiryMode(seconds)
data class AfterRead(private val seconds: Long = 0L): ExpiryMode(seconds) data class AfterRead(private val seconds: Long = 0L): ExpiryMode(seconds)

View File

@ -74,14 +74,14 @@ object UpdateMessageBuilder {
.format() .format()
} }
2 -> { 2 -> {
Phrase.from(context, R.string.groupMemberTwoNew) Phrase.from(context, R.string.groupMemberNewTwo)
.put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0))) .put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0)))
.put(OTHER_NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(1))) .put(OTHER_NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(1)))
.format() .format()
} }
else -> { else -> {
val newMemberCountMinusOne = newMemberCount - 1 val newMemberCountMinusOne = newMemberCount - 1
Phrase.from(context, R.string.groupMemberMoreNew) Phrase.from(context, R.string.groupMemberNewMultiple)
.put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0))) .put(NAME_KEY, getSenderName(updateData.updatedMembers.elementAt(0)))
.put(COUNT_KEY, newMemberCountMinusOne) .put(COUNT_KEY, newMemberCountMinusOne)
.format() .format()

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name" translatable="false">Session</string> <string name="app_name" translatable="false">Session</string>
<string name="notificationsHeaderMute">Notifications - Muted</string>
<string name="about">About</string> <string name="about">About</string>
<string name="accept">Accept</string> <string name="accept">Accept</string>
<string name="accountIDCopy">Copy Account ID</string> <string name="accountIDCopy">Copy Account ID</string>
@ -156,7 +157,7 @@
<string name="callsVoiceAndVideo">Voice and Video Calls</string> <string name="callsVoiceAndVideo">Voice and Video Calls</string>
<string name="callsVoiceAndVideoBeta">Voice and Video Calls (Beta)</string> <string name="callsVoiceAndVideoBeta">Voice and Video Calls (Beta)</string>
<string name="callsVoiceAndVideoModalDescription">Your IP is visible to your call partner and an Oxen Foundation server while using beta calls.</string> <string name="callsVoiceAndVideoModalDescription">Your IP is visible to your call partner and an Oxen Foundation server while using beta calls.</string>
<string name="callsVoiceAndVideoToggleDescription">Enables voice and video calls to and from other users</string> <string name="callsVoiceAndVideoToggleDescription">Enables voice and video calls to and from other users.</string>
<string name="callsYouCalled">You called {name}</string> <string name="callsYouCalled">You called {name}</string>
<string name="callsYouMissedCallPermissions">You missed a call from <b>{name}</b> because you haven\'t enabled <b>Voice and Video Calls</b> in Privacy Settings.</string> <string name="callsYouMissedCallPermissions">You missed a call from <b>{name}</b> because you haven\'t enabled <b>Voice and Video Calls</b> in Privacy Settings.</string>
<string name="cameraErrorNotFound">No camera found</string> <string name="cameraErrorNotFound">No camera found</string>
@ -192,8 +193,9 @@
<string name="clearMessagesNoteToSelfDescription">Are you sure you want to clear all Note to Self messages from your device?</string> <string name="clearMessagesNoteToSelfDescription">Are you sure you want to clear all Note to Self messages from your device?</string>
<string name="close">Close</string> <string name="close">Close</string>
<string name="closeWindow">Close Window</string> <string name="closeWindow">Close Window</string>
<string name="communityBanDescription">This will ban the selected user from this Community. Are you sure you want to continue?</string> <string name="commitHashDesktop">Commit Hash: {hash}</string>
<string name="communityBanDeleteDescription">This will ban the selected user from this Community and delete all their messages. Are you sure you want to continue?</string> <string name="communityBanDeleteDescription">This will ban the selected user from this Community and delete all their messages. Are you sure you want to continue?</string>
<string name="communityBanDescription">This will ban the selected user from this Community. Are you sure you want to continue?</string>
<string name="communityEnterUrl">Enter Community URL</string> <string name="communityEnterUrl">Enter Community URL</string>
<string name="communityEnterUrlErrorInvalid">Invalid URL</string> <string name="communityEnterUrlErrorInvalid">Invalid URL</string>
<string name="communityEnterUrlErrorInvalidDescription">Please check the Community URL and try again.</string> <string name="communityEnterUrlErrorInvalidDescription">Please check the Community URL and try again.</string>
@ -228,7 +230,7 @@
<string name="conversationsAddedToHome">Added to home screen</string> <string name="conversationsAddedToHome">Added to home screen</string>
<string name="conversationsAudioMessages">Audio Messages</string> <string name="conversationsAudioMessages">Audio Messages</string>
<string name="conversationsAutoplayAudioMessage">Autoplay Audio Messages</string> <string name="conversationsAutoplayAudioMessage">Autoplay Audio Messages</string>
<string name="conversationsAutoplayAudioMessageDescription">Autoplay consecutively sent audio messages</string> <string name="conversationsAutoplayAudioMessageDescription">Autoplay consecutively sent audio messages.</string>
<string name="conversationsBlockedContacts">Blocked Contacts</string> <string name="conversationsBlockedContacts">Blocked Contacts</string>
<string name="conversationsCommunities">Communities</string> <string name="conversationsCommunities">Communities</string>
<string name="conversationsDelete">Delete Conversation</string> <string name="conversationsDelete">Delete Conversation</string>
@ -249,7 +251,7 @@
<string name="conversationsSendWithEnterKeyDescription">Tapping the Enter Key will send message instead of starting a new line.</string> <string name="conversationsSendWithEnterKeyDescription">Tapping the Enter Key will send message instead of starting a new line.</string>
<string name="conversationsSettingsAllMedia">All Media</string> <string name="conversationsSettingsAllMedia">All Media</string>
<string name="conversationsSpellCheck">Spell Check</string> <string name="conversationsSpellCheck">Spell Check</string>
<string name="conversationsSpellCheckDescription">Enable spell check when typing messages</string> <string name="conversationsSpellCheckDescription">Enable spell check when typing messages.</string>
<string name="conversationsStart">Start Conversation</string> <string name="conversationsStart">Start Conversation</string>
<string name="copied">Copied</string> <string name="copied">Copied</string>
<string name="copy">Copy</string> <string name="copy">Copy</string>
@ -328,6 +330,7 @@
<string name="disappearingMessagesTurnedOff"><b>{name}</b> has turned disappearing messages off. Messages they send will no longer disappear.</string> <string name="disappearingMessagesTurnedOff"><b>{name}</b> has turned disappearing messages off. Messages they send will no longer disappear.</string>
<string name="disappearingMessagesTurnedOffGroup"><b>{name}</b> has turned disappearing messages <b>off</b>.</string> <string name="disappearingMessagesTurnedOffGroup"><b>{name}</b> has turned disappearing messages <b>off</b>.</string>
<string name="disappearingMessagesTurnedOffYou"><b>You</b> turned <b>off</b> disappearing messages. Messages you send will no longer disappear.</string> <string name="disappearingMessagesTurnedOffYou"><b>You</b> turned <b>off</b> disappearing messages. Messages you send will no longer disappear.</string>
<string name="disappearingMessagesTurnedOffYouGroup"><b>You</b> turned <b>off</b> disappearing messages.</string>
<string name="disappearingMessagesTypeRead">read</string> <string name="disappearingMessagesTypeRead">read</string>
<string name="disappearingMessagesTypeSent">sent</string> <string name="disappearingMessagesTypeSent">sent</string>
<string name="disappearingMessagesUpdated"><b>{admin_name}</b> updated disappearing message settings.</string> <string name="disappearingMessagesUpdated"><b>{admin_name}</b> updated disappearing message settings.</string>
@ -363,6 +366,12 @@
<item quantity="one">And %1$d other has reacted %2$s to this message.</item> <item quantity="one">And %1$d other has reacted %2$s to this message.</item>
<item quantity="other">And %1$d others have reacted %2$s to this message.</item> <item quantity="other">And %1$d others have reacted %2$s to this message.</item>
</plurals> </plurals>
<string name="emojiReactsHoverNameDesktop">{name} reacted with {emoji_name}</string>
<string name="emojiReactsHoverNameTwoDesktop">{name} and {other_name} reacted with {emoji_name}</string>
<string name="emojiReactsHoverTwoNameMultipleDesktop">{name} and <span>{count} others</span> reacted with {emoji_name}</string>
<string name="emojiReactsHoverYouNameDesktop">You reacted with {emoji_name}</string>
<string name="emojiReactsHoverYouNameMultipleDesktop">You and <span>{count} others</span> reacted with {emoji_name}</string>
<string name="emojiReactsHoverYouNameTwoDesktop">You and {name} reacted with {emoji_name}</string>
<string name="emojiReactsNotification">Reacted to your message {emoji}</string> <string name="emojiReactsNotification">Reacted to your message {emoji}</string>
<string name="enable">Enable</string> <string name="enable">Enable</string>
<string name="errorConnection">Please check your internet connection and try again.</string> <string name="errorConnection">Please check your internet connection and try again.</string>
@ -375,6 +384,7 @@
<string name="followSystemSettings">Follow system settings</string> <string name="followSystemSettings">Follow system settings</string>
<string name="from">From:</string> <string name="from">From:</string>
<string name="fullScreenToggle">Toggle Full Screen</string> <string name="fullScreenToggle">Toggle Full Screen</string>
<string name="gif">GIF</string>
<string name="giphyWarning">Giphy</string> <string name="giphyWarning">Giphy</string>
<string name="giphyWarningDescription">{app_name} will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.</string> <string name="giphyWarningDescription">{app_name} will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.</string>
<string name="groupAddMemberMaximum">Groups have a maximum of 100 members</string> <string name="groupAddMemberMaximum">Groups have a maximum of 100 members</string>
@ -409,23 +419,15 @@
<string name="groupMemberLeft"><b>{name}</b> left the group.</string> <string name="groupMemberLeft"><b>{name}</b> left the group.</string>
<string name="groupMemberLeftMultiple"><b>{name}</b> and <b>{count} others</b> left the group.</string> <string name="groupMemberLeftMultiple"><b>{name}</b> and <b>{count} others</b> left the group.</string>
<string name="groupMemberLeftTwo"><b>{name}</b> and <b>{other_name}</b> left the group.</string> <string name="groupMemberLeftTwo"><b>{name}</b> and <b>{other_name}</b> left the group.</string>
<string name="groupMemberMoreNew"><b>{name}</b> and <b>{count} others</b> joined the group.</string> <string name="groupMemberNew"><b>{name}</b> was invited to join the group.</string>
<string name="groupMemberNew"><b>{name}</b> joined the group.</string>
<string name="groupMemberNewHistory"><b>{name}</b> was invited to join the group. Chat history was shared.</string> <string name="groupMemberNewHistory"><b>{name}</b> was invited to join the group. Chat history was shared.</string>
<string name="groupMemberNewHistoryMultiple"><b>{name}</b> and <b>{count} others</b> were invited to join the group. Chat history was shared.</string> <string name="groupMemberNewHistoryMultiple"><b>{name}</b> and <b>{count} others</b> were invited to join the group. Chat history was shared.</string>
<string name="groupMemberNewHistoryTwo"><b>{name}</b> and <b>{other_name}</b> were invited to join the group. Chat history was shared.</string> <string name="groupMemberNewHistoryTwo"><b>{name}</b> and <b>{other_name}</b> were invited to join the group. Chat history was shared.</string>
<string name="groupMemberNewMultiple"><b>{name}</b> and <b>{count} others</b> joined the group.</string> <string name="groupMemberNewMultiple"><b>{name}</b> and <b>{count} others</b> were invited to join the group.</string>
<string name="groupMemberNewTwo"><b>{name}</b> and <b>{other_name}</b> were invited to join the group.</string> <string name="groupMemberNewTwo"><b>{name}</b> and <b>{other_name}</b> were invited to join the group.</string>
<string name="groupMemberNewYouHistory"><b>{name}</b> was invited to join the group. Chat history was shared.</string>
<string name="groupMemberNewYouHistoryMultiple"><b>You</b> and <b>{count} others</b> were invited to join the group. Chat history was shared.</string> <string name="groupMemberNewYouHistoryMultiple"><b>You</b> and <b>{count} others</b> were invited to join the group. Chat history was shared.</string>
<string name="groupMemberNewYouHistoryTwo"><b>You</b> and <b>{name}</b> were invited to join the group. Chat history was shared.</string> <string name="groupMemberNewYouHistoryTwo"><b>You</b> and <b>{name}</b> were invited to join the group. Chat history was shared.</string>
<string name="groupMemberNewYouMultiple"><b>You</b> and <b>{count} others</b> were invited to join the group.</string>
<string name="groupMemberNewYouTwo"><b>You</b> and <b>{name}</b> were invited to join the group.</string>
<string name="groupMemberYouAndMoreNew"><b>You</b> and <b>{count} others</b> joined the group.</string>
<string name="groupMemberYouAndOtherNew"><b>You</b> and <b>{other_name}</b> joined the group.</string>
<string name="groupMemberTwoNew"><b>{name}</b> and <b>{other_name}</b> joined the group.</string>
<string name="groupMemberYouLeft"><b>You</b> left the group.</string> <string name="groupMemberYouLeft"><b>You</b> left the group.</string>
<string name="groupMemberYouNew"><b>You</b> joined the group.</string>
<string name="groupMembers">Group Members</string> <string name="groupMembers">Group Members</string>
<string name="groupMembersNone">There are no other members in this group.</string> <string name="groupMembersNone">There are no other members in this group.</string>
<string name="groupName">Group Name</string> <string name="groupName">Group Name</string>
@ -440,9 +442,8 @@
<string name="groupPromotedYouMultiple"><b>You</b> and <b>{count} others</b> were promoted to Admin.</string> <string name="groupPromotedYouMultiple"><b>You</b> and <b>{count} others</b> were promoted to Admin.</string>
<string name="groupPromotedYouTwo"><b>You</b> and <b>{name}</b> were promoted to Admin.</string> <string name="groupPromotedYouTwo"><b>You</b> and <b>{name}</b> were promoted to Admin.</string>
<string name="groupRemoveDescription">Would you like to remove <b>{name}</b> from <b>{group_name}</b>?</string> <string name="groupRemoveDescription">Would you like to remove <b>{name}</b> from <b>{group_name}</b>?</string>
<string name="groupRemoveDescriptionTwo">Would you like to remove <b>{name}</b> and <b>{other_name}</b> from <b>{group_name}</b>?</string>
<string name="groupRemoveMessages">Remove user and their messages</string>
<string name="groupRemoveDescriptionMultiple">Would you like to remove <b>{name}</b> and <b>{count} others</b> from <b>{group_name}</b>?</string> <string name="groupRemoveDescriptionMultiple">Would you like to remove <b>{name}</b> and <b>{count} others</b> from <b>{group_name}</b>?</string>
<string name="groupRemoveDescriptionTwo">Would you like to remove <b>{name}</b> and <b>{other_name}</b> from <b>{group_name}</b>?</string>
<plurals name="groupRemoveMessages"> <plurals name="groupRemoveMessages">
<item quantity="one">Remove user and their messages</item> <item quantity="one">Remove user and their messages</item>
<item quantity="other">Remove users and their messages</item> <item quantity="other">Remove users and their messages</item>
@ -504,6 +505,7 @@
<string name="loading">Loading...</string> <string name="loading">Loading...</string>
<string name="lockApp">Lock App</string> <string name="lockApp">Lock App</string>
<string name="lockAppDescription">Require fingerprint, PIN, pattern or password to unlock {app_name}.</string> <string name="lockAppDescription">Require fingerprint, PIN, pattern or password to unlock {app_name}.</string>
<string name="lockAppDescriptionIos">Require Touch ID, Face ID or your passcode to unlock {app_name}.</string>
<string name="lockAppEnablePasscode">You must enable a passcode in your iOS Settings in order to use Screen Lock.</string> <string name="lockAppEnablePasscode">You must enable a passcode in your iOS Settings in order to use Screen Lock.</string>
<string name="lockAppLocked">{app_name} is locked</string> <string name="lockAppLocked">{app_name} is locked</string>
<string name="lockAppQuickResponse">Quick response unavailable when {app_name} is locked!</string> <string name="lockAppQuickResponse">Quick response unavailable when {app_name} is locked!</string>
@ -602,6 +604,8 @@
<string name="notificationsFastModeDescription">You\'ll be notified of new messages reliably and immediately using Google\'s notification Servers.</string> <string name="notificationsFastModeDescription">You\'ll be notified of new messages reliably and immediately using Google\'s notification Servers.</string>
<string name="notificationsFastModeDescriptionIos">You\'ll be notified of new messages reliably and immediately using Apple\'s notification Servers.</string> <string name="notificationsFastModeDescriptionIos">You\'ll be notified of new messages reliably and immediately using Apple\'s notification Servers.</string>
<string name="notificationsGoToDevice">Go to device notification settings</string> <string name="notificationsGoToDevice">Go to device notification settings</string>
<string name="notificationsHeaderAllMessages">Notifications - All</string>
<string name="notificationsHeaderMentionsOnly">Notifications - Mentions Only</string>
<string name="notificationsIosGroup">{name} to {conversation_name}</string> <string name="notificationsIosGroup">{name} to {conversation_name}</string>
<string name="notificationsIosRestart">You may have received messages while your {device} was restarting.</string> <string name="notificationsIosRestart">You may have received messages while your {device} was restarting.</string>
<string name="notificationsLedColor">LED color</string> <string name="notificationsLedColor">LED color</string>
@ -657,7 +661,7 @@
<string name="passwordCurrentIncorrect">Your current password is incorrect.</string> <string name="passwordCurrentIncorrect">Your current password is incorrect.</string>
<string name="passwordDescription">Require password to unlock {app_name}.</string> <string name="passwordDescription">Require password to unlock {app_name}.</string>
<string name="passwordEnter">Enter password</string> <string name="passwordEnter">Enter password</string>
<string name="passwordEnterCurrent">Please enter your current password.</string> <string name="passwordEnterCurrent">Please enter your current password</string>
<string name="passwordEnterNew">Please enter your new password</string> <string name="passwordEnterNew">Please enter your new password</string>
<string name="passwordError">Password must only contain letters, numbers and symbols</string> <string name="passwordError">Password must only contain letters, numbers and symbols</string>
<string name="passwordErrorLength">Password must be between 6 and 64 characters long</string> <string name="passwordErrorLength">Password must be between 6 and 64 characters long</string>
@ -672,14 +676,16 @@
<string name="paste">Paste</string> <string name="paste">Paste</string>
<string name="permissionsAppleMusic">{app_name} needs to use Apple Music to play media attachments.</string> <string name="permissionsAppleMusic">{app_name} needs to use Apple Music to play media attachments.</string>
<string name="permissionsAutoUpdate">Auto Update</string> <string name="permissionsAutoUpdate">Auto Update</string>
<string name="permissionsAutoUpdateDescription">Automatically check for updates on startup</string> <string name="permissionsAutoUpdateDescription">Automatically check for updates on startup.</string>
<string name="permissionsFaceId">The screen lock feature on {app_name} uses Face ID.</string> <string name="permissionsFaceId">The screen lock feature on {app_name} uses Face ID.</string>
<string name="permissionsKeepInSystemTray">Keep in System Tray</string> <string name="permissionsKeepInSystemTray">Keep in System Tray</string>
<string name="permissionsKeepInSystemTrayDescription">{app_name} continues running in the background when you close the window</string> <string name="permissionsKeepInSystemTrayDescription">{app_name} continues running in the background when you close the window</string>
<string name="permissionsLibrary">{app_name} needs photo library access to continue. You can enable access in the iOS settings.</string> <string name="permissionsLibrary">{app_name} needs photo library access to continue. You can enable access in the iOS settings.</string>
<string name="permissionsMicrophone">Microphone</string> <string name="permissionsMicrophone">Microphone</string>
<string name="permissionsMicrophoneAccessRequired">{app_name} needs microphone access to send audio messages, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Microphone\".</string> <string name="permissionsMicrophoneAccessRequired">{app_name} needs microphone access to send audio messages, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Microphone\".</string>
<string name="permissionsMicrophoneDescription">Allow access to microphone</string> <string name="permissionsMicrophoneAccessRequiredDesktop">You can enable microphone access in {app_name}\'s privacy settings</string>
<string name="permissionsMicrophoneAccessRequiredIos">{app_name} needs microphone access to make calls and record audio messages.</string>
<string name="permissionsMicrophoneDescription">Allow access to microphone.</string>
<string name="permissionsRequired">Permission required</string> <string name="permissionsRequired">Permission required</string>
<string name="permissionsStorageDenied">{app_name} needs storage access so you can send and save attachments. Tap Settings -&gt; Permissions, and turn \"Files and media\" on.</string> <string name="permissionsStorageDenied">{app_name} needs storage access so you can send and save attachments. Tap Settings -&gt; Permissions, and turn \"Files and media\" on.</string>
<string name="permissionsStorageDeniedLegacy">{app_name} needs storage access so you can send and save attachments. Tap Settings -&gt; Permissions, and turn \"Storage\" on.</string> <string name="permissionsStorageDeniedLegacy">{app_name} needs storage access so you can send and save attachments. Tap Settings -&gt; Permissions, and turn \"Storage\" on.</string>
@ -722,10 +728,10 @@
<string name="recoveryPasswordErrorTitle">Incorrect Recovery Password</string> <string name="recoveryPasswordErrorTitle">Incorrect Recovery Password</string>
<string name="recoveryPasswordExplanation">To load your account, enter your recovery password.</string> <string name="recoveryPasswordExplanation">To load your account, enter your recovery password.</string>
<string name="recoveryPasswordHidePermanently">Hide Recovery Password Permanently</string> <string name="recoveryPasswordHidePermanently">Hide Recovery Password Permanently</string>
<string name="recoveryPasswordHidePermanentlyDescription1">Without your recovery password, you cannot load your account on new devices.\n\nWe strongly recommend you save your recovery password in a safe and secure place before continuing.</string> <string name="recoveryPasswordHidePermanentlyDescription1">Without your recovery password, you cannot load your account on new devices. \n\nWe strongly recommend you save your recovery password in a safe and secure place before continuing.</string>
<string name="recoveryPasswordHidePermanentlyDescription2">Are you sure you want to permanently hide your recovery password on this device? This cannot be undone.</string> <string name="recoveryPasswordHidePermanentlyDescription2">Are you sure you want to permanently hide your recovery password on this device? This cannot be undone.</string>
<string name="recoveryPasswordHideRecoveryPassword">Hide Recovery Password</string> <string name="recoveryPasswordHideRecoveryPassword">Hide Recovery Password</string>
<string name="recoveryPasswordHideRecoveryPasswordDescription">Permanently hide your recover password on this device.</string> <string name="recoveryPasswordHideRecoveryPasswordDescription">Permanently hide your recovery password on this device.</string>
<string name="recoveryPasswordRestoreDescription">Enter your recovery password to load your account. If you haven\'t saved it, you can find it in your app settings.</string> <string name="recoveryPasswordRestoreDescription">Enter your recovery password to load your account. If you haven\'t saved it, you can find it in your app settings.</string>
<string name="recoveryPasswordView">View Password</string> <string name="recoveryPasswordView">View Password</string>
<string name="recoveryPasswordWarningSendDescription">This is your recovery password. If you send it to someone they\'ll have full access to your account.</string> <string name="recoveryPasswordWarningSendDescription">This is your recovery password. If you send it to someone they\'ll have full access to your account.</string>
@ -770,7 +776,6 @@
<string name="sessionHelp">Help</string> <string name="sessionHelp">Help</string>
<string name="sessionInviteAFriend">Invite a Friend</string> <string name="sessionInviteAFriend">Invite a Friend</string>
<string name="sessionMessageRequests">Message Requests</string> <string name="sessionMessageRequests">Message Requests</string>
<string name="sessionNetworkSent">{token_name_long} ({<span>{token_name_short}</span>)</string>
<string name="sessionNotifications">Notifications</string> <string name="sessionNotifications">Notifications</string>
<string name="sessionPermissions">Permissions</string> <string name="sessionPermissions">Permissions</string>
<string name="sessionPrivacy">Privacy</string> <string name="sessionPrivacy">Privacy</string>
@ -782,18 +787,19 @@
<string name="shareAccountIdDescription">Invite your friend to chat with you on {app_name} by sharing your Account ID with them.</string> <string name="shareAccountIdDescription">Invite your friend to chat with you on {app_name} by sharing your Account ID with them.</string>
<string name="shareAccountIdDescriptionCopied">Share with your friends wherever you usually speak with them — then move the conversation here.</string> <string name="shareAccountIdDescriptionCopied">Share with your friends wherever you usually speak with them — then move the conversation here.</string>
<string name="shareExtensionDatabaseError">There is an issue opening the database. Please restart the app and try again.</string> <string name="shareExtensionDatabaseError">There is an issue opening the database. Please restart the app and try again.</string>
<string name="shareToSession">Share to {app_Name}</string> <string name="shareToSession">Share to {app_name}</string>
<string name="show">Show</string> <string name="show">Show</string>
<string name="showAll">Show All</string> <string name="showAll">Show All</string>
<string name="showLess">Show Less</string> <string name="showLess">Show Less</string>
<string name="stickers">Stickers</string> <string name="stickers">Stickers</string>
<string name="supportGoTo">Go to Support Page</string> <string name="supportGoTo">Go to Support Page</string>
<string name="systemInformationDesktop">System Information: {information}</string>
<string name="theContinue">Continue</string> <string name="theContinue">Continue</string>
<string name="theDefault">Default</string> <string name="theDefault">Default</string>
<string name="theError">Error</string> <string name="theError">Error</string>
<string name="tryAgain">Try Again</string> <string name="tryAgain">Try Again</string>
<string name="typingIndicators">Typing Indicators</string> <string name="typingIndicators">Typing Indicators</string>
<string name="typingIndicatorsDescription">See and share typing indicators</string> <string name="typingIndicatorsDescription">See and share typing indicators.</string>
<string name="undo">Undo</string> <string name="undo">Undo</string>
<string name="unknown">Unknown</string> <string name="unknown">Unknown</string>
<string name="updateApp">App updates</string> <string name="updateApp">App updates</string>
@ -821,18 +827,4 @@
<string name="window">Window</string> <string name="window">Window</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="you">You</string> <string name="you">You</string>
<!-- USED as text for the Legacy ExpiryType enum defined in State.kt - however, I don' think
there's any way to set "Legacy" disappearing messages any more, that's gone. Although we
may still need to cater to _seeing_ Legacy disappearing messages from clients which haven't
been updated, perhaps?
Figma: https://www.figma.com/design/tEgZ8ujg76DdtPwEJv8zFp/Disappearing-Messages?t=25H0THKH9VADKm9s-0
Morgan 2024/07/31: "once the Onboarding release has gone out we can remove the 'Legacy'
disappearing message settings (there is a PR for iOS to do so already)"
-->
<string name="expiration_type_disappear_legacy">Legacy</string>
<!-- Missing from CrowdIn circa 2024-08-22 -->
<string name="messageStatusUploading">Uploading</string>
</resources> </resources>