mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 02:55:23 +00:00
Strings update
Updating strings Removed non crowdin strings Removed the locale wrapper as we do not support in app language changes
This commit is contained in:
parent
d30ff252ab
commit
304679dac6
@ -88,7 +88,6 @@ android {
|
|||||||
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
||||||
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
||||||
buildConfigField "String", "USER_AGENT", "\"OWA\""
|
buildConfigField "String", "USER_AGENT", "\"OWA\""
|
||||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
|
||||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||||
resourceConfigurations += []
|
resourceConfigurations += []
|
||||||
|
|
||||||
|
@ -35,14 +35,12 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.session.libsession.avatars.AvatarHelper;
|
|
||||||
import org.session.libsession.database.MessageDataProvider;
|
import org.session.libsession.database.MessageDataProvider;
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2;
|
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2;
|
||||||
import org.session.libsession.messaging.sending_receiving.pollers.Poller;
|
import org.session.libsession.messaging.sending_receiving.pollers.Poller;
|
||||||
import org.session.libsession.snode.SnodeModule;
|
import org.session.libsession.snode.SnodeModule;
|
||||||
import org.session.libsession.utilities.Address;
|
|
||||||
import org.session.libsession.utilities.ConfigFactoryUpdateListener;
|
import org.session.libsession.utilities.ConfigFactoryUpdateListener;
|
||||||
import org.session.libsession.utilities.Device;
|
import org.session.libsession.utilities.Device;
|
||||||
import org.session.libsession.utilities.Environment;
|
import org.session.libsession.utilities.Environment;
|
||||||
@ -51,8 +49,6 @@ import org.session.libsession.utilities.SSKEnvironment;
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
import org.session.libsession.utilities.Util;
|
import org.session.libsession.utilities.Util;
|
||||||
import org.session.libsession.utilities.WindowDebouncer;
|
import org.session.libsession.utilities.WindowDebouncer;
|
||||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
|
||||||
import org.session.libsignal.utilities.HTTP;
|
import org.session.libsignal.utilities.HTTP;
|
||||||
import org.session.libsignal.utilities.JsonUtil;
|
import org.session.libsignal.utilities.JsonUtil;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
@ -91,17 +87,14 @@ import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
|||||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||||
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
|
||||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
import org.webrtc.PeerConnectionFactory;
|
||||||
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -110,7 +103,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.EntryPoints;
|
import dagger.hilt.EntryPoints;
|
||||||
import dagger.hilt.android.HiltAndroidApp;
|
import dagger.hilt.android.HiltAndroidApp;
|
||||||
import kotlin.Unit;
|
|
||||||
import network.loki.messenger.BuildConfig;
|
import network.loki.messenger.BuildConfig;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
import network.loki.messenger.libsession_util.ConfigBase;
|
import network.loki.messenger.libsession_util.ConfigBase;
|
||||||
@ -320,10 +312,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeLocaleParser() {
|
|
||||||
LocaleParser.Companion.configure(new LocaleParseHelper());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExpiringMessageManager getExpiringMessageManager() {
|
public ExpiringMessageManager getExpiringMessageManager() {
|
||||||
return expiringMessageManager;
|
return expiringMessageManager;
|
||||||
}
|
}
|
||||||
@ -424,12 +412,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(Context base) {
|
|
||||||
initializeLocaleParser();
|
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ProviderInitializationException extends RuntimeException { }
|
private static class ProviderInitializationException extends RuntimeException { }
|
||||||
private void setUpPollingIfNeeded() {
|
private void setUpPollingIfNeeded() {
|
||||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||||
|
@ -17,8 +17,6 @@ import androidx.appcompat.app.ActionBar;
|
|||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper;
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.WindowUtil;
|
import org.thoughtcrime.securesms.conversation.v2.WindowUtil;
|
||||||
import org.thoughtcrime.securesms.util.ActivityUtilitiesKt;
|
import org.thoughtcrime.securesms.util.ActivityUtilitiesKt;
|
||||||
import org.thoughtcrime.securesms.util.ThemeState;
|
import org.thoughtcrime.securesms.util.ThemeState;
|
||||||
@ -97,7 +95,6 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
initializeScreenshotSecurity(true);
|
initializeScreenshotSecurity(true);
|
||||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
|
||||||
String name = getResources().getString(R.string.app_name);
|
String name = getResources().getString(R.string.app_name);
|
||||||
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
||||||
int color = getResources().getColor(R.color.app_icon_background);
|
int color = getResources().getColor(R.color.app_icon_background);
|
||||||
@ -137,9 +134,4 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(Context newBase) {
|
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,20 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper;
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public abstract class BaseActivity extends FragmentActivity {
|
public abstract class BaseActivity extends FragmentActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
|
||||||
String name = getResources().getString(R.string.app_name);
|
String name = getResources().getString(R.string.app_name);
|
||||||
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
||||||
int color = getResources().getColor(R.color.app_icon_background);
|
int color = getResources().getColor(R.color.app_icon_background);
|
||||||
setTaskDescription(new ActivityManager.TaskDescription(name, icon, color));
|
setTaskDescription(new ActivityManager.TaskDescription(name, icon, color));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(Context newBase) {
|
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ class DeleteMediaDialog {
|
|||||||
iconAttribute(R.attr.dialog_alert_icon)
|
iconAttribute(R.attr.dialog_alert_icon)
|
||||||
title(context.resources.getQuantityString(R.plurals.deleteMessage, recordCount, recordCount))
|
title(context.resources.getQuantityString(R.plurals.deleteMessage, recordCount, recordCount))
|
||||||
text(context.resources.getString(R.string.deleteMessageDescriptionEveryone))
|
text(context.resources.getString(R.string.deleteMessageDescriptionEveryone))
|
||||||
button(R.string.delete) { doDelete.run() }
|
dangerButton(R.string.delete) { doDelete.run() }
|
||||||
cancelButton()
|
cancelButton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import com.squareup.phrase.Phrase
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
|
||||||
import org.thoughtcrime.securesms.permissions.SettingsDialog
|
|
||||||
|
|
||||||
class MissingMicrophonePermissionDialog {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun show(context: Context) = SettingsDialog.show(
|
|
||||||
context,
|
|
||||||
Phrase.from(context, R.string.permissionsMicrophoneAccessRequired)
|
|
||||||
.put(APP_NAME_KEY, context.getString(R.string.app_name))
|
|
||||||
.format().toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,26 +11,21 @@ import androidx.recyclerview.widget.ListAdapter
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.squareup.phrase.Phrase
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewConversationActionBarBinding
|
import network.loki.messenger.databinding.ViewConversationActionBarBinding
|
||||||
import network.loki.messenger.databinding.ViewConversationSettingBinding
|
import network.loki.messenger.databinding.ViewConversationSettingBinding
|
||||||
import network.loki.messenger.libsession_util.util.ExpiryMode.AfterRead
|
import network.loki.messenger.libsession_util.util.ExpiryMode.AfterRead
|
||||||
import org.session.libsession.LocalisedTimeUtil
|
|
||||||
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||||
import org.session.libsession.utilities.ExpirationUtil
|
import org.session.libsession.utilities.ExpirationUtil
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY
|
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY
|
|
||||||
import org.session.libsession.utilities.modifyLayoutParams
|
import org.session.libsession.utilities.modifyLayoutParams
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase
|
import org.thoughtcrime.securesms.database.LokiAPIDatabase
|
||||||
import org.thoughtcrime.securesms.ui.getSubbedString
|
import org.thoughtcrime.securesms.ui.getSubbedString
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ConversationActionBarView @JvmOverloads constructor(
|
class ConversationActionBarView @JvmOverloads constructor(
|
||||||
@ -125,7 +120,7 @@ class ConversationActionBarView @JvmOverloads constructor(
|
|||||||
settings += ConversationSetting(
|
settings += ConversationSetting(
|
||||||
recipient.mutedUntil.takeUnless { it == Long.MAX_VALUE }
|
recipient.mutedUntil.takeUnless { it == Long.MAX_VALUE }
|
||||||
?.let {
|
?.let {
|
||||||
context.getString(R.string.notificationsHeaderMute)
|
context.getString(R.string.notificationsMuted)
|
||||||
}
|
}
|
||||||
?: context.getString(R.string.notificationsMuted),
|
?: context.getString(R.string.notificationsMuted),
|
||||||
ConversationSettingType.NOTIFICATION,
|
ConversationSettingType.NOTIFICATION,
|
||||||
|
@ -2084,7 +2084,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
showSessionDialog {
|
showSessionDialog {
|
||||||
title(resources.getQuantityString(R.plurals.deleteMessage, messages.count(), messages.count()))
|
title(resources.getQuantityString(R.plurals.deleteMessage, messages.count(), messages.count()))
|
||||||
text(resources.getString(R.string.deleteMessageDescriptionEveryone))
|
text(resources.getString(R.string.deleteMessageDescriptionEveryone))
|
||||||
button(R.string.delete) { messages.forEach(viewModel::deleteForEveryone); endActionMode() }
|
dangerButton(R.string.delete) { messages.forEach(viewModel::deleteForEveryone); endActionMode() }
|
||||||
cancelButton { endActionMode() }
|
cancelButton { endActionMode() }
|
||||||
}
|
}
|
||||||
// Otherwise if this is a 1-on-1 conversation we may decided to delete just for ourselves or delete for everyone
|
// Otherwise if this is a 1-on-1 conversation we may decided to delete just for ourselves or delete for everyone
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2
|
package org.thoughtcrime.securesms.conversation.v2
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
@ -14,33 +11,22 @@ import androidx.core.util.getOrDefault
|
|||||||
import androidx.core.util.set
|
import androidx.core.util.set
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import com.bumptech.glide.RequestManager
|
||||||
import kotlin.math.min
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import com.bumptech.glide.RequestManager
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import com.squareup.phrase.Phrase
|
import kotlin.math.min
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
|
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
|
||||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
|
||||||
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
|
|
||||||
import org.thoughtcrime.securesms.ui.getSubbedString
|
|
||||||
|
|
||||||
class ConversationAdapter(
|
class ConversationAdapter(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -5,7 +5,6 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
@ -19,7 +18,6 @@ import androidx.core.content.pm.ShortcutInfoCompat
|
|||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import java.io.IOException
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.sending_receiving.leave
|
import org.session.libsession.messaging.sending_receiving.leave
|
||||||
@ -31,8 +29,6 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.guava.Optional
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
import org.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
|
|
||||||
import org.thoughtcrime.securesms.media.MediaOverviewActivity
|
|
||||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity
|
import org.thoughtcrime.securesms.ShortcutLauncherActivity
|
||||||
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
|
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
|
||||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
||||||
@ -41,12 +37,16 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
|
|||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
|
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
|
||||||
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
|
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
|
||||||
|
import org.thoughtcrime.securesms.media.MediaOverviewActivity
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||||
import org.thoughtcrime.securesms.showMuteDialog
|
import org.thoughtcrime.securesms.showMuteDialog
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
|
import org.thoughtcrime.securesms.ui.findActivity
|
||||||
|
import org.thoughtcrime.securesms.ui.getSubbedString
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
object ConversationMenuHelper {
|
object ConversationMenuHelper {
|
||||||
|
|
||||||
@ -183,7 +183,15 @@ object ConversationMenuHelper {
|
|||||||
// or if the user has not granted audio/microphone permissions
|
// or if the user has not granted audio/microphone permissions
|
||||||
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
|
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
|
||||||
Log.d("Loki", "Attempted to make a call without audio permissions")
|
Log.d("Loki", "Attempted to make a call without audio permissions")
|
||||||
MissingMicrophonePermissionDialog.show(context)
|
|
||||||
|
Permissions.with(context.findActivity())
|
||||||
|
.request(Manifest.permission.RECORD_AUDIO)
|
||||||
|
.withPermanentDenialDialog(
|
||||||
|
context.getSubbedString(R.string.permissionsMicrophoneAccessRequired,
|
||||||
|
APP_NAME_KEY to context.getString(R.string.app_name))
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,10 @@ import network.loki.messenger.databinding.ViewControlMessageBinding
|
|||||||
import network.loki.messenger.libsession_util.util.ExpiryMode
|
import network.loki.messenger.libsession_util.util.ExpiryMode
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
||||||
|
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.getColorFromAttr
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
|
|
||||||
import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages
|
import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages
|
||||||
import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode
|
import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
|
import org.thoughtcrime.securesms.ui.findActivity
|
||||||
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
|
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
|
||||||
import org.thoughtcrime.securesms.ui.getSubbedString
|
import org.thoughtcrime.securesms.ui.getSubbedString
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -138,7 +139,13 @@ class ControlMessageView : LinearLayout {
|
|||||||
!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO) -> {
|
!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO) -> {
|
||||||
showInfo()
|
showInfo()
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
MissingMicrophonePermissionDialog.show(context)
|
Permissions.with(context.findActivity())
|
||||||
|
.request(Manifest.permission.RECORD_AUDIO)
|
||||||
|
.withPermanentDenialDialog(
|
||||||
|
context.getSubbedString(R.string.permissionsMicrophoneAccessRequired,
|
||||||
|
APP_NAME_KEY to context.getString(R.string.app_name))
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ public class AttachmentManager {
|
|||||||
.request(Manifest.permission.READ_MEDIA_IMAGES)
|
.request(Manifest.permission.READ_MEDIA_IMAGES)
|
||||||
.request(Manifest.permission.READ_MEDIA_AUDIO)
|
.request(Manifest.permission.READ_MEDIA_AUDIO)
|
||||||
.withRationaleDialog(
|
.withRationaleDialog(
|
||||||
Phrase.from(c, R.string.permissionsStorageSend)
|
Phrase.from(c, R.string.permissionsMusicAudio)
|
||||||
.put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString()
|
.put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString()
|
||||||
)
|
)
|
||||||
.withPermanentDenialDialog(
|
.withPermanentDenialDialog(
|
||||||
|
@ -1,236 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database.loaders;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.database.Cursor;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.loader.content.AsyncTaskLoader;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.session.libsession.utilities.Address;
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
|
||||||
import org.thoughtcrime.securesms.util.RelativeDay;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMediaLoader.BucketedThreadMedia> {
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final String TAG = BucketedThreadMediaLoader.class.getSimpleName();
|
|
||||||
|
|
||||||
private final Address address;
|
|
||||||
private final ContentObserver observer;
|
|
||||||
|
|
||||||
public BucketedThreadMediaLoader(@NonNull Context context, @NonNull Address address) {
|
|
||||||
super(context);
|
|
||||||
this.address = address;
|
|
||||||
this.observer = new ForceLoadContentObserver();
|
|
||||||
|
|
||||||
onContentChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
if (takeContentChanged()) {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStopLoading() {
|
|
||||||
cancelLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onAbandon() {
|
|
||||||
DatabaseComponent.get(getContext()).mediaDatabase().unsubscribeToMediaChanges(observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BucketedThreadMedia loadInBackground() {
|
|
||||||
BucketedThreadMedia result = new BucketedThreadMedia(getContext());
|
|
||||||
long threadId = DatabaseComponent.get(getContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(getContext(), address, true));
|
|
||||||
|
|
||||||
MediaDatabase mediaDatabase = DatabaseComponent.get(getContext()).mediaDatabase();
|
|
||||||
|
|
||||||
mediaDatabase.subscribeToMediaChanges(observer);
|
|
||||||
try (Cursor cursor = mediaDatabase.getGalleryMediaForThread(threadId)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
result.add(MediaDatabase.MediaRecord.from(getContext(), cursor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BucketedThreadMedia {
|
|
||||||
|
|
||||||
private final TimeBucket TODAY;
|
|
||||||
private final TimeBucket YESTERDAY;
|
|
||||||
private final TimeBucket THIS_WEEK;
|
|
||||||
private final TimeBucket THIS_MONTH;
|
|
||||||
private final MonthBuckets OLDER;
|
|
||||||
|
|
||||||
private final TimeBucket[] TIME_SECTIONS;
|
|
||||||
|
|
||||||
public BucketedThreadMedia(@NonNull Context context) {
|
|
||||||
String localisedTodayString = DateUtils.INSTANCE.getLocalisedRelativeDayString(RelativeDay.TODAY);
|
|
||||||
String localisedYesterdayString = DateUtils.INSTANCE.getLocalisedRelativeDayString(RelativeDay.YESTERDAY);
|
|
||||||
|
|
||||||
this.TODAY = new TimeBucket(localisedTodayString, TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -1), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, 1000));
|
|
||||||
this.YESTERDAY = new TimeBucket(localisedYesterdayString, TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -2), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -1));
|
|
||||||
this.THIS_WEEK = new TimeBucket(context.getString(R.string.attachmentsThisWeek), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -7), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -2));
|
|
||||||
this.THIS_MONTH = new TimeBucket(context.getString(R.string.attachmentsThisMonth), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -30), TimeBucket.addToCalendar(Calendar.DAY_OF_YEAR, -7));
|
|
||||||
this.TIME_SECTIONS = new TimeBucket[] { TODAY, YESTERDAY, THIS_WEEK, THIS_MONTH };
|
|
||||||
this.OLDER = new MonthBuckets();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(MediaDatabase.MediaRecord mediaRecord) {
|
|
||||||
for (TimeBucket timeSection : TIME_SECTIONS) {
|
|
||||||
if (timeSection.inRange(mediaRecord.getDate())) {
|
|
||||||
timeSection.add(mediaRecord);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OLDER.add(mediaRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSectionCount() {
|
|
||||||
return (int)Stream.of(TIME_SECTIONS)
|
|
||||||
.filter(timeBucket -> !timeBucket.isEmpty())
|
|
||||||
.count() +
|
|
||||||
OLDER.getSectionCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSectionItemCount(int section) {
|
|
||||||
List<TimeBucket> activeTimeBuckets = Stream.of(TIME_SECTIONS).filter(timeBucket -> !timeBucket.isEmpty()).toList();
|
|
||||||
|
|
||||||
if (section < activeTimeBuckets.size()) return activeTimeBuckets.get(section).getItemCount();
|
|
||||||
else return OLDER.getSectionItemCount(section - activeTimeBuckets.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaDatabase.MediaRecord get(int section, int item) {
|
|
||||||
List<TimeBucket> activeTimeBuckets = Stream.of(TIME_SECTIONS).filter(timeBucket -> !timeBucket.isEmpty()).toList();
|
|
||||||
|
|
||||||
if (section < activeTimeBuckets.size()) return activeTimeBuckets.get(section).getItem(item);
|
|
||||||
else return OLDER.getItem(section - activeTimeBuckets.size(), item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName(int section, Locale locale) {
|
|
||||||
List<TimeBucket> activeTimeBuckets = Stream.of(TIME_SECTIONS).filter(timeBucket -> !timeBucket.isEmpty()).toList();
|
|
||||||
|
|
||||||
if (section < activeTimeBuckets.size()) return activeTimeBuckets.get(section).getName();
|
|
||||||
else return OLDER.getName(section - activeTimeBuckets.size(), locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TimeBucket {
|
|
||||||
|
|
||||||
private final List<MediaDatabase.MediaRecord> records = new LinkedList<>();
|
|
||||||
|
|
||||||
private final long startTime;
|
|
||||||
private final long endtime;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
TimeBucket(String name, long startTime, long endtime) {
|
|
||||||
this.name = name;
|
|
||||||
this.startTime = startTime;
|
|
||||||
this.endtime = endtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(MediaDatabase.MediaRecord record) {
|
|
||||||
this.records.add(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean inRange(long timestamp) {
|
|
||||||
return timestamp > startTime && timestamp <= endtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isEmpty() {
|
|
||||||
return records.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getItemCount() {
|
|
||||||
return records.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaDatabase.MediaRecord getItem(int position) {
|
|
||||||
return records.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static long addToCalendar(int field, int amount) {
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.add(field, amount);
|
|
||||||
return calendar.getTimeInMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MonthBuckets {
|
|
||||||
|
|
||||||
private final Map<Date, List<MediaDatabase.MediaRecord>> months = new HashMap<>();
|
|
||||||
|
|
||||||
void add(MediaDatabase.MediaRecord record) {
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.setTimeInMillis(record.getDate());
|
|
||||||
|
|
||||||
int year = calendar.get(Calendar.YEAR) - 1900;
|
|
||||||
int month = calendar.get(Calendar.MONTH);
|
|
||||||
Date date = new Date(year, month, 1);
|
|
||||||
|
|
||||||
if (months.containsKey(date)) {
|
|
||||||
months.get(date).add(record);
|
|
||||||
} else {
|
|
||||||
List<MediaDatabase.MediaRecord> list = new LinkedList<>();
|
|
||||||
list.add(record);
|
|
||||||
months.put(date, list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSectionCount() {
|
|
||||||
return months.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSectionItemCount(int section) {
|
|
||||||
return months.get(getSection(section)).size();
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaDatabase.MediaRecord getItem(int section, int position) {
|
|
||||||
return months.get(getSection(section)).get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
Date getSection(int section) {
|
|
||||||
ArrayList<Date> keys = new ArrayList<>(months.keySet());
|
|
||||||
Collections.sort(keys, Collections.reverseOrder());
|
|
||||||
|
|
||||||
return keys.get(section);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getName(int section, Locale locale) {
|
|
||||||
Date sectionDate = getSection(section);
|
|
||||||
|
|
||||||
return new SimpleDateFormat("MMMM, yyyy", locale).format(sectionDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,10 @@
|
|||||||
package org.thoughtcrime.securesms.media
|
package org.thoughtcrime.securesms.media
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
|
import org.thoughtcrime.securesms.util.RelativeDay
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.temporal.WeekFields
|
import java.time.temporal.WeekFields
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -29,16 +32,15 @@ class FixedTimeBuckets(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the given time against the buckets and return the appropriate string resource the time
|
* Test the given time against the buckets and return the appropriate string the time
|
||||||
* bucket. If no bucket is appropriate, it will return null.
|
* bucket. If no bucket is appropriate, it will return null.
|
||||||
*/
|
*/
|
||||||
@StringRes
|
fun getBucketText(context: Context, time: ZonedDateTime): String? {
|
||||||
fun getBucketText(time: ZonedDateTime): Int? {
|
|
||||||
return when {
|
return when {
|
||||||
time >= startOfToday -> R.string.BucketedThreadMedia_Today
|
time >= startOfToday -> DateUtils.getLocalisedRelativeDayString(RelativeDay.TODAY)
|
||||||
time >= startOfYesterday -> R.string.BucketedThreadMedia_Yesterday
|
time >= startOfYesterday -> DateUtils.getLocalisedRelativeDayString(RelativeDay.YESTERDAY)
|
||||||
time >= startOfThisWeek -> R.string.attachmentsThisWeek
|
time >= startOfThisWeek -> context.getString(R.string.attachmentsThisWeek)
|
||||||
time >= startOfThisMonth -> R.string.attachmentsThisMonth
|
time >= startOfThisMonth -> context.getString(R.string.attachmentsThisMonth)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,12 +247,7 @@ private fun DeleteConfirmationDialog(
|
|||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = context.resources.getQuantityString(
|
title = context.resources.getQuantityString(
|
||||||
R.plurals.ConversationFragment_delete_selected_messages, numSelected
|
R.plurals.deleteMessage, numSelected
|
||||||
),
|
|
||||||
text = context.resources.getQuantityString(
|
|
||||||
R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages,
|
|
||||||
numSelected,
|
|
||||||
numSelected,
|
|
||||||
),
|
),
|
||||||
buttons = listOf(
|
buttons = listOf(
|
||||||
DialogButtonModel(GetString(R.string.delete), color = LocalColors.current.danger, onClick = onAccepted),
|
DialogButtonModel(GetString(R.string.delete), color = LocalColors.current.danger, onClick = onAccepted),
|
||||||
|
@ -130,7 +130,7 @@ class MediaOverviewViewModel(
|
|||||||
.groupBy { record ->
|
.groupBy { record ->
|
||||||
val time =
|
val time =
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(record.date), ZoneId.of("UTC"))
|
ZonedDateTime.ofInstant(Instant.ofEpochMilli(record.date), ZoneId.of("UTC"))
|
||||||
timeBuckets.getBucketText(time)?.let(application::getString)
|
timeBuckets.getBucketText(application, time)
|
||||||
?: time.toLocalDate().withDayOfMonth(1)
|
?: time.toLocalDate().withDayOfMonth(1)
|
||||||
}
|
}
|
||||||
.map { (bucket, records) ->
|
.map { (bucket, records) ->
|
||||||
|
@ -52,9 +52,6 @@ object DateUtils : android.text.format.DateUtils() {
|
|||||||
return isToday(`when` + TimeUnit.DAYS.toMillis(1))
|
return isToday(`when` + TimeUnit.DAYS.toMillis(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertDelta(millis: Long, to: TimeUnit): Int {
|
|
||||||
return to.convert(System.currentTimeMillis() - millis, TimeUnit.MILLISECONDS).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to get the String for a relative day in a locale-aware fashion
|
// Method to get the String for a relative day in a locale-aware fashion
|
||||||
public fun getLocalisedRelativeDayString(relativeDay: RelativeDay): String {
|
public fun getLocalisedRelativeDayString(relativeDay: RelativeDay): String {
|
||||||
@ -76,10 +73,12 @@ object DateUtils : android.text.format.DateUtils() {
|
|||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getRelativeTimeSpanString(comparisonTime.timeInMillis,
|
val temp = getRelativeTimeSpanString(
|
||||||
|
comparisonTime.timeInMillis,
|
||||||
now.timeInMillis,
|
now.timeInMillis,
|
||||||
DAY_IN_MILLIS,
|
DAY_IN_MILLIS,
|
||||||
FORMAT_SHOW_DATE).toString()
|
FORMAT_SHOW_DATE).toString()
|
||||||
|
return temp
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFormattedDateTime(time: Long, template: String, locale: Locale): String {
|
fun getFormattedDateTime(time: Long, template: String, locale: Locale): String {
|
||||||
@ -142,30 +141,4 @@ object DateUtils : android.text.format.DateUtils() {
|
|||||||
private fun getLocalizedPattern(template: String, locale: Locale): String {
|
private fun getLocalizedPattern(template: String, locale: Locale): String {
|
||||||
return DateFormat.getBestDateTimePattern(locale, template)
|
return DateFormat.getBestDateTimePattern(locale, template)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* e.g. 2020-09-04T19:17:51Z
|
|
||||||
* https://www.iso.org/iso-8601-date-and-time-format.html
|
|
||||||
*
|
|
||||||
* @return The timestamp if able to be parsed, otherwise -1.
|
|
||||||
*/
|
|
||||||
@SuppressLint("ObsoleteSdkInt")
|
|
||||||
@JvmStatic
|
|
||||||
public fun parseIso8601(date: String?): Long {
|
|
||||||
|
|
||||||
if (date.isNullOrEmpty()) { return -1 }
|
|
||||||
|
|
||||||
val format = if (Build.VERSION.SDK_INT >= 24) {
|
|
||||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault())
|
|
||||||
} else {
|
|
||||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return format.parse(date).time
|
|
||||||
} catch (e: ParseException) {
|
|
||||||
Log.w(TAG, "Failed to parse date.", e)
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,26 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.util.dynamiclanguage
|
|
||||||
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.core.os.ConfigurationCompat
|
|
||||||
import network.loki.messenger.BuildConfig
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParserHelperProtocol
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class LocaleParseHelper: LocaleParserHelperProtocol {
|
|
||||||
|
|
||||||
override fun appSupportsTheExactLocale(locale: Locale?): Boolean {
|
|
||||||
return if (locale == null) {
|
|
||||||
false
|
|
||||||
} else Arrays.asList(*BuildConfig.LANGUAGES).contains(locale.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findBestSystemLocale(): Locale {
|
|
||||||
val config = Resources.getSystem().configuration
|
|
||||||
|
|
||||||
val firstMatch = ConfigurationCompat.getLocales(config)
|
|
||||||
.getFirstMatch(BuildConfig.LANGUAGES)
|
|
||||||
|
|
||||||
return firstMatch ?: Locale.ENGLISH
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!--
|
|
||||||
All localisable strings are now in the 'libsession' module (which will be renamed to
|
|
||||||
'common' in the near future), and all content description / AccessibilityID_ strings are in
|
|
||||||
their own 'content-descriptions' module.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- TODO: These need to be removed and the text generated by DateUtils.getLocalisedRelativeDayString - but
|
|
||||||
it's going to be a nuisance because that returns a string not a resource ID so just leaving them for now -AL -->
|
|
||||||
<string name="BucketedThreadMedia_Today">Today</string>
|
|
||||||
<string name="BucketedThreadMedia_Yesterday">Yesterday</string>
|
|
||||||
|
|
||||||
<!-- TODO: We'll also need plurals for these strings -->
|
|
||||||
<plurals name="ConversationFragment_delete_selected_messages">
|
|
||||||
<item quantity="one">Delete selected message?</item>
|
|
||||||
<item quantity="other">Delete selected messages?</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="ConversationFragment_this_will_permanently_delete_all_n_selected_messages">
|
|
||||||
<item quantity="one">This will permanently delete the selected message.</item>
|
|
||||||
<item quantity="other">This will permanently delete all %1$d selected messages.</item>
|
|
||||||
</plurals>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -59,12 +59,6 @@ public final class LanguageResourcesTest {
|
|||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> buildConfigResources() {
|
|
||||||
Set<String> set = new HashSet<>();
|
|
||||||
Collections.addAll(set, BuildConfig.LANGUAGES);
|
|
||||||
assertEquals("List contains duplicates", BuildConfig.LANGUAGES.length, set.size());
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fails if "a" is not a subset of "b", lists the additional values found in "a"
|
* Fails if "a" is not a subset of "b", lists the additional values found in "a"
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.Parameterized;
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.LanguageString;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
|
||||||
public final class LanguageStringTest {
|
|
||||||
|
|
||||||
private final Locale expected;
|
|
||||||
private final String input;
|
|
||||||
|
|
||||||
@Parameterized.Parameters
|
|
||||||
public static Collection<Object[]> data() {
|
|
||||||
return Arrays.asList(new Object[][]{
|
|
||||||
|
|
||||||
/* Language */
|
|
||||||
{ new Locale("en"), "en" },
|
|
||||||
{ new Locale("de"), "de" },
|
|
||||||
{ new Locale("fr"), "FR" },
|
|
||||||
|
|
||||||
/* Language and region */
|
|
||||||
{ new Locale("en", "US"), "en_US" },
|
|
||||||
{ new Locale("es", "US"), "es_US" },
|
|
||||||
{ new Locale("es", "MX"), "es_MX" },
|
|
||||||
{ new Locale("es", "MX"), "es_mx" },
|
|
||||||
{ new Locale("de", "DE"), "de_DE" },
|
|
||||||
|
|
||||||
/* Not parsable input */
|
|
||||||
{ null, null },
|
|
||||||
{ null, "" },
|
|
||||||
{ null, "zz" },
|
|
||||||
{ null, "zz_ZZ" },
|
|
||||||
{ null, "fr_ZZ" },
|
|
||||||
{ null, "zz_FR" },
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public LanguageStringTest(Locale expected, String input) {
|
|
||||||
this.expected = expected;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parse() {
|
|
||||||
assertEquals(expected, LanguageString.parseLocale(input));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
import org.robolectric.annotation.Config;
|
|
||||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
|
||||||
|
|
||||||
import network.loki.messenger.BuildConfig;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
|
|
||||||
//FIXME AC: This test group is outdated.
|
|
||||||
@Ignore("This test group uses outdated instrumentation and needs a migration to modern tools.")
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
@Config(manifest = Config.NONE, application = Application.class)
|
|
||||||
public final class LocaleParserTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findBestMatchingLocaleForLanguage_all_build_config_languages_can_be_resolved() {
|
|
||||||
for (String lang : buildConfigLanguages()) {
|
|
||||||
Locale locale = LocaleParser.findBestMatchingLocaleForLanguage(lang);
|
|
||||||
assertEquals(lang, locale.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Config(qualifiers = "fr")
|
|
||||||
public void findBestMatchingLocaleForLanguage_a_non_build_config_language_defaults_to_device_value_which_is_supported_directly() {
|
|
||||||
String unsupportedLanguage = getUnsupportedLanguage();
|
|
||||||
assertEquals(Locale.FRENCH, LocaleParser.findBestMatchingLocaleForLanguage(unsupportedLanguage));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Config(qualifiers = "en-rCA")
|
|
||||||
public void findBestMatchingLocaleForLanguage_a_non_build_config_language_defaults_to_device_value_which_is_not_supported_directly() {
|
|
||||||
String unsupportedLanguage = getUnsupportedLanguage();
|
|
||||||
assertEquals(Locale.CANADA, LocaleParser.findBestMatchingLocaleForLanguage(unsupportedLanguage));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getUnsupportedLanguage() {
|
|
||||||
String unsupportedLanguage = "af";
|
|
||||||
assertFalse("Language should be an unsupported one", buildConfigLanguages().contains(unsupportedLanguage));
|
|
||||||
return unsupportedLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> buildConfigLanguages() {
|
|
||||||
return Arrays.asList(BuildConfig.LANGUAGES);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package org.session.libsession.utilities.dynamiclanguage;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
import androidx.annotation.MainThread;
|
|
||||||
import androidx.core.os.ConfigurationCompat;
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public final class DynamicLanguageActivityHelper {
|
|
||||||
|
|
||||||
private static final String TAG = DynamicLanguageActivityHelper.class.getSimpleName();
|
|
||||||
|
|
||||||
private static String reentryProtection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the activity isn't in the specified language, it will restart the activity.
|
|
||||||
*/
|
|
||||||
@MainThread
|
|
||||||
public static void recreateIfNotInCorrectLanguage(Activity activity, String language) {
|
|
||||||
Locale currentActivityLocale = ConfigurationCompat.getLocales(activity.getResources().getConfiguration()).get(0);
|
|
||||||
Locale selectedLocale = LocaleParser.findBestMatchingLocaleForLanguage(language);
|
|
||||||
|
|
||||||
if (currentActivityLocale.equals(selectedLocale)) {
|
|
||||||
reentryProtection = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String reentryKey = activity.getClass().getName() + ":" + selectedLocale;
|
|
||||||
if (!reentryKey.equals(reentryProtection)) {
|
|
||||||
reentryProtection = reentryKey;
|
|
||||||
Log.d(TAG, String.format("Activity Locale %s, Selected locale %s, restarting", currentActivityLocale, selectedLocale));
|
|
||||||
activity.recreate();
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, String.format("Skipping recreate as looks like looping, Activity Locale %s, Selected locale %s", currentActivityLocale, selectedLocale));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.session.libsession.utilities.dynamiclanguage;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a context with an alternative language.
|
|
||||||
*/
|
|
||||||
public final class DynamicLanguageContextWrapper {
|
|
||||||
|
|
||||||
public static Context updateContext(Context context, String language) {
|
|
||||||
final Locale newLocale = LocaleParser.findBestMatchingLocaleForLanguage(language);
|
|
||||||
|
|
||||||
Locale.setDefault(newLocale);
|
|
||||||
|
|
||||||
final Resources resources = context.getResources();
|
|
||||||
final Configuration config = resources.getConfiguration();
|
|
||||||
final Configuration newConfig = copyWithNewLocale(config, newLocale);
|
|
||||||
|
|
||||||
resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Configuration copyWithNewLocale(Configuration config, Locale locale) {
|
|
||||||
final Configuration copy = new Configuration(config);
|
|
||||||
copy.setLocale(locale);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package org.session.libsession.utilities.dynamiclanguage;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public final class LanguageString {
|
|
||||||
|
|
||||||
private LanguageString() { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param languageString String in format language_REGION, e.g. en_US
|
|
||||||
* @return Locale, or null if cannot parse
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Locale parseLocale(@Nullable String languageString) {
|
|
||||||
if (languageString == null || languageString.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Locale locale = createLocale(languageString);
|
|
||||||
|
|
||||||
if (!isValid(locale)) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Locale createLocale(@NonNull String languageString) {
|
|
||||||
final String language[] = languageString.split("_");
|
|
||||||
if (language.length == 2) {
|
|
||||||
return new Locale(language[0], language[1]);
|
|
||||||
} else {
|
|
||||||
return new Locale(language[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isValid(@NonNull Locale locale) {
|
|
||||||
try {
|
|
||||||
return locale.getISO3Language() != null && locale.getISO3Country() != null;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package org.session.libsession.utilities.dynamiclanguage
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class LocaleParser(val helper: LocaleParserHelperProtocol) {
|
|
||||||
companion object {
|
|
||||||
lateinit var shared: LocaleParser
|
|
||||||
|
|
||||||
fun configure(helper: LocaleParserHelperProtocol) {
|
|
||||||
if (Companion::shared.isInitialized) { return }
|
|
||||||
shared = LocaleParser(helper)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a language, gets the best choice from the apps list of supported languages and the
|
|
||||||
* Systems set of languages.
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
fun findBestMatchingLocaleForLanguage(language: String?): Locale? {
|
|
||||||
val locale = LanguageString.parseLocale(language)
|
|
||||||
return if (shared.helper.appSupportsTheExactLocale(locale)) {
|
|
||||||
locale
|
|
||||||
} else {
|
|
||||||
shared.helper.findBestSystemLocale()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package org.session.libsession.utilities.dynamiclanguage
|
|
||||||
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
interface LocaleParserHelperProtocol {
|
|
||||||
fun appSupportsTheExactLocale(locale: Locale?): Boolean
|
|
||||||
fun findBestSystemLocale(): Locale
|
|
||||||
}
|
|
@ -146,7 +146,7 @@
|
|||||||
<string name="callsIncomingUnknown">Incoming call</string>
|
<string name="callsIncomingUnknown">Incoming call</string>
|
||||||
<string name="callsMissed">Missed Call</string>
|
<string name="callsMissed">Missed Call</string>
|
||||||
<string name="callsMissedCallFrom">Missed call from {name}</string>
|
<string name="callsMissedCallFrom">Missed call from {name}</string>
|
||||||
<string name="callsNotificationsRequired">Voice and Video Calls require notifications to be enabled in your device system settings. </string>
|
<string name="callsNotificationsRequired">Voice and Video Calls require notifications to be enabled in your device system settings.</string>
|
||||||
<string name="callsPermissionsRequired">Call Permissions Required</string>
|
<string name="callsPermissionsRequired">Call Permissions Required</string>
|
||||||
<string name="callsPermissionsRequiredDescription">You can enable the \"Voice and Video Calls\" permission in Privacy Settings.</string>
|
<string name="callsPermissionsRequiredDescription">You can enable the \"Voice and Video Calls\" permission in Privacy Settings.</string>
|
||||||
<string name="callsReconnecting">Reconnecting…</string>
|
<string name="callsReconnecting">Reconnecting…</string>
|
||||||
@ -436,7 +436,7 @@
|
|||||||
<string name="groupNameEnter">Enter a group name</string>
|
<string name="groupNameEnter">Enter a group name</string>
|
||||||
<string name="groupNameEnterPlease">Please enter a group name.</string>
|
<string name="groupNameEnterPlease">Please enter a group name.</string>
|
||||||
<string name="groupNameEnterShorter">Please enter a shorter group name.</string>
|
<string name="groupNameEnterShorter">Please enter a shorter group name.</string>
|
||||||
<string name="groupNameNew">Group name is now {group_name}. </string>
|
<string name="groupNameNew">Group name is now {group_name}.</string>
|
||||||
<string name="groupNameUpdated">Group name updated.</string>
|
<string name="groupNameUpdated">Group name updated.</string>
|
||||||
<string name="groupNoMessages">You have no messages from <b>{group_name}</b>. Send a message to start the conversation!</string>
|
<string name="groupNoMessages">You have no messages from <b>{group_name}</b>. Send a message to start the conversation!</string>
|
||||||
<string name="groupOnlyAdmin">You are the only admin in <b>{group_name}</b>.\n\nGroup members and settings cannot be changed without an admin.</string>
|
<string name="groupOnlyAdmin">You are the only admin in <b>{group_name}</b>.\n\nGroup members and settings cannot be changed without an admin.</string>
|
||||||
@ -692,7 +692,7 @@
|
|||||||
<string name="permissionsMicrophoneAccessRequiredIos">{app_name} needs microphone access to make calls and record audio messages.</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="permissionsMicrophoneDescription">Allow access to microphone.</string>
|
||||||
<string name="permissionsMusicAudio">{app_name} needs music and audio access in order to send files, music and audio.</string>
|
<string name="permissionsMusicAudio">{app_name} needs music and audio access in order to send files, music and audio.</string>
|
||||||
<string name="permissionsRequired">Permission required</string>
|
<string name="permissionsRequired">Permission Required</string>
|
||||||
<string name="permissionsStorageDenied">{app_name} needs photo library access so you can send photos and videos, but it has been permanently denied. Tap Settings -> Permissions, and turn \"Photos and videos\" on.</string>
|
<string name="permissionsStorageDenied">{app_name} needs photo library access so you can send photos and videos, but it has been permanently denied. Tap Settings -> Permissions, and turn \"Photos and videos\" on.</string>
|
||||||
<string name="permissionsStorageDeniedLegacy">{app_name} needs storage access so you can send and save attachments. Tap Settings -> Permissions, and turn \"Storage\" on.</string>
|
<string name="permissionsStorageDeniedLegacy">{app_name} needs storage access so you can send and save attachments. Tap Settings -> Permissions, and turn \"Storage\" on.</string>
|
||||||
<string name="permissionsStorageSave">{app_name} needs storage access to save attachments and media.</string>
|
<string name="permissionsStorageSave">{app_name} needs storage access to save attachments and media.</string>
|
||||||
@ -814,7 +814,7 @@
|
|||||||
<string name="updateError">Cannot Update</string>
|
<string name="updateError">Cannot Update</string>
|
||||||
<string name="updateErrorDescription">{app_name} failed to update. Please go to {session_download_url} and install the new version manually, then contact our Help Center to let us know about this problem.</string>
|
<string name="updateErrorDescription">{app_name} failed to update. Please go to {session_download_url} and install the new version manually, then contact our Help Center to let us know about this problem.</string>
|
||||||
<string name="updateNewVersion">A new version of {app_name} is available, tap to update</string>
|
<string name="updateNewVersion">A new version of {app_name} is available, tap to update</string>
|
||||||
<string name="updateNewVersionDescription">A new version of {app_name} is available. </string>
|
<string name="updateNewVersionDescription">A new version of {app_name} is available.</string>
|
||||||
<string name="updateReleaseNotes">Go to Release Notes</string>
|
<string name="updateReleaseNotes">Go to Release Notes</string>
|
||||||
<string name="updateSession">{app_name} Update</string>
|
<string name="updateSession">{app_name} Update</string>
|
||||||
<string name="updateVersion">Version {version}</string>
|
<string name="updateVersion">Version {version}</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user