mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-21 15:05:19 +00:00
Merge pull request #1664 from oxen-io/fix/strings-update
Strings update
This commit is contained in:
commit
41386fc716
@ -88,7 +88,6 @@ android {
|
||||
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
||||
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
||||
buildConfigField "String", "USER_AGENT", "\"OWA\""
|
||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||
resourceConfigurations += []
|
||||
|
||||
|
@ -35,14 +35,12 @@ import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.session.libsession.avatars.AvatarHelper;
|
||||
import org.session.libsession.database.MessageDataProvider;
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||
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.Poller;
|
||||
import org.session.libsession.snode.SnodeModule;
|
||||
import org.session.libsession.utilities.Address;
|
||||
import org.session.libsession.utilities.ConfigFactoryUpdateListener;
|
||||
import org.session.libsession.utilities.Device;
|
||||
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.Util;
|
||||
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.JsonUtil;
|
||||
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.util.Broadcaster;
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -110,7 +103,6 @@ import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.EntryPoints;
|
||||
import dagger.hilt.android.HiltAndroidApp;
|
||||
import kotlin.Unit;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import network.loki.messenger.R;
|
||||
import network.loki.messenger.libsession_util.ConfigBase;
|
||||
@ -320,10 +312,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
public void initializeLocaleParser() {
|
||||
LocaleParser.Companion.configure(new LocaleParseHelper());
|
||||
}
|
||||
|
||||
public ExpiringMessageManager getExpiringMessageManager() {
|
||||
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 void setUpPollingIfNeeded() {
|
||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
|
@ -17,8 +17,6 @@ import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
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.util.ActivityUtilitiesKt;
|
||||
import org.thoughtcrime.securesms.util.ThemeState;
|
||||
@ -97,7 +95,6 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
initializeScreenshotSecurity(true);
|
||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
||||
String name = getResources().getString(R.string.app_name);
|
||||
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
||||
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;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
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;
|
||||
|
||||
public abstract class BaseActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
||||
String name = getResources().getString(R.string.app_name);
|
||||
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
|
||||
int color = getResources().getColor(R.color.app_icon_background);
|
||||
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)
|
||||
title(context.resources.getQuantityString(R.plurals.deleteMessage, recordCount, recordCount))
|
||||
text(context.resources.getString(R.string.deleteMessageDescriptionEveryone))
|
||||
button(R.string.delete) { doDelete.run() }
|
||||
dangerButton(R.string.delete) { doDelete.run() }
|
||||
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.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.squareup.phrase.Phrase
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewConversationActionBarBinding
|
||||
import network.loki.messenger.databinding.ViewConversationSettingBinding
|
||||
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.open_groups.OpenGroup
|
||||
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_LARGE_KEY
|
||||
import org.session.libsession.utilities.modifyLayoutParams
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase
|
||||
import org.thoughtcrime.securesms.ui.getSubbedString
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ConversationActionBarView @JvmOverloads constructor(
|
||||
@ -125,7 +120,7 @@ class ConversationActionBarView @JvmOverloads constructor(
|
||||
settings += ConversationSetting(
|
||||
recipient.mutedUntil.takeUnless { it == Long.MAX_VALUE }
|
||||
?.let {
|
||||
context.getString(R.string.notificationsHeaderMute)
|
||||
context.getString(R.string.notificationsMuted)
|
||||
}
|
||||
?: context.getString(R.string.notificationsMuted),
|
||||
ConversationSettingType.NOTIFICATION,
|
||||
|
@ -2084,7 +2084,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
showSessionDialog {
|
||||
title(resources.getQuantityString(R.plurals.deleteMessage, messages.count(), messages.count()))
|
||||
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() }
|
||||
}
|
||||
// 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
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.util.SparseArray
|
||||
import android.util.SparseBooleanArray
|
||||
import android.view.MotionEvent
|
||||
@ -14,33 +11,22 @@ import androidx.core.util.getOrDefault
|
||||
import androidx.core.util.set
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.math.min
|
||||
import com.bumptech.glide.RequestManager
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
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.VisibleMessageView
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.squareup.phrase.Phrase
|
||||
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
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.math.min
|
||||
|
||||
class ConversationAdapter(
|
||||
context: Context,
|
||||
|
@ -5,7 +5,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
@ -19,7 +18,6 @@ import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.squareup.phrase.Phrase
|
||||
import java.io.IOException
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
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.guava.Optional
|
||||
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.calls.WebRtcCallActivity
|
||||
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.groups.EditClosedGroupActivity
|
||||
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
|
||||
import org.thoughtcrime.securesms.media.MediaOverviewActivity
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||
import org.thoughtcrime.securesms.showMuteDialog
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.ui.findActivity
|
||||
import org.thoughtcrime.securesms.ui.getSubbedString
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import java.io.IOException
|
||||
|
||||
object ConversationMenuHelper {
|
||||
|
||||
@ -183,7 +183,15 @@ object ConversationMenuHelper {
|
||||
// or if the user has not granted audio/microphone permissions
|
||||
else if (!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,10 @@ import network.loki.messenger.databinding.ViewControlMessageBinding
|
||||
import network.loki.messenger.libsession_util.util.ExpiryMode
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
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.TextSecurePreferences
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.thoughtcrime.securesms.MissingMicrophonePermissionDialog
|
||||
import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages
|
||||
import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode
|
||||
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.preferences.PrivacySettingsActivity
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.ui.findActivity
|
||||
import org.thoughtcrime.securesms.ui.getSubbedCharSequence
|
||||
import org.thoughtcrime.securesms.ui.getSubbedString
|
||||
import javax.inject.Inject
|
||||
@ -138,7 +139,13 @@ class ControlMessageView : LinearLayout {
|
||||
!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO) -> {
|
||||
showInfo()
|
||||
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_AUDIO)
|
||||
.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()
|
||||
)
|
||||
.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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.RelativeDay
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.WeekFields
|
||||
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.
|
||||
*/
|
||||
@StringRes
|
||||
fun getBucketText(time: ZonedDateTime): Int? {
|
||||
fun getBucketText(context: Context, time: ZonedDateTime): String? {
|
||||
return when {
|
||||
time >= startOfToday -> R.string.BucketedThreadMedia_Today
|
||||
time >= startOfYesterday -> R.string.BucketedThreadMedia_Yesterday
|
||||
time >= startOfThisWeek -> R.string.attachmentsThisWeek
|
||||
time >= startOfThisMonth -> R.string.attachmentsThisMonth
|
||||
time >= startOfToday -> DateUtils.getLocalisedRelativeDayString(RelativeDay.TODAY)
|
||||
time >= startOfYesterday -> DateUtils.getLocalisedRelativeDayString(RelativeDay.YESTERDAY)
|
||||
time >= startOfThisWeek -> context.getString(R.string.attachmentsThisWeek)
|
||||
time >= startOfThisMonth -> context.getString(R.string.attachmentsThisMonth)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
@ -247,12 +247,7 @@ private fun DeleteConfirmationDialog(
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = context.resources.getQuantityString(
|
||||
R.plurals.ConversationFragment_delete_selected_messages, numSelected
|
||||
),
|
||||
text = context.resources.getQuantityString(
|
||||
R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages,
|
||||
numSelected,
|
||||
numSelected,
|
||||
R.plurals.deleteMessage, numSelected
|
||||
),
|
||||
buttons = listOf(
|
||||
DialogButtonModel(GetString(R.string.delete), color = LocalColors.current.danger, onClick = onAccepted),
|
||||
|
@ -130,7 +130,7 @@ class MediaOverviewViewModel(
|
||||
.groupBy { record ->
|
||||
val time =
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(record.date), ZoneId.of("UTC"))
|
||||
timeBuckets.getBucketText(time)?.let(application::getString)
|
||||
timeBuckets.getBucketText(application, time)
|
||||
?: time.toLocalDate().withDayOfMonth(1)
|
||||
}
|
||||
.map { (bucket, records) ->
|
||||
|
@ -52,9 +52,6 @@ object DateUtils : android.text.format.DateUtils() {
|
||||
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
|
||||
public fun getLocalisedRelativeDayString(relativeDay: RelativeDay): String {
|
||||
@ -76,10 +73,12 @@ object DateUtils : android.text.format.DateUtils() {
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}
|
||||
|
||||
return getRelativeTimeSpanString(comparisonTime.timeInMillis,
|
||||
val temp = getRelativeTimeSpanString(
|
||||
comparisonTime.timeInMillis,
|
||||
now.timeInMillis,
|
||||
DAY_IN_MILLIS,
|
||||
FORMAT_SHOW_DATE).toString()
|
||||
return temp
|
||||
}
|
||||
|
||||
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 {
|
||||
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"?>
|
||||
<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>
|
@ -1,87 +0,0 @@
|
||||
package org.thoughtcrime.securesms.l10n;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
//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 LanguageResourcesTest {
|
||||
|
||||
@Test
|
||||
public void language_entries_match_language_values_in_length() {
|
||||
Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
String[] values = resources.getStringArray(R.array.language_values);
|
||||
String[] entries = resources.getStringArray(R.array.language_entries);
|
||||
assertEquals(values.length, entries.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void language_options_matches_available_resources() {
|
||||
Set<String> languageEntries = languageEntries();
|
||||
Set<String> foundResources = buildConfigResources();
|
||||
if (!languageEntries.equals(foundResources)) {
|
||||
assertSubset(foundResources, languageEntries, "Missing language_entries for resources");
|
||||
assertSubset(languageEntries, foundResources, "Missing resources for language_entries");
|
||||
fail("Unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> languageEntries() {
|
||||
Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
String[] values = resources.getStringArray(R.array.language_values);
|
||||
|
||||
List<String> tail = Arrays.asList(values).subList(1, values.length);
|
||||
Set<String> set = new HashSet<>(tail);
|
||||
|
||||
assertEquals("First is not the default", "zz", values[0]);
|
||||
assertEquals("List contains duplicates", tail.size(), set.size());
|
||||
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"
|
||||
*/
|
||||
private static void assertSubset(Set<String> a, Set<String> b, String message) {
|
||||
Set<String> delta = subtract(a, b);
|
||||
if (!delta.isEmpty()) {
|
||||
fail(message + ": " + String.join(", ", delta));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a - Set b
|
||||
*/
|
||||
private static Set<String> subtract(Set<String> a, Set<String> b) {
|
||||
Set<String> set = new HashSet<>(a);
|
||||
set.removeAll(b);
|
||||
return set;
|
||||
}
|
||||
}
|
@ -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="callsMissed">Missed Call</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="callsPermissionsRequiredDescription">You can enable the \"Voice and Video Calls\" permission in Privacy Settings.</string>
|
||||
<string name="callsReconnecting">Reconnecting…</string>
|
||||
@ -436,7 +436,7 @@
|
||||
<string name="groupNameEnter">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="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="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>
|
||||
@ -692,7 +692,7 @@
|
||||
<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="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="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>
|
||||
@ -814,7 +814,7 @@
|
||||
<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="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="updateSession">{app_name} Update</string>
|
||||
<string name="updateVersion">Version {version}</string>
|
||||
|
Loading…
Reference in New Issue
Block a user