Merge pull request #1664 from oxen-io/fix/strings-update

Strings update
This commit is contained in:
ThomasSession 2024-09-12 16:55:54 +10:00 committed by GitHub
commit 41386fc716
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 48 additions and 779 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

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

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