diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt index 7c8ec46a5d..d5d52441d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components import android.annotation.SuppressLint import android.content.Context import android.graphics.PointF +import android.media.ExifInterface import android.net.Uri import android.os.AsyncTask import android.util.AttributeSet @@ -49,8 +50,31 @@ class ZoomingImageView @JvmOverloads constructor( this.photoView = findViewById(R.id.image_view) this.subsamplingImageView = findViewById(R.id.subsampling_image_view) + } + + private fun getSubsamplingOrientation(context: Context, uri: Uri): Int { + var exifOrientation = ExifInterface.ORIENTATION_UNDEFINED + try { + PartAuthority.getAttachmentStream(context, uri).use { input -> + val exif = ExifInterface(input) + exifOrientation = exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_UNDEFINED + ) + } + } catch (e: Exception) { + Log.w(TAG, "Failed to read EXIF orientation", e) + } - subsamplingImageView.orientation = SubsamplingScaleImageView.ORIENTATION_USE_EXIF + return when (exifOrientation) { + ExifInterface.ORIENTATION_ROTATE_90, + ExifInterface.ORIENTATION_TRANSPOSE -> SubsamplingScaleImageView.ORIENTATION_90 + ExifInterface.ORIENTATION_ROTATE_180, + ExifInterface.ORIENTATION_FLIP_VERTICAL -> SubsamplingScaleImageView.ORIENTATION_180 + ExifInterface.ORIENTATION_ROTATE_270, + ExifInterface.ORIENTATION_TRANSVERSE -> SubsamplingScaleImageView.ORIENTATION_270 + else -> SubsamplingScaleImageView.ORIENTATION_0 + } } fun setInteractor(interactor: ZoomImageInteractions?) { @@ -62,39 +86,33 @@ class ZoomingImageView @JvmOverloads constructor( val context = context val maxTextureSize = BitmapUtil.getMaxTextureSize() - Log.i( - TAG, - "Max texture size: $maxTextureSize" - ) - - object : AsyncTask?>() { - override fun doInBackground(vararg params: Void?): Pair? { + object : AsyncTask() { + override fun doInBackground(vararg params: Void?): ImageLoadResult? { if (MediaUtil.isGif(contentType)) return null - try { - val inputStream = PartAuthority.getAttachmentStream(context, uri) - return BitmapUtil.getDimensions(inputStream) - } catch (e: IOException) { - Log.w(TAG, e) - return null - } catch (e: BitmapDecodingException) { + val dimStream = PartAuthority.getAttachmentStream(context, uri) + val dimensions = BitmapUtil.getDimensions(dimStream) + val orientation = getSubsamplingOrientation(context, uri) + + return ImageLoadResult(dimensions.first, dimensions.second, orientation) + } catch (e: Exception) { Log.w(TAG, e) return null } } - override fun onPostExecute(dimensions: Pair?) { + override fun onPostExecute(result: ImageLoadResult?) { Log.i( TAG, - "Dimensions: " + (if (dimensions == null) "(null)" else dimensions.first.toString() + ", " + dimensions.second) + "Dimensions: " + (if (result == null) "(null)" else "${result.width} , ${result.height} - orientation: ${result.orientation}") ) - if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) { + if (result == null || (result.width <= maxTextureSize && result.height <= maxTextureSize)) { Log.i(TAG, "Loading in standard image view...") setImageViewUri(glideRequests, uri) } else { Log.i(TAG, "Loading in subsampling image view...") - setSubsamplingImageViewUri(uri) + setSubsamplingImageViewUri(uri, result.orientation) } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) @@ -116,13 +134,14 @@ class ZoomingImageView @JvmOverloads constructor( } @SuppressLint("ClickableViewAccessibility") - private fun setSubsamplingImageViewUri(uri: Uri) { + private fun setSubsamplingImageViewUri(uri: Uri, orientation: Int) { subsamplingImageView.setBitmapDecoderFactory(AttachmentBitmapDecoderFactory()) subsamplingImageView.setRegionDecoderFactory(AttachmentRegionDecoderFactory()) - subsamplingImageView.visibility = VISIBLE photoView.visibility = GONE + subsamplingImageView.orientation = orientation + val gestureDetector = GestureDetector( context, object : GestureDetector.SimpleOnGestureListener() { @@ -132,7 +151,6 @@ class ZoomingImageView @JvmOverloads constructor( } } ) - subsamplingImageView.setImage(ImageSource.uri(uri)) subsamplingImageView.setOnTouchListener { v, event -> gestureDetector.onTouchEvent(event) @@ -158,6 +176,8 @@ class ZoomingImageView @JvmOverloads constructor( } } + data class ImageLoadResult(val width: Int, val height: Int, val orientation: Int) + companion object { private val TAG: String = ZoomingImageView::class.java.simpleName } 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 dbd3306912..5c5f85920d 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 @@ -96,6 +96,7 @@ import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview +import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeClock @@ -265,6 +266,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, @Inject lateinit var clock: SnodeClock @Inject lateinit var messageSender: MessageSender @Inject lateinit var resendMessageUtilities: ResendMessageUtilities + @Inject lateinit var messageNotifier: MessageNotifier @Inject @ManagerScope lateinit var scope: CoroutineScope @@ -771,12 +773,12 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, override fun onResume() { super.onResume() - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId) + messageNotifier.setVisibleThread(viewModel.threadId) } override fun onPause() { super.onPause() - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1) + messageNotifier.setVisibleThread(-1) } override fun getSystemService(name: String): Any? { 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 801f2e3fb4..4ebfc1c1e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -55,7 +55,6 @@ import org.session.libsession.utilities.recipients.RecipientData import org.session.libsession.utilities.recipients.displayName import org.session.libsession.utilities.updateContact import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.auth.LoginStateRepository import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 @@ -550,7 +549,7 @@ class HomeActivity : ScreenLockActionBarActivity(), override fun onPause() { super.onPause() - ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(false) + messageNotifier.setHomeScreenVisible(false) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt index ad321cd6a8..a026e156e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt @@ -12,6 +12,7 @@ import com.squareup.phrase.Phrase import kotlinx.coroutines.delay import network.loki.messenger.R import org.session.libsession.utilities.NonTranslatableStringConstants +import org.session.libsession.utilities.StringSubstitutionConstants import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY @@ -22,6 +23,8 @@ import org.thoughtcrime.securesms.ui.AnimatedSessionProCTA import org.thoughtcrime.securesms.ui.CTAFeature import org.thoughtcrime.securesms.ui.OpenURLAlertDialog import org.thoughtcrime.securesms.ui.PinProCTA +import org.thoughtcrime.securesms.ui.SessionProCTA +import org.thoughtcrime.securesms.ui.SimpleSessionProCTA import org.thoughtcrime.securesms.ui.UserProfileModal import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme @@ -158,18 +161,19 @@ fun HomeDialogs( if (showDonation && dialogsState.donationCTA) { val context = LocalContext.current - AnimatedSessionProCTA( - heroImageBg = R.drawable.cta_hero_generic_bg, - heroImageAnimatedFg = R.drawable.cta_hero_generic_fg, - title = stringResource(R.string.proExpired), //todo DONATION need crowdin strings + SimpleSessionProCTA( + heroImage = R.drawable.cta_hero_flower, + title = Phrase.from(context,R.string.donateSessionHelp) + .put(StringSubstitutionConstants.APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME) + .format() + .toString(), showProBadge = false, - text = Phrase.from(context,R.string.proExpiredDescription) - .put(PRO_KEY, NonTranslatableStringConstants.PRO) - .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) + text = Phrase.from(context,R.string.donateSessionDescription) + .put(StringSubstitutionConstants.APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME) .format() - .toString(),//todo DONATION need crowdin strings + .toString(), positiveButtonText = stringResource(R.string.donate), - negativeButtonText = stringResource(R.string.cancel), //todo DONATION need crowdin strings + negativeButtonText = stringResource(R.string.maybeLater), onUpgrade = { sendCommand(HideDonationCTADialog) sendCommand(ShowDonationConfirmation) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt index 891ba0a687..c6c96c25e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt @@ -75,24 +75,7 @@ import org.thoughtcrime.securesms.home.PathActivity import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.TempAvatar import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.UserAvatar -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ClearData -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideAnimatedProCTA -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideAvatarPickerOptions -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideClearDataDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideSimpleDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideUrlDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideUsernameDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.OnAvatarDialogDismissed -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.OnDonateClicked -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.RemoveAvatar -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.SaveAvatar -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.SetUsername -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowAnimatedProCTA -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowAvatarDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowClearDataDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowUrlDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowUsernameDialog -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.UpdateUsername +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.* import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsActivity import org.thoughtcrime.securesms.pro.ProDataState @@ -417,6 +400,8 @@ fun Settings( if(uiState.showUrlDialog != null){ OpenURLAlertDialog( url = uiState.showUrlDialog, + onLinkOpened = { sendCommand(OnLinkOpened(uiState.showUrlDialog)) }, + onLinkCopied = { sendCommand(OnLinkCopied(uiState.showUrlDialog)) }, onDismissRequest = { sendCommand(HideUrlDialog) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index 253f3027d6..f9fb3fad79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -51,9 +51,9 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.textSi import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProDetailsRepository +import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.pro.getDefaultSubscriptionStateData import org.thoughtcrime.securesms.reviews.InAppReviewManager import org.thoughtcrime.securesms.ui.SimpleDialogData @@ -61,6 +61,7 @@ import org.thoughtcrime.securesms.util.AnimatedImageUtils import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUtils import org.thoughtcrime.securesms.util.ClearDataUtils +import org.thoughtcrime.securesms.util.DonationManager import org.thoughtcrime.securesms.util.DonationManager.Companion.URL_DONATE import org.thoughtcrime.securesms.util.NetworkConnectivity import org.thoughtcrime.securesms.util.mapToStateFlow @@ -84,6 +85,7 @@ class SettingsViewModel @Inject constructor( private val avatarUploadManager: AvatarUploadManager, private val attachmentProcessor: AttachmentProcessor, private val proDetailsRepository: ProDetailsRepository, + private val donationManager: DonationManager, ) : ViewModel() { private val TAG = "SettingsViewModel" @@ -604,6 +606,20 @@ class SettingsViewModel @Inject constructor( is Commands.HideSimpleDialog -> { _uiState.update { it.copy(showSimpleDialog = null) } } + + is Commands.OnLinkOpened -> { + // if the link was for donation, mark it as seen + if(command.url == URL_DONATE) { + donationManager.onDonationSeen() + } + } + + is Commands.OnLinkCopied -> { + // if the link was for donation, mark it as seen + if(command.url == URL_DONATE) { + donationManager.onDonationCopied() + } + } } } @@ -683,5 +699,8 @@ class SettingsViewModel @Inject constructor( data object OnDonateClicked: Commands data class ClearData(val clearNetwork: Boolean): Commands + + data class OnLinkOpened(val url: String) : Commands + data class OnLinkCopied(val url: String) : Commands } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DonationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DonationManager.kt index 7cdd4e1c08..e9ac3fbf4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DonationManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DonationManager.kt @@ -15,7 +15,7 @@ class DonationManager @Inject constructor( val prefs: TextSecurePreferences ){ companion object { - const val URL_DONATE = "https://session.foundation/donate#app" + const val URL_DONATE = "https://getsession.org/donate#app" } // increment in days between showing the donation CTA, matching the list index to the number of views of the CTA diff --git a/app/src/main/res/drawable/cta_hero_flower.webp b/app/src/main/res/drawable/cta_hero_flower.webp new file mode 100644 index 0000000000..b7fbe5ae9e Binary files /dev/null and b/app/src/main/res/drawable/cta_hero_flower.webp differ