diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 691792a747..ad9c0010bf 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -173,7 +173,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity setContentView(R.layout.conversation_activity); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - fragment = initFragment(R.id.fragment_content, new ConversationFragment(), masterSecret); + fragment = initFragment(R.id.fragment_content, new ConversationFragment(), masterSecret, dynamicLanguage.getCurrentLocale()); initializeReceivers(); initializeViews(); diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index 64f56ef21e..0e64f9ed84 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -18,7 +18,6 @@ package org.thoughtcrime.securesms; import android.content.Context; import android.database.Cursor; -import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -36,6 +35,7 @@ import org.thoughtcrime.securesms.util.LRUCache; import java.lang.ref.SoftReference; import java.util.Collections; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -64,16 +64,19 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re private final SelectionClickListener selectionClickListener; private final Context context; private final MasterSecret masterSecret; + private final Locale locale; private final boolean groupThread; private final boolean pushDestination; private final LayoutInflater inflater; - public ConversationAdapter(Context context, MasterSecret masterSecret, SelectionClickListener selectionClickListener, - boolean groupThread, boolean pushDestination) + public ConversationAdapter(Context context, MasterSecret masterSecret, Locale locale, + SelectionClickListener selectionClickListener, boolean groupThread, + boolean pushDestination) { super(context, null, 0); this.context = context; this.masterSecret = masterSecret; + this.locale = locale; this.selectionClickListener = selectionClickListener; this.groupThread = groupThread; this.pushDestination = pushDestination; @@ -87,7 +90,7 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); MessageRecord messageRecord = getMessageRecord(id, cursor, type); - item.set(masterSecret, messageRecord, batchSelected, selectionClickListener, + item.set(masterSecret, messageRecord, locale, batchSelected, selectionClickListener, groupThread, pushDestination); } diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 4fe62fe872..f9bc4daa6c 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import java.util.LinkedList; import java.util.List; +import java.util.Locale; public class ConversationFragment extends ListFragment implements LoaderManager.LoaderCallbacks @@ -60,11 +61,13 @@ public class ConversationFragment extends ListFragment private Recipients recipients; private long threadId; private ActionMode actionMode; + private Locale locale; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); this.masterSecret = getArguments().getParcelable("master_secret"); + this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); } @Override @@ -116,7 +119,7 @@ public class ConversationFragment extends ListFragment private void initializeListAdapter() { if (this.recipients != null && this.threadId != -1) { - this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, selectionClickListener, + this.setListAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, (!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient(), DirectoryHelper.isPushDestination(getActivity(), this.recipients))); getListView().setRecyclerListener((ConversationAdapter)getListAdapter()); diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 0d89130e4f..3f389192ee 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Emoji; +import java.util.Locale; import java.util.Set; /** @@ -73,6 +74,7 @@ public class ConversationItem extends LinearLayout { private MessageRecord messageRecord; private MasterSecret masterSecret; + private Locale locale; private boolean groupThread; private boolean pushDestination; @@ -138,12 +140,14 @@ public class ConversationItem extends LinearLayout { public void set(@NonNull MasterSecret masterSecret, @NonNull MessageRecord messageRecord, + @NonNull Locale locale, @NonNull Set batchSelected, @NonNull SelectionClickListener selectionClickListener, boolean groupThread, boolean pushDestination) { this.masterSecret = masterSecret; this.messageRecord = messageRecord; + this.locale = locale; this.batchSelected = batchSelected; this.selectionClickListener = selectionClickListener; this.groupThread = groupThread; @@ -287,7 +291,7 @@ public class ConversationItem extends LinearLayout { if (messageRecord.isPush()) timestamp = messageRecord.getDateSent(); else timestamp = messageRecord.getDateReceived(); - dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), timestamp)); + dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, timestamp)); } private void setFailedStatusIcons() { diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 25c2c09ddb..1dad112658 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -42,7 +42,6 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; - public class ConversationListActivity extends PassphraseRequiredActionBarActivity implements ConversationListFragment.ConversationSelectedListener { @@ -67,7 +66,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); getSupportActionBar().setTitle(R.string.app_name); - fragment = initFragment(android.R.id.content, new ConversationListFragment(), masterSecret); + fragment = initFragment(android.R.id.content, new ConversationListFragment(), masterSecret, dynamicLanguage.getCurrentLocale()); initializeContactUpdatesReceiver(); diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index acbba4831f..11fbd49eab 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord; import java.util.Collections; import java.util.HashSet; +import java.util.Locale; import java.util.Set; /** @@ -47,6 +48,7 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter, ActionMode.Callback, ItemClickListener { - private MasterSecret masterSecret; private ActionMode actionMode; private RecyclerView list; private ReminderView reminderView; private FloatingActionButton fab; + private Locale locale; private String queryFilter = ""; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); masterSecret = getArguments().getParcelable("master_secret"); + locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); } @Override @@ -140,7 +142,7 @@ public class ConversationListFragment extends Fragment } private void initializeListAdapter() { - list.setAdapter(new ConversationListAdapter(getActivity(), masterSecret, null, this)); + list.setAdapter(new ConversationListAdapter(getActivity(), masterSecret, locale, null, this)); list.setRecyclerListener(new RecyclerListener() { @Override public void onViewRecycled(ViewHolder holder) { diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 4f8edb9e8c..daf867c42c 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Emoji; import org.thoughtcrime.securesms.util.RecipientViewUtil; +import java.util.Locale; import java.util.Set; import static org.thoughtcrime.securesms.util.SpanUtil.color; @@ -86,7 +87,7 @@ public class ConversationListItem extends RelativeLayout initializeContactWidgetVisibility(); } - public void set(ThreadRecord thread, Set selectedThreads, boolean batchMode) { + public void set(ThreadRecord thread, Locale locale, Set selectedThreads, boolean batchMode) { this.selectedThreads = selectedThreads; this.recipients = thread.getRecipients(); this.threadId = thread.getThreadId(); @@ -103,7 +104,7 @@ public class ConversationListItem extends RelativeLayout this.subjectView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); if (thread.getDate() > 0) { - CharSequence date = DateUtils.getBriefRelativeTimeSpanString(context, thread.getDate()); + CharSequence date = DateUtils.getBriefRelativeTimeSpanString(context, locale, thread.getDate()); dateView.setText(read ? date : color(getResources().getColor(R.color.textsecure_primary), date)); dateView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); } diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java index 9c4370b3c6..392902cf24 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -54,6 +54,7 @@ import java.sql.Date; import java.text.SimpleDateFormat; import java.util.HashSet; import java.util.LinkedList; +import java.util.Locale; /** * @author Jake McGinty @@ -147,7 +148,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity sentDate.setText("-"); receivedContainer.setVisibility(View.GONE); } else { - SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this); + Locale dateLocale = dynamicLanguage.getCurrentLocale(); + SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale); sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent()))); if (messageRecord.getDateReceived() != messageRecord.getDateSent() && !messageRecord.isOutgoing()) { @@ -169,7 +171,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity toFromRes = R.string.message_details_header__from; } toFrom.setText(toFromRes); - conversationItem.set(masterSecret, messageRecord, new HashSet(), new NullSelectionListener(), + conversationItem.set(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(), + new HashSet(), new NullSelectionListener(), recipients != messageRecord.getRecipients(), DirectoryHelper.isPushDestination(this, recipients)); recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, messageRecord, diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index ab7835f4c7..410a471ee3 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -19,9 +19,13 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.MessageRetrievalService; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import java.util.Locale; + public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity implements MasterSecretListener { private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName(); + public static final String LOCALE_EXTRA = "locale_extra"; + private static final int STATE_NORMAL = 0; private static final int STATE_CREATE_PASSPHRASE = 1; private static final int STATE_PROMPT_PASSPHRASE = 2; @@ -78,9 +82,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA protected T initFragment(@IdRes int target, @NonNull T fragment, - @NonNull MasterSecret masterSecret) { + @NonNull MasterSecret masterSecret, + @Nullable Locale locale) { Bundle args = new Bundle(); args.putParcelable("master_secret", masterSecret); + args.putSerializable(LOCALE_EXTRA, locale); + fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .replace(target, fragment) @@ -88,6 +95,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA return fragment; } + protected T initFragment(@IdRes int target, + @NonNull T fragment, + @NonNull MasterSecret masterSecret) { + return initFragment(target, fragment, masterSecret, null); + } + private void routeApplicationState(MasterSecret masterSecret) { Intent intent = getIntentForState(masterSecret, getApplicationState(masterSecret)); if (intent != null) { diff --git a/src/org/thoughtcrime/securesms/util/DateUtils.java b/src/org/thoughtcrime/securesms/util/DateUtils.java index 641bfb75e8..af9df22ddb 100644 --- a/src/org/thoughtcrime/securesms/util/DateUtils.java +++ b/src/org/thoughtcrime/securesms/util/DateUtils.java @@ -24,6 +24,7 @@ import java.util.Locale; import org.thoughtcrime.securesms.R; +import java.util.Date; import java.util.concurrent.TimeUnit; /** @@ -39,7 +40,12 @@ public class DateUtils extends android.text.format.DateUtils { return (int) to.convert(System.currentTimeMillis() - millis, TimeUnit.MILLISECONDS); } - public static String getBriefRelativeTimeSpanString(final Context c, final long timestamp) { + private static String getFormattedDateTime(long time, String template, Locale locale) { + String localizedPattern = new SimpleDateFormat(template, locale).toLocalizedPattern(); + return new SimpleDateFormat(localizedPattern, locale).format(new Date(time)); + } + + public static String getBriefRelativeTimeSpanString(final Context c, final Locale locale, final long timestamp) { if (isWithin(timestamp, 1, TimeUnit.MINUTES)) { return c.getString(R.string.DateUtils_now); } else if (isWithin(timestamp, 1, TimeUnit.HOURS)) { @@ -49,34 +55,34 @@ public class DateUtils extends android.text.format.DateUtils { int hours = convertDelta(timestamp, TimeUnit.HOURS); return c.getResources().getQuantityString(R.plurals.hours_ago, hours, hours); } else if (isWithin(timestamp, 6, TimeUnit.DAYS)) { - return formatDateTime(c, timestamp, DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY); + return getFormattedDateTime(timestamp, "EEE", locale); } else if (isWithin(timestamp, 365, TimeUnit.DAYS)) { - return formatDateTime(c, timestamp, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL); + return getFormattedDateTime(timestamp, "MMM d", locale); } else { - return formatDateTime(c, timestamp, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL); + return getFormattedDateTime(timestamp, "MMM d, yyyy", locale); } } - public static String getExtendedRelativeTimeSpanString(final Context c, final long timestamp) { + public static String getExtendedRelativeTimeSpanString(final Context c, final Locale locale, final long timestamp) { if (isWithin(timestamp, 1, TimeUnit.MINUTES)) { return c.getString(R.string.DateUtils_now); } else if (isWithin(timestamp, 1, TimeUnit.HOURS)) { int mins = (int)TimeUnit.MINUTES.convert(System.currentTimeMillis() - timestamp, TimeUnit.MILLISECONDS); return c.getResources().getQuantityString(R.plurals.minutes_ago, mins, mins); } else { - int formatFlags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_TIME; - if (isWithin(timestamp, 6, TimeUnit.DAYS)) { - formatFlags |= DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY; - } else if (isWithin(timestamp, 365, TimeUnit.DAYS)) { - formatFlags |= DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; - } else { - formatFlags |= DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL; - } - return DateUtils.formatDateTime(c, timestamp, formatFlags); + StringBuilder format = new StringBuilder(); + if (isWithin(timestamp, 6, TimeUnit.DAYS)) format.append("EEE "); + else if (isWithin(timestamp, 365, TimeUnit.DAYS)) format.append("MMM d, "); + else format.append("MMM d, yyyy, "); + + if (DateFormat.is24HourFormat(c)) format.append("HH:mm"); + else format.append("hh:mm a"); + + return getFormattedDateTime(timestamp, format.toString(), locale); } } - public static SimpleDateFormat getDetailedDateFormatter(Context context) { + public static SimpleDateFormat getDetailedDateFormatter(Context context, Locale locale) { String dateFormatPattern; if (DateFormat.is24HourFormat(context)) { @@ -85,7 +91,7 @@ public class DateUtils extends android.text.format.DateUtils { dateFormatPattern = "MMM d, yyyy hh:mm:ss a zzz"; } - return new SimpleDateFormat(dateFormatPattern, Locale.getDefault()); + return new SimpleDateFormat(dateFormatPattern, locale); } } diff --git a/src/org/thoughtcrime/securesms/util/DynamicLanguage.java b/src/org/thoughtcrime/securesms/util/DynamicLanguage.java index debe29855a..5b9086b66b 100644 --- a/src/org/thoughtcrime/securesms/util/DynamicLanguage.java +++ b/src/org/thoughtcrime/securesms/util/DynamicLanguage.java @@ -35,6 +35,10 @@ public class DynamicLanguage { setContextLocale(service, currentLocale); } + public Locale getCurrentLocale() { + return currentLocale; + } + private static void setContextLocale(Context context, Locale selectedLocale) { Configuration configuration = context.getResources().getConfiguration();