diff --git a/app/build.gradle b/app/build.gradle index 4cd08255d6..420e7440e9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,29 +10,26 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion" classpath "com.google.gms:google-services:$googleServicesVersion" - classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" } } plugins { - id 'kotlin-kapt' + id 'com.google.devtools.ksp' id 'com.google.dagger.hilt.android' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'witness' -apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' -apply plugin: 'dagger.hilt.android.plugin' configurations.all { exclude module: "commons-logging" } -def canonicalVersionCode = 374 -def canonicalVersionName = "1.18.5" +def canonicalVersionCode = 376 +def canonicalVersionName = "1.18.6" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, @@ -89,7 +86,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.4.7' + kotlinCompilerExtensionVersion '1.5.14' } defaultConfig { @@ -238,10 +235,14 @@ android { } dependencies { + implementation project(':content-descriptions') - implementation("com.google.dagger:hilt-android:2.46.1") - kapt("com.google.dagger:hilt-android-compiler:2.44") + ksp("com.google.dagger:hilt-compiler:$daggerHiltVersion") + ksp("androidx.hilt:hilt-compiler:$jetpackHiltVersion") + ksp "com.github.bumptech.glide:ksp:$glideVersion" + implementation 'androidx.compose.material3:material3-android:1.2.1' + implementation("com.google.dagger:hilt-android:$daggerHiltVersion") implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "com.google.android.material:material:$materialVersion" @@ -263,12 +264,15 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.5.3' implementation "androidx.core:core-ktx:$coreVersion" implementation "androidx.work:work-runtime-ktx:2.7.1" + playImplementation ("com.google.firebase:firebase-messaging:18.0.0") { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } + if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300' + implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' implementation 'org.conscrypt:conscrypt-android:2.5.2' @@ -281,8 +285,6 @@ dependencies { implementation 'commons-net:commons-net:3.7.2' implementation 'com.github.chrisbanes:PhotoView:2.1.3' implementation "com.github.bumptech.glide:glide:$glideVersion" - annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" - kapt "com.github.bumptech.glide:compiler:$glideVersion" implementation 'com.makeramen:roundedimageview:2.1.0' implementation 'com.pnikosis:materialish-progress:1.5' implementation 'org.greenrobot:eventbus:3.0.0' @@ -290,8 +292,6 @@ dependencies { implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' implementation 'com.melnykov:floatingactionbutton:1.3.0' implementation 'com.google.zxing:android-integration:3.1.0' - implementation "com.google.dagger:hilt-android:$daggerVersion" - kapt "com.google.dagger:hilt-compiler:$daggerVersion" implementation 'mobi.upod:time-duration-picker:1.1.3' implementation 'com.google.zxing:core:3.2.1' implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') { @@ -415,8 +415,3 @@ def autoResConfig() { .collect { matcher -> matcher.group(1) } .sort() } - -// Allow references to generated code -kapt { - correctErrorTypes = true -} diff --git a/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt b/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt index 6fb3888ff2..037af234f3 100644 --- a/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt +++ b/app/src/androidTest/java/network/loki/messenger/HomeActivityTests.kt @@ -25,6 +25,7 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import com.adevinta.android.barista.interaction.PermissionGranter +import com.squareup.phrase.Phrase import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf @@ -37,10 +38,11 @@ import org.junit.runner.RunWith import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.guava.Optional +import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBar import org.thoughtcrime.securesms.home.HomeActivity -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide @RunWith(AndroidJUnit4::class) @LargeTest @@ -71,7 +73,7 @@ class HomeActivityTests { onView(allOf(isDescendantOfA(withId(R.id.inputBar)),withId(R.id.inputBarEditText))).perform(ViewActions.replaceText(messageToSend)) if (linkPreview != null) { val activity = activityMonitor.waitForActivity() as ConversationActivityV2 - val glide = GlideApp.with(activity) + val glide = Glide.with(activity) activity.findViewById(R.id.inputBar).updateLinkPreviewDraft(glide, linkPreview) } onView(allOf(isDescendantOfA(withId(R.id.inputBar)),inputButtonWithDrawable(R.drawable.ic_arrow_up))).perform(ViewActions.click()) @@ -92,10 +94,10 @@ class HomeActivityTests { device.pressKeyCode(67) // Continue with display name - objectFromDesc(R.string.continue_2).click() + objectFromDesc(R.string.theContinue).click() // Continue with default push notification setting - objectFromDesc(R.string.continue_2).click() + objectFromDesc(R.string.theContinue).click() // PN select if (hasViewedSeed) { @@ -127,7 +129,7 @@ class HomeActivityTests { @Test fun testLaunches_dismiss_seedView() { setupLoggedInState() - objectFromDesc(R.string.continue_2).click() + objectFromDesc(R.string.theContinue).click() objectFromDesc(R.string.copy).click() pressBack() onView(withId(R.id.seedReminderView)).check(matches(not(isDisplayed()))) @@ -172,7 +174,11 @@ class HomeActivityTests { // then the URL dialog should be displayed with a known punycode url val amazonPuny = "https://www.xn--mazon-wqa.com/" - val dialogPromptText = InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.dialog_open_url_explanation, amazonPuny) + // Substitute the URL into our string + val c = InstrumentationRegistry.getInstrumentation().targetContext + val dialogPromptText = Phrase.from(c, R.string.urlOpenDescription) + .put(URL_KEY, amazonPuny) + .format().toString() onView(isRoot()).perform(waitFor(1000)) // no other way for this to work apparently onView(withText(dialogPromptText)).check(matches(isDisplayed())) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e04ad44ca..9669a3adc7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,7 +60,6 @@ - @@ -130,12 +129,12 @@ + android:label="@string/sessionSettings" /> @@ -147,11 +146,11 @@ android:name="org.thoughtcrime.securesms.preferences.BlockedContactsActivity" android:screenOrientation="portrait" android:theme="@style/Theme.Session.DayNight.FlatActionBar" - android:label="@string/blocked_contacts_title" + android:label="@string/conversationsBlockedContacts" /> @@ -264,7 +263,6 @@ { + lokiAPIDatabase.clearSnodePool(); + lokiAPIDatabase.clearOnionRequestPaths(); + TextSecurePreferences.setHasAppliedPatchSnodeVersion(this, true); + }); + } + messagingModuleConfiguration = new MessagingModuleConfiguration( this, storage, diff --git a/app/src/main/java/org/thoughtcrime/securesms/BaseActivity.java b/app/src/main/java/org/thoughtcrime/securesms/BaseActivity.java index d5286698cd..d44978b05b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BaseActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BaseActivity.java @@ -4,12 +4,8 @@ import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Build; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import androidx.annotation.NonNull; + import androidx.fragment.app.FragmentActivity; -import android.view.KeyEvent; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper; diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt index af38c31ff3..191d7c3b6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaDialog.kt @@ -8,19 +8,8 @@ class DeleteMediaDialog { @JvmStatic fun show(context: Context, recordCount: Int, doDelete: Runnable) = context.showSessionDialog { iconAttribute(R.attr.dialog_alert_icon) - title( - context.resources.getQuantityString( - R.plurals.MediaOverviewActivity_Media_delete_confirm_title, - recordCount, - recordCount - ) - ) - text( - context.resources.getQuantityString(R.plurals.MediaOverviewActivity_Media_delete_confirm_message, - recordCount, - recordCount - ) - ) + title(context.resources.getString(R.string.deleteMessages)) + text(context.resources.getString(R.string.deleteMessageDescriptionEveryone)) button(R.string.delete) { doDelete.run() } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt index 0390a3007d..01c6fb00ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/DeleteMediaPreviewDialog.kt @@ -9,8 +9,8 @@ class DeleteMediaPreviewDialog { fun show(context: Context, doDelete: Runnable) { context.showSessionDialog { iconAttribute(R.attr.dialog_alert_icon) - title(R.string.MediaPreviewActivity_media_delete_confirmation_title) - text(R.string.MediaPreviewActivity_media_delete_confirmation_message) + title(R.string.deleteMessage) + text(R.string.deleteMessageDescriptionEveryone) button(R.string.delete) { doDelete.run() } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaDocumentsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/MediaDocumentsAdapter.java index bbc00472a5..c6345c4dd8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaDocumentsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaDocumentsAdapter.java @@ -60,7 +60,10 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter if (slide != null && slide.hasDocument()) { viewHolder.documentView.setDocument((DocumentSlide)slide, false); - viewHolder.date.setText(DateUtils.getRelativeDate(getContext(), locale, mediaRecord.getDate())); + + String relativeDate = DateUtils.INSTANCE.getRelativeDate(getContext(), locale, mediaRecord.getDate()); + viewHolder.date.setText(relativeDate); + viewHolder.documentView.setVisibility(View.VISIBLE); viewHolder.date.setVisibility(View.VISIBLE); viewHolder.documentView.setOnClickListener(view -> { @@ -71,7 +74,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter getContext().startActivity(intent); } catch (ActivityNotFoundException anfe) { Log.w(TAG, "No activity existed to view the media."); - Toast.makeText(getContext(), R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show(); + Toast.makeText(getContext(), R.string.attachmentsErrorOpen, Toast.LENGTH_LONG).show(); } }); } else { @@ -104,7 +107,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { Cursor cursor = getCursorAtPositionOrThrow(position); MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor); - viewHolder.textView.setText(DateUtils.getRelativeDate(getContext(), locale, mediaRecord.getDate())); + viewHolder.textView.setText(DateUtils.INSTANCE.getRelativeDate(getContext(), locale, mediaRecord.getDate())); } public static class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java index 62766d1cd7..9a262a2b0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java @@ -29,7 +29,7 @@ import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter; import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.util.MediaUtil; @@ -46,7 +46,7 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter { private static final String TAG = MediaGalleryAdapter.class.getSimpleName(); private final Context context; - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final Locale locale; private final ItemClickListener itemClickListener; private final Set selected; @@ -74,7 +74,7 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter { } MediaGalleryAdapter(@NonNull Context context, - @NonNull GlideRequests glideRequests, + @NonNull RequestManager glideRequests, BucketedThreadMedia media, Locale locale, ItemClickListener clickListener) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 95ba15c82e..f3366802fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -16,11 +16,12 @@ */ package org.thoughtcrime.securesms; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.res.Resources; import android.database.Cursor; import android.os.Build; import android.os.Bundle; @@ -32,10 +33,8 @@ import android.view.ViewGroup; import android.view.Window; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.Toolbar; @@ -48,36 +47,34 @@ import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; - +import com.bumptech.glide.Glide; import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; import com.google.android.material.tabs.TabLayout; - -import org.session.libsession.messaging.messages.control.DataExtractionNotification; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.snode.SnodeAPI; -import org.session.libsession.utilities.Address; -import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; -import org.thoughtcrime.securesms.database.MediaDatabase; -import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader; -import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia; -import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.session.libsession.utilities.recipients.Recipient; -import org.thoughtcrime.securesms.util.AttachmentUtil; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; -import org.thoughtcrime.securesms.util.StickyHeaderDecoration; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.task.ProgressDialogAsyncTask; - +import com.squareup.phrase.Phrase; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Locale; - import kotlin.Unit; import network.loki.messenger.R; +import org.session.libsession.messaging.messages.control.DataExtractionNotification; +import org.session.libsession.messaging.sending_receiving.MessageSender; +import org.session.libsession.snode.SnodeAPI; +import org.session.libsession.utilities.Address; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.ViewUtil; +import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.task.ProgressDialogAsyncTask; +import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; +import org.thoughtcrime.securesms.database.MediaDatabase; +import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia; +import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader; +import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader; +import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.util.AttachmentUtil; +import org.thoughtcrime.securesms.util.SaveAttachmentTask; +import org.thoughtcrime.securesms.util.StickyHeaderDecoration; /** * Activity for displaying media attachments in-app @@ -117,17 +114,26 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { } private void initializeResources() { - Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); - this.viewPager = ViewUtil.findById(this, R.id.pager); - this.toolbar = ViewUtil.findById(this, R.id.toolbar); + this.toolbar = ViewUtil.findById(this, R.id.search_toolbar); this.tabLayout = ViewUtil.findById(this, R.id.tab_layout); - this.recipient = Recipient.from(this, address, true); + + Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); + if (address == null) { + Log.w(TAG, "Got null address in initializeResources."); + } else { + this.recipient = Recipient.from(this, address, true); + } } private void initializeToolbar() { setSupportActionBar(this.toolbar); ActionBar actionBar = getSupportActionBar(); + if (actionBar == null) { + Log.w(TAG, "Could not get support actionbar"); + return; + } + // Implied else that the actionbar is fine to work with... actionBar.setTitle(recipient.toShortString()); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); @@ -176,8 +182,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { @Override public CharSequence getPageTitle(int position) { - if (position == 0) return getString(R.string.MediaOverviewActivity_Media); - else if (position == 1) return getString(R.string.MediaOverviewActivity_Documents); + if (position == 0) return getString(R.string.media); + else if (position == 1) return getString(R.string.files); else throw new AssertionError(); } } @@ -227,7 +233,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols)); this.recyclerView.setAdapter(new MediaGalleryAdapter(getContext(), - GlideApp.with(this), + Glide.with(this), new BucketedThreadMedia(getContext()), locale, this)); @@ -325,13 +331,19 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { Permissions.with(this) .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) .maxSdkVersion(Build.VERSION_CODES.P) - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) - .onAnyDenied(() -> Toast.makeText(getContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) + .withPermanentDenialDialog(Phrase.from(context, R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString()) + .onAnyDenied(() -> Toast.makeText(getContext(), + Phrase.from(context, R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString(), + Toast.LENGTH_LONG).show()) .onAllGranted(() -> { new ProgressDialogAsyncTask>( context, - R.string.MediaOverviewActivity_collecting_attachments, - R.string.please_wait) { + R.string.attachmentsCollecting, + R.string.waitOneMoment) { @Override protected List doInBackground(Void... params) { List attachments = new LinkedList<>(); @@ -382,8 +394,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { recordCount, () -> new ProgressDialogAsyncTask( requireContext(), - R.string.MediaOverviewActivity_Media_delete_progress_title, - R.string.MediaOverviewActivity_Media_delete_progress_message) { + R.string.deleting, + R.string.deleting) { @Override protected Void doInBackground(MediaDatabase.MediaRecord... records) { if (records == null || records.length == 0) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 2e67becbfd..b88e6a66ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -16,17 +16,17 @@ */ package org.thoughtcrime.securesms; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.CursorIndexOutOfBoundsException; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -44,7 +44,6 @@ import android.view.WindowInsetsController; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -56,7 +55,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; - +import com.squareup.phrase.Phrase; +import java.io.IOException; +import java.util.Locale; +import java.util.WeakHashMap; +import kotlin.Unit; +import network.loki.messenger.R; import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; @@ -72,21 +76,14 @@ import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; - -import java.io.IOException; -import java.util.Locale; -import java.util.WeakHashMap; - -import kotlin.Unit; -import network.loki.messenger.R; +import org.thoughtcrime.securesms.util.SaveAttachmentTask; /** * Activity for displaying media attachments in-app @@ -242,12 +239,12 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im CharSequence relativeTimeSpan; if (mediaItem.date > 0) { - relativeTimeSpan = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), mediaItem.date); + relativeTimeSpan = DateUtils.INSTANCE.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), mediaItem.date); } else { - relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft); + relativeTimeSpan = getString(R.string.draft); } - if (mediaItem.outgoing) getSupportActionBar().setTitle(getString(R.string.MediaPreviewActivity_you)); + if (mediaItem.outgoing) getSupportActionBar().setTitle(getString(R.string.you)); else if (mediaItem.recipient != null) getSupportActionBar().setTitle(mediaItem.recipient.toShortString()); else getSupportActionBar().setTitle(""); @@ -258,7 +255,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im @Override public void onResume() { super.onResume(); - initializeMedia(); } @@ -281,7 +277,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im mediaPager.setOffscreenPageLimit(1); albumRail = findViewById(R.id.media_preview_album_rail); - albumRailAdapter = new MediaRailAdapter(GlideApp.with(this), this, false); + albumRailAdapter = new MediaRailAdapter(Glide.with(this), this, false); albumRail.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); albumRail.setAdapter(albumRailAdapter); @@ -291,7 +287,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im captionContainer = findViewById(R.id.media_preview_caption_container); playbackControlsContainer = findViewById(R.id.media_preview_playback_controls_container); - setSupportActionBar(findViewById(R.id.toolbar)); + setSupportActionBar(findViewById(R.id.search_toolbar)); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); @@ -361,7 +357,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private void initializeMedia() { if (!isContentTypeSupported(initialMediaType)) { Log.w(TAG, "Unsupported media type sent to MediaPreviewActivity, finishing."); - Toast.makeText(getApplicationContext(), R.string.MediaPreviewActivity_unssuported_media_type, Toast.LENGTH_LONG).show(); + Toast.makeText(getApplicationContext(), R.string.attachmentsErrorNotSupported, Toast.LENGTH_LONG).show(); finish(); } @@ -370,7 +366,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im if (conversationRecipient != null) { getSupportLoaderManager().restartLoader(0, null, this); } else { - adapter = new SingleItemPagerAdapter(this, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize); + adapter = new SingleItemPagerAdapter(this, Glide.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize); mediaPager.setAdapter(adapter); if (initialCaption != null) { @@ -410,6 +406,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im @SuppressWarnings("CodeBlock2Expr") @SuppressLint("InlinedApi") private void saveToDisk() { + Log.w("ACL", "Asked to save to disk!"); MediaItem mediaItem = getCurrentMediaItem(); if (mediaItem == null) return; @@ -417,8 +414,15 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im Permissions.with(this) .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) .maxSdkVersion(Build.VERSION_CODES.P) - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) - .onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) + .withPermanentDenialDialog(Phrase.from(getApplicationContext(), R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString()) + .onAnyDenied(() -> { + String txt = Phrase.from(getApplicationContext(), R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString(); + Toast.makeText(this, txt, Toast.LENGTH_LONG).show(); + }) .onAllGranted(() -> { SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this); long saveDate = (mediaItem.date > 0) ? mediaItem.date : SnodeAPI.getNowWithOffset(); @@ -518,7 +522,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im mediaPager.removeOnPageChangeListener(viewPagerListener); - adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent); + adapter = new CursorPagerAdapter(this, Glide.with(this), getWindow(), data.first, data.second, leftIsRecent); mediaPager.setAdapter(adapter); viewModel.setCursor(this, data.first, leftIsRecent); @@ -588,7 +592,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private static class SingleItemPagerAdapter extends MediaItemAdapter { - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final Window window; private final Uri uri; private final String mediaType; @@ -596,7 +600,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private final LayoutInflater inflater; - SingleItemPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, + SingleItemPagerAdapter(@NonNull Context context, @NonNull RequestManager glideRequests, @NonNull Window window, @NonNull Uri uri, @NonNull String mediaType, long size) { @@ -663,14 +667,14 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private final WeakHashMap mediaViews = new WeakHashMap<>(); private final Context context; - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final Window window; private final Cursor cursor; private final boolean leftIsRecent; private int autoPlayPosition; - CursorPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, + CursorPagerAdapter(@NonNull Context context, @NonNull RequestManager glideRequests, @NonNull Window window, @NonNull Cursor cursor, int autoPlayPosition, boolean leftIsRecent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt index f294e387ff..847546d653 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.kt @@ -10,18 +10,18 @@ fun showMuteDialog( context: Context, onMuteDuration: (Long) -> Unit ): AlertDialog = context.showSessionDialog { - title(R.string.MuteDialog_mute_notifications) + title(R.string.notificationsMute) items(Option.values().map { it.stringRes }.map(context::getString).toTypedArray()) { onMuteDuration(Option.values()[it].getTime()) } } private enum class Option(@StringRes val stringRes: Int, val getTime: () -> Long) { - ONE_HOUR(R.string.arrays__mute_for_one_hour, duration = TimeUnit.HOURS.toMillis(1)), - TWO_HOURS(R.string.arrays__mute_for_two_hours, duration = TimeUnit.DAYS.toMillis(2)), - ONE_DAY(R.string.arrays__mute_for_one_day, duration = TimeUnit.DAYS.toMillis(1)), - SEVEN_DAYS(R.string.arrays__mute_for_seven_days, duration = TimeUnit.DAYS.toMillis(7)), - FOREVER(R.string.arrays__mute_forever, getTime = { Long.MAX_VALUE }); + ONE_HOUR(R.string.notificationsMute1Hour, duration = TimeUnit.HOURS.toMillis(1)), + TWO_HOURS(R.string.notificationsMute2Hours, duration = TimeUnit.DAYS.toMillis(2)), + ONE_DAY(R.string.notificationsMute1Day, duration = TimeUnit.DAYS.toMillis(1)), + SEVEN_DAYS(R.string.notificationsMute1Week, duration = TimeUnit.DAYS.toMillis(7)), + FOREVER(R.string.notificationsMute, getTime = { Long.MAX_VALUE }); constructor(@StringRes stringRes: Int, duration: Long): this(stringRes, { System.currentTimeMillis() + duration }) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java index afc993df8a..16b5856766 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java @@ -16,6 +16,8 @@ */ package org.thoughtcrime.securesms; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.animation.Animator; import android.app.KeyguardManager; import android.content.ComponentName; @@ -25,20 +27,18 @@ import android.content.ServiceConnection; import android.graphics.PorterDuff; import android.os.Bundle; import android.os.IBinder; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.RelativeSizeSpan; -import android.text.style.TypefaceSpan; import android.view.View; import android.view.animation.Animation; import android.view.animation.BounceInterpolator; import android.view.animation.TranslateAnimation; import android.widget.Button; import android.widget.ImageView; - +import android.widget.TextView; import androidx.core.hardware.fingerprint.FingerprintManagerCompat; import androidx.core.os.CancellationSignal; - +import com.squareup.phrase.Phrase; +import java.security.Signature; +import network.loki.messenger.R; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.components.AnimatingToggle; @@ -46,11 +46,6 @@ import org.thoughtcrime.securesms.crypto.BiometricSecretProvider; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.AnimationCompleteListener; -import java.security.InvalidKeyException; -import java.security.Signature; - -import network.loki.messenger.R; - //TODO Rename to ScreenLockActivity and refactor to Kotlin. public class PassphrasePromptActivity extends BaseActionBarActivity { @@ -158,6 +153,16 @@ public class PassphrasePromptActivity extends BaseActionBarActivity { } private void initializeResources() { + + TextView statusTitle = findViewById(R.id.app_lock_status_title); + if (statusTitle != null) { + Context c = getApplicationContext(); + String lockedTxt = Phrase.from(c, R.string.lockAppLocked) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + statusTitle.setText(lockedTxt); + } + visibilityToggle = findViewById(R.id.button_toggle); fingerprintPrompt = findViewById(R.id.fingerprint_auth_container); lockScreenButton = findViewById(R.id.lock_screen_auth_container); @@ -165,10 +170,6 @@ public class PassphrasePromptActivity extends BaseActionBarActivity { fingerprintCancellationSignal = new CancellationSignal(); fingerprintListener = new FingerprintListener(); - SpannableString hint = new SpannableString(" " + getString(R.string.PassphrasePromptActivity_enter_passphrase)); - hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp); fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN); diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index dbe7c4a433..1c9f4b2e57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -34,7 +34,7 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA private BroadcastReceiver clearKeyReceiver; @Override - protected final void onCreate(Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate(" + savedInstanceState + ")"); onPreCreate(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java index f03840c1ab..d7a4011952 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java @@ -132,7 +132,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity } private void initializeToolbar() { - Toolbar toolbar = findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.search_toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.java index 37fdf2367d..2090e64925 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ShortcutLauncherActivity.java @@ -37,7 +37,7 @@ public class ShortcutLauncherActivity extends AppCompatActivity { String serializedAddress = getIntent().getStringExtra(KEY_SERIALIZED_ADDRESS); if (serializedAddress == null) { - Toast.makeText(this, R.string.ShortcutLauncherActivity_invalid_shortcut, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.invalidShortcut, Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, HomeActivity.class)); finish(); return; diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java index b0fbd8e22f..63fad1e6de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java @@ -105,7 +105,7 @@ public final class AvatarSelection { extraIntents.add(new Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO")); } - Intent chooserIntent = Intent.createChooser(galleryIntent, context.getString(R.string.CreateProfileActivity_profile_photo)); + Intent chooserIntent = Intent.createChooser(galleryIntent, context.getString(R.string.profileDisplayPicture)); if (!extraIntents.isEmpty()) { chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0])); diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index ea4108ba9a..2e3fdbf4d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -22,6 +22,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job @@ -38,7 +39,6 @@ import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator @@ -70,7 +70,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { } private val viewModel by viewModels() - private val glide by lazy { GlideApp.with(this) } + private val glide by lazy { Glide.with(this) } private lateinit var binding: ActivityWebrtcBinding private var uiJob: Job? = null private var wantsToAnswer = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java b/app/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java deleted file mode 100644 index e56651db25..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java +++ /dev/null @@ -1,312 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.Manifest; -import android.animation.Animator; -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.graphics.drawable.BitmapDrawable; -import android.net.Uri; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.loader.app.LoaderManager; -import android.util.Pair; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.ViewTreeObserver; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.OvershootInterpolator; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.PopupWindow; - -import org.thoughtcrime.securesms.permissions.Permissions; -import org.session.libsession.utilities.ViewUtil; - -import network.loki.messenger.R; - -public class AttachmentTypeSelector extends PopupWindow { - - public static final int ADD_GALLERY = 1; - public static final int ADD_DOCUMENT = 2; - public static final int ADD_SOUND = 3; - public static final int ADD_CONTACT_INFO = 4; - public static final int TAKE_PHOTO = 5; - public static final int ADD_LOCATION = 6; - public static final int ADD_GIF = 7; - - private static final int ANIMATION_DURATION = 300; - - @SuppressWarnings("unused") - private static final String TAG = AttachmentTypeSelector.class.getSimpleName(); - - private final @NonNull Context context; - public int keyboardHeight; - private final @NonNull LoaderManager loaderManager; - private final @NonNull RecentPhotoViewRail recentRail; - private final @NonNull ImageView imageButton; - private final @NonNull ImageView audioButton; - private final @NonNull ImageView documentButton; - private final @NonNull ImageView contactButton; - private final @NonNull ImageView cameraButton; - private final @NonNull ImageView locationButton; - private final @NonNull ImageView gifButton; - private final @NonNull ImageView closeButton; - - private @Nullable View currentAnchor; - private @Nullable AttachmentClickedListener listener; - - public AttachmentTypeSelector(@NonNull Context context, @NonNull LoaderManager loaderManager, @Nullable AttachmentClickedListener listener, int keyboardHeight) { - super(context); - - this.context = context; - this.keyboardHeight = keyboardHeight; - - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.attachment_type_selector, null, true); - - this.listener = listener; - this.loaderManager = loaderManager; - this.recentRail = ViewUtil.findById(layout, R.id.recent_photos); - this.imageButton = ViewUtil.findById(layout, R.id.gallery_button); - this.audioButton = ViewUtil.findById(layout, R.id.audio_button); - this.documentButton = ViewUtil.findById(layout, R.id.document_button); - this.contactButton = ViewUtil.findById(layout, R.id.contact_button); - this.cameraButton = ViewUtil.findById(layout, R.id.camera_button); - this.locationButton = ViewUtil.findById(layout, R.id.location_button); - this.gifButton = ViewUtil.findById(layout, R.id.giphy_button); - this.closeButton = ViewUtil.findById(layout, R.id.close_button); - - this.imageButton.setOnClickListener(new PropagatingClickListener(ADD_GALLERY)); - this.audioButton.setOnClickListener(new PropagatingClickListener(ADD_SOUND)); - this.documentButton.setOnClickListener(new PropagatingClickListener(ADD_DOCUMENT)); - this.contactButton.setOnClickListener(new PropagatingClickListener(ADD_CONTACT_INFO)); - this.cameraButton.setOnClickListener(new PropagatingClickListener(TAKE_PHOTO)); - this.locationButton.setOnClickListener(new PropagatingClickListener(ADD_LOCATION)); - this.gifButton.setOnClickListener(new PropagatingClickListener(ADD_GIF)); - this.closeButton.setOnClickListener(new CloseClickListener()); - this.recentRail.setListener(new RecentPhotoSelectedListener()); - - setContentView(layout); - setWidth(LinearLayout.LayoutParams.MATCH_PARENT); - setHeight(LinearLayout.LayoutParams.WRAP_CONTENT); - setBackgroundDrawable(new BitmapDrawable()); - setAnimationStyle(0); - setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - setFocusable(true); - setTouchable(true); - - updateHeight(); - - loaderManager.initLoader(1, null, recentRail); - } - - public void show(@NonNull Activity activity, final @NonNull View anchor) { - updateHeight(); - - if (Permissions.hasAll(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) { - recentRail.setVisibility(View.VISIBLE); - loaderManager.restartLoader(1, null, recentRail); - } else { - recentRail.setVisibility(View.GONE); - } - - this.currentAnchor = anchor; - - showAtLocation(anchor, Gravity.BOTTOM, 0, 0); - - getContentView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - getContentView().getViewTreeObserver().removeGlobalOnLayoutListener(this); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - animateWindowInCircular(anchor, getContentView()); - } else { - animateWindowInTranslate(getContentView()); - } - } - }); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - animateButtonIn(imageButton, ANIMATION_DURATION / 2); - animateButtonIn(cameraButton, ANIMATION_DURATION / 2); - - animateButtonIn(audioButton, ANIMATION_DURATION / 3); - animateButtonIn(locationButton, ANIMATION_DURATION / 3); - animateButtonIn(documentButton, ANIMATION_DURATION / 4); - animateButtonIn(gifButton, ANIMATION_DURATION / 4); - animateButtonIn(contactButton, 0); - animateButtonIn(closeButton, 0); - } - } - - private void updateHeight() { - int thresholdInDP = 120; - float scale = context.getResources().getDisplayMetrics().density; - int thresholdInPX = (int)(thresholdInDP * scale); - View contentView = ViewUtil.findById(getContentView(), R.id.contentView); - LinearLayout.LayoutParams contentViewLayoutParams = (LinearLayout.LayoutParams)contentView.getLayoutParams(); - contentViewLayoutParams.height = keyboardHeight > thresholdInPX ? keyboardHeight : LinearLayout.LayoutParams.WRAP_CONTENT; - contentView.setLayoutParams(contentViewLayoutParams); - } - - @Override - public void dismiss() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - animateWindowOutCircular(currentAnchor, getContentView()); - } else { - animateWindowOutTranslate(getContentView()); - } - } - - public void setListener(@Nullable AttachmentClickedListener listener) { - this.listener = listener; - } - - private void animateButtonIn(View button, int delay) { - AnimationSet animation = new AnimationSet(true); - Animation scale = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, - Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.0f); - - animation.addAnimation(scale); - animation.setInterpolator(new OvershootInterpolator(1)); - animation.setDuration(ANIMATION_DURATION); - animation.setStartOffset(delay); - button.startAnimation(animation); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void animateWindowInCircular(@Nullable View anchor, @NonNull View contentView) { - Pair coordinates = getClickOrigin(anchor, contentView); - Animator animator = ViewAnimationUtils.createCircularReveal(contentView, - coordinates.first, - coordinates.second, - 0, - Math.max(contentView.getWidth(), contentView.getHeight())); - animator.setDuration(ANIMATION_DURATION); - animator.start(); - } - - private void animateWindowInTranslate(@NonNull View contentView) { - Animation animation = new TranslateAnimation(0, 0, contentView.getHeight(), 0); - animation.setDuration(ANIMATION_DURATION); - - getContentView().startAnimation(animation); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void animateWindowOutCircular(@Nullable View anchor, @NonNull View contentView) { - Pair coordinates = getClickOrigin(anchor, contentView); - Animator animator = ViewAnimationUtils.createCircularReveal(getContentView(), - coordinates.first, - coordinates.second, - Math.max(getContentView().getWidth(), getContentView().getHeight()), - 0); - - animator.setDuration(ANIMATION_DURATION); - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - AttachmentTypeSelector.super.dismiss(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - - animator.start(); - } - - private void animateWindowOutTranslate(@NonNull View contentView) { - Animation animation = new TranslateAnimation(0, 0, 0, contentView.getTop() + contentView.getHeight()); - animation.setDuration(ANIMATION_DURATION); - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - AttachmentTypeSelector.super.dismiss(); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - - getContentView().startAnimation(animation); - } - - private Pair getClickOrigin(@Nullable View anchor, @NonNull View contentView) { - if (anchor == null) return new Pair<>(0, 0); - - final int[] anchorCoordinates = new int[2]; - anchor.getLocationOnScreen(anchorCoordinates); - anchorCoordinates[0] += anchor.getWidth() / 2; - anchorCoordinates[1] += anchor.getHeight() / 2; - - final int[] contentCoordinates = new int[2]; - contentView.getLocationOnScreen(contentCoordinates); - - int x = anchorCoordinates[0] - contentCoordinates[0]; - int y = anchorCoordinates[1] - contentCoordinates[1]; - - return new Pair<>(x, y); - } - - private class RecentPhotoSelectedListener implements RecentPhotoViewRail.OnItemClickedListener { - @Override - public void onItemClicked(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size) { - animateWindowOutTranslate(getContentView()); - - if (listener != null) listener.onQuickAttachment(uri, mimeType, bucketId, dateTaken, width, height, size); - } - } - - private class PropagatingClickListener implements View.OnClickListener { - - private final int type; - - private PropagatingClickListener(int type) { - this.type = type; - } - - @Override - public void onClick(View v) { - animateWindowOutTranslate(getContentView()); - - if (listener != null) listener.onClick(type); - } - - } - - private class CloseClickListener implements View.OnClickListener { - @Override - public void onClick(View v) { - dismiss(); - } - } - - public interface AttachmentClickedListener { - void onClick(int type); - void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java index 573e8d2d2b..0139e9932c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -25,8 +25,8 @@ import org.session.libsession.utilities.Address; import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientExporter; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator; import java.util.Objects; @@ -117,10 +117,10 @@ public class AvatarImageView extends AppCompatImageView { } private void updateAvatar(Recipient recipient) { - setAvatar(GlideApp.with(getContext()), recipient, false); + setAvatar(Glide.with(getContext()), recipient, false); } - public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) { + public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) { if (recipient != null) { if (recipient.isLocalNumber()) { setImageDrawable(new ResourceContactPhoto(R.drawable.ic_note_to_self).asDrawable(getContext(), recipient.getColor().toAvatarColor(getContext()), inverted)); @@ -156,7 +156,7 @@ public class AvatarImageView extends AppCompatImageView { } } - public void clear(@NonNull GlideRequests glideRequests) { + public void clear(@NonNull RequestManager glideRequests) { glideRequests.clear(this); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/CustomDefaultPreference.java b/app/src/main/java/org/thoughtcrime/securesms/components/CustomDefaultPreference.java deleted file mode 100644 index 178803a9f0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/CustomDefaultPreference.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.app.Dialog; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.preference.DialogPreference; -import androidx.preference.PreferenceDialogFragmentCompat; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import org.session.libsignal.utilities.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; - -import network.loki.messenger.R; -import org.thoughtcrime.securesms.components.CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.CustomPreferenceValidator; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.net.URI; -import java.net.URISyntaxException; - - -public class CustomDefaultPreference extends DialogPreference { - - private static final String TAG = CustomDefaultPreference.class.getSimpleName(); - - private final int inputType; - private final String customPreference; - private final String customToggle; - - private CustomPreferenceValidator validator; - private String defaultValue; - - public CustomDefaultPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - int[] attributeNames = new int[]{android.R.attr.inputType, R.attr.custom_pref_toggle}; - TypedArray attributes = context.obtainStyledAttributes(attrs, attributeNames); - - this.inputType = attributes.getInt(0, 0); - this.customPreference = getKey(); - this.customToggle = attributes.getString(1); - this.validator = new CustomDefaultPreferenceDialogFragmentCompat.NullValidator(); - - attributes.recycle(); - - setPersistent(false); - setDialogLayoutResource(R.layout.custom_default_preference_dialog); - } - - public CustomDefaultPreference setValidator(CustomPreferenceValidator validator) { - this.validator = validator; - return this; - } - - public CustomDefaultPreference setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - this.setSummary(getSummary()); - return this; - } - - @Override - public String getSummary() { - if (isCustom()) { - return getContext().getString(R.string.CustomDefaultPreference_using_custom, - getPrettyPrintValue(getCustomValue())); - } else { - return getContext().getString(R.string.CustomDefaultPreference_using_default, - getPrettyPrintValue(getDefaultValue())); - } - } - - private String getPrettyPrintValue(String value) { - if (TextUtils.isEmpty(value)) return getContext().getString(R.string.CustomDefaultPreference_none); - else return value; - } - - private boolean isCustom() { - return TextSecurePreferences.getBooleanPreference(getContext(), customToggle, false); - } - - private void setCustom(boolean custom) { - TextSecurePreferences.setBooleanPreference(getContext(), customToggle, custom); - } - - private String getCustomValue() { - return TextSecurePreferences.getStringPreference(getContext(), customPreference, ""); - } - - private void setCustomValue(String value) { - TextSecurePreferences.setStringPreference(getContext(), customPreference, value); - } - - private String getDefaultValue() { - return defaultValue; - } - - - public static class CustomDefaultPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat { - - private static final String INPUT_TYPE = "input_type"; - - private Spinner spinner; - private EditText customText; - private TextView defaultLabel; - - public static CustomDefaultPreferenceDialogFragmentCompat newInstance(String key) { - CustomDefaultPreferenceDialogFragmentCompat fragment = new CustomDefaultPreferenceDialogFragmentCompat(); - Bundle b = new Bundle(1); - b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key); - fragment.setArguments(b); - return fragment; - } - - @Override - protected void onBindDialogView(@NonNull View view) { - Log.i(TAG, "onBindDialogView"); - super.onBindDialogView(view); - - CustomDefaultPreference preference = (CustomDefaultPreference)getPreference(); - - this.spinner = (Spinner) view.findViewById(R.id.default_or_custom); - this.defaultLabel = (TextView) view.findViewById(R.id.default_label); - this.customText = (EditText) view.findViewById(R.id.custom_edit); - - this.customText.setInputType(preference.inputType); - this.customText.addTextChangedListener(new TextValidator()); - this.customText.setText(preference.getCustomValue()); - this.spinner.setOnItemSelectedListener(new SelectionLister()); - this.defaultLabel.setText(preference.getPrettyPrintValue(preference.defaultValue)); - } - - - @Override - public @NonNull Dialog onCreateDialog(Bundle instanceState) { - Dialog dialog = super.onCreateDialog(instanceState); - - CustomDefaultPreference preference = (CustomDefaultPreference)getPreference(); - - if (preference.isCustom()) spinner.setSelection(1, true); - else spinner.setSelection(0, true); - - return dialog; - } - - @Override - public void onDialogClosed(boolean positiveResult) { - CustomDefaultPreference preference = (CustomDefaultPreference)getPreference(); - - if (positiveResult) { - if (spinner != null) preference.setCustom(spinner.getSelectedItemPosition() == 1); - if (customText != null) preference.setCustomValue(customText.getText().toString()); - - preference.setSummary(preference.getSummary()); - } - } - - interface CustomPreferenceValidator { - public boolean isValid(String value); - } - - private static class NullValidator implements CustomPreferenceValidator { - @Override - public boolean isValid(String value) { - return true; - } - } - - private class TextValidator implements TextWatcher { - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) {} - - @Override - public void afterTextChanged(Editable s) { - CustomDefaultPreference preference = (CustomDefaultPreference)getPreference(); - - if (spinner.getSelectedItemPosition() == 1) { - Button positiveButton = ((AlertDialog)getDialog()).getButton(AlertDialog.BUTTON_POSITIVE); - positiveButton.setEnabled(preference.validator.isValid(s.toString())); - } - } - } - - public static class UriValidator implements CustomPreferenceValidator { - @Override - public boolean isValid(String value) { - if (TextUtils.isEmpty(value)) return true; - - try { - new URI(value); - return true; - } catch (URISyntaxException mue) { - return false; - } - } - } - - public static class HostnameValidator implements CustomPreferenceValidator { - @Override - public boolean isValid(String value) { - if (TextUtils.isEmpty(value)) return true; - - try { - URI uri = new URI(null, value, null, null); - return true; - } catch (URISyntaxException mue) { - return false; - } - } - } - - public static class PortValidator implements CustomPreferenceValidator { - @Override - public boolean isValid(String value) { - try { - Integer.parseInt(value); - return true; - } catch (NumberFormatException e) { - return false; - } - } - } - - private class SelectionLister implements AdapterView.OnItemSelectedListener { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - CustomDefaultPreference preference = (CustomDefaultPreference)getPreference(); - Button positiveButton = ((AlertDialog)getDialog()).getButton(AlertDialog.BUTTON_POSITIVE); - - defaultLabel.setVisibility(position == 0 ? View.VISIBLE : View.GONE); - customText.setVisibility(position == 0 ? View.GONE : View.VISIBLE); - positiveButton.setEnabled(position == 0 || preference.validator.isValid(customText.getText().toString())); - } - - @Override - public void onNothingSelected(AdapterView parent) { - defaultLabel.setVisibility(View.VISIBLE); - customText.setVisibility(View.GONE); - } - } - - } - - - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/DocumentView.java b/app/src/main/java/org/thoughtcrime/securesms/components/DocumentView.java index d1d70fdac0..f51b4a7d93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/DocumentView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/DocumentView.java @@ -105,7 +105,7 @@ public class DocumentView extends FrameLayout { this.documentSlide = documentSlide; - this.fileName.setText(documentSlide.getFileName().or(getContext().getString(R.string.DocumentView_unknown_file))); + this.fileName.setText(documentSlide.getFileName().or(getContext().getString(R.string.attachmentsErrorNotSupported))); this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize())); this.document.setText(getFileType(documentSlide.getFileName())); this.setOnClickListener(new OpenClickedListener(documentSlide)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java index d98c56ede5..ae9f5e6e70 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/FromTextView.java @@ -54,7 +54,7 @@ public class FromTextView extends EmojiTextView { if (recipient.isLocalNumber()) { - builder.append(getContext().getString(R.string.note_to_self)); + builder.append(getContext().getString(R.string.noteToSelf)); } else if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) { SpannableString profileName = new SpannableString(" (~" + recipient.getProfileName() + ") "); profileName.setSpan(new CenterAlignedRelativeSizeSpan(0.75f), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java b/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java index 14b70a53d6..bb973c23ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/MediaView.java @@ -13,7 +13,7 @@ import android.view.Window; import android.widget.FrameLayout; import network.loki.messenger.R; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.video.VideoPlayer; @@ -54,7 +54,7 @@ public class MediaView extends FrameLayout { this.videoView = new Stub<>(findViewById(R.id.video_player_stub)); } - public void set(@NonNull GlideRequests glideRequests, + public void set(@NonNull RequestManager glideRequests, @NonNull Window window, @NonNull Uri source, @NonNull String mediaType, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 6d59bbfc92..571c778d4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -20,8 +20,8 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager class ProfilePictureView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -29,7 +29,7 @@ class ProfilePictureView @JvmOverloads constructor( private val TAG = "ProfilePictureView" private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this) - private val glide: GlideRequests = GlideApp.with(this) + private val glide: RequestManager = Glide.with(this) private val prefs = AppTextSecurePreferences(context) private val userPublicKey = prefs.getLocalNumber() var publicKey: String? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java b/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java index 542b7e8ba2..e1d3236a96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java @@ -28,7 +28,7 @@ import com.bumptech.glide.signature.MediaStoreSignature; import network.loki.messenger.R; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.session.libsession.utilities.ViewUtil; @@ -118,7 +118,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo Key signature = new MediaStoreSignature(mimeType, dateModified, orientation); - GlideApp.with(getContext().getApplicationContext()) + Glide.with(getContext().getApplicationContext()) .load(uri) .signature(signature) .diskCacheStrategy(DiskCacheStrategy.NONE) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SearchToolbar.java b/app/src/main/java/org/thoughtcrime/securesms/components/SearchToolbar.java index 9032b26a2b..4655622ed2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/SearchToolbar.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SearchToolbar.java @@ -44,10 +44,9 @@ public class SearchToolbar extends LinearLayout { inflate(getContext(), R.layout.search_toolbar, this); setOrientation(VERTICAL); - Toolbar toolbar = findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.search_toolbar); - toolbar.setNavigationIcon( - getContext().getResources().getDrawable(R.drawable.ic_baseline_clear_24)); + toolbar.setNavigationIcon(getContext().getResources().getDrawable(R.drawable.ic_baseline_clear_24)); toolbar.inflateMenu(R.menu.conversation_list_search); this.searchItem = toolbar.getMenu().findItem(R.id.action_filter_search); @@ -56,8 +55,8 @@ public class SearchToolbar extends LinearLayout { searchView.setSubmitButtonEnabled(false); - if (searchText != null) searchText.setHint(R.string.SearchToolbar_search); - else searchView.setQueryHint(getResources().getString(R.string.SearchToolbar_search)); + if (searchText != null) searchText.setHint(R.string.search); + else searchView.setQueryHint(getResources().getString(R.string.search)); searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java b/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java index 98a623eef3..fd20283ae1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java @@ -8,7 +8,7 @@ import android.view.View; import android.widget.FrameLayout; import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideClickListener; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java b/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java index 6e7993a575..02520700e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SwitchPreferenceCompat.java @@ -1,15 +1,18 @@ package org.thoughtcrime.securesms.components; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.content.Context; import android.util.AttributeSet; - import androidx.preference.CheckBoxPreference; import androidx.preference.Preference; - +import com.squareup.phrase.Phrase; import network.loki.messenger.R; public class SwitchPreferenceCompat extends CheckBoxPreference { + private static String LOCK_SCREEN_KEY = "pref_android_screen_lock"; + private Preference.OnPreferenceClickListener listener; public SwitchPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) { @@ -34,6 +37,19 @@ public class SwitchPreferenceCompat extends CheckBoxPreference { private void setLayoutRes() { setWidgetLayoutResource(R.layout.switch_compat_preference); + + if (this.hasKey()) { + String key = this.getKey(); + + // Substitute app name into lockscreen preference summary + if (key.equalsIgnoreCase(LOCK_SCREEN_KEY)) { + Context c = getContext(); + CharSequence substitutedSummaryCS = Phrase.from(c, R.string.lockAppDescriptionAndroid) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format(); + this.setSummary(substitutedSummaryCS); + } + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java index 36a607c819..918b992a08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java @@ -1,36 +1,33 @@ package org.thoughtcrime.securesms.components; +import static org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY; + import android.animation.LayoutTransition; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import com.annimon.stream.Stream; import com.pnikosis.materialishprogress.ProgressWheel; - +import com.squareup.phrase.Phrase; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import network.loki.messenger.R; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; -import org.thoughtcrime.securesms.database.AttachmentDatabase; +import org.session.libsession.utilities.ViewUtil; import org.thoughtcrime.securesms.events.PartProgressEvent; import org.thoughtcrime.securesms.mms.Slide; -import org.session.libsession.utilities.ViewUtil; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import network.loki.messenger.R; - public class TransferControlView extends FrameLayout { @Nullable private List slides; @@ -191,7 +188,8 @@ public class TransferControlView extends FrameLayout { return slides.get(0).getContentDescription(); } else { int downloadCount = Stream.of(slides).reduce(0, (count, slide) -> slide.getTransferState() != AttachmentTransferProgress.TRANSFER_PROGRESS_DONE ? count + 1 : count); - return getContext().getResources().getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount); + + return Phrase.from(getContext(), R.string.andMore).put(COUNT_KEY, downloadCount).format().toString(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java index 265f707df9..b246bca4d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java @@ -25,7 +25,7 @@ import network.loki.messenger.R; import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder; import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; @@ -62,7 +62,7 @@ public class ZoomingImageView extends FrameLayout { } @SuppressLint("StaticFieldLeak") - public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType) + public void setImageUri(@NonNull RequestManager glideRequests, @NonNull Uri uri, @NonNull String contentType) { final Context context = getContext(); final int maxTextureSize = BitmapUtil.getMaxTextureSize(); @@ -97,7 +97,7 @@ public class ZoomingImageView extends FrameLayout { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { + private void setImageViewUri(@NonNull RequestManager glideRequests, @NonNull Uri uri) { photoView.setVisibility(View.VISIBLE); subsamplingImageView.setVisibility(View.GONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java index 72419f24c6..d34db1d810 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java @@ -12,7 +12,7 @@ import android.widget.ImageView; import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.util.ResUtil; import org.session.libsession.utilities.ThemeUtil; @@ -87,7 +87,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider, } @Override - public void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull ImageView imageView, int index) { + public void loadCategoryTabIcon(@NonNull RequestManager glideRequests, @NonNull ImageView imageView, int index) { Drawable drawable = ResUtil.getDrawable(context, models.get(index).getIconAttr()); imageView.setImageDrawable(drawable); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPages.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPages.java index 78d085fb71..dddcb56a8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPages.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPages.java @@ -1,13 +1,9 @@ package org.thoughtcrime.securesms.components.emoji; import android.net.Uri; - import network.loki.messenger.R; -import org.session.libsignal.utilities.Pair; import org.thoughtcrime.securesms.emoji.EmojiCategory; - import java.util.Arrays; -import java.util.LinkedList; import java.util.List; class EmojiPages { @@ -58,24 +54,6 @@ class EmojiPages { new Emoji("\ud83c\udfc1"), new Emoji("\ud83d\udea9"), new Emoji("\ud83c\udf8c"), new Emoji("\ud83c\udff4"), new Emoji("\ud83c\udff3\ufe0f"), new Emoji("\ud83c\udff3\ufe0f\u200d\ud83c\udf08"), new Emoji("\ud83c\udde6\ud83c\udde8"), new Emoji("\ud83c\udde6\ud83c\udde9"), new Emoji("\ud83c\udde6\ud83c\uddea"), new Emoji("\ud83c\udde6\ud83c\uddeb"), new Emoji("\ud83c\udde6\ud83c\uddec"), new Emoji("\ud83c\udde6\ud83c\uddee"), new Emoji("\ud83c\udde6\ud83c\uddf1"), new Emoji("\ud83c\udde6\ud83c\uddf2"), new Emoji("\ud83c\udde6\ud83c\uddf4"), new Emoji("\ud83c\udde6\ud83c\uddf6"), new Emoji("\ud83c\udde6\ud83c\uddf7"), new Emoji("\ud83c\udde6\ud83c\uddf8"), new Emoji("\ud83c\udde6\ud83c\uddf9"), new Emoji("\ud83c\udde6\ud83c\uddfa"), new Emoji("\ud83c\udde6\ud83c\uddfc"), new Emoji("\ud83c\udde6\ud83c\uddfd"), new Emoji("\ud83c\udde6\ud83c\uddff"), new Emoji("\ud83c\udde7\ud83c\udde6"), new Emoji("\ud83c\udde7\ud83c\udde7"), new Emoji("\ud83c\udde7\ud83c\udde9"), new Emoji("\ud83c\udde7\ud83c\uddea"), new Emoji("\ud83c\udde7\ud83c\uddeb"), new Emoji("\ud83c\udde7\ud83c\uddec"), new Emoji("\ud83c\udde7\ud83c\udded"), new Emoji("\ud83c\udde7\ud83c\uddee"), new Emoji("\ud83c\udde7\ud83c\uddef"), new Emoji("\ud83c\udde7\ud83c\uddf1"), new Emoji("\ud83c\udde7\ud83c\uddf2"), new Emoji("\ud83c\udde7\ud83c\uddf3"), new Emoji("\ud83c\udde7\ud83c\uddf4"), new Emoji("\ud83c\udde7\ud83c\uddf6"), new Emoji("\ud83c\udde7\ud83c\uddf7"), new Emoji("\ud83c\udde7\ud83c\uddf8"), new Emoji("\ud83c\udde7\ud83c\uddf9"), new Emoji("\ud83c\udde7\ud83c\uddfb"), new Emoji("\ud83c\udde7\ud83c\uddfc"), new Emoji("\ud83c\udde7\ud83c\uddfe"), new Emoji("\ud83c\udde7\ud83c\uddff"), new Emoji("\ud83c\udde8\ud83c\udde6"), new Emoji("\ud83c\udde8\ud83c\udde8"), new Emoji("\ud83c\udde8\ud83c\udde9"), new Emoji("\ud83c\udde8\ud83c\uddeb"), new Emoji("\ud83c\udde8\ud83c\uddec"), new Emoji("\ud83c\udde8\ud83c\udded"), new Emoji("\ud83c\udde8\ud83c\uddee"), new Emoji("\ud83c\udde8\ud83c\uddf0"), new Emoji("\ud83c\udde8\ud83c\uddf1"), new Emoji("\ud83c\udde8\ud83c\uddf2"), new Emoji("\ud83c\udde8\ud83c\uddf3"), new Emoji("\ud83c\udde8\ud83c\uddf4"), new Emoji("\ud83c\udde8\ud83c\uddf5"), new Emoji("\ud83c\udde8\ud83c\uddf7"), new Emoji("\ud83c\udde8\ud83c\uddfa"), new Emoji("\ud83c\udde8\ud83c\uddfb"), new Emoji("\ud83c\udde8\ud83c\uddfc"), new Emoji("\ud83c\udde8\ud83c\uddfd"), new Emoji("\ud83c\udde8\ud83c\uddfe"), new Emoji("\ud83c\udde8\ud83c\uddff"), new Emoji("\ud83c\udde9\ud83c\uddea"), new Emoji("\ud83c\udde9\ud83c\uddec"), new Emoji("\ud83c\udde9\ud83c\uddef"), new Emoji("\ud83c\udde9\ud83c\uddf0"), new Emoji("\ud83c\udde9\ud83c\uddf2"), new Emoji("\ud83c\udde9\ud83c\uddf4"), new Emoji("\ud83c\udde9\ud83c\uddff"), new Emoji("\ud83c\uddea\ud83c\udde6"), new Emoji("\ud83c\uddea\ud83c\udde8"), new Emoji("\ud83c\uddea\ud83c\uddea"), new Emoji("\ud83c\uddea\ud83c\uddec"), new Emoji("\ud83c\uddea\ud83c\udded"), new Emoji("\ud83c\uddea\ud83c\uddf7"), new Emoji("\ud83c\uddea\ud83c\uddf8"), new Emoji("\ud83c\uddea\ud83c\uddf9"), new Emoji("\ud83c\uddea\ud83c\uddfa"), new Emoji("\ud83c\uddeb\ud83c\uddee"), new Emoji("\ud83c\uddeb\ud83c\uddef"), new Emoji("\ud83c\uddeb\ud83c\uddf0"), new Emoji("\ud83c\uddeb\ud83c\uddf2"), new Emoji("\ud83c\uddeb\ud83c\uddf4"), new Emoji("\ud83c\uddeb\ud83c\uddf7"), new Emoji("\ud83c\uddec\ud83c\udde6"), new Emoji("\ud83c\uddec\ud83c\udde7"), new Emoji("\ud83c\uddec\ud83c\udde9"), new Emoji("\ud83c\uddec\ud83c\uddea"), new Emoji("\ud83c\uddec\ud83c\uddeb"), new Emoji("\ud83c\uddec\ud83c\uddec"), new Emoji("\ud83c\uddec\ud83c\udded"), new Emoji("\ud83c\uddec\ud83c\uddee"), new Emoji("\ud83c\uddec\ud83c\uddf1"), new Emoji("\ud83c\uddec\ud83c\uddf2"), new Emoji("\ud83c\uddec\ud83c\uddf3"), new Emoji("\ud83c\uddec\ud83c\uddf5"), new Emoji("\ud83c\uddec\ud83c\uddf6"), new Emoji("\ud83c\uddec\ud83c\uddf7"), new Emoji("\ud83c\uddec\ud83c\uddf8"), new Emoji("\ud83c\uddec\ud83c\uddf9"), new Emoji("\ud83c\uddec\ud83c\uddfa"), new Emoji("\ud83c\uddec\ud83c\uddfc"), new Emoji("\ud83c\uddec\ud83c\uddfe"), new Emoji("\ud83c\udded\ud83c\uddf0"), new Emoji("\ud83c\udded\ud83c\uddf2"), new Emoji("\ud83c\udded\ud83c\uddf3"), new Emoji("\ud83c\udded\ud83c\uddf7"), new Emoji("\ud83c\udded\ud83c\uddf9"), new Emoji("\ud83c\udded\ud83c\uddfa"), new Emoji("\ud83c\uddee\ud83c\udde8"), new Emoji("\ud83c\uddee\ud83c\udde9"), new Emoji("\ud83c\uddee\ud83c\uddea"), new Emoji("\ud83c\uddee\ud83c\uddf1"), new Emoji("\ud83c\uddee\ud83c\uddf2"), new Emoji("\ud83c\uddee\ud83c\uddf3"), new Emoji("\ud83c\uddee\ud83c\uddf4"), new Emoji("\ud83c\uddee\ud83c\uddf6"), new Emoji("\ud83c\uddee\ud83c\uddf7"), new Emoji("\ud83c\uddee\ud83c\uddf8"), new Emoji("\ud83c\uddee\ud83c\uddf9"), new Emoji("\ud83c\uddef\ud83c\uddea"), new Emoji("\ud83c\uddef\ud83c\uddf2"), new Emoji("\ud83c\uddef\ud83c\uddf4"), new Emoji("\ud83c\uddef\ud83c\uddf5"), new Emoji("\ud83c\uddf0\ud83c\uddea"), new Emoji("\ud83c\uddf0\ud83c\uddec"), new Emoji("\ud83c\uddf0\ud83c\udded"), new Emoji("\ud83c\uddf0\ud83c\uddee"), new Emoji("\ud83c\uddf0\ud83c\uddf2"), new Emoji("\ud83c\uddf0\ud83c\uddf3"), new Emoji("\ud83c\uddf0\ud83c\uddf5"), new Emoji("\ud83c\uddf0\ud83c\uddf7"), new Emoji("\ud83c\uddf0\ud83c\uddfc"), new Emoji("\ud83c\uddf0\ud83c\uddfe"), new Emoji("\ud83c\uddf0\ud83c\uddff"), new Emoji("\ud83c\uddf1\ud83c\udde6"), new Emoji("\ud83c\uddf1\ud83c\udde7"), new Emoji("\ud83c\uddf1\ud83c\udde8"), new Emoji("\ud83c\uddf1\ud83c\uddee"), new Emoji("\ud83c\uddf1\ud83c\uddf0"), new Emoji("\ud83c\uddf1\ud83c\uddf7"), new Emoji("\ud83c\uddf1\ud83c\uddf8"), new Emoji("\ud83c\uddf1\ud83c\uddf9"), new Emoji("\ud83c\uddf1\ud83c\uddfa"), new Emoji("\ud83c\uddf1\ud83c\uddfb"), new Emoji("\ud83c\uddf1\ud83c\uddfe"), new Emoji("\ud83c\uddf2\ud83c\udde6"), new Emoji("\ud83c\uddf2\ud83c\udde8"), new Emoji("\ud83c\uddf2\ud83c\udde9"), new Emoji("\ud83c\uddf2\ud83c\uddea"), new Emoji("\ud83c\uddf2\ud83c\uddeb"), new Emoji("\ud83c\uddf2\ud83c\uddec"), new Emoji("\ud83c\uddf2\ud83c\udded"), new Emoji("\ud83c\uddf2\ud83c\uddf0"), new Emoji("\ud83c\uddf2\ud83c\uddf1"), new Emoji("\ud83c\uddf2\ud83c\uddf2"), new Emoji("\ud83c\uddf2\ud83c\uddf3"), new Emoji("\ud83c\uddf2\ud83c\uddf4"), new Emoji("\ud83c\uddf2\ud83c\uddf5"), new Emoji("\ud83c\uddf2\ud83c\uddf6"), new Emoji("\ud83c\uddf2\ud83c\uddf7"), new Emoji("\ud83c\uddf2\ud83c\uddf8"), new Emoji("\ud83c\uddf2\ud83c\uddf9"), new Emoji("\ud83c\uddf2\ud83c\uddfa"), new Emoji("\ud83c\uddf2\ud83c\uddfb"), new Emoji("\ud83c\uddf2\ud83c\uddfc"), new Emoji("\ud83c\uddf2\ud83c\uddfd"), new Emoji("\ud83c\uddf2\ud83c\uddfe"), new Emoji("\ud83c\uddf2\ud83c\uddff"), new Emoji("\ud83c\uddf3\ud83c\udde6"), new Emoji("\ud83c\uddf3\ud83c\udde8"), new Emoji("\ud83c\uddf3\ud83c\uddea"), new Emoji("\ud83c\uddf3\ud83c\uddeb"), new Emoji("\ud83c\uddf3\ud83c\uddec"), new Emoji("\ud83c\uddf3\ud83c\uddee"), new Emoji("\ud83c\uddf3\ud83c\uddf1"), new Emoji("\ud83c\uddf3\ud83c\uddf4"), new Emoji("\ud83c\uddf3\ud83c\uddf5"), new Emoji("\ud83c\uddf3\ud83c\uddf7"), new Emoji("\ud83c\uddf3\ud83c\uddfa"), new Emoji("\ud83c\uddf3\ud83c\uddff"), new Emoji("\ud83c\uddf4\ud83c\uddf2"), new Emoji("\ud83c\uddf5\ud83c\udde6"), new Emoji("\ud83c\uddf5\ud83c\uddea"), new Emoji("\ud83c\uddf5\ud83c\uddeb"), new Emoji("\ud83c\uddf5\ud83c\uddec"), new Emoji("\ud83c\uddf5\ud83c\udded"), new Emoji("\ud83c\uddf5\ud83c\uddf0"), new Emoji("\ud83c\uddf5\ud83c\uddf1"), new Emoji("\ud83c\uddf5\ud83c\uddf2"), new Emoji("\ud83c\uddf5\ud83c\uddf3"), new Emoji("\ud83c\uddf5\ud83c\uddf7"), new Emoji("\ud83c\uddf5\ud83c\uddf8"), new Emoji("\ud83c\uddf5\ud83c\uddf9"), new Emoji("\ud83c\uddf5\ud83c\uddfc"), new Emoji("\ud83c\uddf5\ud83c\uddfe"), new Emoji("\ud83c\uddf6\ud83c\udde6"), new Emoji("\ud83c\uddf7\ud83c\uddea"), new Emoji("\ud83c\uddf7\ud83c\uddf4"), new Emoji("\ud83c\uddf7\ud83c\uddf8"), new Emoji("\ud83c\uddf7\ud83c\uddfa"), new Emoji("\ud83c\uddf7\ud83c\uddfc"), new Emoji("\ud83c\uddf8\ud83c\udde6"), new Emoji("\ud83c\uddf8\ud83c\udde7"), new Emoji("\ud83c\uddf8\ud83c\udde8"), new Emoji("\ud83c\uddf8\ud83c\udde9"), new Emoji("\ud83c\uddf8\ud83c\uddea"), new Emoji("\ud83c\uddf8\ud83c\uddec"), new Emoji("\ud83c\uddf8\ud83c\udded"), new Emoji("\ud83c\uddf8\ud83c\uddee"), new Emoji("\ud83c\uddf8\ud83c\uddef"), new Emoji("\ud83c\uddf8\ud83c\uddf0"), new Emoji("\ud83c\uddf8\ud83c\uddf1"), new Emoji("\ud83c\uddf8\ud83c\uddf2"), new Emoji("\ud83c\uddf8\ud83c\uddf3"), new Emoji("\ud83c\uddf8\ud83c\uddf4"), new Emoji("\ud83c\uddf8\ud83c\uddf7"), new Emoji("\ud83c\uddf8\ud83c\uddf8"), new Emoji("\ud83c\uddf8\ud83c\uddf9"), new Emoji("\ud83c\uddf8\ud83c\uddfb"), new Emoji("\ud83c\uddf8\ud83c\uddfd"), new Emoji("\ud83c\uddf8\ud83c\uddfe"), new Emoji("\ud83c\uddf8\ud83c\uddff"), new Emoji("\ud83c\uddf9\ud83c\udde6"), new Emoji("\ud83c\uddf9\ud83c\udde8"), new Emoji("\ud83c\uddf9\ud83c\udde9"), new Emoji("\ud83c\uddf9\ud83c\uddeb"), new Emoji("\ud83c\uddf9\ud83c\uddec"), new Emoji("\ud83c\uddf9\ud83c\udded"), new Emoji("\ud83c\uddf9\ud83c\uddef"), new Emoji("\ud83c\uddf9\ud83c\uddf0"), new Emoji("\ud83c\uddf9\ud83c\uddf1"), new Emoji("\ud83c\uddf9\ud83c\uddf2"), new Emoji("\ud83c\uddf9\ud83c\uddf3"), new Emoji("\ud83c\uddf9\ud83c\uddf4"), new Emoji("\ud83c\uddf9\ud83c\uddf7"), new Emoji("\ud83c\uddf9\ud83c\uddf9"), new Emoji("\ud83c\uddf9\ud83c\uddfb"), new Emoji("\ud83c\uddf9\ud83c\uddfc"), new Emoji("\ud83c\uddf9\ud83c\uddff"), new Emoji("\ud83c\uddfa\ud83c\udde6"), new Emoji("\ud83c\uddfa\ud83c\uddec"), new Emoji("\ud83c\uddfa\ud83c\uddf2"), new Emoji("\ud83c\uddfa\ud83c\uddf8"), new Emoji("\ud83c\uddfa\ud83c\uddfe"), new Emoji("\ud83c\uddfa\ud83c\uddff"), new Emoji("\ud83c\uddfb\ud83c\udde6"), new Emoji("\ud83c\uddfb\ud83c\udde8"), new Emoji("\ud83c\uddfb\ud83c\uddea"), new Emoji("\ud83c\uddfb\ud83c\uddec"), new Emoji("\ud83c\uddfb\ud83c\uddee"), new Emoji("\ud83c\uddfb\ud83c\uddf3"), new Emoji("\ud83c\uddfb\ud83c\uddfa"), new Emoji("\ud83c\uddfc\ud83c\uddeb"), new Emoji("\ud83c\uddfc\ud83c\uddf8"), new Emoji("\ud83c\uddfd\ud83c\uddf0"), new Emoji("\ud83c\uddfe\ud83c\uddea"), new Emoji("\ud83c\uddfe\ud83c\uddf9"), new Emoji("\ud83c\uddff\ud83c\udde6"), new Emoji("\ud83c\uddff\ud83c\uddf2"), new Emoji("\ud83c\uddff\ud83c\uddfc"), new Emoji("\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f"), new Emoji("\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f"), new Emoji("\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f") ), Uri.parse("emoji/Flags.png")); - private static final EmojiPageModel PAGE_EMOTICONS = new StaticEmojiPageModel(EmojiCategory.EMOTICONS, new String[] { - ":-)", ";-)", "(-:", ":->", ":-D", "\\o/", - ":-P", "B-)", ":-$", ":-*", "O:-)", "=-O", - "O_O", "O_o", "o_O", ":O", ":-!", ":-x", - ":-|", ":-\\", ":-(", ":'(", ":-[", ">:-(", - "^.^", "^_^", "\\(\u02c6\u02da\u02c6)/", - "\u30fd(\u00b0\u25c7\u00b0 )\u30ce", "\u00af\\(\u00b0_o)/\u00af", - "\u00af\\_(\u30c4)_/\u00af", "(\u00ac_\u00ac)", - "(>_<)", "(\u2565\ufe4f\u2565)", "(\u261e\uff9f\u30ee\uff9f)\u261e", - "\u261c(\uff9f\u30ee\uff9f\u261c)", "\u261c(\u2312\u25bd\u2312)\u261e", - "(\u256f\u00b0\u25a1\u00b0)\u256f\ufe35", "\u253b\u2501\u253b", - "\u252c\u2500\u252c", "\u30ce(\u00b0\u2013\u00b0\u30ce)", - "(^._.^)\uff89", "\u0e05^\u2022\ufecc\u2022^\u0e05", - "\u0295\u2022\u1d25\u2022\u0294", "(\u2022_\u2022)", - " \u25a0-\u25a0\u00ac <(\u2022_\u2022) ", "(\u25a0_\u25a0\u00ac)", - "\u01aa(\u0693\u05f2)\u200e\u01aa\u200b\u200b" - }, null); - static final List DISPLAY_PAGES = Arrays.asList(PAGE_PEOPLE, PAGE_NATURE, PAGE_FOODS, @@ -83,226 +61,7 @@ class EmojiPages { PAGE_PLACES, PAGE_OBJECTS, PAGE_SYMBOLS, - PAGE_FLAGS, - PAGE_EMOTICONS); + PAGE_FLAGS); - static final List DATA_PAGES = Arrays.asList(PAGE_PEOPLE_0, - PAGE_PEOPLE_1, - PAGE_PEOPLE_2, - PAGE_PEOPLE_3, - PAGE_NATURE, - PAGE_FOODS, - PAGE_ACTIVITY, - PAGE_PLACES, - PAGE_OBJECTS, - PAGE_SYMBOLS, - PAGE_FLAGS, - PAGE_EMOTICONS); - static final List> OBSOLETE = new LinkedList>() {{ - add(new Pair<>("\ud83d\udc6e", "\ud83d\udc6e\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc6e\ud83c\udffb", "\ud83d\udc6e\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc6e\ud83c\udffc", "\ud83d\udc6e\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc6e\ud83c\udffd", "\ud83d\udc6e\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc6e\ud83c\udffe", "\ud83d\udc6e\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc6e\ud83c\udfff", "\ud83d\udc6e\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udd75\ufe0f", "\ud83d\udd75\ufe0f\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udd75\ud83c\udffb", "\ud83d\udd75\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udd75\ud83c\udffc", "\ud83d\udd75\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udd75\ud83c\udffd", "\ud83d\udd75\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udd75\ud83c\udffe", "\ud83d\udd75\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udd75\ud83c\udfff", "\ud83d\udd75\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc82", "\ud83d\udc82\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc82\ud83c\udffb", "\ud83d\udc82\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc82\ud83c\udffc", "\ud83d\udc82\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc82\ud83c\udffd", "\ud83d\udc82\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc82\ud83c\udffe", "\ud83d\udc82\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc82\ud83c\udfff", "\ud83d\udc82\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc77", "\ud83d\udc77\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc77\ud83c\udffb", "\ud83d\udc77\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc77\ud83c\udffc", "\ud83d\udc77\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc77\ud83c\udffd", "\ud83d\udc77\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc77\ud83c\udffe", "\ud83d\udc77\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc77\ud83c\udfff", "\ud83d\udc77\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc73", "\ud83d\udc73\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc73\ud83c\udffb", "\ud83d\udc73\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc73\ud83c\udffc", "\ud83d\udc73\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc73\ud83c\udffd", "\ud83d\udc73\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc73\ud83c\udffe", "\ud83d\udc73\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc73\ud83c\udfff", "\ud83d\udc73\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc71", "\ud83d\udc71\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc71\ud83c\udffb", "\ud83d\udc71\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc71\ud83c\udffc", "\ud83d\udc71\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc71\ud83c\udffd", "\ud83d\udc71\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc71\ud83c\udffe", "\ud83d\udc71\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc71\ud83c\udfff", "\ud83d\udc71\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd9", "\ud83e\uddd9\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd9\ud83c\udffb", "\ud83e\uddd9\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd9\ud83c\udffc", "\ud83e\uddd9\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd9\ud83c\udffd", "\ud83e\uddd9\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd9\ud83c\udffe", "\ud83e\uddd9\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd9\ud83c\udfff", "\ud83e\uddd9\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddda", "\ud83e\uddda\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddda\ud83c\udffb", "\ud83e\uddda\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddda\ud83c\udffc", "\ud83e\uddda\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddda\ud83c\udffd", "\ud83e\uddda\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddda\ud83c\udffe", "\ud83e\uddda\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddda\ud83c\udfff", "\ud83e\uddda\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddb", "\ud83e\udddb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddb\ud83c\udffb", "\ud83e\udddb\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddb\ud83c\udffc", "\ud83e\udddb\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddb\ud83c\udffd", "\ud83e\udddb\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddb\ud83c\udffe", "\ud83e\udddb\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddb\ud83c\udfff", "\ud83e\udddb\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\udddc", "\ud83e\udddc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddc\ud83c\udffb", "\ud83e\udddc\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddc\ud83c\udffc", "\ud83e\udddc\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddc\ud83c\udffd", "\ud83e\udddc\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddc\ud83c\udffe", "\ud83e\udddc\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddc\ud83c\udfff", "\ud83e\udddc\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddd", "\ud83e\udddd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddd\ud83c\udffb", "\ud83e\udddd\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddd\ud83c\udffc", "\ud83e\udddd\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddd\ud83c\udffd", "\ud83e\udddd\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddd\ud83c\udffe", "\ud83e\udddd\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddd\ud83c\udfff", "\ud83e\udddd\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddde", "\ud83e\uddde\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\udddf", "\ud83e\udddf\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\ude4d", "\ud83d\ude4d\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4d\ud83c\udffb", "\ud83d\ude4d\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4d\ud83c\udffc", "\ud83d\ude4d\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4d\ud83c\udffd", "\ud83d\ude4d\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4d\ud83c\udffe", "\ud83d\ude4d\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4d\ud83c\udfff", "\ud83d\ude4d\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4e", "\ud83d\ude4e\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4e\ud83c\udffb", "\ud83d\ude4e\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4e\ud83c\udffc", "\ud83d\ude4e\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4e\ud83c\udffd", "\ud83d\ude4e\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4e\ud83c\udffe", "\ud83d\ude4e\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4e\ud83c\udfff", "\ud83d\ude4e\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude45", "\ud83d\ude45\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude45\ud83c\udffb", "\ud83d\ude45\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude45\ud83c\udffc", "\ud83d\ude45\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude45\ud83c\udffd", "\ud83d\ude45\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude45\ud83c\udffe", "\ud83d\ude45\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude45\ud83c\udfff", "\ud83d\ude45\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude46", "\ud83d\ude46\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude46\ud83c\udffb", "\ud83d\ude46\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude46\ud83c\udffc", "\ud83d\ude46\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude46\ud83c\udffd", "\ud83d\ude46\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude46\ud83c\udffe", "\ud83d\ude46\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude46\ud83c\udfff", "\ud83d\ude46\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc81", "\ud83d\udc81\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc81\ud83c\udffb", "\ud83d\udc81\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc81\ud83c\udffc", "\ud83d\udc81\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc81\ud83c\udffd", "\ud83d\udc81\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc81\ud83c\udffe", "\ud83d\udc81\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc81\ud83c\udfff", "\ud83d\udc81\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4b", "\ud83d\ude4b\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4b\ud83c\udffb", "\ud83d\ude4b\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4b\ud83c\udffc", "\ud83d\ude4b\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4b\ud83c\udffd", "\ud83d\ude4b\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4b\ud83c\udffe", "\ud83d\ude4b\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude4b\ud83c\udfff", "\ud83d\ude4b\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\ude47", "\ud83d\ude47\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\ude47\ud83c\udffb", "\ud83d\ude47\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\ude47\ud83c\udffc", "\ud83d\ude47\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\ude47\ud83c\udffd", "\ud83d\ude47\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\ude47\ud83c\udffe", "\ud83d\ude47\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\ude47\ud83c\udfff", "\ud83d\ude47\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc86", "\ud83d\udc86\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc86\ud83c\udffb", "\ud83d\udc86\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc86\ud83c\udffc", "\ud83d\udc86\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc86\ud83c\udffd", "\ud83d\udc86\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc86\ud83c\udffe", "\ud83d\udc86\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc86\ud83c\udfff", "\ud83d\udc86\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc87", "\ud83d\udc87\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc87\ud83c\udffb", "\ud83d\udc87\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc87\ud83c\udffc", "\ud83d\udc87\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc87\ud83c\udffd", "\ud83d\udc87\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc87\ud83c\udffe", "\ud83d\udc87\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udc87\ud83c\udfff", "\ud83d\udc87\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83d\udeb6", "\ud83d\udeb6\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb6\ud83c\udffb", "\ud83d\udeb6\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb6\ud83c\udffc", "\ud83d\udeb6\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb6\ud83c\udffd", "\ud83d\udeb6\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb6\ud83c\udffe", "\ud83d\udeb6\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb6\ud83c\udfff", "\ud83d\udeb6\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc3", "\ud83c\udfc3\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc3\ud83c\udffb", "\ud83c\udfc3\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc3\ud83c\udffc", "\ud83c\udfc3\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc3\ud83c\udffd", "\ud83c\udfc3\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc3\ud83c\udffe", "\ud83c\udfc3\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc3\ud83c\udfff", "\ud83c\udfc3\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc6f", "\ud83d\udc6f\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd6", "\ud83e\uddd6\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd6\ud83c\udffb", "\ud83e\uddd6\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd6\ud83c\udffc", "\ud83e\uddd6\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd6\ud83c\udffd", "\ud83e\uddd6\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd6\ud83c\udffe", "\ud83e\uddd6\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd6\ud83c\udfff", "\ud83e\uddd6\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83e\uddd7", "\ud83e\uddd7\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd7\ud83c\udffb", "\ud83e\uddd7\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd7\ud83c\udffc", "\ud83e\uddd7\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd7\ud83c\udffd", "\ud83e\uddd7\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd7\ud83c\udffe", "\ud83e\uddd7\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd7\ud83c\udfff", "\ud83e\uddd7\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd8", "\ud83e\uddd8\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd8\ud83c\udffb", "\ud83e\uddd8\ud83c\udffb\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd8\ud83c\udffc", "\ud83e\uddd8\ud83c\udffc\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd8\ud83c\udffd", "\ud83e\uddd8\ud83c\udffd\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd8\ud83c\udffe", "\ud83e\uddd8\ud83c\udffe\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83e\uddd8\ud83c\udfff", "\ud83e\uddd8\ud83c\udfff\u200d\u2640\ufe0f")); - add(new Pair<>("\ud83c\udfcc\ufe0f", "\ud83c\udfcc\ufe0f\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcc\ud83c\udffb", "\ud83c\udfcc\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcc\ud83c\udffc", "\ud83c\udfcc\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcc\ud83c\udffd", "\ud83c\udfcc\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcc\ud83c\udffe", "\ud83c\udfcc\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcc\ud83c\udfff", "\ud83c\udfcc\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc4", "\ud83c\udfc4\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc4\ud83c\udffb", "\ud83c\udfc4\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc4\ud83c\udffc", "\ud83c\udfc4\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc4\ud83c\udffd", "\ud83c\udfc4\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc4\ud83c\udffe", "\ud83c\udfc4\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfc4\ud83c\udfff", "\ud83c\udfc4\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udea3", "\ud83d\udea3\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udea3\ud83c\udffb", "\ud83d\udea3\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udea3\ud83c\udffc", "\ud83d\udea3\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udea3\ud83c\udffd", "\ud83d\udea3\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udea3\ud83c\udffe", "\ud83d\udea3\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udea3\ud83c\udfff", "\ud83d\udea3\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfca", "\ud83c\udfca\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfca\ud83c\udffb", "\ud83c\udfca\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfca\ud83c\udffc", "\ud83c\udfca\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfca\ud83c\udffd", "\ud83c\udfca\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfca\ud83c\udffe", "\ud83c\udfca\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfca\ud83c\udfff", "\ud83c\udfca\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\u26f9\ufe0f", "\u26f9\ufe0f\u200d\u2642\ufe0f")); - add(new Pair<>("\u26f9\ud83c\udffb", "\u26f9\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\u26f9\ud83c\udffc", "\u26f9\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\u26f9\ud83c\udffd", "\u26f9\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\u26f9\ud83c\udffe", "\u26f9\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\u26f9\ud83c\udfff", "\u26f9\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcb\ufe0f", "\ud83c\udfcb\ufe0f\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcb\ud83c\udffb", "\ud83c\udfcb\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcb\ud83c\udffc", "\ud83c\udfcb\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcb\ud83c\udffd", "\ud83c\udfcb\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcb\ud83c\udffe", "\ud83c\udfcb\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83c\udfcb\ud83c\udfff", "\ud83c\udfcb\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb4", "\ud83d\udeb4\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb4\ud83c\udffb", "\ud83d\udeb4\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb4\ud83c\udffc", "\ud83d\udeb4\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb4\ud83c\udffd", "\ud83d\udeb4\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb4\ud83c\udffe", "\ud83d\udeb4\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb4\ud83c\udfff", "\ud83d\udeb4\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb5", "\ud83d\udeb5\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb5\ud83c\udffb", "\ud83d\udeb5\ud83c\udffb\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb5\ud83c\udffc", "\ud83d\udeb5\ud83c\udffc\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb5\ud83c\udffd", "\ud83d\udeb5\ud83c\udffd\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb5\ud83c\udffe", "\ud83d\udeb5\ud83c\udffe\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udeb5\ud83c\udfff", "\ud83d\udeb5\ud83c\udfff\u200d\u2642\ufe0f")); - add(new Pair<>("\ud83d\udc8f", "\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68")); - add(new Pair<>("\ud83d\udc91", "\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc68")); - add(new Pair<>("\ud83d\udc6a", "\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66")); - }}; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java index 0aa3c33c71..acb53f7767 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java @@ -17,7 +17,7 @@ import android.widget.FrameLayout; import org.thoughtcrime.securesms.components.InputAwareLayout.InputView; import org.thoughtcrime.securesms.components.RepeatableImageKey; import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import java.util.Arrays; @@ -158,7 +158,7 @@ public class MediaKeyboard extends FrameLayout implements InputView, this.searchButton = view.findViewById(R.id.media_keyboard_search); this.addButton = view.findViewById(R.id.media_keyboard_add); - this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this); + this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(Glide.with(this), this); categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); categoryTabs.setAdapter(categoryTabAdapter); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java index 0d1b98d474..08a2ec528f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java @@ -9,20 +9,20 @@ import android.widget.ImageView; import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider.TabIconProvider; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import network.loki.messenger.R; public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter { - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final EventListener eventListener; private TabIconProvider tabIconProvider; private int activePosition; private int count; - public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) { + public MediaKeyboardBottomTabAdapter(@NonNull RequestManager glideRequests, @NonNull EventListener eventListener) { this.glideRequests = glideRequests; this.eventListener = eventListener; } @@ -71,7 +71,7 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter() { - lateinit var glide: GlideRequests + lateinit var glide: RequestManager val selectedContacts = mutableSetOf() var items = listOf() set(value) { field = value; notifyDataSetChanged() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt index 0b0ddf4b3d..7c74fb8983 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt @@ -11,7 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import network.loki.messenger.databinding.ContactSelectionListFragmentBinding import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks>, ContactClickListener { private lateinit var binding: ContactSelectionListFragmentBinding @@ -27,7 +27,7 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks): List { - return getItems(contacts, context.getString(R.string.fragment_contact_selection_contacts_title)) { + return getItems(contacts, context.getString(R.string.contactsContacts)) { !it.isGroupRecipient } } private fun getClosedGroups(contacts: List): List { - return getItems(contacts, context.getString(R.string.fragment_contact_selection_closed_groups_title)) { + return getItems(contacts, context.getString(R.string.conversationsGroups)) { it.address.isClosedGroup } } - private fun getOpenGroups(contacts: List): List { - return getItems(contacts, context.getString(R.string.fragment_contact_selection_open_groups_title)) { + private fun getCommunities(contacts: List): List { + return getItems(contacts, context.getString(R.string.conversationsCommunities)) { it.address.isCommunity } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java index 5284fb0015..046c20002d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactUtil.java @@ -18,10 +18,10 @@ public final class ContactUtil { String contactName = ContactUtil.getDisplayName(contact); if (!TextUtils.isEmpty(contactName)) { - return context.getString(R.string.MessageNotifier_contact_message, EmojiStrings.BUST_IN_SILHOUETTE, contactName); + return EmojiStrings.BUST_IN_SILHOUETTE + " " + contactName; } - return SpanUtil.italic(context.getString(R.string.MessageNotifier_unknown_contact_message)); + return SpanUtil.italic(context.getString(R.string.unknown)); } private static @NonNull String getDisplayName(@Nullable Contact contact) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index e1ea0c5e2e..83084d2673 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -159,7 +159,7 @@ public class ContactsCursorLoader extends CursorLoader { private Cursor getGroupsHeaderCursor() { MatrixCursor groupHeader = new MatrixCursor(CONTACT_PROJECTION, 1); - groupHeader.addRow(new Object[]{ getContext().getString(R.string.ContactsCursorLoader_groups), + groupHeader.addRow(new Object[]{ getContext().getString(R.string.conversationsGroups), "", ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", @@ -221,16 +221,6 @@ public class ContactsCursorLoader extends CursorLoader { return groupContacts; } - private Cursor getNewNumberCursor() { - MatrixCursor newNumberCursor = new MatrixCursor(CONTACT_PROJECTION, 1); - newNumberCursor.addRow(new Object[] { getContext().getString(R.string.contact_selection_list__unknown_contact), - filter, - ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM, - "\u21e2", - NEW_TYPE }); - return newNumberCursor; - } - private static boolean isCursorListEmpty(List list) { int sum = 0; for (Cursor cursor : list) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt index 538cd35077..b39fa5b8d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsActivity.kt @@ -12,7 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import network.loki.messenger.R import network.loki.messenger.databinding.ActivitySelectContactsBinding import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks> { private lateinit var binding: ActivitySelectContactsBinding @@ -21,7 +21,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana private lateinit var usersToExclude: Set private val selectContactsAdapter by lazy { - SelectContactsAdapter(this, GlideApp.with(this)) + SelectContactsAdapter(this, Glide.with(this)) } companion object { @@ -35,7 +35,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana super.onCreate(savedInstanceState, isReady) binding = ActivitySelectContactsBinding.inflate(layoutInflater) setContentView(binding.root) - supportActionBar!!.title = resources.getString(R.string.activity_select_contacts_title) + supportActionBar!!.title = resources.getString(R.string.contactSelect) usersToExclude = intent.getStringArrayExtra(usersToExcludeKey)?.toSet() ?: setOf() val emptyStateText = intent.getStringExtra(emptyStateTextKey) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsAdapter.kt index 5e3ae1213c..2a788f71a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/SelectContactsAdapter.kt @@ -4,10 +4,10 @@ import android.content.Context import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import org.session.libsession.utilities.Address -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.session.libsession.utilities.recipients.Recipient -class SelectContactsAdapter(private val context: Context, private val glide: GlideRequests) : RecyclerView.Adapter() { +class SelectContactsAdapter(private val context: Context, private val glide: RequestManager) : RecyclerView.Adapter() { val selectedMembers = mutableSetOf() var members = listOf() set(value) { field = value; notifyDataSetChanged() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index f9fd528707..3ca733ba77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -10,11 +10,11 @@ import network.loki.messenger.databinding.ViewUserBinding import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager class UserView : LinearLayout { private lateinit var binding: ViewUserBinding - var openGroupThreadID: Long = -1 // FIXME: This is a bit ugly + var openGroupThreadID: Long = -1L // FIXME: This is a bit ugly enum class ActionIndicator { None, @@ -45,13 +45,15 @@ class UserView : LinearLayout { // endregion // region Updating - fun bind(user: Recipient, glide: GlideRequests, actionIndicator: ActionIndicator, isSelected: Boolean = false) { + fun bind(user: Recipient, glide: RequestManager, actionIndicator: ActionIndicator, isSelected: Boolean = false) { val isLocalUser = user.isLocalNumber + fun getUserDisplayName(publicKey: String): String { - if (isLocalUser) return context.getString(R.string.MessageRecord_you) + if (isLocalUser) return context.getString(R.string.you) val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey) return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey } + val address = user.address.serialize() binding.profilePictureView.update(user) binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24) @@ -84,8 +86,7 @@ class UserView : LinearLayout { } } - fun unbind() { - binding.profilePictureView.recycle() - } + fun unbind() { binding.profilePictureView.recycle() } + // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt index 8f2da7a733..4ee50daa7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActionBarView.kt @@ -11,21 +11,26 @@ 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 +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.COUNT_KEY +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.util.DateUtils -import java.util.Locale -import javax.inject.Inject @AndroidEntryPoint class ConversationActionBarView @JvmOverloads constructor( @@ -82,7 +87,7 @@ class ConversationActionBarView @JvmOverloads constructor( fun update(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) { binding.profilePictureView.update(recipient) - binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.note_to_self) + binding.conversationTitleView.text = recipient.takeUnless { it.isLocalNumber }?.toShortString() ?: context.getString(R.string.noteToSelf) updateSubtitle(recipient, openGroup, config) binding.conversationTitleContainer.modifyLayoutParams { @@ -92,37 +97,58 @@ class ConversationActionBarView @JvmOverloads constructor( fun updateSubtitle(recipient: Recipient, openGroup: OpenGroup? = null, config: ExpirationConfiguration? = null) { val settings = mutableListOf() + + // Specify the disappearing messages subtitle if we should if (config?.isEnabled == true) { - val prefix = when (config.expiryMode) { - is ExpiryMode.AfterRead -> R.string.expiration_type_disappear_after_read - else -> R.string.expiration_type_disappear_after_send - }.let(context::getString) + // Get the type of disappearing message and the abbreviated duration.. + val dmTypeString = when (config.expiryMode) { + is AfterRead -> context.getString(R.string.read) + else -> context.getString(R.string.send) + } + val durationAbbreviated = ExpirationUtil.getExpirationAbbreviatedDisplayValue(config.expiryMode.expirySeconds) + + // ..then substitute into the string.. + val subtitleTxt = Phrase.from(context, R.string.disappearingMessagesDisappear) + .put(DISAPPEARING_MESSAGES_TYPE_KEY, dmTypeString) + .put(TIME_KEY, durationAbbreviated) + .format().toString() + + // .. and apply to the subtitle. settings += ConversationSetting( - "$prefix - ${ExpirationUtil.getExpirationAbbreviatedDisplayValue(context, config.expiryMode.expirySeconds)}", + subtitleTxt, ConversationSettingType.EXPIRATION, R.drawable.ic_timer, resources.getString(R.string.AccessibilityId_disappearing_messages_type_and_time) ) } + if (recipient.isMuted) { settings += ConversationSetting( recipient.mutedUntil.takeUnless { it == Long.MAX_VALUE } - ?.let { context.getString(R.string.ConversationActivity_muted_until_date, DateUtils.getFormattedDateTime(it, "EEE, MMM d, yyyy HH:mm", Locale.getDefault())) } - ?: context.getString(R.string.ConversationActivity_muted_forever), + ?.let { + val mutedDuration = it.milliseconds + val durationString = LocalisedTimeUtil.getDurationWithSingleLargestTimeUnit(context, mutedDuration) + Phrase.from(context, R.string.notificationsMuteFor) + .put(TIME_LARGE_KEY, durationString) + .format().toString() + } + ?: context.getString(R.string.notificationsMuted), ConversationSettingType.NOTIFICATION, R.drawable.ic_outline_notifications_off_24 ) } + if (recipient.isGroupRecipient) { val title = if (recipient.isCommunityRecipient) { val userCount = openGroup?.let { lokiApiDb.getUserCount(it.room, it.server) } ?: 0 - context.getString(R.string.ConversationActivity_active_member_count, userCount) + resources.getQuantityString(R.plurals.membersActive, userCount, userCount) } else { val userCount = groupDb.getGroupMemberAddresses(recipient.address.toGroupString(), true).size - context.getString(R.string.ConversationActivity_member_count, userCount) + resources.getQuantityString(R.plurals.members, userCount, userCount) } settings += ConversationSetting(title, ConversationSettingType.MEMBER_COUNT) } + settingsAdapter.submitList(settings) binding.settingsTabLayout.isVisible = settings.size > 1 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt index 38da11ae24..236977457a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessages.kt @@ -1,7 +1,10 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages import android.content.Context +import com.squareup.phrase.Phrase import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.MessagingModuleConfiguration @@ -12,13 +15,13 @@ import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol +import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.getExpirationTypeDisplayValue import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds class DisappearingMessages @Inject constructor( @ApplicationContext private val context: Context, @@ -43,21 +46,18 @@ class DisappearingMessages @Inject constructor( } fun showFollowSettingDialog(context: Context, message: MessageRecord) = context.showSessionDialog { - title(R.string.dialog_disappearing_messages_follow_setting_title) + title(R.string.disappearingMessagesFollowSetting) text(if (message.expiresIn == 0L) { - context.getString(R.string.dialog_disappearing_messages_follow_setting_off_body) + context.getString(R.string.disappearingMessagesFollowSettingOff) } else { - context.getString( - R.string.dialog_disappearing_messages_follow_setting_on_body, - ExpirationUtil.getExpirationDisplayValue( - context, - message.expiresIn.milliseconds - ), - context.getExpirationTypeDisplayValue(message.isNotDisappearAfterRead) - ) + Phrase.from(context, R.string.disappearingMessagesFollowSettingOn) + .put(TIME_LARGE_KEY, ExpirationUtil.getExpirationDisplayValue(context, message.expiresIn.milliseconds)) + .put(DISAPPEARING_MESSAGES_TYPE_KEY, context.getExpirationTypeDisplayValue(message.isNotDisappearAfterRead)) + .format().toString() }) + dangerButton( - text = if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.dialog_disappearing_messages_follow_setting_set, + text = if (message.expiresIn == 0L) R.string.dialog_disappearing_messages_follow_setting_confirm else R.string.set, contentDescription = if (message.expiresIn == 0L) R.string.AccessibilityId_confirm else R.string.AccessibilityId_set_button ) { set(message.threadId, message.recipient.address, message.expiryMode, message.recipient.isClosedGroupRecipient) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt index f66512d79f..db7b487ae7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt @@ -72,7 +72,7 @@ class DisappearingMessagesActivity: PassphraseRequiredActionBarActivity() { } private fun setUpToolbar() { - setSupportActionBar(binding.toolbar) + setSupportActionBar(binding.searchToolbar) supportActionBar?.apply { title = getString(R.string.activity_disappearing_messages_title) setDisplayHomeAsUpEnabled(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/State.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/State.kt index ced4cc0035..3a18066974 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/State.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/State.kt @@ -51,7 +51,7 @@ enum class ExpiryType( ) { NONE( { ExpiryMode.NONE }, - R.string.expiration_off, + R.string.off, contentDescription = R.string.AccessibilityId_disable_disappearing_messages, ), LEGACY( @@ -61,14 +61,14 @@ enum class ExpiryType( ), AFTER_READ( ExpiryMode::AfterRead, - R.string.expiration_type_disappear_after_read, - R.string.expiration_type_disappear_after_read_description, + R.string.disappearingMessagesDisappearAfterRead, + R.string.disappearingMessagesDisappearAfterReadDescription, R.string.AccessibilityId_disappear_after_read_option ), AFTER_SEND( ExpiryMode::AfterSend, - R.string.expiration_type_disappear_after_send, - R.string.expiration_type_disappear_after_send_description, + R.string.disappearingMessagesDisappearAfterSend, + R.string.disappearingMessagesDisappearAfterSendDescription, R.string.AccessibilityId_disappear_after_send_option ); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/Adapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/Adapter.kt index d78d33a2f9..c421e14efa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/Adapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/Adapter.kt @@ -13,7 +13,7 @@ import kotlin.time.Duration.Companion.seconds fun State.toUiState() = UiState( cards = listOfNotNull( - typeOptions()?.let { ExpiryOptionsCardData(GetString(R.string.activity_disappearing_messages_delete_type), it) }, + typeOptions()?.let { ExpiryOptionsCardData(GetString(R.string.disappearingMessagesDeleteType), it) }, timeOptions()?.let { ExpiryOptionsCardData(GetString(R.string.activity_disappearing_messages_timer), it) } ), showGroupFooter = isGroup && isNewConfigEnabled, @@ -66,11 +66,14 @@ private fun State.typeOption( ) private fun debugTimes(isDebug: Boolean) = if (isDebug) listOf(10.seconds, 30.seconds, 1.minutes) else emptyList() + private fun debugModes(isDebug: Boolean, type: ExpiryType) = debugTimes(isDebug).map { type.mode(it.inWholeSeconds) } + private fun State.debugOptions(): List = debugModes(showDebugOptions, nextType).map { timeOption(it, subtitle = GetString("for testing purposes")) } +// Standard list of available disappearing message times private val afterSendTimes = listOf(12.hours, 1.days, 7.days, 14.days) private val afterReadTimes = buildList { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt index ae17e6a09b..eae9dd5c6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages.ui -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -16,21 +15,20 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode import org.thoughtcrime.securesms.ui.Callbacks -import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.NoOpCallbacks import org.thoughtcrime.securesms.ui.OptionsCard import org.thoughtcrime.securesms.ui.RadioOption -import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.fadingEdges +import org.thoughtcrime.securesms.ui.theme.LocalColors +import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType -typealias ExpiryCallbacks = Callbacks +typealias ExpiryCallbacks = Callbacks typealias ExpiryRadioOption = RadioOption @Composable @@ -59,7 +57,9 @@ fun DisappearingMessages( } if (state.showGroupFooter) Text( - text = stringResource(R.string.activity_disappearing_messages_group_footer), + text = stringResource(R.string.disappearingMessagesDescription) + + "\n" + + stringResource(R.string.disappearingMessagesOnlyAdmins), style = LocalType.current.extraSmall, fontWeight = FontWeight(400), color = LocalColors.current.textSecondary, @@ -72,7 +72,7 @@ fun DisappearingMessages( } if (state.showSetButton) SlimOutlineButton( - stringResource(R.string.disappearing_messages_set_button_title), + stringResource(R.string.set), modifier = Modifier .contentDescription(R.string.AccessibilityId_set_button) .align(Alignment.CenterHorizontally) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt index fcf13c9cf1..3bf4aac554 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt @@ -41,7 +41,7 @@ internal fun StartConversationScreen( LocalColors.current.backgroundSecondary, shape = MaterialTheme.shapes.small )) { - AppBar(stringResource(R.string.dialog_start_conversation_title), onClose = delegate::onDialogClosePressed) + AppBar(stringResource(R.string.conversationsStart), onClose = delegate::onDialogClosePressed) Surface( modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()), color = LocalColors.current.backgroundSecondary @@ -56,21 +56,21 @@ internal fun StartConversationScreen( onClick = delegate::onNewMessageSelected) Divider(startIndent = LocalDimensions.current.dividerIndent) ItemButton( - textId = R.string.activity_create_group_title, + textId = R.string.groupCreate, icon = R.drawable.ic_group, modifier = Modifier.contentDescription(R.string.AccessibilityId_create_group), onClick = delegate::onCreateGroupSelected ) Divider(startIndent = LocalDimensions.current.dividerIndent) ItemButton( - textId = R.string.dialog_join_community_title, + textId = R.string.communityJoin, icon = R.drawable.ic_globe, modifier = Modifier.contentDescription(R.string.AccessibilityId_join_community), onClick = delegate::onJoinCommunitySelected ) Divider(startIndent = LocalDimensions.current.dividerIndent) ItemButton( - textId = R.string.activity_settings_invite_button_title, + textId = R.string.sessionInviteAFriend, icon = R.drawable.ic_invite_friend, Modifier.contentDescription(R.string.AccessibilityId_invite_friend_button), onClick = delegate::onInviteFriend diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt index 54abf66303..df90fe351c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/invitefriend/InviteFriend.kt @@ -16,15 +16,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import network.loki.messenger.R -import org.thoughtcrime.securesms.ui.theme.LocalDimensions -import org.thoughtcrime.securesms.ui.theme.PreviewTheme -import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.components.AppBar import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton import org.thoughtcrime.securesms.ui.components.border import org.thoughtcrime.securesms.ui.contentDescription +import org.thoughtcrime.securesms.ui.theme.LocalColors +import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType +import org.thoughtcrime.securesms.ui.theme.PreviewTheme @Composable internal fun InviteFriend( @@ -38,7 +38,11 @@ internal fun InviteFriend( LocalColors.current.backgroundSecondary, shape = MaterialTheme.shapes.small )) { - AppBar(stringResource(R.string.invite_a_friend), onBack = onBack, onClose = onClose) + AppBar( + stringResource(R.string.sessionInviteAFriend), + onBack = onBack, + onClose = onClose + ) Column( modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing) .padding(top = LocalDimensions.current.spacing), @@ -57,7 +61,7 @@ internal fun InviteFriend( Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing)) Text( - stringResource(R.string.invite_your_friend_to_chat_with_you_on_session_by_sharing_your_account_id_with_them), + stringResource(R.string.shareAccountIdDescription), textAlign = TextAlign.Center, style = LocalType.current.small, color = LocalColors.current.textSecondary, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt index 97740b2a26..2c5437afdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessage.kt @@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.ui.components.SessionTabRow import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.theme.LocalType -private val TITLES = listOf(R.string.enter_account_id, R.string.qrScan) +private val TITLES = listOf(R.string.accountIdEnter, R.string.qrScan) @OptIn(ExperimentalFoundationApi::class) @Composable @@ -63,7 +63,7 @@ internal fun NewMessage( SessionTabRow(pagerState, TITLES) HorizontalPager(pagerState) { when (TITLES[it]) { - R.string.enter_account_id -> EnterAccountId(state, callbacks, onHelp) + R.string.accountIdEnter -> EnterAccountId(state, callbacks, onHelp) R.string.qrScan -> MaybeScanQrCode(qrErrors, onScan = callbacks::onScanQrCode) } } @@ -101,7 +101,7 @@ private fun EnterAccountId( Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing)) BorderlessButtonWithIcon( - text = stringResource(R.string.messageNewDescription), + text = stringResource(R.string.messageNewDescriptionMobile), modifier = Modifier .contentDescription(R.string.AccessibilityId_help_desk_link) .padding(horizontal = LocalDimensions.current.mediumSpacing) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageViewModel.kt index 65c8dd539a..b2f2c9d013 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/newmessage/NewMessageViewModel.kt @@ -4,6 +4,8 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import java.util.concurrent.TimeoutException +import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow @@ -19,8 +21,6 @@ import org.session.libsession.snode.SnodeAPI import org.session.libsignal.utilities.PublicKeyValidation import org.session.libsignal.utilities.timeout import org.thoughtcrime.securesms.ui.GetString -import java.util.concurrent.TimeoutException -import javax.inject.Inject @HiltViewModel internal class NewMessageViewModel @Inject constructor( @@ -59,7 +59,7 @@ internal class NewMessageViewModel @Inject constructor( if (PublicKeyValidation.isValid(value, isPrefixRequired = false) && PublicKeyValidation.hasValidPrefix(value)) { onPublicKey(value) } else { - _qrErrors.tryEmit(application.getString(R.string.this_qr_code_does_not_contain_an_account_id)) + _qrErrors.tryEmit(application.getString(R.string.qrNotAccountId)) } } @@ -99,7 +99,7 @@ internal class NewMessageViewModel @Inject constructor( private fun Exception.toMessage() = when (this) { is SnodeAPI.Error.Generic -> application.getString(R.string.onsErrorNotRecognized) is TimeoutException -> application.getString(R.string.onsErrorUnableToSearch) - else -> application.getString(R.string.fragment_enter_public_key_error_message) + else -> application.getString(R.string.accountIdErrorInvalid) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 62ea2e52d4..a78a0eed7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -18,8 +18,8 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.provider.MediaStore -import android.text.SpannableStringBuilder -import android.text.SpannedString +import android.text.SpannableString +import android.text.Spanned import android.text.TextUtils import android.text.style.StyleSpan import android.util.Pair @@ -35,9 +35,9 @@ import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.core.view.drawToBitmap import androidx.core.text.set import androidx.core.text.toSpannable -import androidx.core.view.drawToBitmap import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment @@ -52,7 +52,19 @@ import androidx.loader.content.Loader import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.annimon.stream.Stream +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import java.lang.ref.WeakReference +import java.util.Locale +import java.util.concurrent.ExecutionException +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicReference +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.math.sqrt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -65,7 +77,6 @@ import network.loki.messenger.databinding.ActivityConversationV2Binding import network.loki.messenger.libsession_util.util.ExpiryMode import nl.komponents.kovenant.ui.successUi import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.applyExpiryMode import org.session.libsession.messaging.messages.control.DataExtractionNotification @@ -85,6 +96,10 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.MediaTypes +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.Stub import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.concurrent.SimpleTask @@ -156,7 +171,7 @@ import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendActivity import org.thoughtcrime.securesms.mms.AudioSlide import org.thoughtcrime.securesms.mms.GifSlide -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.Slide @@ -173,23 +188,13 @@ import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.NetworkUtils import org.thoughtcrime.securesms.util.SaveAttachmentTask +import org.thoughtcrime.securesms.util.drawToBitmap import org.thoughtcrime.securesms.util.isScrolledToBottom import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.start import org.thoughtcrime.securesms.util.toPx -import java.lang.ref.WeakReference -import java.util.Locale -import java.util.concurrent.ExecutionException -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicReference -import javax.inject.Inject -import kotlin.math.abs -import kotlin.math.min -import kotlin.math.roundToInt -import kotlin.math.sqrt private const val TAG = "ConversationActivityV2" @@ -349,7 +354,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe adapter } - private val glide by lazy { GlideApp.with(this) } + private val glide by lazy { Glide.with(this) } private val lockViewHitMargin by lazy { toPx(40, resources) } private val gifButton by lazy { InputBarButton(this, R.drawable.ic_gif_white_24dp, hasOpaqueBackground = true, isGIFButton = true) } private val documentButton by lazy { InputBarButton(this, R.drawable.ic_document_small_dark, hasOpaqueBackground = true) } @@ -706,7 +711,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onFailure(e: ExecutionException?) { - Toast.makeText(this@ConversationActivityV2, R.string.activity_conversation_attachment_prep_failed, Toast.LENGTH_LONG).show() + Toast.makeText(this@ConversationActivityV2, R.string.attachmentsErrorLoad, Toast.LENGTH_LONG).show() } }) return @@ -757,9 +762,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // called from onCreate private fun setUpBlockedBanner() { val recipient = viewModel.recipient?.takeUnless { it.isGroupRecipient } ?: return - val accountID = recipient.address.toString() - val name = sessionContactDb.getContactWithAccountID(accountID)?.displayName(Contact.ContactContext.REGULAR) ?: accountID - binding.blockedBannerTextView.text = resources.getString(R.string.activity_conversation_blocked_banner_text, name) + binding.blockedBannerTextView.text = applicationContext.getString(R.string.blockBlockedDescription) binding.blockedBanner.isVisible = recipient.isBlocked binding.blockedBanner.setOnClickListener { viewModel.unblock() } } @@ -772,8 +775,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding.outdatedBanner.isVisible = shouldShowLegacy if (shouldShowLegacy) { - binding.outdatedBannerTextView.text = - resources.getString(R.string.activity_conversation_outdated_client_banner_text, legacyRecipient!!.name) + + val txt = Phrase.from(applicationContext, R.string.disappearingMessagesLegacy) + .put(NAME_KEY, legacyRecipient!!.name) + .format() + binding?.outdatedBannerTextView?.text = txt } } @@ -1056,33 +1062,64 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe updateUnreadCountIndicator() } + // Method that takes a char sequence that contains one or more elements surrounded in bold tags + // like "Hello world" and returns a SpannableString that will display the appropriate + // elements in bold. If there are no such or elements then the original string is returned + // as a SpannableString without any bold highlighting. + private fun makeBoldBetweenTags(input: CharSequence): SpannableString { + val spannable = SpannableString(input) + var startIndex = 0 + while (true) { + startIndex = input.indexOf("", startIndex) + if (startIndex == -1) break + val endIndex = input.indexOf("", startIndex + 3) + if (endIndex == -1) break + spannable.setSpan(StyleSpan(Typeface.BOLD),startIndex + 3, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + startIndex = endIndex + 4 + } + return spannable + } + + // Update placeholder / control messages in a conversation private fun updatePlaceholder() { val recipient = viewModel.recipient ?: return Log.w("Loki", "recipient was null in placeholder update") val blindedRecipient = viewModel.blindedRecipient val openGroup = viewModel.openGroup - val (textResource, insertParam) = when { - recipient.isLocalNumber -> R.string.activity_conversation_empty_state_note_to_self to null - openGroup != null && !openGroup.canWrite -> R.string.activity_conversation_empty_state_read_only to recipient.toShortString() - blindedRecipient?.blocksCommunityMessageRequests == true -> R.string.activity_conversation_empty_state_blocks_community_requests to recipient.toShortString() - else -> R.string.activity_conversation_empty_state_default to recipient.toShortString() + // Get the correct placeholder text for this type of empty conversation + val isNoteToSelf = recipient.isLocalNumber + val txtCS: CharSequence = when { + recipient.isLocalNumber -> getString(R.string.noteToSelfEmpty) + + // If this is a community which we cannot write to + openGroup != null && !openGroup.canWrite -> { + Phrase.from(applicationContext, R.string.conversationsEmpty) + .put(CONVERSATION_NAME_KEY, openGroup.name) + .format() + } + + // If we're trying to message someone who has blocked community message requests + blindedRecipient?.blocksCommunityMessageRequests == true -> { + Phrase.from(applicationContext, R.string.messageRequestsTurnedOff) + .put(NAME_KEY, recipient.toShortString()) + .format() + } + + else -> { + // If this is a group or community that we CAN send messages to + Phrase.from(applicationContext, R.string.groupNoMessages) + .put(GROUP_NAME_KEY, recipient.toShortString()) + .format() + } } + val showPlaceholder = adapter.itemCount == 0 binding.placeholderText.isVisible = showPlaceholder if (showPlaceholder) { - if (insertParam != null) { - val span = getText(textResource) as SpannedString - val annotations = span.getSpans(0, span.length, StyleSpan::class.java) - val boldSpan = annotations.first() - val spannedParam = insertParam.toSpannable() - spannedParam[0 until spannedParam.length] = StyleSpan(boldSpan.style) - val originalStart = span.getSpanStart(boldSpan) - val originalEnd = span.getSpanEnd(boldSpan) - val newString = SpannableStringBuilder(span) - .replace(originalStart, originalEnd, spannedParam) - binding.placeholderText.text = newString + if (!isNoteToSelf) { + binding.placeholderText.text = makeBoldBetweenTags(txtCS) } else { - binding.placeholderText.setText(textResource) + binding.placeholderText.text = txtCS } } } @@ -1117,11 +1154,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun block(deleteThread: Boolean) { + val recipient = viewModel.recipient ?: return Log.w("Loki", "Recipient was null for block action") showSessionDialog { - title(R.string.RecipientPreferenceActivity_block_this_contact_question) - text(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact) - dangerButton(R.string.RecipientPreferenceActivity_block, R.string.AccessibilityId_block_confirm) { + title(R.string.block) + text( + Phrase.from(context, R.string.blockDescription) + .put(NAME_KEY, recipient.name) + .format() + ) + dangerButton(R.string.block, R.string.AccessibilityId_block_confirm) { viewModel.block() + + // Block confirmation toast added as per SS-64 + val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, recipient.name).format().toString() + Toast.makeText(context, txt, Toast.LENGTH_LONG).show() + if (deleteThread) { viewModel.deleteThread() finish() @@ -1135,7 +1182,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val clip = ClipData.newPlainText("Account ID", accountId) val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(clip) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() } override fun copyOpenGroupUrl(thread: Recipient) { @@ -1147,7 +1194,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val clip = ClipData.newPlainText("Community URL", openGroup.joinURL) val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(clip) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() } override fun showDisappearingMessages(thread: Recipient) { @@ -1160,13 +1207,31 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun unblock() { + val recipient = viewModel.recipient ?: return Log.w("Loki", "Recipient was null for unblock action") + + if (!recipient.isContactRecipient) { + return Log.w("Loki", "Cannot unblock a user who is not a contact recipient - aborting unblock attempt.") + } + showSessionDialog { - title(R.string.ConversationActivity_unblock_this_contact_question) - text(R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact) + title(R.string.blockUnblock) + text( + Phrase.from(context, R.string.blockUnblockName) + .put(NAME_KEY, recipient.name) + .format() + ) dangerButton( - R.string.ConversationActivity_unblock, - R.string.AccessibilityId_block_confirm - ) { viewModel.unblock() } + R.string.blockUnblock, + R.string.AccessibilityId_unblock_confirm + ) { + viewModel.unblock() + + // Unblock confirmation toast added as per SS-64 + val txt = Phrase.from(context, R.string.blockUnblockedUser) + .put(NAME_KEY, recipient.name). + format().toString() + Toast.makeText(context, txt, Toast.LENGTH_LONG).show() + } cancelButton() } } @@ -1666,9 +1731,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val hasSeenGIFMetaDataWarning: Boolean = textSecurePreferences.hasSeenGIFMetaDataWarning() if (!hasSeenGIFMetaDataWarning) { showSessionDialog { - title(R.string.giphy_permission_title) - text(R.string.giphy_permission_message) - button(R.string.continue_2) { + title(R.string.giphyWarning) + text(Phrase.from(context, R.string.giphyWarningDescription).put(APP_NAME_KEY, getString(R.string.app_name)).format()) + button(R.string.theContinue) { textSecurePreferences.setHasSeenGIFMetaDataWarning() selectGif() } @@ -1715,7 +1780,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onFailure(e: ExecutionException?) { - Toast.makeText(this@ConversationActivityV2, R.string.activity_conversation_attachment_prep_failed, Toast.LENGTH_LONG).show() + Toast.makeText(this@ConversationActivityV2, R.string.attachmentsErrorLoad, Toast.LENGTH_LONG).show() } } when (requestCode) { @@ -1798,8 +1863,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else { Permissions.with(this) .request(Manifest.permission.RECORD_AUDIO) - .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) + .withRationaleDialog(getString(R.string.permissionsMicrophoneAccessRequired), R.drawable.ic_baseline_mic_48) + .withPermanentDenialDialog(Phrase.from(applicationContext, R.string.permissionsMicrophoneAccessRequiredAndroid) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString()) .execute() } } @@ -1869,7 +1936,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onFailure(e: ExecutionException) { - Toast.makeText(this@ConversationActivityV2, R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_LONG).show() + Toast.makeText(this@ConversationActivityV2, R.string.audioUnableToRecord, Toast.LENGTH_LONG).show() } }) } @@ -1920,10 +1987,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun showDeleteLocallyUI(messages: Set) { - val messageCount = 1 + val titleStringId = if (messages.count() == 1) R.string.deleteMessage else R.string.deleteMessages showSessionDialog { - title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) - text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) + title(resources.getString(titleStringId)) + text(resources.getString(R.string.deleteMessagesDescriptionDevice)) button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() } cancelButton(::endActionMode) } @@ -1943,13 +2010,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // If the recipient is a community OR a Note-to-Self then we delete the message for everyone if (recipient.isCommunityRecipient || recipient.isLocalNumber) { - val messageCount = 1 // Only used for plurals string showSessionDialog { - title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) - text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) - button(R.string.delete) { - messages.forEach(viewModel::deleteForEveryone); endActionMode() - } + title(resources.getString(R.string.deleteMessage)) + text(resources.getString(R.string.deleteMessageDescriptionEveryone)) + button(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 @@ -1974,13 +2038,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else // Finally, if this is a closed group and you are deleting someone else's message(s) then we can only delete locally. { - val messageCount = 1 showSessionDialog { - title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) - text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) - button(R.string.delete) { - messages.forEach(viewModel::deleteLocally); endActionMode() - } + title(resources.getString(R.string.deleteMessage)) + text(resources.getString(R.string.deleteMessageDescriptionEveryone)) + button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() } cancelButton(::endActionMode) } } @@ -1988,18 +2049,20 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun banUser(messages: Set) { showSessionDialog { - title(R.string.ConversationFragment_ban_selected_user) + title(R.string.banUser) + // ACL TODO - We need a string for the below `text` element text("This will ban the selected user from this room. It won't ban them from other rooms.") - button(R.string.ban) { viewModel.banUser(messages.first().individualRecipient); endActionMode() } + button(R.string.banUser) { viewModel.banUser(messages.first().individualRecipient); endActionMode() } cancelButton(::endActionMode) } } override fun banAndDeleteAll(messages: Set) { showSessionDialog { - title(R.string.ConversationFragment_ban_selected_user) + title(R.string.banUser) + // ACL TODO - We need a string for the below `text` element text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.") - button(R.string.ban) { viewModel.banAndDeleteAll(messages.first()); endActionMode() } + button(R.string.banUser) { viewModel.banAndDeleteAll(messages.first()); endActionMode() } cancelButton(::endActionMode) } } @@ -2035,7 +2098,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe if (TextUtils.isEmpty(result)) { return } val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(ClipData.newPlainText("Message Content", result)) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() endActionMode() } @@ -2044,7 +2107,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val clip = ClipData.newPlainText("Account ID", accountID) val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(clip) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() endActionMode() } @@ -2096,10 +2159,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe Permissions.with(this) .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .maxSdkVersion(Build.VERSION_CODES.P) - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) + .withPermanentDenialDialog(Phrase.from(applicationContext, R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString()) .onAnyDenied { endActionMode() - Toast.makeText(this@ConversationActivityV2, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show() + val txt = Phrase.from(applicationContext, R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString() + Toast.makeText(this@ConversationActivityV2, txt, Toast.LENGTH_LONG).show() } .onAllGranted { endActionMode() @@ -2116,7 +2184,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe return@onAllGranted } Toast.makeText(this, - resources.getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1), + resources.getString(R.string.attachmentsSaveError), Toast.LENGTH_LONG).show() } .execute() @@ -2172,6 +2240,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe searchViewModel.onMissingResult() } } } + binding.searchBottomBar.setData(result.position, result.getResults().size) }) } @@ -2181,6 +2250,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding.searchBottomBar.visibility = View.VISIBLE binding.searchBottomBar.setData(0, 0) binding.inputBar.visibility = View.INVISIBLE + } fun onSearchClosed() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 40a089d4f6..2cd801bca3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -12,6 +12,9 @@ import androidx.core.util.getOrDefault import androidx.core.util.set import androidx.lifecycle.LifecycleCoroutineScope import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.squareup.phrase.Phrase +import java.util.concurrent.atomic.AtomicLong +import kotlin.math.min import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -20,17 +23,16 @@ 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 org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity import org.thoughtcrime.securesms.showSessionDialog -import java.util.concurrent.atomic.AtomicLong -import kotlin.math.min class ConversationAdapter( context: Context, @@ -42,7 +44,7 @@ class ConversationAdapter( private val onItemLongPress: (MessageRecord, Int, VisibleMessageView) -> Unit, private val onDeselect: (MessageRecord, Int) -> Unit, private val onAttachmentNeedsDownload: (DatabaseAttachment) -> Unit, - private val glide: GlideRequests, + private val glide: RequestManager, lifecycleCoroutineScope: LifecycleCoroutineScope ) : CursorRecyclerViewAdapter(context, cursor) { private val messageDB by lazy { DatabaseComponent.get(context).mmsSmsDatabase() } @@ -154,9 +156,15 @@ class ConversationAdapter( if (message.isCallLog && message.isFirstMissedCall) { viewHolder.view.setOnClickListener { context.showSessionDialog { - title(R.string.CallNotificationBuilder_first_call_title) - text(R.string.CallNotificationBuilder_first_call_message) - button(R.string.activity_settings_title) { + val titleTxt = Phrase.from(context, R.string.callsMissedCallFrom) + .put(NAME_KEY, message.individualRecipient.name) + .format().toString() + title(titleTxt) + val bodyTxt = Phrase.from(context, R.string.callsYouMissedCallPermissions) + .put(NAME_KEY, message.individualRecipient.name) + .format().toString() + text(bodyTxt) + button(R.string.sessionSettings) { Intent(context, PrivacySettingsActivity::class.java) .let(context::startActivity) } @@ -190,7 +198,7 @@ class ConversationAdapter( private fun getMessageBefore(position: Int, cursor: Cursor): MessageRecord? { // The message that's visually before the current one is actually after the current // one for the cursor because the layout is reversed - if (isReversed && !cursor.moveToPosition(position + 1)) { return null } + if (isReversed && !cursor.moveToPosition(position + 1)) { return null } if (!isReversed && !cursor.moveToPosition(position - 1)) { return null } return messageDB.readerFor(cursor).current diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt index 9f2046334b..734e7cdf15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt @@ -22,7 +22,11 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.doOnLayout import androidx.vectordrawable.graphics.drawable.AnimatorInflaterCompat +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import java.util.Locale +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -30,7 +34,9 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R +import org.session.libsession.LocalisedTimeUtil.toShortTwoPartString import org.session.libsession.snode.SnodeAPI +import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.ThemeUtil import org.thoughtcrime.securesms.components.emoji.EmojiImageView @@ -46,14 +52,6 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.util.AnimationCompleteListener import org.thoughtcrime.securesms.util.DateUtils -import java.util.Locale -import javax.inject.Inject -import kotlin.time.Duration -import kotlin.time.Duration.Companion.days -import kotlin.time.Duration.Companion.hours -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds @AndroidEntryPoint class ConversationReactionOverlay : FrameLayout { @@ -529,11 +527,11 @@ class ConversationReactionOverlay : FrameLayout { ?: return emptyList() val userPublicKey = getLocalNumber(context)!! // Select message - items += ActionItem(R.attr.menu_select_icon, R.string.conversation_context__menu_select, { handleActionItemClicked(Action.SELECT) }, R.string.AccessibilityId_select) + items += ActionItem(R.attr.menu_select_icon, R.string.select, { handleActionItemClicked(Action.SELECT) }, R.string.AccessibilityId_select) // Reply val canWrite = openGroup == null || openGroup.canWrite if (canWrite && !message.isPending && !message.isFailed && !message.isOpenGroupInvitation) { - items += ActionItem(R.attr.menu_reply_icon, R.string.conversation_context__menu_reply, { handleActionItemClicked(Action.REPLY) }, R.string.AccessibilityId_reply_message) + items += ActionItem(R.attr.menu_reply_icon, R.string.reply, { handleActionItemClicked(Action.REPLY) }, R.string.AccessibilityId_reply_message) } // Copy message text if (!containsControlMessage && hasText) { @@ -541,7 +539,7 @@ class ConversationReactionOverlay : FrameLayout { } // Copy Account ID if (recipient.isGroupRecipient && !recipient.isCommunityRecipient && message.recipient.address.toString() != userPublicKey) { - items += ActionItem(R.attr.menu_copy_icon, R.string.activity_conversation_menu_copy_account_id, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) }) + items += ActionItem(R.attr.menu_copy_icon, R.string.accountIdCopy, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) }) } // Delete message if (userCanDeleteSelectedItems(context, message, openGroup, userPublicKey, blindedPublicKey)) { @@ -550,17 +548,17 @@ class ConversationReactionOverlay : FrameLayout { } // Ban user if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey)) { - items += ActionItem(R.attr.menu_block_icon, R.string.conversation_context__menu_ban_user, { handleActionItemClicked(Action.BAN_USER) }) + items += ActionItem(R.attr.menu_block_icon, R.string.banUser, { handleActionItemClicked(Action.BAN_USER) }) } // Ban and delete all if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey)) { - items += ActionItem(R.attr.menu_trash_icon, R.string.conversation_context__menu_ban_and_delete_all, { handleActionItemClicked(Action.BAN_AND_DELETE_ALL) }) + items += ActionItem(R.attr.menu_trash_icon, R.string.banDeleteAll, { handleActionItemClicked(Action.BAN_AND_DELETE_ALL) }) } // Message detail - items += ActionItem(R.attr.menu_info_icon, R.string.conversation_context__menu_message_details, { handleActionItemClicked(Action.VIEW_INFO) }) + items += ActionItem(R.attr.menu_info_icon, R.string.messageInfo, { handleActionItemClicked(Action.VIEW_INFO) }) // Resend if (message.isFailed) { - items += ActionItem(R.attr.menu_reply_icon, R.string.conversation_context__menu_resend_message, { handleActionItemClicked(Action.RESEND) }) + items += ActionItem(R.attr.menu_reply_icon, R.string.resend, { handleActionItemClicked(Action.RESEND) }) } // Resync if (message.isSyncFailed) { @@ -704,10 +702,6 @@ class ConversationReactionOverlay : FrameLayout { } } -private fun Duration.to2partString(): String? = - toComponents { days, hours, minutes, seconds, nanoseconds -> listOf(days.days, hours.hours, minutes.minutes, seconds.seconds) } - .filter { it.inWholeSeconds > 0L }.take(2).takeIf { it.isNotEmpty() }?.joinToString(" ") - private val MessageRecord.subtitle: ((Context) -> CharSequence?)? get() = if (expiresIn <= 0) { null @@ -715,6 +709,10 @@ private val MessageRecord.subtitle: ((Context) -> CharSequence?)? (expiresIn - (SnodeAPI.nowWithOffset - (expireStarted.takeIf { it > 0 } ?: timestamp))) .coerceAtLeast(0L) .milliseconds - .to2partString() - ?.let { context.getString(R.string.auto_deletes_in, it) } + .toShortTwoPartString() + .let { + Phrase.from(context, R.string.disappearingMessagesCountdownBigMobile) + .put(TIME_LARGE_KEY, it) + .format().toString() + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt index ca5b1cec11..58c5536248 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt @@ -58,7 +58,7 @@ class DeleteOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListen } if (!recipient.isGroupRecipient && !contact.isNullOrEmpty()) { binding.deleteForEveryoneTextView.text = - resources.getString(R.string.delete_message_for_me_and_recipient, contact) + resources.getString(R.string.clearMessagesForEveryone, contact) } binding.deleteForEveryoneTextView.isVisible = !recipient.isClosedGroupRecipient binding.deleteForMeTextView.setOnClickListener(this) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 9514552d28..ccac3382a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -98,7 +98,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) - title = resources.getString(R.string.conversation_context__menu_message_details) + title = resources.getString(R.string.messageInfo) viewModel.timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) @@ -319,13 +319,13 @@ fun PreviewMessageDetails( state = MessageDetailsState( nonImageAttachmentFileDetails = listOf( TitledText(R.string.message_details_header__file_id, "Screen Shot 2023-07-06 at 11.35.50 am.png"), - TitledText(R.string.message_details_header__file_type, "image/png"), - TitledText(R.string.message_details_header__file_size, "195.6kB"), - TitledText(R.string.message_details_header__resolution, "342x312"), + TitledText(R.string.attachmentsFileType, "image/png"), + TitledText(R.string.attachmentsFileSize, "195.6kB"), + TitledText(R.string.attachmentsResolution, "342x312"), ), - sent = TitledText(R.string.message_details_header__sent, "6:12 AM Tue, 09/08/2022"), - received = TitledText(R.string.message_details_header__received, "6:12 AM Tue, 09/08/2022"), - error = TitledText(R.string.message_details_header__error, "Message failed to send"), + sent = TitledText(R.string.sent, "6:12 AM Tue, 09/08/2022"), + received = TitledText(R.string.received, "6:12 AM Tue, 09/08/2022"), + error = TitledText(R.string.error, "Message failed to send"), senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54"), ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index fc54b652ae..7c4b01f601 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -78,9 +78,9 @@ class MessageDetailsViewModel @Inject constructor( MessageDetailsState( attachments = slides.map(::Attachment), record = record, - sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) }, - received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) }, - error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.message_details_header__error, it) }, + sent = dateSent.let(::Date).toString().let { TitledText(R.string.sent, it) }, + received = dateReceived.let(::Date).toString().let { TitledText(R.string.received, it) }, + error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.theError, it) }, senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, sender = individualRecipient, thread = threadDb.getRecipientForThreadId(threadId)!!, @@ -91,13 +91,13 @@ class MessageDetailsViewModel @Inject constructor( private val Slide.details: List get() = listOfNotNull( fileName.orNull()?.let { TitledText(R.string.message_details_header__file_id, it) }, - TitledText(R.string.message_details_header__file_type, asAttachment().contentType), - TitledText(R.string.message_details_header__file_size, Util.getPrettyFileSize(fileSize)), + TitledText(R.string.attachmentsFileType, asAttachment().contentType), + TitledText(R.string.attachmentsFileSize, Util.getPrettyFileSize(fileSize)), takeIf { it is ImageSlide } ?.let(Slide::asAttachment) ?.run { "${width}x$height" } - ?.let { TitledText(R.string.message_details_header__resolution, it) }, - attachmentDb.duration(this)?.let { TitledText(R.string.message_details_header__duration, it) }, + ?.let { TitledText(R.string.attachmentsResolution, it) }, + attachmentDb.duration(this)?.let { TitledText(R.string.attachmentsDuration, it) }, ) private fun AttachmentDatabase.duration(slide: Slide): String? = @@ -157,7 +157,7 @@ data class MessageDetailsState( val sender: Recipient? = null, val thread: Recipient? = null, ) { - val fromTitle = GetString(R.string.message_details_header__from) + val fromTitle = GetString(R.string.from) val canReply = record?.isOpenGroupInvitation != true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt index 54deea1c8d..0263fd95ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt @@ -15,9 +15,10 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.squareup.phrase.Phrase import network.loki.messenger.R import network.loki.messenger.databinding.FragmentModalUrlBottomSheetBinding -import org.thoughtcrime.securesms.util.UiModeUtilities +import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(), View.OnClickListener { private lateinit var binding: FragmentModalUrlBottomSheetBinding @@ -29,7 +30,9 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val explanation = resources.getString(R.string.dialog_open_url_explanation, url) + val explanation = Phrase.from(context, R.string.urlOpenDescription) + .put(URL_KEY, url) + .format() val spannable = SpannableStringBuilder(explanation) val startIndex = explanation.indexOf(url) spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + url.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) @@ -44,7 +47,7 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(), val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) requireContext().startActivity(intent) } catch (e: Exception) { - Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_SHORT).show() + Toast.makeText(context, R.string.communityEnterUrlErrorInvalid, Toast.LENGTH_SHORT).show() } dismiss() } @@ -53,7 +56,7 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(), val clip = ClipData.newPlainText("URL", url) val manager = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(clip) - Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(requireContext(), R.string.copied, Toast.LENGTH_SHORT).show() dismiss() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 57c42a7719..0b61dec9d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -11,16 +11,18 @@ import android.widget.RelativeLayout import android.widget.TextView import androidx.core.view.children import androidx.core.view.isVisible +import com.squareup.phrase.Phrase import network.loki.messenger.R import network.loki.messenger.databinding.AlbumThumbnailViewBinding import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.util.ActivityDispatcher @@ -80,7 +82,7 @@ class AlbumThumbnailView : RelativeLayout { slideSize = -1 } - fun bind(glideRequests: GlideRequests, message: MmsMessageRecord, + fun bind(glideRequests: RequestManager, message: MmsMessageRecord, isStart: Boolean, isEnd: Boolean) { slides = message.slideDeck.thumbnailSlides if (slides.isEmpty()) { @@ -97,7 +99,10 @@ class AlbumThumbnailView : RelativeLayout { binding.albumCellContainer.findViewById(R.id.album_cell_overflow_text)?.let { overflowText -> // overflowText will be null if !overflowed overflowText.isVisible = overflowed // more than max album size - overflowText.text = context.getString(R.string.AlbumThumbnailView_plus, slides.size - MAX_ALBUM_DISPLAY_SIZE) + val txt = Phrase.from(context, R.string.andMore) + .put(COUNT_KEY, slides.size - MAX_ALBUM_DISPLAY_SIZE) + .format() + overflowText.text = txt } this.slideSize = slides.size } @@ -110,10 +115,9 @@ class AlbumThumbnailView : RelativeLayout { // endregion - fun layoutRes(slideCount: Int) = when (slideCount) { - 1 -> R.layout.album_thumbnail_1 // single - 2 -> R.layout.album_thumbnail_2// two sidebyside + 1 -> R.layout.album_thumbnail_1 // single + 2 -> R.layout.album_thumbnail_2 // two side-by-side else -> R.layout.album_thumbnail_3 // three stacked with additional text } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt index 9c414f34fd..9bad72f296 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt @@ -7,7 +7,7 @@ import android.widget.LinearLayout import androidx.core.view.isVisible import network.loki.messenger.databinding.ViewLinkPreviewDraftBinding import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.util.toPx @@ -27,7 +27,7 @@ class LinkPreviewDraftView : LinearLayout { binding.linkPreviewDraftCancelButton.setOnClickListener { cancel() } } - fun update(glide: GlideRequests, linkPreview: LinkPreview) { + fun update(glide: RequestManager, linkPreview: LinkPreview) { // Hide the loader and show the content view binding.linkPreviewDraftContainer.isVisible = true binding.linkPreviewDraftLoader.isVisible = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateSelectionView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateSelectionView.kt index 303c17c5a5..5698ddd0bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateSelectionView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateSelectionView.kt @@ -9,13 +9,13 @@ import android.widget.BaseAdapter import android.widget.ListView import org.session.libsession.messaging.mentions.Mention import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.util.toPx class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) { private var mentionCandidates = listOf() set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.mentionCandidates = newValue } - var glide: GlideRequests? = null + var glide: RequestManager? = null set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.glide = newValue } var openGroupServer: String? = null set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupServer = openGroupServer } @@ -28,7 +28,7 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS private class Adapter(private val context: Context) : BaseAdapter() { var mentionCandidates = listOf() set(newValue) { field = newValue; notifyDataSetChanged() } - var glide: GlideRequests? = null + var glide: RequestManager? = null var openGroupServer: String? = null var openGroupRoom: String? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt index d544263915..14dc6263ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt @@ -8,13 +8,13 @@ import android.widget.LinearLayout import network.loki.messenger.databinding.ViewMentionCandidateBinding import org.session.libsession.messaging.mentions.Mention import org.thoughtcrime.securesms.groups.OpenGroupManager -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager class MentionCandidateView : LinearLayout { private lateinit var binding: ViewMentionCandidateBinding var mentionCandidate = Mention("", "") set(newValue) { field = newValue; update() } - var glide: GlideRequests? = null + var glide: RequestManager? = null var openGroupServer: String? = null var openGroupRoom: String? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt index 46feefb608..edb1067e6c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt @@ -8,9 +8,11 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.StyleSpan import androidx.fragment.app.DialogFragment +import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.StringSubstitutionConstants.COMMUNITY_NAME_KEY import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.dependencies.DatabaseComponent @@ -24,14 +26,14 @@ class BlockedDialog(private val recipient: Recipient, private val context: Conte val contact = contactDB.getContactWithAccountID(accountID) val name = contact?.displayName(Contact.ContactContext.REGULAR) ?: accountID - val explanation = resources.getString(R.string.dialog_blocked_explanation, name) + val explanation = Phrase.from(context, R.string.communityJoinDescription).put(COMMUNITY_NAME_KEY, name).format() val spannable = SpannableStringBuilder(explanation) val startIndex = explanation.indexOf(name) spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - title(resources.getString(R.string.dialog_blocked_title, name)) + title(resources.getString(R.string.blockUnblock)) text(spannable) - button(R.string.ConversationActivity_unblock) { unblock() } + button(R.string.blockUnblock) { unblock() } cancelButton { dismiss() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt index 1af1d669cf..486d6aba87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt @@ -7,12 +7,14 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.StyleSpan import androidx.fragment.app.DialogFragment +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentDownloadJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_NAME_KEY import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent @@ -29,15 +31,19 @@ class DownloadDialog(private val recipient: Recipient) : DialogFragment() { val accountID = recipient.address.toString() val contact = contactDB.getContactWithAccountID(accountID) val name = contact?.displayName(Contact.ContactContext.REGULAR) ?: accountID - title(resources.getString(R.string.dialog_download_title, name)) - val explanation = resources.getString(R.string.dialog_download_explanation, name) + title(getString(R.string.attachmentsAutoDownloadModalTitle)) + + val explanation = Phrase.from(context, R.string.attachmentsAutoDownloadModalDescription) + .put(CONVERSATION_NAME_KEY, recipient.name) + .format() val spannable = SpannableStringBuilder(explanation) + val startIndex = explanation.indexOf(name) spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) text(spannable) - button(R.string.dialog_download_button_title, R.string.AccessibilityId_download_media) { trust() } + button(R.string.download, R.string.AccessibilityId_download_media) { trust() } cancelButton { dismiss() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt index a886e89192..21405b26c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.conversation.v2.dialogs +import org.thoughtcrime.securesms.createSessionDialog import android.app.Dialog import android.graphics.Typeface import android.os.Bundle @@ -8,11 +9,13 @@ import android.text.SpannableStringBuilder import android.text.style.StyleSpan import android.widget.Toast import androidx.fragment.app.DialogFragment +import com.squareup.phrase.Phrase import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.OpenGroupUrlParser +import org.session.libsession.utilities.StringSubstitutionConstants.COMMUNITY_NAME_KEY +import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils -import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities @@ -20,14 +23,18 @@ import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities class JoinOpenGroupDialog(private val name: String, private val url: String) : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { - title(resources.getString(R.string.dialog_join_open_group_title, name)) - val explanation = resources.getString(R.string.dialog_join_open_group_explanation, name) + title(resources.getString(R.string.communityJoin)) + val explanation = Phrase.from(context, R.string.communityJoinDescription).put(COMMUNITY_NAME_KEY, name).format() val spannable = SpannableStringBuilder(explanation) - val startIndex = explanation.indexOf(name) + var startIndex = explanation.indexOf(name) + if (startIndex < 0) { + Log.w("JoinOpenGroupDialog", "Could not find $name in explanation dialog: $explanation") + startIndex = 0 // Limit the startIndex to zero if not found (will be -1) to prevent a crash + } spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) text(spannable) cancelButton { dismiss() } - button(R.string.open_group_invitation_view__join_accessibility_description) { join() } + button(R.string.join) { join() } } private fun join() { @@ -39,7 +46,7 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : D MessagingModuleConfiguration.shared.storage.onOpenGroupAdded(openGroup.server, openGroup.room) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(activity) } catch (e: Exception) { - Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show() + Toast.makeText(activity, R.string.communityErrorDescription, Toast.LENGTH_SHORT).show() } } dismiss() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt index 996dd41f94..94e672eab8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/LinkPreviewDialog.kt @@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.conversation.v2.dialogs import android.app.Dialog import android.os.Bundle import androidx.fragment.app.DialogFragment +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.createSessionDialog @@ -12,9 +14,12 @@ import org.thoughtcrime.securesms.createSessionDialog class LinkPreviewDialog(private val onEnabled: () -> Unit) : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { - title(R.string.dialog_link_preview_title) - text(R.string.dialog_link_preview_explanation) - button(R.string.dialog_link_preview_enable_button_title) { enable() } + title(R.string.linkPreviewsEnable) + val txt = Phrase.from(context, R.string.linkPreviewsFirstDescription) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format() + text(txt) + button(R.string.enable) { enable() } cancelButton { dismiss() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt index 6abb0814d6..177d81a2f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/SendSeedDialog.kt @@ -10,9 +10,9 @@ import org.thoughtcrime.securesms.createSessionDialog class SendSeedDialog(private val proceed: (() -> Unit)? = null) : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { - title(R.string.dialog_send_seed_title) - text(R.string.dialog_send_seed_explanation) - button(R.string.dialog_send_seed_send_button_title) { send() } + title(R.string.warning) + text(R.string.recoveryPasswordWarningSendDescription) + button(R.string.send) { send() } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 8e38c7d38e..c8aacdeb6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.util.addTextChangedListener import org.thoughtcrime.securesms.util.contains @@ -188,7 +188,7 @@ class InputBar @JvmOverloads constructor( private fun startRecordingVoiceMessage() { delegate?.startRecordingVoiceMessage() } - fun draftQuote(thread: Recipient, message: MessageRecord, glide: GlideRequests) { + fun draftQuote(thread: Recipient, message: MessageRecord, glide: RequestManager) { quoteView?.let(binding.inputBarAdditionalContentContainer::removeView) quote = message @@ -238,7 +238,7 @@ class InputBar @JvmOverloads constructor( requestLayout() } - fun updateLinkPreviewDraft(glide: GlideRequests, updatedLinkPreview: LinkPreview) { + fun updateLinkPreviewDraft(glide: RequestManager, updatedLinkPreview: LinkPreview) { // Update our `linkPreview` property with the new (provided as an argument to this function) // then update the View from that. linkPreview = updatedLinkPreview.also { linkPreviewDraftView?.update(glide, it) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt index 24b48ecdf7..f245dcadf4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBarRecordingView.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewInputBarRecordingBinding -import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.animateSizeChange import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.toPx @@ -106,8 +105,7 @@ class InputBarRecordingView : RelativeLayout { timerJob = scope.launch { while (isActive) { val duration = (Date().time - startTimestamp) / 1000L - binding.recordingViewDurationTextView.text = DateUtils.formatElapsedTime(duration) - + binding.recordingViewDurationTextView.text = android.text.format.DateUtils.formatElapsedTime(duration) delay(500) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index 177becd497..88a4b00465 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -16,10 +16,13 @@ import androidx.appcompat.widget.SearchView.OnQueryTextListener 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 import org.session.libsession.utilities.GroupUtil.doubleDecodeGroupID +import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.guava.Optional @@ -38,7 +41,6 @@ import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.showMuteDialog import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.BitmapUtil -import java.io.IOException object ConversationMenuHelper { @@ -164,9 +166,9 @@ object ConversationMenuHelper { if (!TextSecurePreferences.isCallNotificationsEnabled(context)) { context.showSessionDialog { - title(R.string.ConversationActivity_call_title) - text(R.string.ConversationActivity_call_prompt) - button(R.string.activity_settings_title, R.string.AccessibilityId_settings) { + title(R.string.callsPermissionsRequired) + text(R.string.callsPermissionsRequiredDescription) + button(R.string.sessionSettings, R.string.AccessibilityId_settings) { Intent(context, PrivacySettingsActivity::class.java).let(context::startActivity) } cancelButton() @@ -217,7 +219,7 @@ object ConversationMenuHelper { .setIntent(ShortcutLauncherActivity.createIntent(context, thread.address)) .build() if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) { - Toast.makeText(context, context.resources.getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show() + Toast.makeText(context, context.resources.getString(R.string.conversationsAddedToHome), Toast.LENGTH_LONG).show() } } }.execute() @@ -274,15 +276,24 @@ object ConversationMenuHelper { val accountID = TextSecurePreferences.getLocalNumber(context) val isCurrentUserAdmin = admins.any { it.toString() == accountID } val message = if (isCurrentUserAdmin) { - "Because you are the creator of this group it will be deleted for everyone. This cannot be undone." + Phrase.from(context, R.string.groupLeaveDescriptionAdmin) + .put(GROUP_NAME_KEY, group.title) + .format().toString() } else { - context.resources.getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group) + Phrase.from(context, R.string.groupLeaveDescription) + .put(GROUP_NAME_KEY, group.title) + .format().toString() } - fun onLeaveFailed() = Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show() + fun onLeaveFailed() { + val txt = Phrase.from(context, R.string.groupLeaveErrorFailed) + .put(GROUP_NAME_KEY, group.title) + .format().toString() + Toast.makeText(context, txt, Toast.LENGTH_LONG).show() + } context.showSessionDialog { - title(R.string.ConversationActivity_leave_group) + title(R.string.groupLeave) text(message) button(R.string.yes) { try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index 1177b4afc9..ab8498acf4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -8,17 +8,19 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import network.loki.messenger.R 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.NAME_KEY import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages import org.thoughtcrime.securesms.conversation.disappearingmessages.expiryMode import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import javax.inject.Inject @AndroidEntryPoint class ControlMessageView : LinearLayout { @@ -75,8 +77,10 @@ class ControlMessageView : LinearLayout { } } message.isMessageRequestResponse -> { - binding.textView.text = context.getString(R.string.message_requests_accepted) - binding.root.contentDescription=context.getString(R.string.AccessibilityId_message_request_config_message) + binding.textView.text = context.getString(R.string.messageRequestsAccepted) + binding.root.contentDescription = Phrase.from(context, R.string.messageRequestYouHaveAccepted) + .put(NAME_KEY, message.individualRecipient.name) + .format() } message.isCallLog -> { val drawable = when { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt index 9c725ee048..9c1abb0be2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt @@ -21,7 +21,7 @@ class DeletedMessageView : LinearLayout { // region Updating fun bind(message: MessageRecord, @ColorInt textColor: Int) { assert(message.isDeleted) - binding.deleteTitleTextView.text = context.getString(R.string.deleted_message) + binding.deleteTitleTextView.text = context.getString(R.string.deleteMessageDeleted) binding.deleteTitleTextView.setTextColor(textColor) binding.deletedMessageViewIconImageView.imageTintList = ColorStateList.valueOf(textColor) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 4e6066edb3..8cf80dc090 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.mms.ImageSlide class LinkPreviewView : LinearLayout { @@ -32,7 +32,7 @@ class LinkPreviewView : LinearLayout { // region Updating fun bind( message: MmsMessageRecord, - glide: GlideRequests, + glide: RequestManager, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean ) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 77565244a0..6a5f852409 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -18,7 +18,7 @@ import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.database.SessionContactDatabase -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.getAccentColor @@ -68,20 +68,20 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? // region Updating fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long, - isOriginalMissing: Boolean, glide: GlideRequests) { + isOriginalMissing: Boolean, glide: RequestManager) { // Author val author = contactDb.getContactWithAccountID(authorPublicKey) val localNumber = TextSecurePreferences.getLocalNumber(context) val quoteIsLocalUser = localNumber != null && authorPublicKey == localNumber val authorDisplayName = - if (quoteIsLocalUser) context.getString(R.string.QuoteView_you) + if (quoteIsLocalUser) context.getString(R.string.you) else author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}" binding.quoteViewAuthorTextView.text = authorDisplayName binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage)) // Body binding.quoteViewBodyTextView.text = if (isOpenGroupInvitation) - resources.getString(R.string.open_group_invitation_view__open_group_invitation) + resources.getString(R.string.communityInvitation) else MentionUtilities.highlightMentions( text = (body ?: "").toSpannable(), isOutgoingMessage = isOutgoingMessage, @@ -106,7 +106,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? attachments.audioSlide != null -> { binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_microphone) binding.quoteViewAttachmentPreviewImageView.isVisible = true - binding.quoteViewBodyTextView.text = resources.getString(R.string.Slide_audio) + binding.quoteViewBodyTextView.text = resources.getString(R.string.audio) } attachments.documentSlide != null -> { binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_document_large_light) @@ -120,7 +120,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? .root.setRoundedCorners(toPx(4, resources)) binding.quoteViewAttachmentThumbnailImageView.root.setImageResource(glide, slide, false) binding.quoteViewAttachmentThumbnailImageView.root.isVisible = true - binding.quoteViewBodyTextView.text = if (MediaUtil.isVideo(slide.asAttachment())) resources.getString(R.string.Slide_video) else resources.getString(R.string.Slide_image) + binding.quoteViewBodyTextView.text = if (MediaUtil.isVideo(slide.asAttachment())) resources.getString(R.string.video) else resources.getString(R.string.image) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt index 47034cf8ed..922096c9f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/UntrustedAttachmentView.kt @@ -5,12 +5,13 @@ import android.util.AttributeSet import android.widget.LinearLayout import androidx.annotation.ColorInt import androidx.core.content.ContextCompat +import com.squareup.phrase.Phrase import network.loki.messenger.R import network.loki.messenger.databinding.ViewUntrustedAttachmentBinding +import org.session.libsession.utilities.StringSubstitutionConstants.FILE_TYPE_KEY import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.dialogs.DownloadDialog import org.thoughtcrime.securesms.util.ActivityDispatcher -import java.util.Locale class UntrustedAttachmentView: LinearLayout { private val binding: ViewUntrustedAttachmentBinding by lazy { ViewUntrustedAttachmentBinding.bind(this) } @@ -30,13 +31,17 @@ class UntrustedAttachmentView: LinearLayout { // region Updating fun bind(attachmentType: AttachmentType, @ColorInt textColor: Int) { val (iconRes, stringRes) = when (attachmentType) { - AttachmentType.AUDIO -> R.drawable.ic_microphone to R.string.Slide_audio - AttachmentType.DOCUMENT -> R.drawable.ic_document_large_light to R.string.document + AttachmentType.AUDIO -> R.drawable.ic_microphone to R.string.audio + AttachmentType.DOCUMENT -> R.drawable.ic_document_large_light to R.string.files AttachmentType.MEDIA -> R.drawable.ic_image_white_24dp to R.string.media } val iconDrawable = ContextCompat.getDrawable(context,iconRes)!! iconDrawable.mutate().setTint(textColor) - val text = context.getString(R.string.UntrustedAttachmentView_download_attachment, context.getString(stringRes).toLowerCase(Locale.ROOT)) + + val text = Phrase.from(context, R.string.attachmentsTapToDownload) + .put(FILE_TYPE_KEY, stringRes) + .format() + binding.untrustedAttachmentTitle.text = text binding.untrustedAttachmentIcon.setImageDrawable(iconDrawable) binding.untrustedAttachmentTitle.text = text diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index b320e72e26..0f81aff376 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -38,8 +38,8 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getInt import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.SmsMessageRecord -import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.util.GlowViewUtilities import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.getAccentColor @@ -63,7 +63,7 @@ class VisibleMessageContentView : ConstraintLayout { message: MessageRecord, isStartOfMessageCluster: Boolean = true, isEndOfMessageCluster: Boolean = true, - glide: GlideRequests = GlideApp.with(this), + glide: RequestManager = Glide.with(this), thread: Recipient, searchQuery: String? = null, contactIsTrusted: Boolean = true, @@ -117,7 +117,7 @@ class VisibleMessageContentView : ConstraintLayout { binding.quoteView.root.isVisible = true val quote = message.quote!! val quoteText = if (quote.isOriginalMissing) { - context.getString(R.string.QuoteView_original_missing) + context.getString(R.string.messageErrorOriginal) } else { quote.text } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index b2e3bba81b..57598dadff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -16,7 +16,6 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.LinearLayout import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.annotation.StringRes @@ -27,6 +26,13 @@ import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.core.view.marginBottom import dagger.hilt.android.AndroidEntryPoint +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.math.sqrt import network.loki.messenger.R import network.loki.messenger.databinding.ViewEmojiReactionsBinding import network.loki.messenger.databinding.ViewVisibleMessageBinding @@ -52,19 +58,12 @@ import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.UserDetailsBottomSheet -import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.toDp import org.thoughtcrime.securesms.util.toPx -import java.util.Date -import java.util.Locale -import javax.inject.Inject -import kotlin.math.abs -import kotlin.math.min -import kotlin.math.roundToInt -import kotlin.math.sqrt private const val TAG = "VisibleMessageView" @@ -141,7 +140,7 @@ class VisibleMessageView : FrameLayout { message: MessageRecord, previous: MessageRecord? = null, next: MessageRecord? = null, - glide: GlideRequests = GlideApp.with(this), + glide: RequestManager = Glide.with(this), searchQuery: String? = null, contact: Contact? = null, senderAccountID: String, @@ -384,37 +383,37 @@ class VisibleMessageView : FrameLayout { message.isFailed -> MessageStatusInfo(R.drawable.ic_delivery_status_failed, getThemedColor(context, R.attr.danger), - R.string.delivery_status_failed + R.string.messageStatusFailedToSend ) message.isSyncFailed -> MessageStatusInfo( R.drawable.ic_delivery_status_failed, context.getColor(R.color.accent_orange), - R.string.delivery_status_sync_failed + R.string.messageStatusFailedToSync ) message.isPending -> MessageStatusInfo( R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), - R.string.delivery_status_sending + R.string.sending ) message.isSyncing || message.isResyncing -> MessageStatusInfo( R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), - R.string.delivery_status_sending // We COULD tell the user that we're `syncing` (R.string.delivery_status_syncing) but it will likely make more sense to them if we say "Sending" + R.string.messageStatusSyncing ) message.isRead || message.isIncoming -> MessageStatusInfo( R.drawable.ic_delivery_status_read, context.getColorFromAttr(R.attr.message_status_color), - R.string.delivery_status_read + R.string.read ) message.isSent -> MessageStatusInfo( R.drawable.ic_delivery_status_sent, context.getColorFromAttr(R.attr.message_status_color), - R.string.delivery_status_sent + R.string.disappearingMessagesSent ) else -> { // The message isn't one we care about for message statuses we display to the user (i.e., diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt index afed74b1cc..333d924f01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt @@ -5,9 +5,11 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout +import com.squareup.phrase.Phrase import network.loki.messenger.R import network.loki.messenger.databinding.ViewSearchBottomBarBinding - +import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.TOTAL_COUNT_KEY class SearchBottomBar : LinearLayout { private lateinit var binding: ViewSearchBottomBarBinding @@ -35,7 +37,10 @@ class SearchBottomBar : LinearLayout { } } if (count > 0) { - searchPosition.text = resources.getString(R.string.ConversationActivity_search_position, position + 1, count) + searchPosition.text = Phrase.from(context, R.string.searchMatches) + .put(COUNT_KEY, position + 1) + .put(TOTAL_COUNT_KEY, count) + .format() } else { searchPosition.text = "" } @@ -43,6 +48,44 @@ class SearchBottomBar : LinearLayout { setViewEnabled(searchDown, position > 0) } + /* + fun setData(position: Int, count: Int, query: String?) = with(binding) { + searchProgressWheel.visibility = GONE + searchUp.setOnClickListener { v: View? -> + if (eventListener != null) { + eventListener!!.onSearchMoveUpPressed() + } + } + searchDown.setOnClickListener { v: View? -> + if (eventListener != null) { + eventListener!!.onSearchMoveDownPressed() + } + } + + // If we found search results list how many we found + if (count > 0) { + searchPosition.text = Phrase.from(context, R.string.searchMatches) + .put(COUNT_KEY, position + 1) + .put(TOTAL_COUNT_KEY, count) + .format() + } else { + // If there are no results we don't display anything if the query is + // empty, but we'll substitute "No results found for " otherwise. + var txt = "" + if (query != null) { + if (query.isNotEmpty()) { + txt = Phrase.from(context, R.string.searchMatchesNoneSpecific) + .put(QUERY_KEY, query) + .format().toString() + } + } + searchPosition.text = txt + } + setViewEnabled(searchUp, position < count - 1) + setViewEnabled(searchDown, position > 0) + } + */ + fun showLoading() { binding.searchProgressWheel.visibility = VISIBLE } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt index 48bb731c68..82156b32e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt @@ -94,6 +94,8 @@ class SearchViewModel @Inject constructor( } } + public fun getActiveQuery() = activeQuery + class SearchResult(private val results: CursorList, val position: Int) : Closeable { fun getResults(): List { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java index 76b95d7b17..2c5d9fb82c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/AttachmentManager.java @@ -16,6 +16,8 @@ */ package org.thoughtcrime.securesms.conversation.v2.utilities; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; @@ -30,10 +32,14 @@ import android.provider.OpenableColumns; import android.text.TextUtils; import android.util.Pair; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import com.squareup.phrase.Phrase; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import network.loki.messenger.R; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.ListenableFuture; import org.session.libsignal.utilities.Log; @@ -44,7 +50,7 @@ import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.DocumentSlide; import org.thoughtcrime.securesms.mms.GifSlide; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -55,13 +61,6 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.util.MediaUtil; -import java.io.IOException; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import network.loki.messenger.R; - public class AttachmentManager { private final static String TAG = AttachmentManager.class.getSimpleName(); @@ -126,7 +125,7 @@ public class AttachmentManager { } @SuppressLint("StaticFieldLeak") - public ListenableFuture setMedia(@NonNull final GlideRequests glideRequests, + public ListenableFuture setMedia(@NonNull final RequestManager glideRequests, @NonNull final Uri uri, @NonNull final MediaType mediaType, @NonNull final MediaConstraints constraints, @@ -252,13 +251,31 @@ public class AttachmentManager { } else { builder = builder.request(Manifest.permission.READ_EXTERNAL_STORAGE); } - builder.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio)) - .withRationaleDialog(activity.getString(R.string.ConversationActivity_to_send_photos_and_video_allow_signal_access_to_storage), R.drawable.ic_baseline_photo_library_24) + + Context c = activity.getApplicationContext(); + String needStoragePermissionTxt = Phrase.from(c, R.string.permissionsStorageSend) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + String storagePermissionDeniedTxt = Phrase.from(c, R.string.cameraGrantAccessStorageDenied) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + + builder.withPermanentDenialDialog(needStoragePermissionTxt) + .withRationaleDialog(storagePermissionDeniedTxt, R.drawable.ic_baseline_photo_library_24) .onAllGranted(() -> selectMediaType(activity, "*/*", null, requestCode)) // Note: We can use startActivityForResult w/ the ACTION_OPEN_DOCUMENT or ACTION_OPEN_DOCUMENT_TREE intent if we need to modernise this. .execute(); } public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull String body) { + + Context c = activity.getApplicationContext(); + String needStoragePermissionTxt = Phrase.from(c, R.string.permissionsStorageSend) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + String cameraPermissionDeniedTxt = Phrase.from(c, R.string.cameraGrantAccessDenied) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + Permissions.PermissionsBuilder builder = Permissions.with(activity); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { builder = builder.request(Manifest.permission.READ_MEDIA_VIDEO) @@ -266,8 +283,8 @@ public class AttachmentManager { } else { builder = builder.request(Manifest.permission.READ_EXTERNAL_STORAGE); } - builder.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio)) - .withRationaleDialog(activity.getString(R.string.ConversationActivity_to_send_photos_and_video_allow_signal_access_to_storage), R.drawable.ic_baseline_photo_library_24) + builder.withPermanentDenialDialog(cameraPermissionDeniedTxt) + .withRationaleDialog(needStoragePermissionTxt, R.drawable.ic_baseline_photo_library_24) .onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body), requestCode)) .execute(); } @@ -291,10 +308,19 @@ public class AttachmentManager { } public void capturePhoto(Activity activity, int requestCode, Recipient recipient) { + + String cameraPermissionDeniedTxt = Phrase.from(context, R.string.cameraGrantAccessDenied) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format().toString(); + + String requireCameraPermissionTxt = Phrase.from(context, R.string.cameraGrantAccessDescription) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format().toString(); + Permissions.with(activity) .request(Manifest.permission.CAMERA) - .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied)) - .withRationaleDialog(activity.getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera),R.drawable.ic_baseline_photo_camera_24) + .withPermanentDenialDialog(cameraPermissionDeniedTxt) + .withRationaleDialog(requireCameraPermissionTxt, R.drawable.ic_baseline_photo_camera_24) .onAllGranted(() -> { Intent captureIntent = MediaSendActivity.buildCameraIntent(activity, recipient); if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { @@ -326,7 +352,7 @@ public class AttachmentManager { activity.startActivityForResult(intent, requestCode); } catch (ActivityNotFoundException anfe) { Log.w(TAG, "couldn't complete ACTION_GET_CONTENT intent, no activity found. falling back."); - Toast.makeText(activity, R.string.AttachmentManager_cant_open_media_selection, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.attachmentsErrorNoApp, Toast.LENGTH_LONG).show(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt index 4d3e48bc5b..39301cd69f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt @@ -54,13 +54,14 @@ object MentionUtilities { val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val openGroup by lazy { DatabaseComponent.get(context).storage().getOpenGroup(threadID) } - // format the mention text + // Format the mention text if (matcher.find(startIndex)) { while (true) { val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @ + val isYou = isYou(publicKey, userPublicKey, openGroup) val userDisplayName: String? = if (isYou) { - context.getString(R.string.MessageRecord_you) + context.getString(R.string.you) } else { val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey) @Suppress("NAME_SHADOWING") val context = if (openGroup != null) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt index c0ce83f631..f012f925ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt @@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.showSessionDialog object NotificationUtils { fun showNotifyDialog(context: Context, thread: Recipient, notifyTypeHandler: (Int)->Unit) { context.showSessionDialog { - title(R.string.RecipientPreferenceActivity_notification_settings) + title(R.string.sessionNotifications) singleChoiceItems( context.resources.getStringArray(R.array.notify_types), thread.notifyType diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index 02c683aac6..83932b2ce4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -25,8 +25,8 @@ import org.session.libsignal.utilities.SettableFuture import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri -import org.thoughtcrime.securesms.mms.GlideRequest -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.mms.Slide open class ThumbnailView @JvmOverloads constructor( @@ -104,13 +104,13 @@ open class ThumbnailView @JvmOverloads constructor( } fun setImageResource( - glide: GlideRequests, + glide: RequestManager, slide: Slide, isPreview: Boolean ): ListenableFuture = setImageResource(glide, slide, isPreview, 0, 0) fun setImageResource( - glide: GlideRequests, slide: Slide, + glide: RequestManager, slide: Slide, isPreview: Boolean, naturalWidth: Int, naturalHeight: Int ): ListenableFuture { @@ -152,9 +152,9 @@ open class ThumbnailView @JvmOverloads constructor( } private fun buildThumbnailGlideRequest( - glide: GlideRequests, + glide: RequestManager, slide: Slide - ): GlideRequest = glide.load(DecryptableUri(slide.thumbnailUri!!)) + ): RequestBuilder = glide.load(DecryptableUri(slide.thumbnailUri!!)) .diskCacheStrategy(DiskCacheStrategy.NONE) .overrideDimensions() .transition(DrawableTransitionOptions.withCrossFade()) @@ -162,21 +162,21 @@ open class ThumbnailView @JvmOverloads constructor( .missingThumbnailPicture(slide.isInProgress) private fun buildPlaceholderGlideRequest( - glide: GlideRequests, + glide: RequestManager, slide: Slide - ): GlideRequest = glide.asBitmap() + ): RequestBuilder = glide.asBitmap() .load(slide.getPlaceholderRes(context.theme)) .diskCacheStrategy(DiskCacheStrategy.NONE) .overrideDimensions() .fitCenter() - open fun clear(glideRequests: GlideRequests) { + open fun clear(glideRequests: RequestManager) { glideRequests.clear(binding.thumbnailImage) slide = null } fun setImageResource( - glideRequests: GlideRequests, + glideRequests: RequestManager, uri: Uri ): ListenableFuture = glideRequests.load(DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -184,19 +184,19 @@ open class ThumbnailView @JvmOverloads constructor( .transform(CenterCrop()) .intoDrawableTargetAsFuture() - private fun GlideRequest.intoDrawableTargetAsFuture() = + private fun RequestBuilder.intoDrawableTargetAsFuture() = SettableFuture().also { binding.run { GlideDrawableListeningTarget(thumbnailImage, thumbnailLoadIndicator, it) }.let { into(it) } } - private fun GlideRequest.overrideDimensions() = + private fun RequestBuilder.overrideDimensions() = dimensDelegate.resourceSize().takeIf { 0 !in it } ?.let { override(it[WIDTH], it[HEIGHT]) } ?: override(getDefaultWidth(), getDefaultHeight()) } -private fun GlideRequest.missingThumbnailPicture( +private fun RequestBuilder.missingThumbnailPicture( inProgress: Boolean ) = takeIf { inProgress } ?: apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java index 822e40129e..be083256db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -3,14 +3,8 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.net.Uri; -import androidx.annotation.Nullable; - import net.zetetic.database.sqlcipher.SQLiteDatabase; - -import network.loki.messenger.R; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; - import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -24,10 +18,10 @@ public class DraftDatabase extends Database { public static final String DRAFT_VALUE = "value"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - THREAD_ID + " INTEGER, " + DRAFT_TYPE + " TEXT, " + DRAFT_VALUE + " TEXT);"; + THREAD_ID + " INTEGER, " + DRAFT_TYPE + " TEXT, " + DRAFT_VALUE + " TEXT);"; public static final String[] CREATE_INDEXS = { - "CREATE INDEX IF NOT EXISTS draft_thread_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", + "CREATE INDEX IF NOT EXISTS draft_thread_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", }; public DraftDatabase(Context context, SQLCipherOpenHelper databaseHelper) { @@ -59,8 +53,8 @@ public class DraftDatabase extends Database { for (long threadId : threadIds) { where.append(" OR ") - .append(THREAD_ID) - .append(" = ?"); + .append(THREAD_ID) + .append(" = ?"); arguments.add(String.valueOf(threadId)); } @@ -95,12 +89,10 @@ public class DraftDatabase extends Database { } } + // Class to save drafts of text (only) messages if the user is in the middle of writing a message + // and then the app loses focus or is closed. public static class Draft { - public static final String TEXT = "text"; - public static final String IMAGE = "image"; - public static final String VIDEO = "video"; - public static final String AUDIO = "audio"; - public static final String QUOTE = "quote"; + public static final String TEXT = "text"; private final String type; private final String value; @@ -117,48 +109,10 @@ public class DraftDatabase extends Database { public String getValue() { return value; } - - String getSnippet(Context context) { - switch (type) { - case TEXT: return value; - case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet); - case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet); - case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet); - case QUOTE: return context.getString(R.string.DraftDatabase_Draft_quote_snippet); - default: return null; - } - } } public static class Drafts extends LinkedList { - private Draft getDraftOfType(String type) { - for (Draft draft : this) { - if (type.equals(draft.getType())) { - return draft; - } - } - return null; - } - - public String getSnippet(Context context) { - Draft textDraft = getDraftOfType(Draft.TEXT); - if (textDraft != null) { - return textDraft.getSnippet(context); - } else if (size() > 0) { - return get(0).getSnippet(context); - } else { - return ""; - } - } - - public @Nullable Uri getUriSnippet() { - Draft imageDraft = getDraftOfType(Draft.IMAGE); - - if (imageDraft != null && imageDraft.getValue() != null) { - return Uri.parse(imageDraft.getValue()); - } - - return null; - } + // We don't do anything with drafts of a given type anymore (image, audio etc.) - we store TEXT + // drafts, and any files or audio get sent to the recipient when added as a message. } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt index f60c53bbe3..f1f999242c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt @@ -166,6 +166,8 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( const val RESET_SEQ_NO = "UPDATE $lastMessageServerIDTable SET $lastMessageServerID = 0;" + const val EMPTY_VERSION = "0.0.0" + // endregion } @@ -179,7 +181,8 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null val x25519Key = components.getOrNull(3) ?: return@mapNotNull null - Snode(address, port, Snode.KeySet(ed25519Key, x25519Key)) + val version = components.getOrNull(4) ?: EMPTY_VERSION + Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version) } }?.toSet() ?: setOf() } @@ -192,6 +195,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( if (keySet != null) { string += "-${keySet.ed25519Key}-${keySet.x25519Key}" } + string += "-${snode.version}" string } val row = wrap(mapOf( Companion.dummyKey to "dummy_key", snodePool to snodePoolAsString )) @@ -207,6 +211,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( if (keySet != null) { snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}" } + snodeAsString += "-${snode.version}" val row = wrap(mapOf( Companion.indexPath to indexPath, Companion.snode to snodeAsString )) database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath)) } @@ -232,8 +237,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val port = components.getOrNull(1)?.toIntOrNull() val ed25519Key = components.getOrNull(2) val x25519Key = components.getOrNull(3) + val version = components.getOrNull(4) ?: EMPTY_VERSION if (port != null && ed25519Key != null && x25519Key != null) { - Snode(address, port, Snode.KeySet(ed25519Key, x25519Key)) + Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version) } else { null } @@ -251,6 +257,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( return result } + override fun clearSnodePool() { + val database = databaseHelper.writableDatabase + database.delete(snodePoolTable, null, null) + } + override fun clearOnionRequestPaths() { val database = databaseHelper.writableDatabase fun delete(indexPath: String) { @@ -271,7 +282,8 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null val x25519Key = components.getOrNull(3) ?: return@mapNotNull null - Snode(address, port, Snode.KeySet(ed25519Key, x25519Key)) + val version = components.getOrNull(4) ?: EMPTY_VERSION + Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version) } }?.toSet() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index f5c6da5fb9..f48686aded 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -808,8 +808,8 @@ public class ThreadDatabase extends Database { private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) { if (messageRecord.isMms()) { MmsMessageRecord record = (MmsMessageRecord) messageRecord; - if (record.getSharedContacts().size() > 0) { - Contact contact = ((MmsMessageRecord) messageRecord).getSharedContacts().get(0); + if (!record.getSharedContacts().isEmpty()) { + Contact contact = ((MmsMessageRecord)messageRecord).getSharedContacts().get(0); return ContactUtil.getStringSummary(context, contact).toString(); } String attachmentString = record.getSlideDeck().getBody(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index cd1988e83f..75fe0502e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -250,7 +250,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { // Notify the user of the issue so they know they can downgrade until the issue is fixed NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - String channelId = context.getString(R.string.NotificationChannel_failures); + String channelId = context.getString(R.string.failures); if (NotificationChannels.supported()) { NotificationChannel channel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java index 05c13cc55d..40b8c36dd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java @@ -14,6 +14,8 @@ 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; @@ -88,16 +90,18 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader= dateSent) { + return context.getString(R.string.disappearingMessagesSent); + } + return context.getString(R.string.disappearingMessagesRead); + } - public int getUnreadMentionCount() { - return unreadMentionCount; - } + @Override + public SpannableString getDisplayBody(@NonNull Context context) { + if (isGroupUpdateMessage()) { + return emphasisAdded(context.getString(R.string.groupUpdated)); + } else if (isOpenGroupInvitation()) { + return emphasisAdded(context.getString(R.string.communityInvitation)); + } else if (MmsSmsColumns.Types.isLegacyType(type)) { + String txt = Phrase.from(context, R.string.messageErrorOld) + .put(APP_NAME_KEY, context.getString(R.string.app_name)) + .format().toString(); + return emphasisAdded(txt); + } else if (MmsSmsColumns.Types.isDraftMessageType(type)) { + String draftText = context.getString(R.string.draft); + return emphasisAdded(draftText + " " + getBody(), 0, draftText.length()); + } else if (SmsDatabase.Types.isOutgoingCall(type)) { + String txt = Phrase.from(context, R.string.callsYouCalled) + .put(NAME_KEY, getName()) + .format().toString(); + return emphasisAdded(txt); + } else if (SmsDatabase.Types.isIncomingCall(type)) { + String txt = Phrase.from(context, R.string.callsCalledYou) + .put(NAME_KEY, getName()) + .format().toString(); + return emphasisAdded(txt); + } else if (SmsDatabase.Types.isMissedCall(type)) { + String txt = Phrase.from(context, R.string.callsMissedCallFrom) + .put(NAME_KEY, getName()) + .format().toString(); + return emphasisAdded(txt); + } else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) { + int seconds = (int) (getExpiresIn() / 1000); + if (seconds <= 0) { + String txt = Phrase.from(context, R.string.disappearingMessagesTurnedOff) + .put(NAME_KEY, getName()) + .format().toString(); + return emphasisAdded(txt); + } - public long getDate() { - return getDateReceived(); - } + // Implied that disappearing messages is enabled.. + String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); + String disappearAfterWhat = getDisappearingMsgExpiryTypeString(context); // Disappear after send or read? + String txt = Phrase.from(context, R.string.disappearingMessagesSet) + .put(NAME_KEY, getName()) + .put(TIME_KEY, time) + .put(DISAPPEARING_MESSAGES_TYPE_KEY, disappearAfterWhat) + .format().toString(); + return emphasisAdded(txt); - public boolean isArchived() { - return archived; - } + } else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { + String txt = Phrase.from(context, R.string.attachmentsMediaSaved) + .put(NAME_KEY, getName()) + .format().toString(); + return emphasisAdded(txt); - public int getDistributionType() { - return distributionType; - } + } else if (MmsSmsColumns.Types.isScreenshotExtraction(type)) { + String txt = Phrase.from(context, R.string.screenshotTaken) + .put(NAME_KEY, getName()) + .format().toString(); + return emphasisAdded(txt); - public long getExpiresIn() { - return expiresIn; - } + } else if (MmsSmsColumns.Types.isMessageRequestResponse(type)) { + return emphasisAdded(context.getString(R.string.messageRequestsAccepted)); + } else if (getCount() == 0) { + return new SpannableString(context.getString(R.string.messageEmpty)); + } else { + // This is shown when we receive a media message from an un-accepted contact + if (TextUtils.isEmpty(getBody())) { + return new SpannableString(emphasisAdded(context.getString(R.string.mediaMessage))); + } else { + return new SpannableString(getBody()); + } + } + } - public long getLastSeen() { - return lastSeen; - } + private SpannableString emphasisAdded(String sequence) { + return emphasisAdded(sequence, 0, sequence.length()); + } - public boolean isPinned() { - return pinned; - } + private SpannableString emphasisAdded(String sequence, int start, int end) { + SpannableString spannable = new SpannableString(sequence); + spannable.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), + start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return spannable; + } - public int getInitialRecipientHash() { - return initialRecipientHash; - } + public long getCount() { return count; } + + public int getUnreadCount() { return unreadCount; } + + public int getUnreadMentionCount() { return unreadMentionCount; } + + public long getDate() { return getDateReceived(); } + + public boolean isArchived() { return archived; } + + public int getDistributionType() { return distributionType; } + + public long getExpiresIn() { return expiresIn; } + + public long getLastSeen() { return lastSeen; } + + public boolean isPinned() { return pinned; } + + public int getInitialRecipientHash() { return initialRecipientHash; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dms/EnterPublicKeyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/dms/EnterPublicKeyFragment.kt new file mode 100644 index 0000000000..ffc18acbc9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dms/EnterPublicKeyFragment.kt @@ -0,0 +1,101 @@ +package org.thoughtcrime.securesms.dms + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.InputType +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.Toast +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import network.loki.messenger.R +import network.loki.messenger.databinding.FragmentEnterPublicKeyBinding +import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.util.QRCodeUtilities +import org.thoughtcrime.securesms.util.hideKeyboard +import org.thoughtcrime.securesms.util.toPx + +class EnterPublicKeyFragment : Fragment() { + private lateinit var binding: FragmentEnterPublicKeyBinding + + var delegate: EnterPublicKeyDelegate? = null + + private val hexEncodedPublicKey: String + get() { + return TextSecurePreferences.getLocalNumber(requireContext())!! + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentEnterPublicKeyBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { + publicKeyEditText.imeOptions = EditorInfo.IME_ACTION_DONE or 16777216 // Always use incognito keyboard + publicKeyEditText.setRawInputType(InputType.TYPE_CLASS_TEXT) + publicKeyEditText.setOnEditorActionListener { v, actionID, _ -> + if (actionID == EditorInfo.IME_ACTION_DONE) { + v.hideKeyboard() + handlePublicKeyEntered() + true + } else { + false + } + } + publicKeyEditText.addTextChangedListener { text -> createPrivateChatButton.isVisible = !text.isNullOrBlank() } + publicKeyEditText.setOnFocusChangeListener { _, hasFocus -> optionalContentContainer.isVisible = !hasFocus } + mainContainer.setOnTouchListener { _, _ -> + binding.optionalContentContainer.isVisible = true + publicKeyEditText.clearFocus() + publicKeyEditText.hideKeyboard() + true + } + val size = toPx(228, resources) + val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, isInverted = false, hasTransparentBackground = false) + qrCodeImageView.setImageBitmap(qrCode) + publicKeyTextView.text = hexEncodedPublicKey + publicKeyTextView.setOnCreateContextMenuListener { contextMenu, view, _ -> + contextMenu.add(0, view.id, 0, R.string.copy).setOnMenuItemClickListener { + copyPublicKey() + true + } + } + copyButton.setOnClickListener { copyPublicKey() } + shareButton.setOnClickListener { sharePublicKey() } + createPrivateChatButton.setOnClickListener { handlePublicKeyEntered(); publicKeyEditText.hideKeyboard() } + } + } + + private fun copyPublicKey() { + val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Session ID", hexEncodedPublicKey) + clipboard.setPrimaryClip(clip) + Toast.makeText(requireContext(), R.string.copied, Toast.LENGTH_SHORT).show() + } + + private fun sharePublicKey() { + val intent = Intent() + intent.action = Intent.ACTION_SEND + intent.putExtra(Intent.EXTRA_TEXT, hexEncodedPublicKey) + intent.type = "text/plain" + startActivity(intent) + } + + private fun handlePublicKeyEntered() { + val hexEncodedPublicKey = binding.publicKeyEditText.text?.trim()?.toString() + if (hexEncodedPublicKey.isNullOrEmpty()) return + delegate?.handlePublicKeyEntered(hexEncodedPublicKey) + } +} + +fun interface EnterPublicKeyDelegate { + fun handlePublicKeyEntered(publicKey: String) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiCategory.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiCategory.kt index 714996e6c8..c9a59b2107 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiCategory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiCategory.kt @@ -15,8 +15,7 @@ enum class EmojiCategory(val priority: Int, val key: String, @AttrRes val icon: PLACES(4, "Places", R.attr.emoji_category_places), OBJECTS(5, "Objects", R.attr.emoji_category_objects), SYMBOLS(6, "Symbols", R.attr.emoji_category_symbol), - FLAGS(7, "Flags", R.attr.emoji_category_flags), - EMOTICONS(8, "Emoticons", R.attr.emoji_category_emoticons); + FLAGS(7, "Flags", R.attr.emoji_category_flags); @StringRes fun getCategoryLabel(): Int { @@ -31,15 +30,14 @@ enum class EmojiCategory(val priority: Int, val key: String, @AttrRes val icon: @StringRes fun getCategoryLabel(@AttrRes iconAttr: Int): Int { return when (iconAttr) { - R.attr.emoji_category_people -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people - R.attr.emoji_category_nature -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__nature - R.attr.emoji_category_foods -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__food - R.attr.emoji_category_activity -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__activities - R.attr.emoji_category_places -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__places - R.attr.emoji_category_objects -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__objects - R.attr.emoji_category_symbol -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__symbols - R.attr.emoji_category_flags -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__flags - R.attr.emoji_category_emoticons -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__emoticons + R.attr.emoji_category_people -> R.string.emojiCategorySmileys + R.attr.emoji_category_nature -> R.string.emojiCategoryAnimals + R.attr.emoji_category_foods -> R.string.emojiCategoryFood + R.attr.emoji_category_activity -> R.string.emojiCategoryActivities + R.attr.emoji_category_places -> R.string.emojiCategoryTravel + R.attr.emoji_category_objects -> R.string.emojiCategoryObjects + R.attr.emoji_category_symbol -> R.string.emojiCategorySymbols + R.attr.emoji_category_flags -> R.string.emojiCategoryFlags else -> throw AssertionError() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt index 0b221eb3d1..75b1496cc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt @@ -110,10 +110,12 @@ class EmojiSource( val parsedData: ParsedEmojiData = EmojiJsonParser.parse(it, ::getAssetsUri).getOrThrow() return EmojiSource( ScreenDensity.xhdpiRelativeDensityScaleFactor("xhdpi"), + parsedData.copy( - displayPages = parsedData.displayPages + PAGE_EMOTICONS, - dataPages = parsedData.dataPages + PAGE_EMOTICONS + displayPages = parsedData.displayPages, + dataPages = parsedData.dataPages ) + ) { uri: Uri -> EmojiPage.Asset(uri) } } } @@ -137,25 +139,3 @@ data class ObsoleteEmoji(val obsolete: String, val replaceWith: String) data class EmojiMetrics(val rawHeight: Int, val rawWidth: Int, val perRow: Int) private fun getAssetsUri(name: String, format: String): Uri = Uri.parse("file:///android_asset/emoji/$name.$format") - -private val PAGE_EMOTICONS: EmojiPageModel = StaticEmojiPageModel( - EmojiCategory.EMOTICONS, - arrayOf( - ":-)", ";-)", "(-:", ":->", ":-D", "\\o/", - ":-P", "B-)", ":-$", ":-*", "O:-)", "=-O", - "O_O", "O_o", "o_O", ":O", ":-!", ":-x", - ":-|", ":-\\", ":-(", ":'(", ":-[", ">:-(", - "^.^", "^_^", "\\(\u02c6\u02da\u02c6)/", - "\u30fd(\u00b0\u25c7\u00b0 )\u30ce", "\u00af\\(\u00b0_o)/\u00af", - "\u00af\\_(\u30c4)_/\u00af", "(\u00ac_\u00ac)", - "(>_<)", "(\u2565\ufe4f\u2565)", "(\u261e\uff9f\u30ee\uff9f)\u261e", - "\u261c(\uff9f\u30ee\uff9f\u261c)", "\u261c(\u2312\u25bd\u2312)\u261e", - "(\u256f\u00b0\u25a1\u00b0)\u256f\ufe35", "\u253b\u2501\u253b", - "\u252c\u2500\u252c", "\u30ce(\u00b0\u2013\u00b0\u30ce)", - "(^._.^)\uff89", "\u0e05^\u2022\ufecc\u2022^\u0e05", - "\u0295\u2022\u1d25\u2022\u0294", "(\u2022_\u2022)", - " \u25a0-\u25a0\u00ac <(\u2022_\u2022) ", "(\u25a0_\u25a0\u00ac)", - "\u01aa(\u0693\u05f2)\u200e\u01aa\u200b\u200b" - ), - null -) diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java index dcfdb66112..ac0d1f1e05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java @@ -120,7 +120,7 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity protected void onPostExecute(@Nullable Uri uri) { if (uri == null) { - Toast.makeText(GiphyActivity.this, R.string.GiphyActivity_error_while_retrieving_full_resolution_gif, Toast.LENGTH_LONG).show(); + Toast.makeText(GiphyActivity.this, R.string.errorUnknown, Toast.LENGTH_LONG).show(); } else if (viewHolder == finishingImage) { Intent intent = new Intent(); intent.setData(uri); @@ -165,8 +165,8 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity @Override public CharSequence getPageTitle(int position) { - if (position == 0) return context.getString(R.string.GiphyFragmentPagerAdapter_gifs); - else return context.getString(R.string.GiphyFragmentPagerAdapter_stickers); + if (position == 0) return context.getString(R.string.gif); + else return context.getString(R.string.stickers); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyAdapter.java index 8972fd8e88..b884ef6a19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyAdapter.java @@ -29,8 +29,8 @@ import org.session.libsession.utilities.ViewUtil; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl; import org.thoughtcrime.securesms.giph.model.GiphyImage; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; import java.util.List; import java.util.concurrent.ExecutionException; @@ -43,7 +43,7 @@ class GiphyAdapter extends RecyclerView.Adapter { private static final String TAG = GiphyAdapter.class.getSimpleName(); private final Context context; - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private List images; private OnItemClickListener listener; @@ -117,7 +117,7 @@ class GiphyAdapter extends RecyclerView.Adapter { } } - GiphyAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @NonNull List images) { + GiphyAdapter(@NonNull Context context, @NonNull RequestManager glideRequests, @NonNull List images) { this.context = context.getApplicationContext(); this.glideRequests = glideRequests; this.images = images; @@ -150,7 +150,7 @@ class GiphyAdapter extends RecyclerView.Adapter { holder.thumbnail.setAspectRatio(image.getGifAspectRatio()); holder.gifProgress.setVisibility(View.GONE); - RequestBuilder thumbnailRequest = GlideApp.with(context) + RequestBuilder thumbnailRequest = Glide.with(context) .load(new ChunkedImageUrl(image.getStillUrl(), image.getStillSize())) .diskCacheStrategy(DiskCacheStrategy.NONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyFragment.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyFragment.java index 5e401c23fc..d4b1d642dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyFragment.java @@ -19,7 +19,7 @@ import android.widget.TextView; import org.thoughtcrime.securesms.giph.model.GiphyImage; import org.thoughtcrime.securesms.giph.net.GiphyLoader; import org.thoughtcrime.securesms.giph.util.InfiniteScrollListener; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.ViewUtil; @@ -54,7 +54,7 @@ public abstract class GiphyFragment extends Fragment implements LoaderManager.Lo public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); - this.giphyAdapter = new GiphyAdapter(getActivity(), GlideApp.with(this), new LinkedList<>()); + this.giphyAdapter = new GiphyAdapter(getActivity(), Glide.with(this), new LinkedList<>()); this.giphyAdapter.setListener(this); setLayoutManager(TextSecurePreferences.isGifSearchInGridLayout(getContext())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt index 7bfea9aab0..0f562c80b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.conversation.start.StartConversationDelegate import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide import org.thoughtcrime.securesms.util.fadeIn import org.thoughtcrime.securesms.util.fadeOut import javax.inject.Inject @@ -55,7 +55,7 @@ class CreateGroupFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val adapter = SelectContactsAdapter(requireContext(), GlideApp.with(requireContext())) + val adapter = SelectContactsAdapter(requireContext(), Glide.with(requireContext())) binding.backButton.setOnClickListener { delegate.onDialogBackPressed() } binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() } binding.contactSearch.callbacks = object : KeyboardPageSearchView.Callbacks { @@ -76,17 +76,20 @@ class CreateGroupFragment : Fragment() { if (isLoading) return@setOnClickListener val name = binding.nameEditText.text.trim() if (name.isEmpty()) { - return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show() + return@setOnClickListener Toast.makeText(context, R.string.groupNameEnterPlease, Toast.LENGTH_LONG).show() } + + // Limit the group name length if it exceeds the limit if (name.length > resources.getInteger(R.integer.max_group_and_community_name_length_chars)) { - return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show() + return@setOnClickListener Toast.makeText(context, R.string.groupNameEnterShorter, Toast.LENGTH_LONG).show() } + val selectedMembers = adapter.selectedMembers if (selectedMembers.isEmpty()) { - return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() + return@setOnClickListener Toast.makeText(context, R.string.groupCreateErrorNoMembers, Toast.LENGTH_LONG).show() } if (selectedMembers.count() >= groupSizeLimit) { // Minus one because we're going to include self later - return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() + return@setOnClickListener Toast.makeText(context, R.string.groupAddMemberMaximum, Toast.LENGTH_LONG).show() } val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!! isLoading = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt index da982589c2..1711c75d9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt @@ -16,7 +16,10 @@ import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import java.io.IOException +import javax.inject.Inject import network.loki.messenger.R import nl.komponents.kovenant.Promise import nl.komponents.kovenant.task @@ -26,6 +29,7 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.groupSizeLimit import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil +import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.recipients.Recipient @@ -37,11 +41,9 @@ import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.ClosedGroupManager.updateLegacyGroup -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide import org.thoughtcrime.securesms.util.fadeIn import org.thoughtcrime.securesms.util.fadeOut -import java.io.IOException -import javax.inject.Inject @AndroidEntryPoint class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { @@ -76,9 +78,9 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { private val memberListAdapter by lazy { if (isSelfAdmin) - EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin, this::onMemberClick) + EditClosedGroupMembersAdapter(this, Glide.with(this), isSelfAdmin, this::onMemberClick) else - EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin) + EditClosedGroupMembersAdapter(this, Glide.with(this), isSelfAdmin) } private lateinit var mainContentContainer: LinearLayout @@ -111,13 +113,17 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { name = originalName + // Update the group member count + val memberCountTV = findViewById(R.id.editGroupMemberCount) + memberCountTV.text = resources.getQuantityString(R.plurals.members, members.size, members.size) + mainContentContainer = findViewById(R.id.mainContentContainer) - cntGroupNameEdit = findViewById(R.id.cntGroupNameEdit) - cntGroupNameDisplay = findViewById(R.id.cntGroupNameDisplay) - edtGroupName = findViewById(R.id.edtGroupName) - emptyStateContainer = findViewById(R.id.emptyStateContainer) - lblGroupNameDisplay = findViewById(R.id.lblGroupNameDisplay) - loaderContainer = findViewById(R.id.loaderContainer) + cntGroupNameEdit = findViewById(R.id.cntGroupNameEdit) + cntGroupNameDisplay = findViewById(R.id.cntGroupNameDisplay) + edtGroupName = findViewById(R.id.edtGroupName) + emptyStateContainer = findViewById(R.id.emptyStateContainer) + lblGroupNameDisplay = findViewById(R.id.lblGroupNameDisplay) + loaderContainer = findViewById(R.id.loaderContainer) findViewById(R.id.addMembersClosedGroupButton).setOnClickListener { onAddMembersClick() @@ -129,7 +135,19 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } lblGroupNameDisplay.text = originalName - cntGroupNameDisplay.setOnClickListener { isEditingName = true } + + // Only allow admins to click on the name of closed groups to edit them.. + if (isSelfAdmin) { + cntGroupNameDisplay.setOnClickListener { isEditingName = true } + } + else // ..and also hide the edit `drawableEnd` for non-admins. + { + // Note: compoundDrawables returns 4 drawables (drawablesStart/Top/End/Bottom) - + // so the `drawableEnd` component is at index 2, which we replace with null. + val cd = lblGroupNameDisplay.compoundDrawables + lblGroupNameDisplay.setCompoundDrawables(cd[0], cd[1], null, cd[3]) + } + findViewById(R.id.btnCancelGroupNameEdit).setOnClickListener { isEditingName = false } findViewById(R.id.btnSaveGroupNameEdit).setOnClickListener { saveName() } edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE) @@ -245,10 +263,10 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { private fun saveName() { val name = edtGroupName.text.toString().trim() if (name.isEmpty()) { - return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_missing_error, Toast.LENGTH_SHORT).show() + return Toast.makeText(this, R.string.groupNameEnterPlease, Toast.LENGTH_SHORT).show() } if (name.length >= 64) { - return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_too_long_error, Toast.LENGTH_SHORT).show() + return Toast.makeText(this, R.string.groupNameEnterShorter, Toast.LENGTH_SHORT).show() } this.name = name lblGroupNameDisplay.text = name @@ -283,18 +301,19 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } if (members.isEmpty()) { - return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() + return Toast.makeText(this, R.string.groupCreateErrorNoMembers, Toast.LENGTH_LONG).show() } val maxGroupMembers = if (isClosedGroup) groupSizeLimit else legacyGroupSizeLimit if (members.size >= maxGroupMembers) { - return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() + return Toast.makeText(this, R.string.groupAddMemberMaximum, Toast.LENGTH_LONG).show() } val userPublicKey = TextSecurePreferences.getLocalNumber(this)!! val userAsRecipient = Recipient.from(this, Address.fromSerialized(userPublicKey), false) if (!members.contains(userAsRecipient) && !members.map { it.address.toString() }.containsAll(originalMembers.minus(userPublicKey))) { + // ACL TODO - Need a proper string for this val message = "Can't leave while adding or removing other members." return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupMembersAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupMembersAdapter.kt index b2d0f6255a..5127e3be72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupMembersAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupMembersAdapter.kt @@ -5,13 +5,13 @@ import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.contacts.UserView -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences class EditClosedGroupMembersAdapter( private val context: Context, - private val glide: GlideRequests, + private val glide: RequestManager, private val admin: Boolean, private val memberClickListener: ((String) -> Unit)? = null ) : RecyclerView.Adapter() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt index 964e1e1770..2137dc7f11 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt @@ -12,6 +12,7 @@ import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.material.tabs.TabLayoutMediator +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -22,6 +23,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.OpenGroupUrlParser +import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.conversation.start.StartConversationDelegate @@ -47,6 +49,7 @@ class JoinCommunityFragment : Fragment() { super.onViewCreated(view, savedInstanceState) binding.backButton.setOnClickListener { delegate.onDialogBackPressed() } binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() } + fun showLoader() { binding.loader.visibility = View.VISIBLE binding.loader.animate().setDuration(150).alpha(1.0f).start() @@ -61,18 +64,24 @@ class JoinCommunityFragment : Fragment() { } }) } + fun joinCommunityIfPossible(url: String) { val openGroup = try { OpenGroupUrlParser.parseUrl(url) } catch (e: OpenGroupUrlParser.Error) { when (e) { - is OpenGroupUrlParser.Error.MalformedURL -> return Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show() - is OpenGroupUrlParser.Error.InvalidPublicKey -> return Toast.makeText(activity, R.string.invalid_public_key, Toast.LENGTH_SHORT).show() - is OpenGroupUrlParser.Error.NoPublicKey -> return Toast.makeText(activity, R.string.invalid_public_key, Toast.LENGTH_SHORT).show() - is OpenGroupUrlParser.Error.NoRoom -> return Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show() + is OpenGroupUrlParser.Error.MalformedURL, OpenGroupUrlParser.Error.NoRoom -> { + val txt = Phrase.from(context, R.string.groupErrorJoin).put(GROUP_NAME_KEY, url).format().toString() + return Toast.makeText(activity, txt, Toast.LENGTH_SHORT).show() + } + is OpenGroupUrlParser.Error.InvalidPublicKey, OpenGroupUrlParser.Error.NoPublicKey -> { + return Toast.makeText(activity, R.string.communityEnterUrlErrorInvalidDescription, Toast.LENGTH_SHORT).show() + } } } + showLoader() + lifecycleScope.launch(Dispatchers.IO) { try { val sanitizedServer = openGroup.server.removeSuffix("/") @@ -90,10 +99,11 @@ class JoinCommunityFragment : Fragment() { delegate.onDialogClosePressed() } } catch (e: Exception) { - Log.e("Loki", "Couldn't join open group.", e) + Log.e("Loki", "Couldn't join community.", e) withContext(Dispatchers.Main) { hideLoader() - Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show() + val txt = Phrase.from(context, R.string.groupErrorJoin).put(GROUP_NAME_KEY, url).format().toString() + Toast.makeText(activity, txt, Toast.LENGTH_SHORT).show() } return@launch } @@ -107,8 +117,8 @@ class JoinCommunityFragment : Fragment() { ) val mediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, pos -> tab.text = when (pos) { - 0 -> getString(R.string.activity_join_public_chat_enter_community_url_tab_title) - 1 -> getString(R.string.activity_join_public_chat_scan_qr_code_tab_title) + 0 -> getString(R.string.communityUrl) + 1 -> getString(R.string.qrScan) else -> throw IllegalStateException() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index 8bb7a39d4a..b8f3ba8012 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -1,18 +1,20 @@ package org.thoughtcrime.securesms.groups import android.content.Context +import android.widget.Toast import androidx.annotation.WorkerThread -import okhttp3.HttpUrl +import com.squareup.phrase.Phrase +import java.util.concurrent.Executors +import network.loki.messenger.R import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.open_groups.GroupMemberRole import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller +import org.session.libsession.utilities.StringSubstitutionConstants.COMMUNITY_NAME_KEY import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities -import java.util.concurrent.Executors object OpenGroupManager { private val executorService = Executors.newScheduledThreadPool(4) @@ -111,35 +113,43 @@ object OpenGroupManager { @WorkerThread fun delete(server: String, room: String, context: Context) { - val storage = MessagingModuleConfiguration.shared.storage - val configFactory = MessagingModuleConfiguration.shared.configFactory - val threadDB = DatabaseComponent.get(context).threadDatabase() - val openGroupID = "${server.removeSuffix("/")}.$room" - val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) - val recipient = threadDB.getRecipientForThreadId(threadID) ?: return - threadDB.setThreadArchived(threadID) - val groupID = recipient.address.serialize() - // Stop the poller if needed - val openGroups = storage.getAllOpenGroups().filter { it.value.server == server } - if (openGroups.isNotEmpty()) { - synchronized(pollUpdaterLock) { - val poller = pollers[server] - poller?.stop() - pollers.remove(server) + try { + val storage = MessagingModuleConfiguration.shared.storage + val configFactory = MessagingModuleConfiguration.shared.configFactory + val threadDB = DatabaseComponent.get(context).threadDatabase() + val openGroupID = "${server.removeSuffix("/")}.$room" + val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) + val recipient = threadDB.getRecipientForThreadId(threadID) ?: return + threadDB.setThreadArchived(threadID) + val groupID = recipient.address.serialize() + // Stop the poller if needed + val openGroups = storage.getAllOpenGroups().filter { it.value.server == server } + if (openGroups.isNotEmpty()) { + synchronized(pollUpdaterLock) { + val poller = pollers[server] + poller?.stop() + pollers.remove(server) + } } + configFactory.userGroups?.eraseCommunity(server, room) + configFactory.convoVolatile?.eraseCommunity(server, room) + // Delete + storage.removeLastDeletionServerID(room, server) + storage.removeLastMessageServerID(room, server) + storage.removeLastInboxMessageId(server) + storage.removeLastOutboxMessageId(server) + val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase() + lokiThreadDB.removeOpenGroupChat(threadID) + storage.deleteConversation(threadID) // Must be invoked on a background thread + GroupManager.deleteGroup(groupID, context) // Must be invoked on a background thread + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) + } + catch (e: Exception) { + Log.e("Loki", "Failed to leave (delete) community", e) + val serverAndRoom = "$server.$room" + val txt = Phrase.from(context, R.string.communityLeaveError).put(COMMUNITY_NAME_KEY, serverAndRoom).format().toString() + Toast.makeText(context, txt, Toast.LENGTH_LONG).show() } - configFactory.userGroups?.eraseCommunity(server, room) - configFactory.convoVolatile?.eraseCommunity(server, room) - // Delete - storage.removeLastDeletionServerID(room, server) - storage.removeLastMessageServerID(room, server) - storage.removeLastInboxMessageId(server) - storage.removeLastOutboxMessageId(server) - val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase() - lokiThreadDB.removeOpenGroupChat(threadID) - storage.deleteConversation(threadID) // Must be invoked on a background thread - GroupManager.deleteGroup(groupID, context) // Must be invoked on a background thread - ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) } @WorkerThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 68aea84417..d87941fcc1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -136,7 +136,7 @@ class ConversationView : LinearLayout { } private fun getTitle(recipient: Recipient): String? = when { - recipient.isLocalNumber -> context.getString(R.string.note_to_self) + recipient.isLocalNumber -> context.getString(R.string.noteToSelf) else -> recipient.toShortString() // Internally uses the Contact API } @@ -147,7 +147,7 @@ class ConversationView : LinearLayout { private fun ThreadRecord.getSnippetPrefix(): CharSequence? = when { recipient.isLocalNumber || lastMessage?.isControlMessage == true -> null - lastMessage?.isOutgoing == true -> resources.getString(R.string.MessageRecord_you) + lastMessage?.isOutgoing == true -> resources.getString(R.string.you) else -> lastMessage?.individualRecipient?.toShortString() } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt index aa4e0d9017..7b0a1bd5a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/EmptyView.kt @@ -9,20 +9,23 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.thoughtcrime.securesms.ui.Divider +import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions +import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors -import org.thoughtcrime.securesms.ui.theme.LocalColors -import org.thoughtcrime.securesms.ui.theme.LocalType @Composable internal fun EmptyView(newAccount: Boolean) { @@ -44,7 +47,10 @@ internal fun EmptyView(newAccount: Boolean) { textAlign = TextAlign.Center ) Text( - stringResource(R.string.welcome_to_session), + stringResource(R.string.onboardingBubbleWelcomeToSession).let { txt -> + val c = LocalContext.current + Phrase.from(txt).put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString() + }, style = LocalType.current.base, color = LocalColors.current.primary, textAlign = TextAlign.Center diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index ee82a708c4..d1afd5b9ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -10,6 +10,8 @@ import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.activity.viewModels +import androidx.annotation.PluralsRes +import androidx.annotation.StringRes import androidx.core.os.bundleOf import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -17,6 +19,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest @@ -42,6 +45,9 @@ import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.toHexString +import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.start.StartConversationFragment @@ -62,8 +68,8 @@ import org.thoughtcrime.securesms.home.search.GlobalSearchInputLayout import org.thoughtcrime.securesms.home.search.GlobalSearchResult import org.thoughtcrime.securesms.home.search.GlobalSearchViewModel import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity -import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.preferences.SettingsActivity @@ -73,11 +79,14 @@ import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.ui.setThemedContent import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.IP2Country +import org.thoughtcrime.securesms.util.RelativeDay import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.start import java.io.IOException +import java.util.Calendar +import java.util.Locale import javax.inject.Inject @AndroidEntryPoint @@ -85,13 +94,15 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickListener, GlobalSearchInputLayout.GlobalSearchInputLayoutListener { + private val TAG = "HomeActivity" + companion object { const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT" const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING" } private lateinit var binding: ActivityHomeBinding - private lateinit var glide: GlideRequests + private lateinit var glide: RequestManager @Inject lateinit var threadDb: ThreadDatabase @Inject lateinit var mmsSmsDatabase: MmsSmsDatabase @@ -143,6 +154,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) + // Bail if there is already an instance of the application running if (!isTaskRoot) { finish(); return } // Set content view @@ -151,7 +163,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Set custom toolbar setSupportActionBar(binding.toolbar) // Set up Glide - glide = GlideApp.with(this) + glide = Glide.with(this) // Set up toolbar buttons binding.profileButton.setOnClickListener { openSettings() } binding.searchViewContainer.setOnClickListener { @@ -247,17 +259,17 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), globalSearchViewModel.result.map { result -> result.query to when { result.query.isEmpty() -> buildList { - add(GlobalSearchAdapter.Model.Header(R.string.contacts)) + add(GlobalSearchAdapter.Model.Header(R.string.contactContacts)) add(GlobalSearchAdapter.Model.SavedMessages(publicKey)) addAll(result.groupedContacts) } else -> buildList { result.contactAndGroupList.takeUnless { it.isEmpty() }?.let { - add(GlobalSearchAdapter.Model.Header(R.string.contacts)) + add(GlobalSearchAdapter.Model.Header(R.string.contactContacts)) addAll(it) } result.messageResults.takeUnless { it.isEmpty() }?.let { - add(GlobalSearchAdapter.Model.Header(R.string.global_search_messages)) + add(GlobalSearchAdapter.Model.Header(R.string.messages)) addAll(it) } } @@ -434,7 +446,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), val clip = ClipData.newPlainText("Account ID", thread.recipient.address.toString()) val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(clip) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() } else if (thread.recipient.isCommunityRecipient) { val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) @@ -443,7 +455,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), val clip = ClipData.newPlainText("Community URL", openGroup.joinURL) val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager manager.setPrimaryClip(clip) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() } } bottomSheet.onBlockTapped = { @@ -489,9 +501,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), private fun blockConversation(thread: ThreadRecord) { showSessionDialog { - title(R.string.RecipientPreferenceActivity_block_this_contact_question) - text(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact) - button(R.string.RecipientPreferenceActivity_block) { + title(R.string.block) + text(Phrase.from(context, R.string.blockDescription) + .put(NAME_KEY, thread.recipient.name) + .format()) + button(R.string.block) { lifecycleScope.launch(Dispatchers.IO) { storage.setBlocked(listOf(thread.recipient), true) @@ -499,6 +513,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), binding.recyclerView.adapter!!.notifyDataSetChanged() } } + // Block confirmation toast added as per SS-64 + val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, thread.recipient.name).format().toString() + Toast.makeText(context, txt, Toast.LENGTH_LONG).show() } cancelButton() } @@ -506,16 +523,18 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), private fun unblockConversation(thread: ThreadRecord) { showSessionDialog { - title(R.string.RecipientPreferenceActivity_unblock_this_contact_question) - text(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact) - button(R.string.RecipientPreferenceActivity_unblock) { + title(R.string.blockUnblock) + text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.name).format()) + button(R.string.blockUnblock) { lifecycleScope.launch(Dispatchers.IO) { storage.setBlocked(listOf(thread.recipient), false) - withContext(Dispatchers.Main) { binding.recyclerView.adapter!!.notifyDataSetChanged() } } + // Unblock confirmation toast added as per SS-64 + val txt = Phrase.from(context, R.string.blockUnblockedUser).put(NAME_KEY, thread.recipient.name).format().toString() + Toast.makeText(context, txt, Toast.LENGTH_LONG).show() } cancelButton() } @@ -566,18 +585,42 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), private fun deleteConversation(thread: ThreadRecord) { val threadID = thread.threadId val recipient = thread.recipient - val message = if (recipient.isGroupRecipient) { + val title: String + val message: CharSequence + + if (recipient.isGroupRecipient) { val group = groupDatabase.getGroup(recipient.address.toString()).orNull() + + // If you are an admin of this group you can delete it if (group != null && group.admins.map { it.toString() }.contains(textSecurePreferences.getLocalNumber())) { - getString(R.string.admin_group_leave_warning) + title = getString(R.string.groupDelete) + message = Phrase.from(this.applicationContext, R.string.groupDeleteDescription) + .put(GROUP_NAME_KEY, group.title) + .format() } else { - resources.getString(R.string.activity_home_leave_group_dialog_message) + // Otherwise this is either a community, or it's a group you're not an admin of + title = if (recipient.isCommunityRecipient) getString(R.string.communityLeave) else getString(R.string.groupLeave) + message = Phrase.from(this.applicationContext, R.string.groupLeaveDescription) + .put(GROUP_NAME_KEY, group.title) + .format() } } else { - resources.getString(R.string.activity_home_delete_conversation_dialog_message) + // If this is a 1-on-1 conversation + if (recipient.name != null) { + title = getString(R.string.conversationsDelete) + message = Phrase.from(this.applicationContext, R.string.conversationsDeleteDescription) + .put(NAME_KEY, recipient.name) + .format() + } + else { + // If not group-related and we don't have a recipient name then this must be our Note to Self conversation + title = getString(R.string.noteToSelf) + message = getString(R.string.clearMessagesNoteToSelfDescription) + } } showSessionDialog { + title(title) text(message) button(R.string.yes) { lifecycleScope.launch(Dispatchers.Main) { @@ -590,7 +633,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString() .takeIf(DatabaseComponent.get(context).lokiAPIDatabase()::isClosedGroup) ?.let { MessageSender.explicitLeave(it, false) } - } catch (_: IOException) { + } catch (ioe: IOException) { + Log.w(TAG, "Got an IOException while sending leave group message") } } // Delete the conversation @@ -604,8 +648,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } // Update the badge count ApplicationContext.getInstance(context).messageNotifier.updateNotification(context) + // Notify the user - val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message + val toastMessage = if (recipient.isGroupRecipient) R.string.groupMemberYouLeft else R.string.conversationsDeleted Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show() } } @@ -625,7 +670,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), private fun hideMessageRequests() { showSessionDialog { - text(getString(R.string.hide_message_requests)) + text(getString(R.string.hide)) button(R.string.yes) { textSecurePreferences.setHasHiddenMessageRequests() homeViewModel.tryReload() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index 5410df9d47..47b6252165 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -7,10 +7,10 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_ID +import com.bumptech.glide.RequestManager import network.loki.messenger.R import network.loki.messenger.databinding.ViewMessageRequestBannerBinding import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.mms.GlideRequests class HomeAdapter( private val context: Context, @@ -74,7 +74,7 @@ class HomeAdapter( return data.threads[offsetPosition].threadId } - lateinit var glide: GlideRequests + lateinit var glide: RequestManager override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = when (viewType) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt index db0c4d11cc..191a92b800 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt @@ -16,6 +16,7 @@ import android.widget.TextView import android.widget.Toast import androidx.annotation.ColorRes import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.squareup.phrase.Phrase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job @@ -26,6 +27,7 @@ import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.databinding.ActivityPathBinding import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.getColorFromAttr import org.session.libsignal.utilities.Snode import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity @@ -49,7 +51,13 @@ class PathActivity : PassphraseRequiredActionBarActivity() { super.onCreate(savedInstanceState, isReady) binding = ActivityPathBinding.inflate(layoutInflater) setContentView(binding.root) - supportActionBar!!.title = resources.getString(R.string.activity_path_title) + supportActionBar!!.title = resources.getString(R.string.onionRoutingPath) + + // Substitute the localised version of "Session" into the layout + val appName = applicationContext.getString(R.string.app_name) + val txt = Phrase.from(applicationContext, R.string.onionRoutingPathDescription).put(APP_NAME_KEY, appName).format().toString() + binding.pathDescription.text = txt + binding.pathRowsContainer.disableClipping() binding.learnMoreButton.setOnClickListener { learnMore() } update(false) @@ -98,6 +106,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() { private fun update(isAnimated: Boolean) { binding.pathRowsContainer.removeAllViews() + if (OnionRequestAPI.paths.isNotEmpty()) { val path = OnionRequestAPI.paths.firstOrNull() ?: return finish() val dotAnimationRepeatInterval = path.count().toLong() * 1000 + 1000 @@ -105,8 +114,8 @@ class PathActivity : PassphraseRequiredActionBarActivity() { val isGuardSnode = (OnionRequestAPI.guardSnodes.contains(snode)) getPathRow(snode, LineView.Location.Middle, index.toLong() * 1000 + 2000, dotAnimationRepeatInterval, isGuardSnode) } - val youRow = getPathRow(resources.getString(R.string.activity_path_device_row_title), null, LineView.Location.Top, 1000, dotAnimationRepeatInterval) - val destinationRow = getPathRow(resources.getString(R.string.activity_path_destination_row_title), null, LineView.Location.Bottom, path.count().toLong() * 1000 + 2000, dotAnimationRepeatInterval) + val youRow = getPathRow(resources.getString(R.string.onionRoutingPath), null, LineView.Location.Top, 1000, dotAnimationRepeatInterval) + val destinationRow = getPathRow(resources.getString(R.string.onionRoutingPathDestination), null, LineView.Location.Bottom, path.count().toLong() * 1000 + 2000, dotAnimationRepeatInterval) val rows = listOf( youRow ) + pathRows + listOf( destinationRow ) for (row in rows) { binding.pathRowsContainer.addView(row) @@ -162,11 +171,11 @@ class PathActivity : PassphraseRequiredActionBarActivity() { } private fun getPathRow(snode: Snode, location: LineView.Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long, isGuardSnode: Boolean): LinearLayout { - val title = if (isGuardSnode) resources.getString(R.string.activity_path_guard_node_row_title) else resources.getString(R.string.activity_path_service_node_row_title) + val title = if (isGuardSnode) resources.getString(R.string.onionRoutingPathEntryNode) else resources.getString(R.string.onionRoutingPathServiceNode) val subtitle = if (IP2Country.isInitialized) { - IP2Country.shared.countryNamesCache[snode.ip] ?: resources.getString(R.string.activity_path_resolving_progress) + IP2Country.shared.countryNamesCache[snode.ip] ?: resources.getString(R.string.resolving) } else { - resources.getString(R.string.activity_path_resolving_progress) + resources.getString(R.string.resolving) } return getPathRow(title, subtitle, location, dotAnimationStartDelay, dotAnimationRepeatInterval) } @@ -179,7 +188,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(intent) } catch (e: Exception) { - Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.communityEnterUrlErrorInvalid, Toast.LENGTH_SHORT).show() } } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt b/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt index 33bdd2f2f6..02448722cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/SeedReminder.kt @@ -18,15 +18,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import network.loki.messenger.R -import org.thoughtcrime.securesms.ui.theme.LocalDimensions -import org.thoughtcrime.securesms.ui.theme.PreviewTheme -import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.SessionShieldIcon -import org.thoughtcrime.securesms.ui.theme.ThemeColors -import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.components.SlimPrimaryOutlineButton import org.thoughtcrime.securesms.ui.contentDescription +import org.thoughtcrime.securesms.ui.theme.LocalColors +import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType +import org.thoughtcrime.securesms.ui.theme.PreviewTheme +import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider +import org.thoughtcrime.securesms.ui.theme.ThemeColors @Composable internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) { @@ -49,20 +49,20 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) { Column(Modifier.weight(1f)) { Row { Text( - stringResource(R.string.save_your_recovery_password), + stringResource(R.string.recoveryPasswordBannerTitle), style = LocalType.current.h8 ) Spacer(Modifier.requiredWidth(LocalDimensions.current.xxsSpacing)) SessionShieldIcon() } Text( - stringResource(R.string.save_your_recovery_password_to_make_sure_you_don_t_lose_access_to_your_account), + stringResource(R.string.recoveryPasswordBannerDescription), style = LocalType.current.small ) } Spacer(Modifier.width(LocalDimensions.current.xsSpacing)) SlimPrimaryOutlineButton( - text = stringResource(R.string.continue_2), + text = stringResource(R.string.theContinue), modifier = Modifier .align(Alignment.CenterVertically) .contentDescription(R.string.AccessibilityId_reveal_recovery_phrase_button), @@ -78,6 +78,6 @@ private fun PreviewSeedReminder( @PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors ) { PreviewTheme(colors) { - SeedReminder {} + SeedReminder { } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index cae399dcbf..d5fad90c4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -100,7 +100,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("Account ID", publicKey) clipboard.setPrimaryClip(clip) - Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT) + Toast.makeText(requireContext(), R.string.copied, Toast.LENGTH_SHORT) .show() true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index d390776d1c..947edc3d8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -6,12 +6,14 @@ import android.text.SpannableStringBuilder import android.text.style.StyleSpan import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil +import java.util.Locale import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView +import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Contact as ContactModel import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.GroupConversation import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Header import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Message @@ -19,9 +21,6 @@ import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SavedMes import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SubHeader import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.SearchUtil -import java.util.Locale -import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Contact as ContactModel - class GlobalSearchDiff( private val oldQuery: String?, @@ -78,8 +77,8 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) { } binding.searchResultSubtitle.text = getHighlight(query, membersString) } - is Header, // do nothing for header - is SubHeader, // do nothing for subheader + is Header, // do nothing for header + is SubHeader, // do nothing for subheader is SavedMessages -> Unit // do nothing for saved messages (displays note to self) } } @@ -112,7 +111,7 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run { searchResultSubtitle.text = null val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false) searchResultProfilePicture.update(recipient) - val nameString = if (model.isSelf) root.context.getString(R.string.note_to_self) + val nameString = if (model.isSelf) root.context.getString(R.string.noteToSelf) else model.contact.getSearchName() searchResultTitle.text = getHighlight(query, nameString) } @@ -120,7 +119,7 @@ fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run { fun ContentView.bindModel(model: SavedMessages) { binding.searchResultSubtitle.isVisible = false binding.searchResultTimestamp.isVisible = false - binding.searchResultTitle.setText(R.string.note_to_self) + binding.searchResultTitle.setText(R.string.noteToSelf) binding.searchResultProfilePicture.update(Address.fromSerialized(model.currentUserPublicKey)) binding.searchResultProfilePicture.isVisible = true } @@ -128,11 +127,13 @@ fun ContentView.bindModel(model: SavedMessages) { fun ContentView.bindModel(query: String?, model: Message) = binding.apply { searchResultProfilePicture.isVisible = true searchResultTimestamp.isVisible = true + // val hasUnreads = model.unread > 0 // unreadCountIndicator.isVisible = hasUnreads // if (hasUnreads) { // unreadCountTextView.text = model.unread.toString() // } + searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) searchResultProfilePicture.update(model.messageResult.conversationRecipient) val textSpannable = SpannableStringBuilder() @@ -146,7 +147,7 @@ fun ContentView.bindModel(query: String?, model: Message) = binding.apply { model.messageResult.bodySnippet )) searchResultSubtitle.text = textSpannable - searchResultTitle.text = if (model.isSelf) root.context.getString(R.string.note_to_self) + searchResultTitle.text = if (model.isSelf) root.context.getString(R.string.noteToSelf) else model.messageResult.conversationRecipient.getSearchName() searchResultSubtitle.isVisible = true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java index 807c4548a7..4a3cecf9cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java @@ -1,9 +1,12 @@ package org.thoughtcrime.securesms.linkpreview; +import static org.thoughtcrime.securesms.giph.util.InfiniteScrollListener.TAG; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.annotation.SuppressLint; +import android.os.Build; import android.text.Html; import android.text.SpannableString; import android.text.TextUtils; @@ -12,205 +15,230 @@ import android.text.util.Linkify; import com.annimon.stream.Stream; +import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.util.DateUtils; import org.session.libsignal.utilities.guava.Optional; import org.session.libsession.utilities.Util; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import android.text.format.DateFormat; + import okhttp3.HttpUrl; public final class LinkPreviewUtil { - private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$", Pattern.CASE_INSENSITIVE); - private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$", Pattern.CASE_INSENSITIVE); - private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$", Pattern.CASE_INSENSITIVE); - private static final Pattern OPEN_GRAPH_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE); - private static final Pattern ARTICLE_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE); - private static final Pattern OPEN_GRAPH_CONTENT_PATTERN = Pattern.compile("content\\s*=\\s*\"([^\"]*)\"", Pattern.CASE_INSENSITIVE); - private static final Pattern TITLE_PATTERN = Pattern.compile("<\\s*title[^>]*>(.*)<\\s*/title[^>]*>", Pattern.CASE_INSENSITIVE); - private static final Pattern FAVICON_PATTERN = Pattern.compile("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>", Pattern.CASE_INSENSITIVE); - private static final Pattern FAVICON_HREF_PATTERN = Pattern.compile("href\\s*=\\s*\"([^\"]*)\"", Pattern.CASE_INSENSITIVE); + private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$", Pattern.CASE_INSENSITIVE); + private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$", Pattern.CASE_INSENSITIVE); + private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$", Pattern.CASE_INSENSITIVE); + private static final Pattern OPEN_GRAPH_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*og:([^\"]+)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE); + private static final Pattern ARTICLE_TAG_PATTERN = Pattern.compile("<\\s*meta[^>]*property\\s*=\\s*\"\\s*article:([^\"]+)\"[^>]*/?\\s*>", Pattern.CASE_INSENSITIVE); + private static final Pattern OPEN_GRAPH_CONTENT_PATTERN = Pattern.compile("content\\s*=\\s*\"([^\"]*)\"", Pattern.CASE_INSENSITIVE); + private static final Pattern TITLE_PATTERN = Pattern.compile("<\\s*title[^>]*>(.*)<\\s*/title[^>]*>", Pattern.CASE_INSENSITIVE); + private static final Pattern FAVICON_PATTERN = Pattern.compile("<\\s*link[^>]*rel\\s*=\\s*\".*icon.*\"[^>]*>", Pattern.CASE_INSENSITIVE); + private static final Pattern FAVICON_HREF_PATTERN = Pattern.compile("href\\s*=\\s*\"([^\"]*)\"", Pattern.CASE_INSENSITIVE); - /** - * @return All whitelisted URLs in the source text. - */ - public static @NonNull List findWhitelistedUrls(@NonNull String text) { - SpannableString spannable = new SpannableString(text); - boolean found = Linkify.addLinks(spannable, Linkify.WEB_URLS); + /** + * @return All whitelisted URLs in the source text. + */ + public static @NonNull List findWhitelistedUrls(@NonNull String text) { + SpannableString spannable = new SpannableString(text); + boolean found = Linkify.addLinks(spannable, Linkify.WEB_URLS); - if (!found) { - return Collections.emptyList(); - } - - return Stream.of(spannable.getSpans(0, spannable.length(), URLSpan.class)) - .map(span -> new Link(span.getURL(), spannable.getSpanStart(span))) - .filter(link -> isValidLinkUrl(link.getUrl())) - .toList(); - } - - /** - * @return True if the host is valid. - */ - public static boolean isValidLinkUrl(@Nullable String linkUrl) { - if (linkUrl == null) return false; - - HttpUrl url = HttpUrl.parse(linkUrl); - return url != null && - !TextUtils.isEmpty(url.scheme()) && - "https".equals(url.scheme()) && - isLegalUrl(linkUrl); - } - - /** - * @return True if the top-level domain is valid. - */ - public static boolean isValidMediaUrl(@Nullable String mediaUrl) { - if (mediaUrl == null) return false; - - HttpUrl url = HttpUrl.parse(mediaUrl); - return url != null && - !TextUtils.isEmpty(url.scheme()) && - "https".equals(url.scheme()) && - isLegalUrl(mediaUrl); - } - - public static boolean isLegalUrl(@NonNull String url) { - Matcher matcher = DOMAIN_PATTERN.matcher(url); - - if (matcher.matches()) { - String domain = matcher.group(2); - String cleanedDomain = domain.replaceAll("\\.", ""); - - return ALL_ASCII_PATTERN.matcher(cleanedDomain).matches() || - ALL_NON_ASCII_PATTERN.matcher(cleanedDomain).matches(); - } else { - return false; - } - } - - public static boolean isValidMimeType(@NonNull String url) { - String[] validMimeType = {".jpg", ".png", ".gif", ".jpeg"}; - if (url.contains(".")) { - for (String mimeType : validMimeType) { - if (url.contains(mimeType)) { - return true; + if (!found) { + return Collections.emptyList(); } - } - return false; - } - return true; - } - public static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html) { - return parseOpenGraphFields(html, text -> Html.fromHtml(text).toString()); - } - - static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html, @NonNull HtmlDecoder htmlDecoder) { - if (html == null) { - return new OpenGraph(Collections.emptyMap(), null, null); + return Stream.of(spannable.getSpans(0, spannable.length(), URLSpan.class)) + .map(span -> new Link(span.getURL(), spannable.getSpanStart(span))) + .filter(link -> isValidLinkUrl(link.getUrl())) + .toList(); } - Map openGraphTags = new HashMap<>(); - Matcher openGraphMatcher = OPEN_GRAPH_TAG_PATTERN.matcher(html); + /** + * @return True if the host is valid. + */ + public static boolean isValidLinkUrl(@Nullable String linkUrl) { + if (linkUrl == null) return false; - while (openGraphMatcher.find()) { - String tag = openGraphMatcher.group(); - String property = openGraphMatcher.groupCount() > 0 ? openGraphMatcher.group(1) : null; + HttpUrl url = HttpUrl.parse(linkUrl); + return url != null && + !TextUtils.isEmpty(url.scheme()) && + "https".equals(url.scheme()) && + isLegalUrl(linkUrl); + } - if (property != null) { - Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag); - if (contentMatcher.find() && contentMatcher.groupCount() > 0) { - String content = htmlDecoder.fromEncoded(contentMatcher.group(1)); - openGraphTags.put(property.toLowerCase(), content); + /** + * @return True if the top-level domain is valid. + */ + public static boolean isValidMediaUrl(@Nullable String mediaUrl) { + if (mediaUrl == null) return false; + + HttpUrl url = HttpUrl.parse(mediaUrl); + return url != null && + !TextUtils.isEmpty(url.scheme()) && + "https".equals(url.scheme()) && + isLegalUrl(mediaUrl); + } + + public static boolean isLegalUrl(@NonNull String url) { + Matcher matcher = DOMAIN_PATTERN.matcher(url); + + if (matcher.matches()) { + String domain = matcher.group(2); + String cleanedDomain = domain.replaceAll("\\.", ""); + + return ALL_ASCII_PATTERN.matcher(cleanedDomain).matches() || + ALL_NON_ASCII_PATTERN.matcher(cleanedDomain).matches(); + } else { + return false; } - } } - Matcher articleMatcher = ARTICLE_TAG_PATTERN.matcher(html); - - while (articleMatcher.find()) { - String tag = articleMatcher.group(); - String property = articleMatcher.groupCount() > 0 ? articleMatcher.group(1) : null; - - if (property != null) { - Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag); - if (contentMatcher.find() && contentMatcher.groupCount() > 0) { - String content = htmlDecoder.fromEncoded(contentMatcher.group(1)); - openGraphTags.put(property.toLowerCase(), content); + public static boolean isValidMimeType(@NonNull String url) { + String[] validMimeType = {".jpg", ".png", ".gif", ".jpeg"}; + if (url.contains(".")) { + for (String mimeType : validMimeType) { + if (url.contains(mimeType)) { + return true; + } + } + return false; } - } + return true; } - String htmlTitle = ""; - String faviconUrl = ""; - - Matcher titleMatcher = TITLE_PATTERN.matcher(html); - if (titleMatcher.find() && titleMatcher.groupCount() > 0) { - htmlTitle = htmlDecoder.fromEncoded(titleMatcher.group(1)); + public static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html) { + return parseOpenGraphFields(html, text -> Html.fromHtml(text).toString()); } - Matcher faviconMatcher = FAVICON_PATTERN.matcher(html); - if (faviconMatcher.find()) { - Matcher faviconHrefMatcher = FAVICON_HREF_PATTERN.matcher(faviconMatcher.group()); - if (faviconHrefMatcher.find() && faviconHrefMatcher.groupCount() > 0) { - faviconUrl = faviconHrefMatcher.group(1); - } + static @NonNull OpenGraph parseOpenGraphFields(@Nullable String html, @NonNull HtmlDecoder htmlDecoder) { + if (html == null) { + return new OpenGraph(Collections.emptyMap(), null, null); + } + + Map openGraphTags = new HashMap<>(); + Matcher openGraphMatcher = OPEN_GRAPH_TAG_PATTERN.matcher(html); + + while (openGraphMatcher.find()) { + String tag = openGraphMatcher.group(); + String property = openGraphMatcher.groupCount() > 0 ? openGraphMatcher.group(1) : null; + + if (property != null) { + Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag); + if (contentMatcher.find() && contentMatcher.groupCount() > 0) { + String content = htmlDecoder.fromEncoded(contentMatcher.group(1)); + openGraphTags.put(property.toLowerCase(), content); + } + } + } + + Matcher articleMatcher = ARTICLE_TAG_PATTERN.matcher(html); + + while (articleMatcher.find()) { + String tag = articleMatcher.group(); + String property = articleMatcher.groupCount() > 0 ? articleMatcher.group(1) : null; + + if (property != null) { + Matcher contentMatcher = OPEN_GRAPH_CONTENT_PATTERN.matcher(tag); + if (contentMatcher.find() && contentMatcher.groupCount() > 0) { + String content = htmlDecoder.fromEncoded(contentMatcher.group(1)); + openGraphTags.put(property.toLowerCase(), content); + } + } + } + + String htmlTitle = ""; + String faviconUrl = ""; + + Matcher titleMatcher = TITLE_PATTERN.matcher(html); + if (titleMatcher.find() && titleMatcher.groupCount() > 0) { + htmlTitle = htmlDecoder.fromEncoded(titleMatcher.group(1)); + } + + Matcher faviconMatcher = FAVICON_PATTERN.matcher(html); + if (faviconMatcher.find()) { + Matcher faviconHrefMatcher = FAVICON_HREF_PATTERN.matcher(faviconMatcher.group()); + if (faviconHrefMatcher.find() && faviconHrefMatcher.groupCount() > 0) { + faviconUrl = faviconHrefMatcher.group(1); + } + } + + return new OpenGraph(openGraphTags, htmlTitle, faviconUrl); } - return new OpenGraph(openGraphTags, htmlTitle, faviconUrl); - } + public static final class OpenGraph { - public static final class OpenGraph { + private final Map values; - private final Map values; + private final @Nullable String htmlTitle; + private final @Nullable String faviconUrl; - private final @Nullable String htmlTitle; - private final @Nullable String faviconUrl; + private static final String KEY_TITLE = "title"; + private static final String KEY_DESCRIPTION_URL = "description"; + private static final String KEY_IMAGE_URL = "image"; + private static final String KEY_PUBLISHED_TIME_1 = "published_time"; + private static final String KEY_PUBLISHED_TIME_2 = "article:published_time"; + private static final String KEY_MODIFIED_TIME_1 = "modified_time"; + private static final String KEY_MODIFIED_TIME_2 = "article:modified_time"; - private static final String KEY_TITLE = "title"; - private static final String KEY_DESCRIPTION_URL = "description"; - private static final String KEY_IMAGE_URL = "image"; - private static final String KEY_PUBLISHED_TIME_1 = "published_time"; - private static final String KEY_PUBLISHED_TIME_2 = "article:published_time"; - private static final String KEY_MODIFIED_TIME_1 = "modified_time"; - private static final String KEY_MODIFIED_TIME_2 = "article:modified_time"; + public OpenGraph(@NonNull Map values, @Nullable String htmlTitle, @Nullable String faviconUrl) { + this.values = values; + this.htmlTitle = htmlTitle; + this.faviconUrl = faviconUrl; + } - public OpenGraph(@NonNull Map values, @Nullable String htmlTitle, @Nullable String faviconUrl) { - this.values = values; - this.htmlTitle = htmlTitle; - this.faviconUrl = faviconUrl; + public @NonNull Optional getTitle() { + return Optional.of(Util.getFirstNonEmpty(values.get(KEY_TITLE), htmlTitle)); + } + + public @NonNull Optional getImageUrl() { + return Optional.of(Util.getFirstNonEmpty(values.get(KEY_IMAGE_URL), faviconUrl)); + } + + private static long parseISO8601(String date) { + + if (date == null || date.isEmpty()) { return -1L; } + + SimpleDateFormat format; + if (Build.VERSION.SDK_INT >= 24) { + format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault()); + } else { + format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); + } + + try { + return format.parse(date).getTime(); + } catch (ParseException pe) { + Log.w("OpenGraph", "Failed to parse date.", pe); + return -1L; + } + } + + @SuppressLint("ObsoleteSdkInt") + public long getDate() { + return Stream.of(values.get(KEY_PUBLISHED_TIME_1), + values.get(KEY_PUBLISHED_TIME_2), + values.get(KEY_MODIFIED_TIME_1), + values.get(KEY_MODIFIED_TIME_2)) + .map(OpenGraph::parseISO8601) + .filter(time -> time > 0) + .findFirst() + .orElse(0L); + } } - public @NonNull Optional getTitle() { - return Optional.of(Util.getFirstNonEmpty(values.get(KEY_TITLE), htmlTitle)); + public interface HtmlDecoder { + @NonNull String fromEncoded(@NonNull String html); } - public @NonNull Optional getImageUrl() { - return Optional.of(Util.getFirstNonEmpty(values.get(KEY_IMAGE_URL), faviconUrl)); - } - - @SuppressLint("ObsoleteSdkInt") - public long getDate() { - return Stream.of(values.get(KEY_PUBLISHED_TIME_1), - values.get(KEY_PUBLISHED_TIME_2), - values.get(KEY_MODIFIED_TIME_1), - values.get(KEY_MODIFIED_TIME_2)) - .map(DateUtils::parseIso8601) - .filter(time -> time > 0) - .findFirst() - .orElse(0L); - } - } - - public interface HtmlDecoder { - @NonNull String fromEncoded(@NonNull String html); - } - -} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java index 6492069780..e695274847 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java @@ -14,7 +14,7 @@ import network.loki.messenger.R; import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView; import org.thoughtcrime.securesms.mediasend.Media; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.util.StableIdGenerator; import java.util.ArrayList; @@ -25,7 +25,7 @@ public class MediaRailAdapter extends RecyclerView.Adapter media; private final RailItemListener listener; private final boolean editable; @@ -34,7 +34,7 @@ public class MediaRailAdapter extends RecyclerView.Adapter(); this.listener = listener; @@ -148,7 +148,7 @@ public class MediaRailAdapter extends RecyclerView.Adapter transformation = frontFacing ? new MultiTransformation<>(new CenterCrop(), new FlipTransformation()) : new CenterCrop(); - GlideApp.with(this) + Glide.with(this) .asBitmap() .load(jpegData) .transform(transformation) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderAdapter.java index 778883849b..1973ad1700 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerFolderAdapter.java @@ -14,18 +14,18 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import network.loki.messenger.R; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.RequestManager; import java.util.ArrayList; import java.util.List; class MediaPickerFolderAdapter extends RecyclerView.Adapter { - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final EventListener eventListener; private final List folders; - MediaPickerFolderAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) { + MediaPickerFolderAdapter(@NonNull RequestManager glideRequests, @NonNull EventListener eventListener) { this.glideRequests = glideRequests; this.eventListener = eventListener; this.folders = new ArrayList<>(); @@ -74,7 +74,7 @@ class MediaPickerFolderAdapter extends RecyclerView.Adapter { - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final EventListener eventListener; private final List media; private final List selected; @@ -33,7 +33,7 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter(); @@ -109,7 +109,7 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter selected, int maxSelection, @NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) { + void bind(@NonNull Media media, boolean multiSelect, List selected, int maxSelection, @NonNull RequestManager glideRequests, @NonNull EventListener eventListener) { glideRequests.load(media.getUri()) .diskCacheStrategy(DiskCacheStrategy.NONE) .transition(DrawableTransitionOptions.withCrossFade()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java index 9e3db73bfc..b1c104e32e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java @@ -21,7 +21,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Toast; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.session.libsession.utilities.Util; import java.util.ArrayList; @@ -91,7 +91,7 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem RecyclerView imageList = view.findViewById(R.id.mediapicker_item_list); - adapter = new MediaPickerItemAdapter(GlideApp.with(this), this, maxSelection); + adapter = new MediaPickerItemAdapter(Glide.with(this), this, maxSelection); layoutManager = new GridLayoutManager(requireContext(), 4); imageList.setLayoutManager(layoutManager); @@ -163,7 +163,7 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem @Override public void onMediaSelectionOverflow(int maxSelection) { - Toast.makeText(requireContext(), getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), getString(R.string.attachmentsErrorNumber), Toast.LENGTH_SHORT).show(); } private void initToolbar(Toolbar toolbar) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java index 76c6b4355d..00c5c5cf00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java @@ -95,7 +95,7 @@ class MediaRepository { Uri allMediaThumbnail = imageFolders.getThumbnailTimestamp() > videoFolders.getThumbnailTimestamp() ? imageFolders.getThumbnail() : videoFolders.getThumbnail(); if (allMediaThumbnail != null) { int allMediaCount = Stream.of(mediaFolders).reduce(0, (count, folder) -> count + folder.getItemCount()); - mediaFolders.add(0, new MediaFolder(allMediaThumbnail, context.getString(R.string.MediaRepository_all_media), allMediaCount, Media.ALL_MEDIA_BUCKET_ID)); + mediaFolders.add(0, new MediaFolder(allMediaThumbnail, context.getString(R.string.conversationsSettingsAllMedia), allMediaCount, Media.ALL_MEDIA_BUCKET_ID)); } return mediaFolders; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 3e9fd7905e..13783729fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.mediasend; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.Manifest; import android.content.Context; import android.content.Intent; @@ -20,6 +22,8 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; +import com.squareup.phrase.Phrase; + import org.session.libsession.utilities.Address; import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.Util; @@ -159,7 +163,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple int maxSelection = viewModel.getMaxSelection(); if (viewModel.getSelectedMedia().getValue() != null && viewModel.getSelectedMedia().getValue().size() >= maxSelection) { - Toast.makeText(this, getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(R.string.attachmentsErrorNumber), Toast.LENGTH_SHORT).show(); } else { navigateToCamera(); } @@ -252,7 +256,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple @Override public void onCameraError() { - Toast.makeText(this, R.string.MediaSendActivity_camera_unavailable, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.cameraErrorUnavailable, Toast.LENGTH_SHORT).show(); setResult(RESULT_CANCELED, new Intent()); finish(); } @@ -328,11 +332,12 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple switch (error) { case ITEM_TOO_LARGE: - Toast.makeText(this, R.string.MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit, Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.attachmentsErrorSize, Toast.LENGTH_LONG).show(); break; case TOO_MANY_ITEMS: - int maxSelection = viewModel.getMaxSelection(); - Toast.makeText(this, getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show(); + // In modern session we'll say you can't sent more than 32 items, but if we ever want + // the exact count of how many items the user attempted to send it's: viewModel.getMaxSelection() + Toast.makeText(this, getString(R.string.attachmentsErrorNumber), Toast.LENGTH_SHORT).show(); break; } }); @@ -355,10 +360,19 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple } private void navigateToCamera() { + + Context c = getApplicationContext(); + String permanentDenialTxt = Phrase.from(c, R.string.cameraGrantAccessDenied) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + String requireCameraPermissionsTxt = Phrase.from(c, R.string.cameraGrantAccessDescription) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + Permissions.with(this) .request(Manifest.permission.CAMERA) - .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) + .withRationaleDialog(requireCameraPermissionsTxt, R.drawable.ic_baseline_photo_camera_48) + .withPermanentDenialDialog(permanentDenialTxt) .onAllGranted(() -> { Camera1Fragment fragment = getOrCreateCameraFragment(); getSupportFragmentManager().beginTransaction() @@ -367,7 +381,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple .addToBackStack(null) .commit(); }) - .onAnyDenied(() -> Toast.makeText(MediaSendActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) + .onAnyDenied(() -> Toast.makeText(MediaSendActivity.this, requireCameraPermissionsTxt, Toast.LENGTH_LONG).show()) .execute(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java index cba1529a51..169ac83ead 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java @@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.util.SimpleTextWatcher; import org.thoughtcrime.securesms.imageeditor.model.EditorModel; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.thoughtcrime.securesms.providers.BlobProvider; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.scribbles.ImageEditorFragment; @@ -187,7 +187,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl fragmentPager.addOnPageChangeListener(pageChangeListener); fragmentPager.post(() -> pageChangeListener.onPageSelected(fragmentPager.getCurrentItem())); - mediaRailAdapter = new MediaRailAdapter(GlideApp.with(this), this, true); + mediaRailAdapter = new MediaRailAdapter(Glide.with(this), this, true); mediaRail.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)); mediaRail.setAdapter(mediaRailAdapter); @@ -208,7 +208,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl String displayName = Optional.fromNullable(recipient.getName()) .or(Optional.fromNullable(recipient.getProfileName()) .or(recipient.getAddress().serialize())); - composeText.setHint(getString(R.string.MediaSendActivity_message_to_s, displayName), null); + composeText.setHint(getString(R.string.message, displayName), null); composeText.setOnEditorActionListener((v, actionId, event) -> { boolean isSend = actionId == EditorInfo.IME_ACTION_SEND; if (isSend) sendButton.performClick(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java index fec59e0705..41293c6256 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendGifFragment.java @@ -12,7 +12,7 @@ import android.widget.ImageView; import network.loki.messenger.R; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; public class MediaSendGifFragment extends Fragment implements MediaSendPageFragment { @@ -40,7 +40,7 @@ public class MediaSendGifFragment extends Fragment implements MediaSendPageFragm super.onViewCreated(view, savedInstanceState); uri = getArguments().getParcelable(KEY_URI); - GlideApp.with(this).load(new DecryptableStreamUriLoader.DecryptableUri(uri)).into((ImageView) view); + Glide.with(this).load(new DecryptableStreamUriLoader.DecryptableUri(uri)).into((ImageView) view); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 6584f4e519..2faac58487 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -11,7 +11,7 @@ import network.loki.messenger.databinding.ViewMessageRequestBinding import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.model.ThreadRecord -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.util.DateUtils import java.util.Locale @@ -32,7 +32,7 @@ class MessageRequestView : LinearLayout { // endregion // region Updating - fun bind(thread: ThreadRecord, glide: GlideRequests) { + fun bind(thread: ThreadRecord, glide: RequestManager) { this.thread = thread val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() @@ -59,7 +59,7 @@ class MessageRequestView : LinearLayout { private fun getUserDisplayName(recipient: Recipient): String? { return if (recipient.isLocalNumber) { - context.getString(R.string.note_to_self) + context.getString(R.string.noteToSelf) } else { recipient.name // Internally uses the Contact API } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index caecbcd87d..8ad223e897 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -8,27 +8,29 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.loader.app.LoaderManager import androidx.loader.content.Loader +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ActivityMessageRequestsBinding +import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord -import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.push -import javax.inject.Inject @AndroidEntryPoint class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), ConversationClickListener, LoaderManager.LoaderCallbacks { private lateinit var binding: ActivityMessageRequestsBinding - private lateinit var glide: GlideRequests + private lateinit var glide: RequestManager @Inject lateinit var threadDb: ThreadDatabase @@ -43,7 +45,7 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat binding = ActivityMessageRequestsBinding.inflate(layoutInflater) setContentView(binding.root) - glide = GlideApp.with(this) + glide = Glide.with(this) adapter.setHasStableIds(true) adapter.glide = glide @@ -83,10 +85,12 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat } showSessionDialog { - title(R.string.RecipientPreferenceActivity_block_this_contact_question) - text(R.string.message_requests_block_message) - button(R.string.recipient_preferences__block) { doBlock() } - button(R.string.no) + title(R.string.block) + text(Phrase.from(context, R.string.blockDescription) + .put(NAME_KEY, thread.recipient.name) + .format()) + button(R.string.block) { doBlock() } + button(R.string.no) } } @@ -100,10 +104,10 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat } showSessionDialog { - title(R.string.decline) - text(resources.getString(R.string.message_requests_decline_message)) - button(R.string.decline) { doDecline() } - button(R.string.no) + title(R.string.delete) + text(resources.getString(R.string.messageRequestsDelete)) + button(R.string.delete) { doDecline() } + button(R.string.cancel) } } @@ -123,7 +127,7 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat } showSessionDialog { - text(resources.getString(R.string.message_requests_clear_all_message)) + text(resources.getString(R.string.messageRequestsClearAllExplanation)) button(R.string.yes) { doDeleteAllAndBlock() } button(R.string.no) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt index 3040bb774f..36e51e35be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt @@ -14,7 +14,7 @@ import org.session.libsession.utilities.ThemeUtil import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.GlideRequests +import com.bumptech.glide.RequestManager class MessageRequestsAdapter( context: Context, @@ -22,7 +22,7 @@ class MessageRequestsAdapter( val listener: ConversationClickListener ) : CursorRecyclerViewAdapter(context, cursor) { private val threadDatabase = DatabaseComponent.get(context).threadDatabase() - lateinit var glide: GlideRequests + lateinit var glide: RequestManager class ViewHolder(val view: MessageRequestView) : RecyclerView.ViewHolder(view) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java index 3d45e6a6e7..de3d1c3925 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -69,7 +69,7 @@ public class AudioSlide extends Slide { @NonNull @Override public String getContentDescription() { - return context.getString(R.string.Slide_audio); + return context.getString(R.string.audio); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/ImageSlide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/ImageSlide.java index 87d66743c0..042486dc36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -62,6 +62,6 @@ public class ImageSlide extends Slide { @NonNull @Override public String getContentDescription() { - return context.getString(R.string.Slide_image); + return context.getString(R.string.image); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt index e284c1ce22..bc870f07d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt @@ -49,7 +49,7 @@ abstract class Slide(@JvmField protected val context: Context, protected val att // A missing file name is the legacy way to determine if an audio attachment is // a voice note vs. other arbitrary audio attachments. if (attachment.isVoiceNote || attachment.fileName.isNullOrEmpty()) { - val baseString = context.getString(R.string.attachment_type_voice_message) + val baseString = context.getString(R.string.messageVoice) val languageIsLTR = Util.usingLeftToRightLanguage(context) val attachmentString = if (languageIsLTR) { "🎙 $baseString" diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/VideoSlide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/VideoSlide.java index 1017ad1107..8bd3afb4fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -70,6 +70,6 @@ public class VideoSlide extends Slide { @NonNull @Override public String getContentDescription() { - return context.getString(R.string.Slide_video); + return context.getString(R.string.video); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index 64617eb6a1..9e33e5135d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -72,9 +72,9 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui if (privacy.isDisplayMessage()) { setTicker(getStyledMessage(recipient, trimToDisplayLength(message))); } else if (privacy.isDisplayContact()) { - setTicker(getStyledMessage(recipient, context.getString(R.string.AbstractNotificationBuilder_new_message))); + setTicker(getStyledMessage(recipient, context.getString(R.string.messageNew))); } else { - setTicker(context.getString(R.string.AbstractNotificationBuilder_new_message)); + setTicker(context.getString(R.string.messageNew)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index e80c47a9f6..b763fa19ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -16,6 +16,9 @@ */ package org.thoughtcrime.securesms.notifications; +import static org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY; + +import android.Manifest; import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.Notification; @@ -24,28 +27,39 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; -import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.service.notification.StatusBarNotification; import android.text.SpannableString; import android.text.TextUtils; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; - import com.annimon.stream.Optional; import com.annimon.stream.Stream; import com.goterl.lazysodium.utils.KeyPair; - +import com.squareup.phrase.Phrase; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import me.leolin.shortcutbadger.ShortcutBadger; +import network.loki.messenger.R; import org.session.libsession.messaging.open_groups.OpenGroup; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.utilities.AccountId; import org.session.libsession.messaging.utilities.SodiumUtilities; -import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.ServiceUtil; @@ -56,7 +70,6 @@ import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Util; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.contacts.ContactUtil; -import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities; import org.thoughtcrime.securesms.crypto.KeyPairUtilities; import org.thoughtcrime.securesms.database.LokiThreadDatabase; @@ -74,21 +87,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.SessionMetaProtocol; import org.thoughtcrime.securesms.util.SpanUtil; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import me.leolin.shortcutbadger.ShortcutBadger; -import network.loki.messenger.R; - /** * Handles posting system notifications for new messages. * @@ -133,24 +131,7 @@ public class DefaultMessageNotifier implements MessageNotifier { @Override public void notifyMessageDeliveryFailed(Context context, Recipient recipient, long threadId) { - if (visibleThread != threadId) { - Intent intent = new Intent(context, ConversationActivityV2.class); - intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); - intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); - intent.setData((Uri.parse("custom://" + SnodeAPI.getNowWithOffset()))); - - FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent); - ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify((int)threadId, builder.build()); - } - } - - public void notifyMessagesPending(Context context) { - - if (!TextSecurePreferences.isNotificationsEnabled(context)) { return; } - - PendingMessageNotificationBuilder builder = new PendingMessageNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context)); - ServiceUtil.getNotificationManager(context).notify(PENDING_MESSAGES_ID, builder.build()); + // We do not provide notifications for message delivery failure. } @Override @@ -404,7 +385,19 @@ public class DefaultMessageNotifier implements MessageNotifier { } Notification notification = builder.build(); - NotificationManagerCompat.from(context).notify(notificationId, notification); + + // ACL FIX THIS PROPERLY + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return; + } + NotificationManagerCompat.from(context).notify(notificationId, notification); Log.i(TAG, "Posted notification. " + notification.toString()); } @@ -473,6 +466,19 @@ public class DefaultMessageNotifier implements MessageNotifier { builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag); + + // ACL FIX THIS PROPERLY + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return; + } + Notification notification = builder.build(); NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, notification); Log.i(TAG, "Posted notification. " + notification); @@ -511,11 +517,11 @@ public class DefaultMessageNotifier implements MessageNotifier { // If this is a message request from an unknown user.. if (messageRequest) { - body = SpanUtil.italic(context.getString(R.string.message_requests_notification)); + body = SpanUtil.italic(context.getString(R.string.messageRequestsNew)); // If we received some manner of notification but Session is locked.. } else if (KeyCachingService.isLocked(context)) { - body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message)); + body = SpanUtil.italic(context.getString(R.string.messageNewYouveGotA)); // ----- All further cases assume we know the contact and that Session isn't locked ----- @@ -538,7 +544,7 @@ public class DefaultMessageNotifier implements MessageNotifier { // If this is a notification about an invitation to a community.. } else if (record.isOpenGroupInvitation()) { - body = SpanUtil.italic(context.getString(R.string.ThreadRecord_open_group_invitation)); + body = SpanUtil.italic(context.getString(R.string.communityInvitation)); } String userPublicKey = TextSecurePreferences.getLocalNumber(context); @@ -576,7 +582,7 @@ public class DefaultMessageNotifier implements MessageNotifier { if (threadRecipients != null && !threadRecipients.isGroupRecipient()) { ReactionRecord reaction = lastReact.get(); Recipient reactor = Recipient.from(context, Address.fromSerialized(reaction.getAuthor()), false); - String emoji = context.getString(R.string.reaction_notification, reactor.toShortString(), reaction.getEmoji()); + String emoji = Phrase.from(context, R.string.emojiReactsNotification).put(EMOJI_KEY, reaction.getEmoji()).format().toString(); notificationState.addNotification(new NotificationItem(id, mms, reactor, reactor, threadRecipients, threadId, emoji, reaction.getDateSent(), slideDeck)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java deleted file mode 100644 index dc0e52abc6..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.thoughtcrime.securesms.notifications; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.BitmapFactory; - -import org.session.libsession.utilities.NotificationPrivacyPreference; -import org.session.libsession.utilities.recipients.Recipient; - -import network.loki.messenger.R; - -public class FailedNotificationBuilder extends AbstractNotificationBuilder { - - public FailedNotificationBuilder(Context context, NotificationPrivacyPreference privacy, Intent intent) { - super(context, privacy); - - setSmallIcon(R.drawable.ic_notification); - setLargeIcon(BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_action_warning_red)); - setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed)); - setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_message)); - setTicker(context.getString(R.string.MessageNotifier_error_delivering_message)); - setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)); - setAutoCancel(true); - setAlarms(null, Recipient.VibrateState.DEFAULT); - setChannelId(NotificationChannels.FAILURES); - } - - - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index 7042a1766a..4135faae9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -1,15 +1,21 @@ package org.thoughtcrime.securesms.notifications; +import static org.session.libsession.utilities.StringSubstitutionConstants.CONVERSATION_COUNT_KEY; +import static org.session.libsession.utilities.StringSubstitutionConstants.MESSAGE_COUNT_KEY; +import static org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY; + import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.text.SpannableStringBuilder; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; - +import com.squareup.phrase.Phrase; +import java.util.LinkedList; +import java.util.List; +import network.loki.messenger.R; import org.session.libsession.messaging.contacts.Contact; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.TextSecurePreferences; @@ -19,11 +25,6 @@ import org.thoughtcrime.securesms.database.SessionContactDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.home.HomeActivity; -import java.util.LinkedList; -import java.util.List; - -import network.loki.messenger.R; - public class MultipleRecipientNotificationBuilder extends AbstractNotificationBuilder { private final List messageBodies = new LinkedList<>(); @@ -44,9 +45,12 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu } public void setMessageCount(int messageCount, int threadCount) { - setSubText(context.getString(R.string.MessageNotifier_d_new_messages_in_d_conversations, - messageCount, threadCount)); - setContentInfo(String.valueOf(messageCount)); + String txt = Phrase.from(context, R.string.notificationsAndroidSystem) + .put(MESSAGE_COUNT_KEY, messageCount) + .put(CONVERSATION_COUNT_KEY, threadCount) + .format().toString(); + setSubText(txt); + setContentInfo(String.valueOf(messageCount)); // Note: `setContentInfo` details are only visible in Android API 24 and below - remove when min. API is upgraded. setNumber(messageCount); } @@ -56,7 +60,10 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu displayName = getGroupDisplayName(recipient, threadRecipient.isCommunityRecipient()); } if (privacy.isDisplayContact()) { - setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName)); + String txt = Phrase.from(context, R.string.notificationsMostRecent) + .put(NAME_KEY, displayName) + .format().toString(); + setContentText(txt); } if (recipient.getNotificationChannel() != null) { @@ -66,7 +73,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void addActions(PendingIntent markAsReadIntent) { NotificationCompat.Action markAllAsReadAction = new NotificationCompat.Action(R.drawable.check, - context.getString(R.string.MessageNotifier_mark_all_as_read), + context.getString(R.string.messageMarkRead), markAsReadIntent); addAction(markAllAsReadAction); extend(new NotificationCompat.WearableExtender().addAction(markAllAsReadAction)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 8bf1d7d8d8..17fb8ee736 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -133,7 +133,7 @@ public class NotificationChannels { } else if (!TextUtils.isEmpty(address.serialize())) { return address.serialize(); } else { - return context.getString(R.string.NotificationChannel_missing_display_name); + return context.getString(R.string.unknown); } } @@ -423,15 +423,14 @@ public class NotificationChannels { @TargetApi(26) private static void onCreate(@NonNull Context context, @NonNull NotificationManager notificationManager) { - NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.NotificationChannel_group_messages)); + NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.messages)); notificationManager.createNotificationChannelGroup(messagesGroup); - NotificationChannel messages = new NotificationChannel(getMessagesChannel(context), context.getString(R.string.NotificationChannel_messages), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel failures = new NotificationChannel(FAILURES, context.getString(R.string.NotificationChannel_failures), NotificationManager.IMPORTANCE_HIGH); - NotificationChannel backups = new NotificationChannel(BACKUPS, context.getString(R.string.NotificationChannel_backups), NotificationManager.IMPORTANCE_LOW); - NotificationChannel lockedStatus = new NotificationChannel(LOCKED_STATUS, context.getString(R.string.NotificationChannel_locked_status), NotificationManager.IMPORTANCE_LOW); - NotificationChannel other = new NotificationChannel(OTHER, context.getString(R.string.NotificationChannel_other), NotificationManager.IMPORTANCE_LOW); + NotificationChannel messages = new NotificationChannel(getMessagesChannel(context), context.getString(R.string.theDefault), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.callsSettings), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel failures = new NotificationChannel(FAILURES, context.getString(R.string.failures), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel lockedStatus = new NotificationChannel(LOCKED_STATUS, context.getString(R.string.lockAppStatus), NotificationManager.IMPORTANCE_LOW); + NotificationChannel other = new NotificationChannel(OTHER, context.getString(R.string.other), NotificationManager.IMPORTANCE_LOW); messages.setGroup(CATEGORY_MESSAGES); messages.enableVibration(TextSecurePreferences.isNotificationVibrateEnabled(context)); @@ -440,14 +439,13 @@ public class NotificationChannels { calls.setShowBadge(false); calls.setSound(null, null); - backups.setShowBadge(false); lockedStatus.setShowBadge(false); other.setShowBadge(false); - notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, backups, lockedStatus, other)); + notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, lockedStatus, other)); if (BuildConfig.PLAY_STORE_DISABLED) { - NotificationChannel appUpdates = new NotificationChannel(APP_UPDATES, context.getString(R.string.NotificationChannel_app_updates), NotificationManager.IMPORTANCE_HIGH); + NotificationChannel appUpdates = new NotificationChannel(APP_UPDATES, context.getString(R.string.updateApp), NotificationManager.IMPORTANCE_HIGH); notificationManager.createNotificationChannel(appUpdates); } else { notificationManager.deleteNotificationChannel(APP_UPDATES); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java deleted file mode 100644 index 935d575c56..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.thoughtcrime.securesms.notifications; - - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -import androidx.core.app.NotificationCompat; - -import org.session.libsession.utilities.NotificationPrivacyPreference; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.recipients.Recipient; -import org.thoughtcrime.securesms.home.HomeActivity; - -import network.loki.messenger.R; - -public class PendingMessageNotificationBuilder extends AbstractNotificationBuilder { - - public PendingMessageNotificationBuilder(Context context, NotificationPrivacyPreference privacy) { - super(context, privacy); - - Intent intent = new Intent(context, HomeActivity.class); - - setSmallIcon(R.drawable.ic_notification); - setColor(context.getResources().getColor(R.color.textsecure_primary)); - setCategory(NotificationCompat.CATEGORY_MESSAGE); - - setContentTitle(context.getString(R.string.MessageNotifier_pending_signal_messages)); - setContentText(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages)); - setTicker(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages)); - - setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)); - setAutoCancel(true); - setAlarms(null, Recipient.VibrateState.DEFAULT); - - if (!NotificationChannels.supported()) { - setPriority(TextSecurePreferences.getNotificationPriority(context)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt index 5f218a7a9f..abdbe3f9bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt @@ -3,14 +3,13 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import com.goterl.lazysodium.LazySodiumAndroid -import com.goterl.lazysodium.SodiumAndroid +import androidx.core.content.ContextCompat.getString import com.goterl.lazysodium.interfaces.AEAD import com.goterl.lazysodium.utils.Key import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonBuilder +import network.loki.messenger.R import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters @@ -53,10 +52,10 @@ class PushReceiver @Inject constructor(@ApplicationContext val context: Context) private fun onPush() { Log.d(TAG, "Failed to decode data for message.") val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER) - .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) - .setColor(context.getColor(network.loki.messenger.R.color.textsecure_primary)) - .setContentTitle("Session") - .setContentText("You've got a new message.") + .setSmallIcon(R.drawable.ic_notification) + .setColor(context.getColor(R.color.textsecure_primary)) + .setContentTitle(getString(context, R.string.app_name)) + .setContentText(getString(context, R.string.messageNewYouveGotA)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) NotificationManagerCompat.from(context).notify(11111, builder.build()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index d5aeba6022..b5c7461d2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -38,7 +38,7 @@ import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.database.SessionContactDatabase; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator; @@ -89,7 +89,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil try { // AC: For some reason, if not use ".asBitmap()" method, the returned BitmapDrawable // wraps a recycled bitmap and leads to a crash. - Bitmap iconBitmap = GlideApp.with(context.getApplicationContext()) + Bitmap iconBitmap = Glide.with(context.getApplicationContext()) .asBitmap() .load(contactPhoto) .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -107,7 +107,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil } } else { - setContentTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal)); + setContentTitle(context.getString(R.string.sessionMessenger)); setLargeIcon(new GeneratedContactPhoto("Unknown", R.drawable.ic_profile_default).asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context))); } } @@ -133,7 +133,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil setContentText(stringBuilder.append(message)); this.slideDeck = slideDeck; } else { - setContentText(stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message))); + setContentText(stringBuilder.append(context.getString(R.string.messageNew))); } } @@ -145,7 +145,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil return; RemoteInput remoteInput = new RemoteInput.Builder(AndroidAutoReplyReceiver.VOICE_REPLY_KEY) - .setLabel(context.getString(R.string.MessageNotifier_reply)) + .setLabel(context.getString(R.string.reply)) .build(); NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder = @@ -164,7 +164,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil @NonNull ReplyMethod replyMethod) { Action markAsReadAction = new Action(R.drawable.check, - context.getString(R.string.MessageNotifier_mark_read), + context.getString(R.string.messageMarkRead), markReadIntent); addAction(markAsReadAction); @@ -172,7 +172,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(markAsReadAction); if (quickReplyIntent != null) { - String actionName = context.getString(R.string.MessageNotifier_reply); + String actionName = context.getString(R.string.reply); String label = context.getString(replyMethodLongDescription(replyMethod)); Action replyAction = new Action(R.drawable.ic_reply_white_36dp, actionName, quickReplyIntent); @@ -201,7 +201,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil @StringRes private static int replyMethodLongDescription(@NonNull ReplyMethod replyMethod) { - return R.string.MessageNotifier_reply; + return R.string.reply; } public void putStringExtra(String key, String value) { @@ -222,7 +222,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil if (privacy.isDisplayMessage()) { messageBodies.add(stringBuilder.append(messageBody == null ? "" : messageBody)); } else { - messageBodies.add(stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message))); + messageBodies.add(stringBuilder.append(context.getString(R.string.messageNew))); } } @@ -291,7 +291,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil @SuppressWarnings("ConstantConditions") Uri uri = slideDeck.getThumbnailSlide().getThumbnailUri(); - return GlideApp.with(context.getApplicationContext()) + return Glide.with(context.getApplicationContext()) .asBitmap() .load(new DecryptableStreamUriLoader.DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt index 2820a64a66..50ee344ef9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/OnboardingBackPressAlertDialog.kt @@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors @Composable fun OnboardingBackPressAlertDialog( dismissDialog: () -> Unit, - @StringRes textId: Int = R.string.you_cannot_go_back_further_in_order_to_stop_loading_your_account_session_needs_to_quit, + @StringRes textId: Int = R.string.onboardingBackAccountCreation, quit: () -> Unit ) { AlertDialog( @@ -21,7 +21,7 @@ fun OnboardingBackPressAlertDialog( text = stringResource(textId), buttons = listOf( DialogButtonModel( - GetString(stringResource(R.string.quit)), + GetString(stringResource(R.string.quitButton)), color = LocalColors.current.danger, onClick = quit ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt index e40328b1d8..606205447d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt @@ -34,8 +34,10 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.squareup.phrase.Phrase import kotlinx.coroutines.delay import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.DialogButtonModel import org.thoughtcrime.securesms.ui.GetString @@ -81,12 +83,12 @@ internal fun LandingScreen( showCloseButton = true, // display the 'x' button buttons = listOf( DialogButtonModel( - text = GetString(R.string.activity_landing_terms_of_service), + text = GetString(R.string.onboardingTos), contentDescription = GetString(R.string.AccessibilityId_terms_of_service_button), onClick = openTerms ), DialogButtonModel( - text = GetString(R.string.activity_landing_privacy_policy), + text = GetString(R.string.onboardingPrivacy), contentDescription = GetString(R.string.AccessibilityId_privacy_policy_button), onClick = openPrivacyPolicy ) @@ -129,10 +131,21 @@ internal fun LandingScreen( MESSAGES.take(count), key = { it.stringId } ) { item -> - AnimateMessageText( - stringResource(item.stringId), - item.isOutgoing - ) + // Perform string substitution in the bubbles that require it + if (item.stringId == R.string.onboardingBubbleWelcomeToSession || + item.stringId == R.string.onboardingBubbleSessionIsEngineered) { + AnimateMessageText( + Phrase.from(stringResource(item.stringId)) + .put(APP_NAME_KEY, stringResource(R.string.app_name)) + .format().toString(), + item.isOutgoing + ) + } else { // Non-substituted text bubbles + AnimateMessageText( + stringResource(item.stringId), + item.isOutgoing + ) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt index cc1033ee96..8646bb0648 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccount.kt @@ -24,12 +24,12 @@ import androidx.compose.ui.tooling.preview.Preview import kotlinx.coroutines.flow.Flow import network.loki.messenger.R import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton -import org.thoughtcrime.securesms.ui.theme.LocalDimensions -import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField import org.thoughtcrime.securesms.ui.components.SessionTabRow +import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType +import org.thoughtcrime.securesms.ui.theme.PreviewTheme private val TITLES = listOf(R.string.sessionRecoveryPassword, R.string.qrScan) @@ -93,7 +93,7 @@ private fun RecoveryPassword(state: State, onChange: (String) -> Unit = {}, onCo } Spacer(Modifier.height(LocalDimensions.current.smallSpacing)) Text( - stringResource(R.string.activity_link_enter_your_recovery_password_to_load_your_account_if_you_haven_t_saved_it_you_can_find_it_in_your_app_settings), + stringResource(R.string.recoveryPasswordRestoreDescription), style = LocalType.current.base ) Spacer(Modifier.height(LocalDimensions.current.spacing)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccountActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccountActivity.kt index 3c7a6f6a56..3d68ae371c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccountActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loadaccount/LoadAccountActivity.kt @@ -28,7 +28,7 @@ class LoadAccountActivity : BaseActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - supportActionBar?.setTitle(R.string.activity_link_load_account) + supportActionBar?.setTitle(R.string.loadAccount) prefs.setConfigurationMessageSynced(false) prefs.setRestorationTime(System.currentTimeMillis()) prefs.setLastProfileUpdateTime(0) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt index e56b55aaab..0854cb7d56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt @@ -18,7 +18,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.thoughtcrime.securesms.onboarding.OnboardingBackPressAlertDialog import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsViewModel.UiState import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton @@ -55,28 +57,35 @@ internal fun MessageNotificationsScreen( Column { Spacer(Modifier.weight(1f)) - Column(modifier = Modifier.padding(horizontal = LocalDimensions.current.mediumSpacing)) { Text(stringResource(R.string.notificationsMessage), style = LocalType.current.h4) Spacer(Modifier.height(LocalDimensions.current.smallSpacing)) - Text(stringResource(R.string.onboardingMessageNotificationExplaination), style = LocalType.current.base) + Text( + Phrase.from(stringResource(R.string.onboardingMessageNotificationExplanation)) + .put(APP_NAME_KEY, stringResource(R.string.app_name)) + .format().toString(), + style = LocalType.current.base + ) Spacer(Modifier.height(LocalDimensions.current.spacing)) } NotificationRadioButton( - R.string.activity_pn_mode_fast_mode, - R.string.activity_pn_mode_fast_mode_explanation, + R.string.notificationsFastMode, + R.string.notificationsFastModeDescriptionAndroid, modifier = Modifier.contentDescription(R.string.AccessibilityId_fast_mode_notifications_button), - tag = R.string.activity_pn_mode_recommended_option_tag, + tag = R.string.recommended, checked = state.pushEnabled, onClick = { setEnabled(true) } ) // spacing between buttons is provided by ripple/downstate of NotificationRadioButton + val txt = Phrase.from(stringResource(R.string.onboardingMessageNotificationExplanation)) + .put(APP_NAME_KEY, stringResource(R.string.app_name)) + .format().toString() NotificationRadioButton( - R.string.activity_pn_mode_slow_mode, - R.string.activity_pn_mode_slow_mode_explanation, + R.string.notificationsSlowMode, + R.string.notificationsSlowModeDescription, modifier = Modifier.contentDescription(R.string.AccessibilityId_slow_mode_notifications_button), checked = state.pushDisabled, onClick = { setEnabled(false) } @@ -112,13 +121,32 @@ private fun NotificationRadioButton( RoundedCornerShape(8.dp) ), ) { - Column(modifier = Modifier - .padding(horizontal = LocalDimensions.current.smallSpacing, - vertical = LocalDimensions.current.xsSpacing) - ) { - Text(stringResource(title), style = LocalType.current.h8) + Column( + modifier = Modifier.padding(horizontal = LocalDimensions.current.smallSpacing, vertical = LocalDimensions.current.xsSpacing)) { + Text( + stringResource(title), + style = LocalType.current.h8 + ) + + // If this radio button is the one for slow mode notifications then substitute the app name.. + if (explanation == R.string.notificationsSlowModeDescription) { + val txt = Phrase.from(stringResource(explanation)) + .put(APP_NAME_KEY, stringResource(R.string.app_name)) + .format().toString() + Text( + txt, + style = LocalType.current.small, + modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing) + ) + } else { + // ..otherwise just pass through the text as it is. + Text( + stringResource(explanation), + style = LocalType.current.small, + modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing) + ) + } - Text(stringResource(explanation), style = LocalType.current.small, modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing)) tag?.let { Text( stringResource(it), diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt index 04124a5bfd..0747c57502 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayName.kt @@ -41,7 +41,7 @@ internal fun PickDisplayName( if (state.showDialog) OnboardingBackPressAlertDialog( dismissDialog, - R.string.you_cannot_go_back_further_cancel_account_creation, + R.string.onboardingBackAccountCreation, quit ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt index 0d31363d76..dff01cd520 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/ui/ContinueButton.kt @@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.ui.contentDescription @Composable fun ContinuePrimaryOutlineButton(modifier: Modifier, onContinue: () -> Unit) { PrimaryOutlineButton( - stringResource(R.string.continue_2), + stringResource(R.string.theContinue), modifier = modifier .contentDescription(R.string.AccessibilityId_continue) .fillMaxWidth() diff --git a/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.kt index 373da62c12..021b67facb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/permissions/RationaleDialog.kt @@ -53,8 +53,8 @@ object RationaleDialog { return context.showSessionDialog { view(view) - button(R.string.Permissions_continue) { onPositive.run() } - button(R.string.Permissions_not_now) { onNegative.run() } + button(R.string.theContinue) { onPositive.run() } + button(R.string.notNow) { onNegative.run() } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/permissions/SettingsDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/permissions/SettingsDialog.kt index a4efd8d870..861b9ba1d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/permissions/SettingsDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/permissions/SettingsDialog.kt @@ -9,9 +9,9 @@ class SettingsDialog { @JvmStatic fun show(context: Context, message: String) { context.showSessionDialog { - title(R.string.Permissions_permission_required) + title(R.string.permissionsRequired) text(message) - button(R.string.Permissions_continue, R.string.AccessibilityId_continue) { + button(R.string.theContinue, R.string.AccessibilityId_continue) { context.startActivity(Permissions.getApplicationSettingsIntent(context)) } cancelButton() diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt index 16499cc4bc..def9138ae3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt @@ -22,7 +22,7 @@ class BlockedContactsActivity: PassphraseRequiredActionBarActivity() { showSessionDialog { title(viewModel.getTitle(this@BlockedContactsActivity)) text(viewModel.getMessage(this@BlockedContactsActivity)) - button(R.string.continue_2) { viewModel.unblock() } + button(R.string.theContinue) { viewModel.unblock() } cancelButton() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index e0b92bdbea..e59d86c912 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.BlockedContactLayoutBinding import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide import org.thoughtcrime.securesms.util.adapter.SelectableItem typealias SelectableRecipient = SelectableItem @@ -43,7 +43,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { - val glide = GlideApp.with(itemView) + val glide = Glide.with(itemView) val binding = BlockedContactLayoutBinding.bind(itemView) fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt index dbe09668c5..0604c42fa8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsViewModel.kt @@ -7,10 +7,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.cash.copper.flow.observeQuery import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collect @@ -23,7 +21,6 @@ import network.loki.messenger.R import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.Storage -import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.adapter.SelectableItem import javax.inject.Inject @@ -75,35 +72,9 @@ class BlockedContactsViewModel @Inject constructor(private val storage: Storage) } } - fun getTitle(context: Context): String = - if (state.selectedItems.size == 1) { - context.getString(R.string.Unblock_dialog__title_single, state.selectedItems.first().name) - } else { - context.getString(R.string.Unblock_dialog__title_multiple) - } + fun getTitle(context: Context): String = context.getString(R.string.blockUnblock) - fun getMessage(context: Context): String { - if (state.selectedItems.size == 1) { - return context.getString(R.string.Unblock_dialog__message, state.selectedItems.first().name) - } - val stringBuilder = StringBuilder() - val iterator = state.selectedItems.iterator() - var numberAdded = 0 - while (iterator.hasNext() && numberAdded < 3) { - val nextRecipient = iterator.next() - if (numberAdded > 0) stringBuilder.append(", ") - - stringBuilder.append(nextRecipient.name) - numberAdded++ - } - val overflow = state.selectedItems.size - numberAdded - if (overflow > 0) { - stringBuilder.append(" ") - val string = context.resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow) - stringBuilder.append(string.format(overflow)) - } - return context.getString(R.string.Unblock_dialog__message, stringBuilder.toString()) - } + fun getMessage(context: Context): String = context.getString(R.string.blockUnblock) fun toggle(selectable: SelectableItem) { _state.value = state.run { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/CallToggleListener.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/CallToggleListener.kt index ea747798c8..7383b708e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/CallToggleListener.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/CallToggleListener.kt @@ -19,9 +19,9 @@ internal class CallToggleListener( // check if we've shown the info dialog and check for microphone permissions context.showSessionDialog { - title(R.string.dialog_voice_video_title) - text(R.string.dialog_voice_video_message) - button(R.string.dialog_link_preview_enable_button_title, R.string.AccessibilityId_enable) { requestMicrophonePermission() } + title(R.string.callsVoiceAndVideoBeta) + text(R.string.callsVoiceAndVideoModalDescription) + button(R.string.enable, R.string.AccessibilityId_enable) { requestMicrophonePermission() } cancelButton() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt index 6852d2f63f..f2217b66e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ChatSettingsActivity.kt @@ -9,7 +9,7 @@ class ChatSettingsActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) setContentView(R.layout.activity_fragment_wrapper) - supportActionBar!!.title = resources.getString(R.string.activity_conversations_settings_title) + supportActionBar!!.title = resources.getString(R.string.sessionConversations) val fragment = ChatsPreferenceFragment() val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.fragmentContainer, fragment) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt index 17d97dec7b..d67bc97ef2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ClearAllDataDialog.kt @@ -93,10 +93,12 @@ class ClearAllDataDialog : DialogFragment() { when (step) { Steps.INFO_PROMPT -> { - binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_message) + binding.dialogDescriptionText.setText(R.string.clearDataAllDescription) } Steps.NETWORK_PROMPT -> { - binding.dialogDescriptionText.setText(R.string.dialog_clear_all_data_clear_device_and_network_confirmation) + // ACL TODO - This was `dialog_clear_all_data_clear_device_and_network_confirmation` - need an equivalent? + val txt = "Are you sure you want to delete your data from the network? If you continue you will not be able to restore your messages or contacts." + binding.dialogDescriptionText.text = txt } Steps.DELETING -> { /* do nothing intentionally */ } Steps.RETRY_LOCAL_DELETE_ONLY_PROMPT -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java index 8c3e6190ed..d626b9ce5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/CorrectedPreferenceFragment.java @@ -21,18 +21,15 @@ import androidx.preference.PreferenceGroupAdapter; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.recyclerview.widget.RecyclerView; - -import org.thoughtcrime.securesms.components.CustomDefaultPreference; import org.thoughtcrime.securesms.conversation.v2.ViewUtil; - import network.loki.messenger.R; public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat { - public static final int SINGLE_TYPE = 21; - public static final int TOP_TYPE = 22; - public static final int MIDDLE_TYPE = 23; - public static final int BOTTOM_TYPE = 24; + public static final int SINGLE_TYPE = 21; + public static final int TOP_TYPE = 22; + public static final int MIDDLE_TYPE = 23; + public static final int BOTTOM_TYPE = 24; public static final int CATEGORY_TYPE = 25; public int horizontalPadding; @@ -56,18 +53,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp @Override public void onDisplayPreferenceDialog(Preference preference) { - DialogFragment dialogFragment = null; - - if (preference instanceof CustomDefaultPreference) { - dialogFragment = CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.newInstance(preference.getKey()); - } - - if (dialogFragment != null) { - dialogFragment.setTargetFragment(this, 0); - dialogFragment.show(getFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG"); - } else { - super.onDisplayPreferenceDialog(preference); - } + if (preference != null) super.onDisplayPreferenceDialog(preference); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt index 339047bbf6..2ca747f5ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -10,8 +10,9 @@ import android.widget.TextView import android.widget.Toast import androidx.core.view.isInvisible import androidx.preference.Preference - +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.permissions.Permissions @@ -30,20 +31,31 @@ class HelpSettingsActivity: PassphraseRequiredActionBarActivity() { class HelpSettingsFragment: CorrectedPreferenceFragment() { companion object { - private const val EXPORT_LOGS = "export_logs" - private const val TRANSLATE = "translate_session" - private const val FEEDBACK = "feedback" - private const val FAQ = "faq" - private const val SUPPORT = "support" - - private const val CROWDIN_URL = "https://getsession.org/translate" + private const val EXPORT_LOGS = "export_logs" + private const val TRANSLATE = "translate_session" + private const val FEEDBACK = "feedback" + private const val FAQ = "faq" + private const val SUPPORT = "support" + private const val CROWDIN_URL = "https://getsession.org/translate" private const val FEEDBACK_URL = "https://getsession.org/survey" - private const val FAQ_URL = "https://getsession.org/faq" - private const val SUPPORT_URL = "https://sessionapp.zendesk.com/hc/en-us" + private const val FAQ_URL = "https://getsession.org/faq" + private const val SUPPORT_URL = "https://sessionapp.zendesk.com/hc/en-us" } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_help) + + // String sub the summary text of the `export_logs` element in preferences_help.xml + var exportPref = preferenceScreen.findPreference(EXPORT_LOGS) + exportPref?.summary = Phrase.from(context, R.string.helpReportABugExportLogsDescription) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format() + + // String sub the summary text of the `translate_session` element in preferences_help.xml + var translatePref = preferenceScreen.findPreference(TRANSLATE) + translatePref?.title = Phrase.from(context, R.string.helpHelpUsTranslateSession) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format() } override fun onPreferenceTreeClick(preference: Preference): Boolean { @@ -89,9 +101,14 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() { Permissions.with(this) .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .maxSdkVersion(Build.VERSION_CODES.P) - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) + .withPermanentDenialDialog(Phrase.from(context, R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString()) .onAnyDenied { - Toast.makeText(requireActivity(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show() + val txt = Phrase.from(context, R.string.permissionsStorageSaveDenied) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format().toString() + Toast.makeText(requireActivity(), txt, Toast.LENGTH_LONG).show() } .onAllGranted { ShareLogsDialog(::updateExportButtonAndProgressBarUI).show(parentFragmentManager,"Share Logs Dialog") diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java index 4314b9ae62..a57c0ad728 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ListSummaryPreferenceFragment.java @@ -18,7 +18,7 @@ public abstract class ListSummaryPreferenceFragment extends CorrectedPreferenceF listPref.setSummary(entryIndex >= 0 && entryIndex < listPref.getEntries().length ? listPref.getEntries()[entryIndex] - : getString(R.string.preferences__led_color_unknown)); + : getString(R.string.unknown)); return true; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt index 2a34de808b..0e32cc4335 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt @@ -11,7 +11,7 @@ class NotificationSettingsActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) setContentView(R.layout.activity_fragment_wrapper) - supportActionBar!!.title = resources.getString(R.string.activity_notification_settings_title) + supportActionBar!!.title = resources.getString(R.string.sessionNotifications) val fragment = NotificationsPreferenceFragment() val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.fragmentContainer, fragment) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt index fa6461acc4..6af742838c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt @@ -71,6 +71,7 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() { NotificationChannels.updateMessageVibrate(requireContext(), newValue as Boolean) true } + findPreference(TextSecurePreferences.RINGTONE_PREF)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { val current = prefs.getNotificationRingtone() @@ -89,10 +90,10 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() { startActivityForResult(intent, 1) true } + findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { preference: Preference -> val listPreference = preference as ListPreference - listPreference.setDialogMessage(R.string.preferences_notifications__content_message) listPreferenceDialog(requireContext(), listPreference) { initializeListSummary(findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)) } @@ -138,7 +139,7 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() { override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { val value = newValue as? Uri if (value == null || TextUtils.isEmpty(value.toString())) { - preference.setSummary(R.string.preferences__silent) + preference.setSummary(R.string.none) } else { RingtoneManager.getRingtone(activity, value) ?.getTitle(activity) @@ -176,8 +177,8 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() { @Suppress("unused") private val TAG = NotificationsPreferenceFragment::class.java.simpleName fun getSummary(context: Context): CharSequence = when (isNotificationsEnabled(context)) { - true -> R.string.ApplicationPreferencesActivity_On - false -> R.string.ApplicationPreferencesActivity_Off + true -> R.string.on + false -> R.string.off }.let(context::getString) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt index de136694ff..7d8e254205 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt @@ -11,8 +11,7 @@ class PrivacySettingsActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) setContentView(R.layout.activity_fragment_wrapper) - val fragment = - PrivacySettingsPreferenceFragment() + val fragment = PrivacySettingsPreferenceFragment() val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.fragmentContainer, fragment) transaction.commit() diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt index 21b12496bd..aa174ee8e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt @@ -61,8 +61,8 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() { super.putBoolean(key, value) } } - title = getString(R.string.preferences__message_requests_title) - summary = getString(R.string.preferences__message_requests_summary) + title = getString(R.string.messageRequestsCommunities) + summary = getString(R.string.messageRequestsCommunitiesDescription) }.let(category::addPreference) } } @@ -75,9 +75,9 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() { if (isEnabled && !areNotificationsEnabled(requireActivity())) { // show a dialog saying that calls won't work properly if you don't have notifications on at a system level showSessionDialog { - title(R.string.CallNotificationBuilder_system_notification_title) - text(R.string.CallNotificationBuilder_system_notification_message) - button(R.string.activity_notification_settings_title) { + title(R.string.sessionNotifications) + text(R.string.callsNotificationsRequired) + button(R.string.sessionNotifications) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID) @@ -106,7 +106,7 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() { } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_app_protection) + addPreferencesFromResource(R.xml.preferences_privacy) } override fun onResume() { @@ -119,6 +119,8 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() { requireContext().getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager if (!keyguardManager.isKeyguardSecure) { findPreference(TextSecurePreferences.SCREEN_LOCK)!!.isChecked = false + + // TODO: Ticket SES-2182 raised to investigate & fix app lock / unlock functionality -ACL 2024/06/20 findPreference(TextSecurePreferences.SCREEN_LOCK)!!.isEnabled = false } } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt index 9778a1c8b8..06a932f787 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt @@ -43,7 +43,7 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) - supportActionBar!!.title = resources.getString(R.string.activity_qr_code_title) + supportActionBar!!.title = resources.getString(R.string.qrCode) setComposeContent { Tabs( @@ -56,7 +56,7 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity() { fun onScan(string: String) { if (!PublicKeyValidation.isValid(string)) { - errors.tryEmit(getString(R.string.this_qr_code_does_not_contain_an_account_id)) + errors.tryEmit(getString(R.string.qrNotAccountId)) } else if (!isFinishing) { val recipient = Recipient.from(this, Address.fromSerialized(string), false) start { @@ -106,7 +106,7 @@ fun QrPage(string: String) { ) Text( - text = stringResource(R.string.this_is_your_account_id_other_users_can_scan_it_to_start_a_conversation_with_you), + text = stringResource(R.string.accountIdYoursDescription), color = LocalColors.current.textSecondary, textAlign = TextAlign.Center, style = LocalType.current.small diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt index 60b5fb8b2c..1e0bcd27c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/RadioOptionAdapter.kt @@ -10,7 +10,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.ItemSelectableBinding -import org.thoughtcrime.securesms.mms.GlideApp +import com.bumptech.glide.Glide import org.thoughtcrime.securesms.ui.GetString import java.util.Objects @@ -45,7 +45,7 @@ class RadioOptionAdapter( } class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { - val glide = GlideApp.with(itemView) + val glide = Glide.with(itemView) val binding = ItemSelectableBinding.bind(itemView) fun bind(option: RadioOption, isSelected: Boolean, toggleSelection: (RadioOption) -> Unit) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt new file mode 100644 index 0000000000..1718a712bf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SeedDialog.kt @@ -0,0 +1,41 @@ +package org.thoughtcrime.securesms.preferences + +import android.app.Dialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Bundle +import android.widget.Toast +import androidx.fragment.app.DialogFragment +import network.loki.messenger.R +import org.session.libsignal.crypto.MnemonicCodec +import org.session.libsignal.utilities.hexEncodedPrivateKey +import org.thoughtcrime.securesms.createSessionDialog +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.crypto.MnemonicUtilities + +class SeedDialog: DialogFragment() { + private val seed by lazy { + val hexEncodedSeed = IdentityKeyUtil.retrieve(requireContext(), IdentityKeyUtil.LOKI_SEED) + ?: IdentityKeyUtil.getIdentityKeyPair(requireContext()).hexEncodedPrivateKey // Legacy account + + MnemonicCodec { fileName -> MnemonicUtilities.loadFileContents(requireContext(), fileName) } + .encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { + title(R.string.sessionRecoveryPassword) + text(R.string.recoveryPasswordDescription) + text(seed, R.style.AccountIdTextView) + button(R.string.copy, R.string.AccessibilityId_copy_recovery_phrase) { copySeed() } + button(R.string.close) { dismiss() } + } + + private fun copySeed() { + val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Seed", seed) + clipboard.setPrimaryClip(clip) + Toast.makeText(requireContext(), R.string.copied, Toast.LENGTH_SHORT).show() + dismiss() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index fc98070ca4..9500902411 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -34,7 +34,11 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint +import java.io.File +import java.security.SecureRandom +import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -58,6 +62,7 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.ProfilePictureUtilities import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol +import org.session.libsession.utilities.StringSubstitutionConstants.VERSION_KEY import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.truncateIdForDisplay @@ -77,21 +82,18 @@ import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.Divider import org.thoughtcrime.securesms.ui.LargeItemButton import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable -import org.thoughtcrime.securesms.ui.theme.LocalDimensions -import org.thoughtcrime.securesms.ui.theme.dangerButtonColors import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.setThemedContent +import org.thoughtcrime.securesms.ui.theme.LocalDimensions +import org.thoughtcrime.securesms.ui.theme.dangerButtonColors import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.NetworkUtils import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.show -import java.io.File -import java.security.SecureRandom -import javax.inject.Inject @AndroidEntryPoint class SettingsActivity : PassphraseRequiredActionBarActivity() { @@ -130,7 +132,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { btnGroupNameDisplay.text = getDisplayName() publicKeyTextView.text = hexEncodedPublicKey val gitCommitFirstSixChars = BuildConfig.GIT_HASH.take(6) - versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars)") + + val versionDetails = " ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars)" + val versionString = Phrase.from(applicationContext, R.string.updateVersion).put(VERSION_KEY, versionDetails).format() + versionTextView.text = versionString } binding.composeView.setThemedContent { @@ -189,8 +194,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { AvatarSelection.REQUEST_CODE_AVATAR -> { val outputFile = Uri.fromFile(File(cacheDir, "cropped")) val inputFile: Uri? = data?.data ?: tempFile?.let(Uri::fromFile) - AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar) + AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.photo) } + AvatarSelection.REQUEST_CODE_CROP_IMAGE -> { lifecycleScope.launch(Dispatchers.IO) { try { @@ -367,24 +373,28 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { */ private fun saveDisplayName(): Boolean { val displayName = binding.displayNameEditText.text.toString().trim() + if (displayName.isEmpty()) { - Toast.makeText(this, R.string.activity_settings_display_name_missing_error, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.displayNameErrorDescription, Toast.LENGTH_SHORT).show() return false } + if (displayName.toByteArray().size > ProfileManagerProtocol.NAME_PADDED_LENGTH) { - Toast.makeText(this, R.string.activity_settings_display_name_too_long_error, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.displayNameErrorDescriptionShorter, Toast.LENGTH_SHORT).show() return false } + return updateDisplayName(displayName) } private fun showEditProfilePictureUI() { showSessionDialog { - title(R.string.activity_settings_set_display_picture) + title(R.string.profileDisplayPictureSet) view(R.layout.dialog_change_avatar) button(R.string.activity_settings_upload) { startAvatarSelection() } + if (prefs.getProfileAvatarId() != 0) { - button(R.string.activity_settings_remove) { removeProfilePicture() } + button(R.string.remove) { removeProfilePicture() } } cancelButton() }.apply { @@ -416,7 +426,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { private inner class DisplayNameEditActionModeCallback: ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.title = getString(R.string.activity_settings_display_name_edit_text_hint) + mode.title = getString(R.string.displayNameEnter) mode.menuInflater.inflate(R.menu.menu_apply, menu) this@SettingsActivity.displayNameEditActionMode = mode return true @@ -471,28 +481,64 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { Cell { Column { Crossfade(if (hasPaths) R.drawable.ic_status else R.drawable.ic_path_yellow, label = "path") { - LargeItemButtonWithDrawable(R.string.activity_path_title, it) { show() } + LargeItemButtonWithDrawable(R.string.onionRoutingPath, it) { show() } } Divider() - LargeItemButton(R.string.activity_settings_privacy_button_title, R.drawable.ic_privacy_icon) { show() } + LargeItemButton( + R.string.sessionPrivacy, + R.drawable.ic_privacy_icon + ) { show() } Divider() - LargeItemButton(R.string.activity_settings_notifications_button_title, R.drawable.ic_speaker, Modifier.contentDescription(R.string.AccessibilityId_notifications)) { show() } + LargeItemButton( + R.string.sessionNotifications, + R.drawable.ic_speaker, + Modifier.contentDescription(R.string.AccessibilityId_notifications) + ) { show() } Divider() - LargeItemButton(R.string.activity_settings_conversations_button_title, R.drawable.ic_conversations, Modifier.contentDescription(R.string.AccessibilityId_conversations)) { show() } + LargeItemButton( + R.string.sessionConversations, + R.drawable.ic_conversations, + Modifier.contentDescription(R.string.AccessibilityId_conversations) + ) { show() } Divider() - LargeItemButton(R.string.activity_settings_message_requests_button_title, R.drawable.ic_message_requests, Modifier.contentDescription(R.string.AccessibilityId_message_requests)) { show() } + LargeItemButton( + R.string.sessionMessageRequests, + R.drawable.ic_message_requests, + Modifier.contentDescription(R.string.AccessibilityId_message_requests) + ) { show() } Divider() - LargeItemButton(R.string.activity_settings_message_appearance_button_title, R.drawable.ic_appearance, Modifier.contentDescription(R.string.AccessibilityId_appearance)) { show() } + LargeItemButton( + R.string.sessionAppearance, + R.drawable.ic_appearance, + Modifier.contentDescription(R.string.AccessibilityId_appearance) + ) { show() } Divider() - LargeItemButton(R.string.activity_settings_invite_button_title, R.drawable.ic_invite_friend, Modifier.contentDescription(R.string.AccessibilityId_invite_friend)) { sendInvitationToUseSession() } + LargeItemButton( + R.string.sessionInviteAFriend, + R.drawable.ic_invite_friend, + Modifier.contentDescription(R.string.AccessibilityId_invite_friend) + ) { sendInvitationToUseSession() } Divider() if (!prefs.getHidePassword()) { - LargeItemButton(R.string.sessionRecoveryPassword, R.drawable.ic_shield_outline, Modifier.contentDescription(R.string.AccessibilityId_recovery_password_menu_item)) { show() } + LargeItemButton( + R.string.sessionRecoveryPassword, + R.drawable.ic_shield_outline, + Modifier.contentDescription(R.string.AccessibilityId_recovery_password_menu_item) + ) { show() } Divider() } - LargeItemButton(R.string.activity_settings_help_button, R.drawable.ic_help, Modifier.contentDescription(R.string.AccessibilityId_help)) { show() } + LargeItemButton( + R.string.sessionHelp, + R.drawable.ic_help, + Modifier.contentDescription(R.string.AccessibilityId_help) + ) { show() } Divider() - LargeItemButton(R.string.activity_settings_clear_all_data_button_title, R.drawable.ic_message_details__trash, Modifier.contentDescription(R.string.AccessibilityId_clear_data), dangerButtonColors()) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") } + LargeItemButton( + R.string.sessionClearData, + R.drawable.ic_message_details__trash, + Modifier.contentDescription(R.string.AccessibilityId_clear_data), + dangerButtonColors() + ) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt index 9bfc1dabf2..a3240d3480 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/ShareLogsDialog.kt @@ -11,44 +11,42 @@ import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.webkit.MimeTypeMap -import android.widget.ProgressBar -import android.widget.TextView import android.widget.Toast -import androidx.core.view.isInvisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope - +import com.squareup.phrase.Phrase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext - import network.loki.messenger.BuildConfig import network.loki.messenger.R - +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsignal.utilities.ExternalStorageUtil import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.createSessionDialog import org.thoughtcrime.securesms.util.FileProviderUtil import org.thoughtcrime.securesms.util.StreamUtil - import java.io.File import java.io.FileOutputStream import java.io.IOException import java.util.Objects import java.util.concurrent.TimeUnit - class ShareLogsDialog(private val updateCallback: (Boolean)->Unit): DialogFragment() { private val TAG = "ShareLogsDialog" private var shareJob: Job? = null override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { - title(R.string.dialog_share_logs_title) - text(R.string.dialog_share_logs_explanation) + title(R.string.helpReportABugExportLogs) + val appName = context.getString(R.string.app_name) + val txt = Phrase.from(context, R.string.helpReportABugDescription) + .put(APP_NAME_KEY, appName) + .format().toString() + text(txt) button(R.string.share, dismiss = false) { runShareLogsJob() } cancelButton { updateCallback(false) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/Util.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/Util.kt index 1271ece02e..a136dab26b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/Util.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/Util.kt @@ -5,21 +5,31 @@ import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.widget.Toast +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.ACCOUNT_ID_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.DOWNLOAD_URL_KEY import org.session.libsession.utilities.TextSecurePreferences fun Context.sendInvitationToUseSession() { + + val DOWNLOAD_URL = "https://getsession.org/download" + + val txt = Phrase.from(getString(R.string.accountIdShare)) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .put(ACCOUNT_ID_KEY, TextSecurePreferences.getLocalNumber(this@sendInvitationToUseSession)) + .put(DOWNLOAD_URL_KEY, DOWNLOAD_URL) + .format().toString() + Intent().apply { action = Intent.ACTION_SEND putExtra( Intent.EXTRA_TEXT, - getString( - R.string.accountIdShare, - TextSecurePreferences.getLocalNumber(this@sendInvitationToUseSession) - ) + txt ) type = "text/plain" - }.let { Intent.createChooser(it, getString(R.string.activity_settings_invite_button_title)) } + }.let { Intent.createChooser(it, getString(R.string.sessionInviteAFriend)) } .let(::startActivity) } @@ -27,5 +37,5 @@ fun Context.copyPublicKey() { val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("Account ID", TextSecurePreferences.getLocalNumber(this)) clipboard.setPrimaryClip(clip) - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.copied, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt index 34547c999e..ddf28e9211 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/appearance/AppearanceSettingsActivity.kt @@ -116,7 +116,7 @@ class AppearanceSettingsActivity: PassphraseRequiredActionBarActivity(), View.On setContentView(binding.root) savedInstanceState?.getSparseParcelableArray(SCROLL_PARCEL) ?.let(binding.scrollView::restoreHierarchyState) - supportActionBar!!.title = getString(R.string.activity_settings_message_appearance_button_title) + supportActionBar!!.title = getString(R.string.sessionAppearance) with (binding) { // accent toggles accentContainer.children.forEach { view -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java index d8e90db156..d8002c5cc6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/widgets/SignalListPreference.java @@ -1,77 +1,74 @@ package org.thoughtcrime.securesms.preferences.widgets; - import android.content.Context; -import android.os.Build; -import androidx.annotation.RequiresApi; import androidx.preference.ListPreference; -import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import android.util.AttributeSet; import android.widget.TextView; - import network.loki.messenger.R; public class SignalListPreference extends ListPreference { - private TextView rightSummary; - private CharSequence summary; - private OnPreferenceClickListener clickListener; + private TextView rightSummaryTV; + private CharSequence summary; + private OnPreferenceClickListener clickListener; + private CharSequence summarySpecifiedInLayoutXML; - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - initialize(); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public SignalListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public SignalListPreference(Context context) { - super(context); - initialize(); - } - - private void initialize() { - setWidgetLayoutResource(R.layout.preference_right_summary_widget); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - - this.rightSummary = (TextView)view.findViewById(R.id.right_summary); - setSummary(this.summary); - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(null); - - this.summary = summary; - - if (this.rightSummary != null) { - this.rightSummary.setText(summary); + public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); } - } - @Override - public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) { - this.clickListener = onPreferenceClickListener; - } - - @Override - protected void onClick() { - if (clickListener == null || !clickListener.onPreferenceClick(this)) { - super.onClick(); + public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + public SignalListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public SignalListPreference(Context context) { + super(context); + initialize(); + } + + private void initialize() { + summarySpecifiedInLayoutXML = this.getSummary(); + if (summarySpecifiedInLayoutXML == null) { summarySpecifiedInLayoutXML = ""; } + setWidgetLayoutResource(R.layout.preference_right_summary_widget); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + this.rightSummaryTV = (TextView)view.findViewById(R.id.right_summary); + setSummary(this.summary); + } + + @Override + public void setSummary(CharSequence incomingSummary) { + // Set the left "subtitle" summary such as "The information shown in notifications." etc. + super.setSummary(summarySpecifiedInLayoutXML); + + // Then set the right summary to be the incoming drop-down selected option + this.summary = incomingSummary; + if (this.rightSummaryTV != null) { + this.rightSummaryTV.setText(incomingSummary); + } + } + + @Override + public void setOnPreferenceClickListener (OnPreferenceClickListener + onPreferenceClickListener){ + this.clickListener = onPreferenceClickListener; + } + + @Override + protected void onClick () { + if (clickListener == null || !clickListener.onPreferenceClick(this)) { + super.onClick(); + } } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java index 34a427547b..e7acdea216 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -5,20 +5,15 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; - +import java.util.Collections; +import java.util.List; +import network.loki.messenger.R; import org.session.libsession.messaging.utilities.AccountId; import org.thoughtcrime.securesms.components.ProfilePictureView; import org.thoughtcrime.securesms.components.emoji.EmojiImageView; import org.thoughtcrime.securesms.database.model.MessageId; -import org.thoughtcrime.securesms.mms.GlideApp; - -import java.util.Collections; -import java.util.List; - -import network.loki.messenger.R; final class ReactionRecipientsAdapter extends RecyclerView.Adapter { @@ -66,6 +61,9 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter 5) { TextView count = itemView.findViewById(R.id.footer_view_emoji_count); - count.setText(itemView.getContext().getResources().getQuantityString(R.plurals.ReactionsRecipientAdapter_other_reactors, emoji.getCount() - 5, emoji.getCount() - 5, emoji.getBaseEmoji())); + + // We display the first 5 people to react w/ a given emoji so we'll subtract that to get the 'others' count + int othersCount = emoji.getCount() - 5; + String s = itemView.getResources().getQuantityString(R.plurals.emojiReactsCountOthers, othersCount, othersCount, emoji.getBaseEmoji()); + count.setText(s); + itemView.setVisibility(View.VISIBLE); } else { itemView.setVisibility(View.GONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java index e17634a7a7..bab8591cb6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java @@ -31,7 +31,6 @@ final class ReactWithAnyEmojiRepository { this.emojiPages = new LinkedList<>(); emojiPages.addAll(Stream.of(EmojiSource.getLatest().getDisplayPages()) - .filterNot(p -> p.getIconAttr() == EmojiCategory.EMOTICONS.getIcon()) .map(page -> new ReactWithAnyEmojiPage(Collections.singletonList(new ReactWithAnyEmojiPageBlock(EmojiCategory.getCategoryLabel(page.getIconAttr()), page)))) .toList()); } @@ -39,7 +38,7 @@ final class ReactWithAnyEmojiRepository { List getEmojiPageModels() { List pages = new LinkedList<>(); - pages.add(new ReactWithAnyEmojiPage(Collections.singletonList(new ReactWithAnyEmojiPageBlock(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__recently_used, recentEmojiPageModel)))); + pages.add(new ReactWithAnyEmojiPage(Collections.singletonList(new ReactWithAnyEmojiPageBlock(R.string.emojiCategoryRecentlyUsed, recentEmojiPageModel)))); pages.addAll(emojiPages); return pages; diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt index a46b4a1d63..67c774dbf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt @@ -34,7 +34,7 @@ class RecoveryPasswordActivity : BaseActionBarActivity() { showSessionDialog { title(R.string.recoveryPasswordHidePermanently) htmlText(R.string.recoveryPasswordHidePermanentlyDescription1) - dangerButton(R.string.continue_2, R.string.AccessibilityId_continue) { onHideConfirm() } + dangerButton(R.string.theContinue, R.string.AccessibilityId_continue) { onHideConfirm() } cancelButton() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java index ae19d13b64..70547b4670 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/StickerSelectFragment.java @@ -37,13 +37,13 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import network.loki.messenger.R; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; public class StickerSelectFragment extends Fragment implements LoaderManager.LoaderCallbacks { private RecyclerView recyclerView; - private GlideRequests glideRequests; + private RequestManager glideRequests; private String assetDirectory; private StickerSelectionListener listener; @@ -71,7 +71,7 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); - this.glideRequests = GlideApp.with(this); + this.glideRequests = Glide.with(this); this.assetDirectory = getArguments().getString("assetDirectory"); getLoaderManager().initLoader(0, null, this); @@ -99,11 +99,11 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa class StickersAdapter extends RecyclerView.Adapter { - private final GlideRequests glideRequests; + private final RequestManager glideRequests; private final String[] stickerFiles; private final LayoutInflater layoutInflater; - StickersAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @NonNull String[] stickerFiles) { + StickersAdapter(@NonNull Context context, @NonNull RequestManager glideRequests, @NonNull String[] stickerFiles) { this.glideRequests = glideRequests; this.stickerFiles = stickerFiles; this.layoutInflater = LayoutInflater.from(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java index 15d4d52434..f7e028c0fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java @@ -11,6 +11,7 @@ import android.os.Parcel; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.transition.Transition; @@ -20,8 +21,7 @@ import org.thoughtcrime.securesms.imageeditor.Bounds; import org.thoughtcrime.securesms.imageeditor.Renderer; import org.thoughtcrime.securesms.imageeditor.RendererContext; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequest; +import com.bumptech.glide.Glide; import java.util.concurrent.ExecutionException; @@ -97,8 +97,8 @@ final class UriGlideRenderer implements Renderer { } } - private GlideRequest getBitmapGlideRequest(@NonNull Context context) { - return GlideApp.with(context) + private RequestBuilder getBitmapGlideRequest(@NonNull Context context) { + return Glide.with(context) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.NONE) .override(maxWidth, maxHeight) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 0516dc2856..c8a67926a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.ShareActivity; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.thoughtcrime.securesms.util.BitmapUtil; import java.util.LinkedList; @@ -50,7 +50,7 @@ public class DirectShareService extends ChooserTargetService { if (recipient.getContactPhoto() != null) { try { - avatar = GlideApp.with(this) + avatar = Glide.with(this) .asBitmap() .load(recipient.getContactPhoto()) .circleCrop() diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java index f919af7ad6..e62909843e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -16,6 +16,8 @@ */ package org.thoughtcrime.securesms.service; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.Notification; @@ -31,11 +33,10 @@ import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; - +import com.squareup.phrase.Phrase; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsignal.utilities.Log; @@ -44,9 +45,7 @@ import org.thoughtcrime.securesms.DatabaseUpgradeActivity; import org.thoughtcrime.securesms.DummyActivity; import org.thoughtcrime.securesms.home.HomeActivity; import org.thoughtcrime.securesms.notifications.NotificationChannels; - import java.util.concurrent.TimeUnit; - import network.loki.messenger.R; /** @@ -242,13 +241,19 @@ public class KeyCachingService extends Service { Log.i(TAG, "foregrounding KCS"); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.LOCKED_STATUS); - builder.setContentTitle(getString(R.string.KeyCachingService_passphrase_cached)); - builder.setContentText(getString(R.string.KeyCachingService_signal_passphrase_cached)); + // Replace app name in title string + Context c = getApplicationContext(); + String unlockedTxt = Phrase.from(c, R.string.lockAppUnlocked) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + builder.setContentTitle(unlockedTxt); + + builder.setContentText(getString(R.string.lockAppUnlock)); builder.setSmallIcon(R.drawable.icon_cached); builder.setWhen(0); builder.setPriority(Notification.PRIORITY_MIN); - builder.addAction(R.drawable.ic_menu_lock_dark, getString(R.string.KeyCachingService_lock), buildLockIntent()); + builder.addAction(R.drawable.ic_menu_lock_dark, getString(R.string.lockApp), buildLockIntent()); builder.setContentIntent(buildLaunchIntent()); stopForeground(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java index a56bc8c0de..6119c7f62d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -1,11 +1,16 @@ package org.thoughtcrime.securesms.service; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; + import android.app.IntentService; +import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.widget.Toast; +import com.squareup.phrase.Phrase; + import network.loki.messenger.R; import org.session.libsession.messaging.messages.visible.VisibleMessage; @@ -28,14 +33,29 @@ public class QuickResponseService extends IntentService { @Override protected void onHandleIntent(Intent intent) { - if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(intent.getAction())) { + if (intent == null) { + Log.w(TAG, "Got null intent from QuickResponseService"); + return; + } + + String actionString = intent.getAction(); + if (actionString == null) { + Log.w(TAG, "Got null action from QuickResponseService intent"); + return; + } + + if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(actionString)) { Log.w(TAG, "Received unknown intent: " + intent.getAction()); return; } if (KeyCachingService.isLocked(this)) { Log.w(TAG, "Got quick response request when locked..."); - Toast.makeText(this, R.string.QuickResponseService_quick_response_unavailable_when_Signal_is_locked, Toast.LENGTH_LONG).show(); + Context c = getApplicationContext(); + String txt = Phrase.from(c, R.string.lockAppQuickResponse) + .put(APP_NAME_KEY, c.getString(R.string.app_name)) + .format().toString(); + Toast.makeText(this, txt, Toast.LENGTH_LONG).show(); return; } @@ -55,7 +75,7 @@ public class QuickResponseService extends IntentService { MessageSender.send(message, Address.fromExternal(this, number)); } } catch (URISyntaxException e) { - Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.errorUnknown, Toast.LENGTH_LONG).show(); Log.w(TAG, e); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java index eea6ba00f8..df25a7d5cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.service; +import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; import android.app.DownloadManager; import android.app.Notification; @@ -13,6 +14,8 @@ import android.net.Uri; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import com.squareup.phrase.Phrase; + import org.session.libsession.utilities.FileUtils; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; @@ -64,10 +67,17 @@ public class UpdateApkReadyListener extends BroadcastReceiver { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); + CharSequence title = Phrase.from(context, R.string.updateSession) + .put(APP_NAME_KEY, context.getString(R.string.app_name)).format(); + + CharSequence txt = Phrase.from(context, R.string.updateNewVersion) + .put(APP_NAME_KEY, context.getString(R.string.app_name)).format(); + + Notification notification = new NotificationCompat.Builder(context, NotificationChannels.APP_UPDATES) .setOngoing(true) - .setContentTitle(context.getString(R.string.UpdateApkReadyListener_Signal_update)) - .setContentText(context.getString(R.string.UpdateApkReadyListener_a_new_version_of_signal_is_available_tap_to_update)) + .setContentTitle(title) + .setContentText(txt) .setSmallIcon(R.drawable.ic_notification) .setColor(context.getResources().getColor(R.color.textsecure_primary)) .setPriority(NotificationCompat.PRIORITY_HIGH) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt index df66cef471..e1dc01dd19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt @@ -149,12 +149,12 @@ fun PreviewSimpleDialog(){ AlertDialog( onDismissRequest = {}, title = stringResource(R.string.warning), - text = stringResource(R.string.you_cannot_go_back_further_in_order_to_stop_loading_your_account_session_needs_to_quit), + text = stringResource(R.string.onboardingBackAccountCreation), buttons = listOf( DialogButtonModel( - GetString(stringResource(R.string.quit)), + GetString(stringResource(R.string.quitButton)), color = LocalColors.current.danger, - onClick = {} + onClick = { } ), DialogButtonModel( GetString(stringResource(R.string.cancel)) @@ -174,12 +174,12 @@ fun PreviewXCloseDialog(){ showCloseButton = true, // display the 'x' button buttons = listOf( DialogButtonModel( - text = GetString(R.string.activity_landing_terms_of_service), + text = GetString(R.string.onboardingTos), contentDescription = GetString(R.string.AccessibilityId_terms_of_service_button), onClick = {} ), DialogButtonModel( - text = GetString(R.string.activity_landing_privacy_policy), + text = GetString(R.string.onboardingPrivacy), contentDescription = GetString(R.string.AccessibilityId_privacy_policy_button), onClick = {} ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt index daeb6b853d..effcdca057 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -245,10 +245,10 @@ fun ItemButton( @Preview @Composable -fun PrewviewItemButton() { +fun PreviewItemButton() { PreviewTheme { ItemButton( - textId = R.string.activity_create_group_title, + textId = R.string.groupCreate, icon = R.drawable.ic_group, onClick = {} ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt index c58f7dc97f..5759314496 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/QR.kt @@ -52,9 +52,11 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.common.InputImage +import com.squareup.phrase.Phrase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -91,7 +93,10 @@ fun MaybeScanQrCode( .padding(horizontal = 60.dp) ) { Text( - stringResource(R.string.activity_link_camera_permission_permanently_denied_configure_in_settings), + stringResource(R.string.cameraGrantAccessDenied).let { txt -> + val c = LocalContext.current + Phrase.from(txt).put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString() + }, style = LocalType.current.base, textAlign = TextAlign.Center ) @@ -110,8 +115,14 @@ fun MaybeScanQrCode( horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.weight(1f)) - Text(stringResource(R.string.fragment_scan_qr_code_camera_access_explanation), - style = LocalType.current.xl, textAlign = TextAlign.Center) + Text( + stringResource(R.string.cameraGrantAccessQr).let { txt -> + val c = LocalContext.current + Phrase.from(txt).put(APP_NAME_KEY, c.getString(R.string.app_name)).format().toString() + }, + style = LocalType.current.xl, + textAlign = TextAlign.Center + ) Spacer(modifier = Modifier.height(LocalDimensions.current.spacing)) PrimaryOutlineButton( stringResource(R.string.cameraGrantAccess), diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java index 3bb0d2db21..93b21512ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -20,7 +20,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.session.libsession.utilities.MediaTypes; import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.session.libsession.utilities.Util; @@ -98,7 +98,7 @@ public class BitmapUtil { int attempts = 0; byte[] bytes; - Bitmap scaledBitmap = GlideApp.with(context.getApplicationContext()) + Bitmap scaledBitmap = Glide.with(context.getApplicationContext()) .asBitmap() .load(model) .skipMemoryCache(true) @@ -164,7 +164,7 @@ public class BitmapUtil { throws BitmapDecodingException { try { - return GlideApp.with(context.getApplicationContext()) + return Glide.with(context.getApplicationContext()) .asBitmap() .load(model) .centerInside() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt index 0ba63fc549..379116c287 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt @@ -9,7 +9,9 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.calls.WebRtcCallActivity import org.thoughtcrime.securesms.notifications.NotificationChannels @@ -34,21 +36,26 @@ class CallNotificationBuilder { } @JvmStatic - fun getFirstCallNotification(context: Context): Notification { + fun getFirstCallNotification(context: Context, callerName: String): Notification { val contentIntent = Intent(context, SettingsActivity::class.java) val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - val text = context.getString(R.string.CallNotificationBuilder_first_call_message) + val titleTxt = Phrase.from(context, R.string.callsMissedCallFrom) + .put(NAME_KEY, callerName) + .format().toString() + val bodyTxt = Phrase.from(context, R.string.callsYouMissedCallPermissions) + .put(NAME_KEY, callerName) + .format().toString() val builder = NotificationCompat.Builder(context, NotificationChannels.CALLS) .setSound(null) .setSmallIcon(R.drawable.ic_baseline_call_24) .setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentTitle(context.getString(R.string.CallNotificationBuilder_first_call_title)) - .setContentText(text) - .setStyle(NotificationCompat.BigTextStyle().bigText(text)) + .setContentTitle(titleTxt) + .setContentText(bodyTxt) + .setStyle(NotificationCompat.BigTextStyle().bigText(bodyTxt)) .setAutoCancel(true) return builder.build() @@ -67,27 +74,29 @@ class CallNotificationBuilder { .setContentIntent(pendingIntent) .setOngoing(true) - + var recipName = "Unknown" recipient?.name?.let { name -> builder.setContentTitle(name) + recipName = name } when (type) { TYPE_INCOMING_CONNECTING -> { - builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting)) + builder.setContentText(context.getString(R.string.callsConnecting)) .setNotificationSilent() } TYPE_INCOMING_PRE_OFFER, TYPE_INCOMING_RINGING -> { - builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call)) + val txt = Phrase.from(context, R.string.callsIncoming).put(NAME_KEY, recipName).format() + builder.setContentText(txt) .setCategory(NotificationCompat.CATEGORY_CALL) builder.addAction(getServiceNotificationAction( context, WebRtcCallService.ACTION_DENY_CALL, R.drawable.ic_close_grey600_32dp, - R.string.NotificationBarManager__deny_call + R.string.decline )) - // if notifications aren't enabled, we will trigger the intent from WebRtcCallService + // If notifications aren't enabled, we will trigger the intent from WebRtcCallService builder.setFullScreenIntent(getFullScreenPendingIntent( context ), true) @@ -95,26 +104,26 @@ class CallNotificationBuilder { context, if (type == TYPE_INCOMING_PRE_OFFER) WebRtcCallActivity.ACTION_PRE_OFFER else WebRtcCallActivity.ACTION_ANSWER, R.drawable.ic_phone_grey600_32dp, - R.string.NotificationBarManager__answer_call + R.string.accept )) builder.priority = NotificationCompat.PRIORITY_MAX } TYPE_OUTGOING_RINGING -> { - builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call)) + builder.setContentText(context.getString(R.string.callsConnecting)) builder.addAction(getServiceNotificationAction( context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, - R.string.NotificationBarManager__cancel_call + R.string.cancel )) } else -> { - builder.setContentText(context.getString(R.string.NotificationBarManager_call_in_progress)) + builder.setContentText(context.getString(R.string.callsInProgress)) builder.addAction(getServiceNotificationAction( context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, - R.string.NotificationBarManager__end_call + R.string.callsEnd )).setUsesChronometer(true) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index 7e65e63e8b..0d900ea391 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -1,22 +1,15 @@ package org.thoughtcrime.securesms.util; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; -import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; - import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import network.loki.messenger.R; - public class CommunicationActions { public static void startConversation(@NonNull Context context, @@ -45,13 +38,4 @@ public class CommunicationActions { } }.execute(); } - - public static void openBrowserLink(@NonNull Context context, @NonNull String link) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show(); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.java b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.java deleted file mode 100644 index 66c838cc1d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2014 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.util; - -import android.annotation.SuppressLint; -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import android.os.Build; -import android.text.format.DateFormat; - -import org.session.libsignal.utilities.Log; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -import network.loki.messenger.R; - -/** - * Utility methods to help display dates in a nice, easily readable way. - */ -public class DateUtils extends android.text.format.DateUtils { - - @SuppressWarnings("unused") - private static final String TAG = DateUtils.class.getSimpleName(); - private static final SimpleDateFormat DAY_PRECISION_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd"); - private static final SimpleDateFormat HOUR_PRECISION_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHH"); - - private static boolean isWithin(final long millis, final long span, final TimeUnit unit) { - return System.currentTimeMillis() - millis <= unit.toMillis(span); - } - - private static boolean isYesterday(final long when) { - return DateUtils.isToday(when + TimeUnit.DAYS.toMillis(1)); - } - - private static int convertDelta(final long millis, TimeUnit to) { - return (int) to.convert(System.currentTimeMillis() - millis, TimeUnit.MILLISECONDS); - } - - public static String getFormattedDateTime(long time, String template, Locale locale) { - final String localizedPattern = getLocalizedPattern(template, locale); - return new SimpleDateFormat(localizedPattern, locale).format(new Date(time)); - } - - public static String getHourFormat(Context c) { - return (DateFormat.is24HourFormat(c)) ? "HH:mm" : "hh:mm a"; - } - - public static String getDisplayFormattedTimeSpanString(final Context c, final Locale locale, final long timestamp) { - // If the timestamp is invalid (ie. 0) then assume we're waiting on data and just use the 'Now' copy - if (timestamp == 0 || isWithin(timestamp, 1, TimeUnit.MINUTES)) { - return c.getString(R.string.DateUtils_just_now); - } else if (isToday(timestamp)) { - return getFormattedDateTime(timestamp, getHourFormat(c), locale); - } else if (isWithin(timestamp, 6, TimeUnit.DAYS)) { - return getFormattedDateTime(timestamp, "EEE " + getHourFormat(c), locale); - } else if (isWithin(timestamp, 365, TimeUnit.DAYS)) { - return getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c), locale); - } else { - return getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c) + ", yyyy", locale); - } - } - - public static SimpleDateFormat getDetailedDateFormatter(Context context, Locale locale) { - String dateFormatPattern; - - if (DateFormat.is24HourFormat(context)) { - dateFormatPattern = getLocalizedPattern("MMM d, yyyy HH:mm:ss zzz", locale); - } else { - dateFormatPattern = getLocalizedPattern("MMM d, yyyy hh:mm:ss a zzz", locale); - } - - return new SimpleDateFormat(dateFormatPattern, locale); - } - - public static String getRelativeDate(@NonNull Context context, - @NonNull Locale locale, - long timestamp) - { - if (isToday(timestamp)) { - return context.getString(R.string.DateUtils_today); - } else if (isYesterday(timestamp)) { - return context.getString(R.string.DateUtils_yesterday); - } else { - return getFormattedDateTime(timestamp, "EEE, MMM d, yyyy", locale); - } - } - - public static boolean isSameDay(long t1, long t2) { - return DAY_PRECISION_DATE_FORMAT.format(new Date(t1)).equals(DAY_PRECISION_DATE_FORMAT.format(new Date(t2))); - } - - public static boolean isSameHour(long t1, long t2) { - return HOUR_PRECISION_DATE_FORMAT.format(new Date(t1)).equals(HOUR_PRECISION_DATE_FORMAT.format(new Date(t2))); - } - - private static String getLocalizedPattern(String template, Locale locale) { - 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") - public static long parseIso8601(@Nullable String date) { - SimpleDateFormat format; - if (Build.VERSION.SDK_INT >= 24) { - format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault()); - } else { - format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); - } - - if (date.isEmpty()) { - return -1; - } - - try { - return format.parse(date).getTime(); - } catch (ParseException e) { - Log.w(TAG, "Failed to parse date.", e); - return -1; - } - } - - // region Deprecated - 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_just_now); - } else if (isWithin(timestamp, 1, TimeUnit.HOURS)) { - int mins = convertDelta(timestamp, TimeUnit.MINUTES); - return c.getResources().getString(R.string.DateUtils_minutes_ago, mins); - } else if (isWithin(timestamp, 1, TimeUnit.DAYS)) { - int hours = convertDelta(timestamp, TimeUnit.HOURS); - return c.getResources().getQuantityString(R.plurals.hours_ago, hours, hours); - } else if (isWithin(timestamp, 6, TimeUnit.DAYS)) { - return getFormattedDateTime(timestamp, "EEE", locale); - } else if (isWithin(timestamp, 365, TimeUnit.DAYS)) { - return getFormattedDateTime(timestamp, "MMM d", locale); - } else { - return getFormattedDateTime(timestamp, "MMM d, yyyy", locale); - } - } - - 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_just_now); - } else if (isWithin(timestamp, 1, TimeUnit.HOURS)) { - int mins = (int)TimeUnit.MINUTES.convert(System.currentTimeMillis() - timestamp, TimeUnit.MILLISECONDS); - return c.getResources().getString(R.string.DateUtils_minutes_ago, mins); - } else { - 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); - } - } - // endregion -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt new file mode 100644 index 0000000000..d6df71176a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.util + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.text.format.DateFormat +import org.session.libsignal.utilities.Log +import java.text.DateFormat.SHORT +import java.text.DateFormat.getTimeInstance +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit + +// Enums used to get the locale-aware String for one of the three relative days +public enum class RelativeDay { TODAY, YESTERDAY, TOMORROW } + +/** + * Utility methods to help display dates in a nice, easily readable way. + */ +object DateUtils : android.text.format.DateUtils() { + + @Suppress("unused") + private val TAG: String = DateUtils::class.java.simpleName + private val DAY_PRECISION_DATE_FORMAT = SimpleDateFormat("yyyyMMdd") + private val HOUR_PRECISION_DATE_FORMAT = SimpleDateFormat("yyyyMMddHH") + + private fun isWithin(millis: Long, span: Long, unit: TimeUnit): Boolean { + return System.currentTimeMillis() - millis <= unit.toMillis(span) + } + + private fun isYesterday(`when`: Long): Boolean { + 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 { + + val now = Calendar.getInstance() + + // To compare a time to 'now' we need to use get a date relative it, so plus or minus a day, or not + val dayAddition = when (relativeDay) { + RelativeDay.TOMORROW -> { 1 } + RelativeDay.YESTERDAY -> { -1 } + else -> 0 // Today + } + + val comparisonTime = Calendar.getInstance().apply { + add(Calendar.DAY_OF_YEAR, dayAddition) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + return getRelativeTimeSpanString(comparisonTime.timeInMillis, + now.timeInMillis, + DAY_IN_MILLIS, + FORMAT_SHOW_DATE).toString() + } + + // Method to get the locale-aware String for the word "Now" +// public fun getLocalisedNowString(): String { +// val now = Calendar.getInstance().timeInMillis +// return getRelativeTimeSpanString(now, now,MINUTE_IN_MILLIS, FORMAT_SHOW_TIME).toString() +// } + + // THIS DOES NOT WORK + public fun getLocalisedNowString(): String { + + val now = Calendar.getInstance().timeInMillis + val relativeTime = getRelativeTimeSpanString(now, now, MINUTE_IN_MILLIS, FORMAT_SHOW_TIME).toString() + + // Create a DateFormat instance for the current time + val timeFormat = getTimeInstance(SHORT, Locale.getDefault()) + val formattedTime = timeFormat.format(Calendar.getInstance().time) + + // Check if the relative time indicates "0 minutes ago" or similar and replace it with the formatted time + return if (relativeTime == "0 minutes ago" || relativeTime == "in 0 minutes") { + formattedTime + } else { + relativeTime + } + } + + fun getFormattedDateTime(time: Long, template: String, locale: Locale): String { + val localizedPattern = getLocalizedPattern(template, locale) + return SimpleDateFormat(localizedPattern, locale).format(Date(time)) + } + + fun getHourFormat(c: Context?): String { + return if ((DateFormat.is24HourFormat(c))) "HH:mm" else "hh:mm a" + } + + fun getDisplayFormattedTimeSpanString(c: Context, locale: Locale, timestamp: Long): String { + // If the timestamp is invalid (ie. 0) then assume we're waiting on data and just use the 'Now' copy + return if (timestamp == 0L || isWithin(timestamp, 1, TimeUnit.MINUTES)) { + getLocalisedNowString() + //c.getString(R.string.DateUtils_just_now) // ACL REMOVE WHEN HAPPY + } else if (isToday(timestamp)) { + getFormattedDateTime(timestamp, getHourFormat(c), locale) + } else if (isWithin(timestamp, 6, TimeUnit.DAYS)) { + getFormattedDateTime(timestamp, "EEE " + getHourFormat(c), locale) + } else if (isWithin(timestamp, 365, TimeUnit.DAYS)) { + getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c), locale) + } else { + getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c) + ", yyyy", locale) + } + } + + fun getDetailedDateFormatter(context: Context?, locale: Locale): SimpleDateFormat { + val dateFormatPattern = if (DateFormat.is24HourFormat(context)) { + getLocalizedPattern("MMM d, yyyy HH:mm:ss zzz", locale) + } else { + getLocalizedPattern("MMM d, yyyy hh:mm:ss a zzz", locale) + } + + return SimpleDateFormat(dateFormatPattern, locale) + } + + // Method to get the String for a relative day in a locale-aware fashion, including using the + // auto-localised words for "today" and "yesterday" as appropriate. + fun getRelativeDate( + context: Context, + locale: Locale, + timestamp: Long + ): String { + return if (isToday(timestamp)) { + getLocalisedRelativeDayString(RelativeDay.TODAY) + //context.getString(R.string.DateUtils_today) // ACL REMOVE WHEN HAPPY + } else if (isYesterday(timestamp)) { + getLocalisedRelativeDayString(RelativeDay.YESTERDAY) + //context.getString(R.string.DateUtils_yesterday) // ACL REMOVE WHEN HAPPY + } else { + getFormattedDateTime(timestamp, "EEE, MMM d, yyyy", locale) + } + } + + fun isSameDay(t1: Long, t2: Long): Boolean { + return DAY_PRECISION_DATE_FORMAT.format(Date(t1)) == DAY_PRECISION_DATE_FORMAT.format(Date(t2)) + } + + fun isSameHour(t1: Long, t2: Long): Boolean { + return HOUR_PRECISION_DATE_FORMAT.format(Date(t1)) == HOUR_PRECISION_DATE_FORMAT.format(Date(t2)) + } + + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LongClickCopySpan.java b/app/src/main/java/org/thoughtcrime/securesms/util/LongClickCopySpan.java index ce8439a20d..7172018116 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LongClickCopySpan.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LongClickCopySpan.java @@ -7,8 +7,6 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import android.text.TextPaint; import android.text.style.URLSpan; -import android.view.View; -import android.widget.Toast; import network.loki.messenger.R; @@ -24,14 +22,6 @@ public class LongClickCopySpan extends URLSpan { super(url); } - void onLongClick(View widget) { - Context context = widget.getContext(); - String preparedUrl = prepareUrl(getURL()); - copyUrl(context, preparedUrl); - Toast.makeText(context, - context.getString(R.string.ConversationItem_copied_text, preparedUrl), Toast.LENGTH_SHORT).show(); - } - @Override public void updateDrawState(@NonNull TextPaint ds) { super.updateDrawState(ds); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java index 5c4894c12b..fa2fd6b503 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java @@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.DocumentSlide; import org.thoughtcrime.securesms.mms.GifSlide; -import org.thoughtcrime.securesms.mms.GlideApp; +import com.bumptech.glide.Glide; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.MmsSlide; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -115,7 +115,7 @@ public class MediaUtil { if (MediaUtil.isGif(contentType)) { try { - GifDrawable drawable = GlideApp.with(context) + GifDrawable drawable = Glide.with(context) .asGif() .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt index 8df2a7cd2b..584910d31d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt @@ -32,8 +32,8 @@ import java.util.concurrent.TimeUnit class SaveAttachmentTask @JvmOverloads constructor(context: Context, count: Int = 1) : ProgressDialogAsyncTask>( context, - context.resources.getQuantityString(R.plurals.ConversationFragment_saving_n_attachments, count, count), - context.resources.getQuantityString(R.plurals.ConversationFragment_saving_n_attachments_to_sd_card, count, count) + context.resources.getString(R.string.saving), + context.resources.getString(R.string.saving) ) { companion object { @@ -47,12 +47,13 @@ class SaveAttachmentTask @JvmOverloads constructor(context: Context, count: Int @JvmOverloads fun showWarningDialog(context: Context, count: Int = 1, onAcceptListener: () -> Unit = {}) { context.showSessionDialog { - title(R.string.ConversationFragment_save_to_sd_card) + title(R.string.permissionsRequired) iconAttribute(R.attr.dialog_alert_icon) - text(context.resources.getQuantityString( - R.plurals.ConversationFragment_saving_n_media_to_storage_warning, - count, - count)) + + // ACL TODO - Need a replacement plurals string for the below! + val txt = context.resources.getQuantityString(R.plurals.ConversationFragment_saving_n_media_to_storage_warning, count, count) + text(txt) + button(R.string.yes) { onAcceptListener() } button(R.string.no) } @@ -235,18 +236,12 @@ class SaveAttachmentTask @JvmOverloads constructor(context: Context, count: Int when (result.first) { RESULT_FAILURE -> { - val message = context.resources.getQuantityText( - R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, - attachmentCount) + val message = context.resources.getString(R.string.attachmentsSaveError) Toast.makeText(context, message, Toast.LENGTH_LONG).show() } RESULT_SUCCESS -> { - val message = if (!TextUtils.isEmpty(result.second)) { - context.resources.getString(R.string.SaveAttachmentTask_saved_to, result.second) - } else { - context.resources.getString(R.string.SaveAttachmentTask_saved) - } + val message = context.resources.getString(R.string.saved) Toast.makeText(context, message, Toast.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt index 7e14b7234f..a0528ad432 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt @@ -5,7 +5,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import com.squareup.phrase.Phrase +import network.loki.messenger.R import network.loki.messenger.databinding.FragmentScanQrCodePlaceholderBinding +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY class ScanQRCodePlaceholderFragment: Fragment() { private lateinit var binding: FragmentScanQrCodePlaceholderBinding @@ -19,10 +22,13 @@ class ScanQRCodePlaceholderFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.grantCameraAccessButton.setOnClickListener { delegate?.requestCameraAccess() } + + binding.needCameraPermissionsTV.text = Phrase.from(context, R.string.cameraGrantAccessQr) + .put(APP_NAME_KEY, getString(R.string.app_name)) + .format() } } interface ScanQRCodePlaceholderFragmentDelegate { - fun requestCameraAccess() } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java b/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java index dd5e146edd..6707a078c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Trimmer.java @@ -30,8 +30,8 @@ public class Trimmer { progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setCancelable(false); progressDialog.setIndeterminate(false); - progressDialog.setTitle(R.string.trimmer__deleting); - progressDialog.setMessage(context.getString(R.string.trimmer__deleting_old_messages)); + progressDialog.setTitle(R.string.deleting); + progressDialog.setMessage(context.getString(R.string.deleting)); progressDialog.setMax(100); progressDialog.show(); } @@ -53,9 +53,6 @@ public class Trimmer { @Override protected void onPostExecute(Void result) { progressDialog.dismiss(); - Toast.makeText(context, - R.string.trimmer__old_messages_successfully_deleted, - Toast.LENGTH_LONG).show(); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java index 08d3a52a2f..715ff322f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -182,7 +182,7 @@ public class VideoPlayer extends FrameLayout { //noinspection ConstantConditions this.videoView.setVideoURI(videoSource.getUri()); } else { - Toast.makeText(getContext(), getContext().getString(R.string.VideoPlayer_error_playing_video), Toast.LENGTH_LONG).show(); + Toast.makeText(getContext(), getContext().getString(R.string.videoErrorPlay), Toast.LENGTH_LONG).show(); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index 3d40b5f746..376cb41792 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -65,7 +65,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP val sentTimestamp = nextMessage.sentTimestamp ?: continue if (textSecurePreferences.setShownCallNotification()) { // first time call notification encountered - val notification = CallNotificationBuilder.getFirstCallNotification(context) + val notification = CallNotificationBuilder.getFirstCallNotification(context, sender) context.getSystemService(NotificationManager::class.java).notify(CallNotificationBuilder.WEBRTC_NOTIFICATION, notification) insertMissedCall(sender, sentTimestamp, isFirstCall = true) } else { diff --git a/app/src/main/res/layout-sw400dp/activity_display_name.xml b/app/src/main/res/layout-sw400dp/activity_display_name.xml new file mode 100644 index 0000000000..5456791d32 --- /dev/null +++ b/app/src/main/res/layout-sw400dp/activity_display_name.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + +