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:
ThomasSession 2024-09-12 16:29:27 +10:00
parent d30ff252ab
commit 304679dac6
28 changed files with 48 additions and 698 deletions

View File

@ -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 += []

View File

@ -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);

View File

@ -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)));
}
} }

View File

@ -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)));
}
} }

View File

@ -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()
} }
} }

View File

@ -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()
)
}
}

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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
} }

View File

@ -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()
} }
} }

View File

@ -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(

View File

@ -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);
}
}
}
}

View File

@ -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
} }
} }

View File

@ -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),

View File

@ -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) ->

View File

@ -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
}
}
} }

View File

@ -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
}
}

View File

@ -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>

View File

@ -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"

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -1,8 +0,0 @@
package org.session.libsession.utilities.dynamiclanguage
import java.util.Locale
interface LocaleParserHelperProtocol {
fun appSupportsTheExactLocale(locale: Locale?): Boolean
fun findBestSystemLocale(): Locale
}

View File

@ -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 -&gt; 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 -&gt; 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 -&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>
<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>